Adds a new PlHook subclass for token-authenticated requests.
authorVincent Zanotti <vincent.zanotti@m4x.org>
Sat, 1 Jan 2011 23:06:07 +0000 (00:06 +0100)
committerVincent Zanotti <vincent.zanotti@m4x.org>
Sun, 2 Jan 2011 14:08:33 +0000 (15:08 +0100)
The new hook checks for either a valid token (in which case the
authentication level is assumed to always match), or a valid session (in
which case the auth level is checked against the session). Note that
this finally allows token-based handlers to be permission checked.

Signed-off-by: Vincent Zanotti <vincent.zanotti@m4x.org>
classes/platal.php
classes/plfeed.php
classes/plmodule.php
include/platal.inc.php

index e5187e4..755cf67 100644 (file)
@@ -38,18 +38,12 @@ abstract class PlHook
         $this->type  = $type;
     }
 
-    public function needAuth()
-    {
-        return $this->auth > S::i('auth', AUTH_PUBLIC);
-    }
-
     public function checkPerms()
     {
-        if (!$this->perms || $this->auth == AUTH_PUBLIC) { // No perms, no check
-            return true;
-        }
-        $s_perms = S::v('perms');
-        return $s_perms->hasFlagCombination($this->perms);
+        // Don't check permissions if there are no permission requirement
+        // (either no requested group membership, or public auth is allowed).
+        return !$this->perms || $this->auth == AUTH_PUBLIC ||
+            Platal::session()->checkPerms($this->perms);
     }
 
     public function hasType($type)
