X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;f=classes%2Fplatal.php;h=eeafbb91b64991ffb58d7a186ee7c38cfed1c143;hb=9007d4955f2487e82c0f9f4059e1f6b450bdb528;hp=e5187e4e8dbf62f58af101fe17813230adb7a194;hpb=15ba1697972827b89892b9866c4f6c0859c67ce8;p=platal.git diff --git a/classes/platal.php b/classes/platal.php index e5187e4..eeafbb9 100644 --- a/classes/platal.php +++ b/classes/platal.php @@ -1,6 +1,6 @@ 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 +59,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 +78,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 +97,152 @@ 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 API requests. + * It is intended to be used for passive API requests, authenticated either by + * an existing session (with a valid XSRF token), or by an alternative single + * request auth mechanism implemented by PlSession::apiAuth. + * + * This hook is suitable for read-write requests against the website, provided + * $auth is set appropriately. Note that the auth level is only checked for + * session-authenticated users, as "apiAuth" users are assumed to always have + * the requested level (use another hook otherwise). + * + * The callback will be passed as arguments the PlPage, the authenticated + * PlUser, the JSON decoded payload, and the remaining path components, as with + * any other hook. + * + * If the callback intends to JSON-encode its returned value, it is advised to + * use PlPage::jsonAssign, and return PL_JSON to enable automatic encoding. + */ +class PlApiHook 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; + } + + private function getEncodedPayload($method) + { + return $method == "GET" ? "" : file_get_contents("php://input"); + } + + private function decodePayload($encodedPayload) + { + return empty($encodedPayload) ? array() : json_decode($encodedPayload, true); + } + + protected function run(PlPage &$page, array $args) + { + $method = $_SERVER['REQUEST_METHOD']; + $encodedPayload = $this->getEncodedPayload($method); + $jsonPayload = $this->decodePayload($encodedPayload); + $resource = '/' . implode('/', $args); + + // If the payload wasn't a valid JSON encoded object, bail out early. + if (is_null($jsonPayload)) { + $page->trigError("Could not decode the JSON-encoded payload sent with the request."); + return PL_BAD_REQUEST; + } + + // Authenticate the request. Try first with the existing session (which + // is less expensive to check), by veryfing that the XSRF token is + // valid; otherwise fallbacks to API-type authentication from PlSession. + if (S::logged() && S::has_xsrf_token() && Platal::session()->checkAuth($this->actualAuth)) { + $user = S::user(); + } else { + $user = Platal::session()->apiAuth($method, $resource, $encodedPayload); + } + + // 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; + } + } + + // Invoke the callback, whose signature is (PlPage, PlUser, jsonPayload). + array_shift($args); + array_unshift($args, $page, $user, $jsonPayload); + return call_user_func_array($this->callback, $args); + } +} + +/** 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 ////....). 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) + { + // See PlApiHook::__construct. + 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) @@ -126,51 +262,51 @@ class PlHookTree public $aliased = null; public $children = array(); - public function addChild(array $path, PlHook $hook) + public function addChildren(array $hooks) { global $platal; - $next = array_shift($path); - $alias = null; - if ($next && $next{0} == '%') { - $alias = $next; - $next = $platal->hook_map(substr($next, 1)); - } - if (!$next) { - return; - } - @$child =& $this->children[$next]; - if (!$child) { - $child = new PlHookTree(); - $this->children[$next] =& $child; - $child->aliased = $alias; - } - if (empty($path)) { - $child->hook = $hook; - } else { - $child->addChild($path, $hook); - } - } - - private function findChildAux(array $remain, array $matched, array $aliased) - { - $next = @$remain[0]; - if ($this->aliased) { - $aliased = $matched; - } - if (!empty($next)) { - $child = @$this->children[$next]; - if ($child) { - array_shift($remain); - $matched[] = $next; - return $child->findChildAux($remain, $matched, $aliased); + foreach ($hooks as $path=>$hook) { + $path = explode('/', $path); + $element = $this; + foreach ($path as $next) { + $alias = null; + if ($next{0} == '%') { + $alias = $next; + $next = $platal->hook_map(substr($next, 1)); + } + if (!isset($element->children[$next])) { + $child = new PlHookTree(); + $child->aliased = $alias; + $element->children[$next] = $child; + } else { + $child = $element->children[$next]; + } + $element = $child; } + $element->hook = $hook; } - return array($this->hook, $matched, $remain, $aliased); } public function findChild(array $path) { - return $this->findChildAux($path, array(), array()); + $remain = $path; + $matched = array(); + $aliased = array(); + $element = $this; + while (true) + { + $next = @$remain[0]; + if ($element->aliased) { + $aliased = $matched; + } + if (empty($next) || !isset($element->children[$next])) { + break; + } + $element = $element->children[$next]; + array_shift($remain); + $matched[] = $next; + } + return array($element->hook, $matched, $remain, $aliased); } private function findNearestChildAux(array $remain, array $matched, array $aliased) @@ -265,10 +401,7 @@ abstract class Platal foreach ($modules as $module) { $module = strtolower($module); $this->mods[$module] = $m = PLModule::factory($module); - $hooks = $m->handlers(); - foreach ($hooks as $path=>$hook) { - $this->hooks->addChild(explode('/', $path), $hook); - } + $this->hooks->addChildren($m->handlers()); } if ($globals->mode == '') { @@ -364,6 +497,10 @@ abstract class Platal $page->assign('platal', $this); $res = $this->call_hook($page); switch ($res) { + case PL_BAD_REQUEST: + $this->mods['core']->handler_400($page); + break; + case PL_FORBIDDEN: $this->mods['core']->handler_403($page); break;