@@ -62,7 +56,7 @@ abstract class PlHook
     public function call(PlPage &$page, array $args)
     {
         global $globals, $session, $platal;
-        if ($this->needAuth()) {
+        if (!$session->checkAuth($this->auth)) {
             if ($this->hasType(DO_AUTH)) {
                 if (!$session->start($this->auth)) {
                     $platal->force_login($page);
@@ -81,14 +75,18 @@ abstract class PlHook
     }
 }
 
+/** The standard plat/al hook, for interactive requests.
+ * It optionally does active authentication (DO_AUTH). The handler is invoked
+ * with the PlPage object, and with each of the remaining path components.
+ */
 class PlStdHook extends PlHook
 {
-    private $hook;
+    private $callback;
 
     public function __construct($callback, $auth = AUTH_PUBLIC, $perms = 'user', $type = DO_AUTH)
     {
         parent::__construct($auth, $perms, $type);
-        $this->hook = $callback;
+        $this->callback = $callback;
     }
 
     protected function run(PlPage &$page, array $args)
@@ -96,17 +94,76 @@ class PlStdHook extends PlHook
         global $session, $platal;
 
         $args[0] = $page;
-        $val = call_user_func_array($this->hook, $args);
+        $val = call_user_func_array($this->callback, $args);
         if ($val == PL_DO_AUTH) {
             if (!$session->start($session->loggedLevel())) {
                 $platal->force_login($page);
             }
-            $val = call_user_func_array($this->hook, $args);
+            $val = call_user_func_array($this->callback, $args);
         }
         return $val;
     }
 }
 
+/** A specialized hook for token-based requests.
+ * It is intended for purely passive requests (typically for serving CSV or RSS
+ * content outside the browser), and can fallback to regular session-based
+ * authentication when the token is not valid/available.
+ *
+ * Note that $auth is only applied for session-backed authentication; it is
+ * assumed that token-based auth is always enough for the hook (otherwise, just
+ * use PlStdHook above).
+ *
+ * Also, this hook requires that the first two unmatched path components are the
+ * user and token (for instance /<matched path>/<user>/<token>/....). They will
+ * be popped before being passed to the handler, and replaced by the request's
+ * PlUser object.
+ */
+class PlTokenHook extends PlHook
+{
+    private $actualAuth;
+    private $callback;
+
+    public function __construct($callback, $auth = AUTH_PUBLIC, $perms = 'user', $type = NO_AUTH)
+    {
+        // As mentioned above, $auth is only applied for session-based auth
+        // (as opposed to token-based). PlHook is initialized to AUTH_PUBLIC to
+        // avoid it refusing to approve requests; this is important as the user
+        // is not yet authenticated at that point (see below for the actual
+        // permissions check).
+        parent::__construct(AUTH_PUBLIC, $perms, $type);
+        $this->actualAuth = $auth;
+        $this->callback = $callback;
+    }
+
+    protected function run(PlPage &$page, array $args)
+    {
+        // Retrieve the user, either from the session (less expensive, as it is
+        // already there), or from the in-path (user, token) pair.
+        if (S::logged() && Platal::session()->checkAuth($this->actualAuth)) {
+            $user = S::user();
+        } else {
+            $user = Platal::session()->tokenAuth(@$args[1], @$args[2]);
+        }
+
+        // Check the permissions, unless the handler is fully public.
+        if ($this->actualAuth > AUTH_PUBLIC) {
+            if (is_null($user) || !$user->checkPerms($this->perms)) {
+                return PL_FORBIDDEN;
+            }
+        }
+
+        // Replace the first three remaining elements of the path with the
+        // PlPage and PlUser objects.
+        array_shift($args);
+        $args[0] = $page;
+        $args[1] = $user;
+        return call_user_func_array($this->callback, $args);
+    }
+}
+
+/** A specialized plat/al hook for serving wiki pages.
+ */
 class PlWikiHook extends PlHook
 {
     public function __construct($auth = AUTH_PUBLIC, $perms = 'user', $type = DO_AUTH)
index d54199a..ce9ea08 100644 (file)
@@ -95,15 +95,10 @@ abstract class PlFeed implements PlIterator
         return $this->iterator->last();
     }
 
-    public function run(PlPage& $page, $login, $token, $require_auth = true, $type = 'rss2')
+    public function run(PlPage& $page, PlUser& $user, $require_auth = true, $type = 'rss2')
     {
-        $user = Platal::session()->tokenAuth($login, $token);
-        if (empty($user)) {
-            if ($require_auth) {
-                return PL_FORBIDDEN;
-            } else {
-                $user = null;
-            }
+        if (empty($user) && $require_auth) {
+            return PL_FORBIDDEN;
         }
 
         $page->assign('rss_hash', $token);
index 55de69f..92071b8 100644 (file)
@@ -34,24 +34,48 @@ abstract class PLModule
     abstract public function handlers();
 
     /** Register a hook
-     * @param fun name of the handler (the exact name will be handler_$fun)
-     * @param auth authentification level of needed to run this handler
+     * @param fun name of the handler (the exact name will be handler_$fun); the
+     *   handler will be invoked with the PlPage object, and the unmatched path
+     *   components
+     * @param auth authentification level required to run this handler
      * @param perms permission required to run this handler
      * @param type additionnal flags
      *
      * Perms syntax is the following:
-     * perms = rights(,rights)*
-     * rights = right(:right)*
+     *   perms = rights(,rights)*
+     *   rights = right(:right)*
      * right is an atomic right permission (like 'admin', 'user', 'groupadmin', 'groupmember'...)
      *
-     * If type is set to NO_AUTH, the system will return 403 instead of asking auth data
-     * this is useful for Ajax handler
-     * If type is not set to NO_SKIN, the system will consider redirecting the user to https
+     * If type is set to NO_AUTH, the system will return 403 instead of asking
+     * auth data; this is useful for Ajax handler. If type is not set to
+     * NO_SKIN, the system will consider redirecting the user to https.
      */
     public function make_hook($fun, $auth, $perms = 'user', $type = DO_AUTH)
     {
-        return new PlStdHook(array($this, 'handler_' . $fun),
-                             $auth, $perms, $type);
+        return new PlStdHook(array($this, 'handler_' . $fun), $auth, $perms, $type);
+    }
+
+    /** Register a token-authentified hook (rss, csv, ical, ...)
+     * @param fun name of the handler (the exact name will be handler_$fun); the
+     *   handler will be invoked with the PlPage object, the PlUser of the
+     *   request, and the unmatched path components
+     * @param auth authentification level required, when not token-authentified
+     * @param perms permission required to run this handler
+     * @param type additionnal flags
+     *
+     * See {@link make_hook} above for details on permissions and additional
+     * flags. Note that DO_AUTH has no effect here, as the request will always
+     * be passively identified.
+     *
+     * This hook requires that the first two unmatched path components form a
+     * valid (user, token) pair; if not, a session-based authentification will
+     * be attempted, in which case $auth will be honored.
+     * Note that because token-based authentication is weak, it should only be
+     * used for readonly handlers normally served in AUTH_COOKIE.
+     */
+    public function make_token_hook($fun, $auth, $perms = 'user', $type = NO_HTTPS)
+    {
+        return new PlTokenHook(array($this, 'handler_' . $fun), $auth, $perms, $type);
     }
 
     /** Register a hook that points to a wiki page.
index a638d0d..a174482 100644 (file)
@@ -24,14 +24,24 @@ $TIME_BEGIN = microtime(true);
 require_once dirname(__FILE__) . '/version.inc.php';
 require_once dirname(__FILE__) . '/misc.inc.php';
 
-define('PERMS_EXT',   'ext');
+// Common basic permission flags.
 define('PERMS_USER',  'user');
 define('PERMS_ADMIN', 'admin');
 
-define('SKINNED', 0);
-define('SIMPLE',  1);
-define('NO_SKIN', 2);
-
+// Page style options, used when rendering pages. Options are exclusive.
+define('SKINNED', 0);  // Page is rendered with the normal skin.
+define('SIMPLE',  1);  // Page is rendered with a light skin (no leftnav).
+define('NO_SKIN', 2);  // Page content is passed as-is (use for csv, xml, ...).
+
+// Hook options bitmasks. Authentication options are mutually exclusive, but
+// others (NO_HTTPS at the moment) are not.
+//
+// With PlStdHook, NO_AUTH indicates that no session will be started, and that
+// the actual handler is responsible for doing authentication; DO_AUTH forces
+// the engine to try to authenticate the user, including redirecting to the
+// login page. Note that DO_AUTH is ignored if AUTH_PUBLIC is requested.
+//
+// Options NO_AUTH and DO_AUTH are ignored with PlTokenHook.
 define('NO_AUTH', 0);
 define('DO_AUTH', 1);
 define('NO_HTTPS', 2);