Moving to GitHub.
[platal.git] / modules / openid.php
index e3ac3bc..6c2f1d4 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /***************************************************************************
- *  Copyright (C) 2003-2008 Polytechnique.org                              *
+ *  Copyright (C) 2003-2014 Polytechnique.org                              *
  *  http://opensource.polytechnique.org/                                   *
  *                                                                         *
  *  This program is free software; you can redistribute it and/or modify   *
  * Reading the source of the server can also help understanding the code below.
  */
 
-/* **checkid_immediate is not supported (yet)**, which means that we will
- * always ask for confirmation before redirecting to a third-party.
- * A sensible way to implement it would be to add a "Always trust this site"
- * checkbox to the form, and to store trusted websites per user. This still
- * raises the question of removing websites from that list.
- * Another possibility is to maintain a global whitelist.
- */
 
 class OpenidModule extends PLModule
 {
     function handlers()
     {
         return array(
-            'openid'            => $this->make_hook('openid', AUTH_PUBLIC),
-            'openid/trust'      => $this->make_hook('trust', AUTH_COOKIE),
-            'openid/idp_xrds'   => $this->make_hook('idp_xrds', AUTH_PUBLIC),
-            'openid/user_xrds'  => $this->make_hook('user_xrds', AUTH_PUBLIC),
-            'openid/melix'      => $this->make_hook('melix', AUTH_PUBLIC),
+            'openid'               => $this->make_hook('openid',        AUTH_PUBLIC),
+            'openid/melix'         => $this->make_hook('melix',         AUTH_PUBLIC),
+            'openid/xrds'          => $this->make_hook('xrds',          AUTH_PUBLIC),
+            'openid/trust'         => $this->make_hook('trust',         AUTH_PASSWD, 'user'),
+            'openid/trusted'       => $this->make_hook('trusted',       AUTH_PASSWD, 'user'),
+            'admin/openid/trusted' => $this->make_hook('admin_trusted', AUTH_PASSWD, 'admin'),
         );
     }
 
-    function handler_openid(&$page, $x = null)
+    function handler_openid($page, $login = null)
     {
         $this->load('openid.inc.php');
-
-        // Spec §4.1.2: if "openid.mode" is absent, whe SHOULD assume that
-        // the request is not an OpenId message
-        // Thus, we try to render the discovery page
-        if (!array_key_exists('openid_mode', $_REQUEST)) {
-            return $this->render_discovery_page($page, get_user($x));
+        $requested_user = User::getSilent($login);
+        $server = new OpenId();
+
+        // Spec §4.1.2: if "openid.mode" is absent, we SHOULD assume that
+        // the request is not an OpenId message.
+        if (!$server->IsOpenIdRequest()) {
+            if ($requested_user) {
+                $server->RenderDiscoveryPage($page, $requested_user);
+                return;
+            } else {
+                pl_redirect('Xorg/OpenId');
+            }
+            exit;
         }
 
-        // Now, deal with the OpenId message
-        // Create a server and decode the request
-        $server = init_openid_server();
-        $request = $server->decodeRequest();
-
-        // In modes 'checkid_immediate' and 'checkid_setup', the request
-        // needs some logic and can not be automatically answered by the server
-
-        // Immediate mode
-        if ($request->mode == 'checkid_immediate') {
-
-            // We deny immediate requests, unless:
-            //   - the user identifier is known by the RP
-            //   - the user is logged in
-            //   - the user identifier matches the user logged in
-            //   - the user and has whitelisted the site
-            $answer = !$request->idSelect()
-                      && S::logged()
-                      && $request->identity == S::user()->login()
-                      && is_trusted_site(S::user(), $request->trust_root);
-            $response =& $request->answer($answer);
-
-        // Setup mode
-        } else if ($request->mode == 'checkid_setup') {
-
-            // We redirect to a page where the user will authenticate
-            // and confirm the use of his/her OpenId
-            // Save request in session before jumping to confirmation page
-            S::set('openid_request', serialize($request));
-            pl_redirect('openid/trust');
-            return;
-
-        // Other requests can be automatically handled by the server
+        // Initializes the OpenId environment from the request.
+        $server->Initialize();
+
+        // In modes 'checkid_immediate' and 'checkid_setup', we need to check
+        // by ourselves that we want to allow the user to be authenticated.
+        // Otherwise it can simply be forwarded to the Server object.
+        if ($server->IsAuthorizationRequest()) {
+            $authorized = S::logged() &&
+                $server->IsUserAuthorized(S::user()) &&
+                $server->IsEndpointTrusted(S::user());
+
+            if ($authorized) {
+                // TODO(vzanotti): SReg requests are currently not honored if
+                // the website is already trusted. We may want to redirect SReg
+                // requests to /openid/trust, to allow the user to choose.
+                $server->AnswerRequest(true);
+            } else if ($server->IsImmediateRequest()) {
+                $server->AnswerRequest(false);
+            } else {
+                // The user is currently not authorized to get her authorization
+                // request approved. Two possibilities:
+                //  * the endpoint is not yet trusted => redirect to openid/trust
+                //  * the user is not logged in => log in the user.
+                //
+                // The second case requires a special handling when the request
+                // was POSTed, as our current log in mechanism does not preserve
+                // POST arguments.
+                $openid_args = $server->GetQueryStringForRequest();
+                if (S::logged()) {
+                    pl_redirect('openid/trust', $openid_args);
+                } else if (Post::has('openid_mode')) {
+                    pl_redirect('openid', $openid_args);
+                } else {
+                    return PL_DO_AUTH;
+                }
+            }
         } else {
-            $response =& $server->handleRequest($request);
+            $server->HandleRequest();
         }
 
-        // Render response
-        $webresponse =& $server->encodeResponse($response);
-        $this->render_openid_response($webresponse);
+        // All requests should have been answered at this point. The best here
+        // is to get the user back to a safe page.
+        pl_redirect('');
     }
 
-    function handler_trust(&$page, $x = null)
+    function handler_melix($page, $login = null)
     {
         $this->load('openid.inc.php');
 
-        // Recover request in session
-        $request = S::v('openid_request');
-        if (is_null($request)) {
-            // There is no authentication information, something went wrong
-            pl_redirect('/');
-            return;
-        }
-
-        // Unserialize the request
-        require_once 'Auth/OpenID/Server.php';
-        $request = unserialize($request);
+        global $globals;
+        $melix = ($login ? $login . '@' . $globals->mail->alias_dom : null);
 
-        $server = init_openid_server();
-        $user = S::user();
-        $identity = null;
-        $claimed_id = null;
-
-        // Set the identity to the user currently logged in
-        // if an OP Identifier was initially used
-        if ($request->identity == Auth_OpenID_IDENTIFIER_SELECT) {
-            $identity = $user->hruid;
-            $claimed_id = get_user_openid_url($user);
-        // Check that the identity matches the user currently logged in
-        // if an User Identifier was initially used
-        } else if ($request->identity != $user->hruid) {
-            $response =& $request->answer(false);
-            $webresponse =& $server->encodeResponse($response);
-            $this->render_openid_response($webresponse);
-            return;
-        }
-
-        // Prepare Simple Registration response fields
-        require_once 'Auth/OpenID/SReg.php';
-        $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
-        $sreg_response = Auth_OpenID_SRegResponse::extractResponse($sreg_request, get_sreg_data($user));
-
-        // Check the whitelist
-        $whitelisted = is_trusted_site($user, $request->trust_root);
-
-        // Ask the user for confirmation
-        if (!$whitelisted && $_SERVER['REQUEST_METHOD'] != 'POST') {
-            $page->changeTpl('openid/trust.tpl');
-            $page->assign('relying_party', $request->trust_root);
-            $page->assign_by_ref('sreg_data', $sreg_response->data);
-            return;
-        }
-
-        // At this point $_SERVER['REQUEST_METHOD'] == 'POST'
-        // Answer to the Relying Party
-        if ($whitelisted || isset($_POST['trust'])) {
-            S::kill('openid_request');
-            $response =& $request->answer(true, null, $identity, $claimed_id);
-
-            // Add the simple registration response values to the OpenID
-            // response message.
-            $sreg_response->toMessage($response->fields);
-
-        } else { // !$whitelisted && !isset($_POST['trust'])
-            S::kill('openid_request');
-            $response =& $request->answer(false);
+        if ($melix && ($requested_user = User::getSilent($melix))) {
+            $server = new OpenId();
+            $server->RenderDiscoveryPage($page, $requested_user);
+        } else {
+            pl_redirect('Xorg/OpenId');
         }
-
-        // Generate a response to send to the user agent.
-        $webresponse =& $server->encodeResponse($response);
-        $this->render_openid_response($webresponse);
     }
 
-    function handler_idp_xrds(&$page)
+    function handler_xrds($page, $login = null)
     {
         $this->load('openid.inc.php');
+        $requested_user = User::getSilent($login);
+        $server = new OpenId();
 
-        // Set XRDS content-type and template
-        header('Content-type: application/xrds+xml');
-        $page->changeTpl('openid/idp_xrds.tpl', NO_SKIN);
-
-        // Set variables
-        $page->assign('type2', Auth_OpenID_TYPE_2_0_IDP);
-        $page->assign('sreg', Auth_OpenID_SREG_URI);
-        $page->assign('provider', get_openid_url());
-    }
-
-    function handler_user_xrds(&$page, $x = null)
-    {
-        $this->load('openid.inc.php');
-
-        // Make sure the user exists
-        $user = get_user($x);
-        if (is_null($user)) {
+        if (!$login) {
+            $server->RenderMainXrdsPage($page);
+        } else if ($requested_user) {
+            $server->RenderUserXrdsPage($page, $requested_user);
+        } else {
             return PL_NOT_FOUND;
         }
-
-        // Set XRDS content-type and template
-        header('Content-type: application/xrds+xml');
-        $page->changeTpl('openid/user_xrds.tpl', NO_SKIN);
-
-        // Set variables
-        $page->assign('type2', Auth_OpenID_TYPE_2_0);
-        $page->assign('type1', Auth_OpenID_TYPE_1_1);
-        $page->assign('sreg', Auth_OpenID_SREG_URI);
-        $page->assign('provider', get_openid_url());
-        $page->assign('local_id', $user->hruid);
     }
 
-    function handler_melix(&$page, $x = null)
+    function handler_trust($page)
     {
         $this->load('openid.inc.php');
-        $user = get_user_by_alias($x);
-
-        // This will redirect to the canonic URL, which was not used
-        // if this hook was triggered
-        return $this->render_discovery_page(&$page, $user);
-    }
-
-    //--------------------------------------------------------------------//
-
-    function render_discovery_page(&$page, $user)
-    {
-
-        // Show the documentation if this is not the OpenId page of an user
-        if (is_null($user)) {
-            pl_redirect('Xorg/OpenId');
-        }
+        $server = new OpenId();
+        $user = S::user();
 
-        // Redirect to the canonic URL if we are using an alias
-        // There might be a risk of redirection loop here
-        // if $_SERVER was not exactly what we expect
-        $current_url = 'http' . (empty($_SERVER['HTTPS']) ? '' : 's') . '://'
-                       . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
-        $canonic_url = get_user_openid_url($user);
-        if ($current_url != $canonic_url) {
-            http_redirect($canonic_url);
+        // Initializes the OpenId environment from the request.
+        if (!$server->Initialize() || !$server->IsAuthorizationRequest()) {
+            $page->kill("Ta requête OpenID a échoué, merci de réessayer.");
         }
 
-        // Include X-XRDS-Location response-header for Yadis discovery
-        header('X-XRDS-Location: ' . get_user_xrds_url($user));
-
-        // Select template
-        $page->changeTpl('openid/openid.tpl');
+        // Prepares the SREG data, if any is required.
+        $sreg_response = $server->GetSRegDataForRequest($user);
 
-        // Sets the title of the html page.
-        $page->setTitle($user->fullName());
+        // Asks the user about her trust level of the current request, if not
+        // done yet.
+        if (!Post::has('trust_accept') && !Post::has('trust_cancel')) {
+            $page->changeTpl('openid/trust.tpl');
+            $page->assign('openid_query', $server->GetQueryStringForRequest());
+            $page->assign('relying_party', $server->GetEndpoint());
+            $page->assign('sreg_data', $sreg_response->contents());
 
-        // Sets the <link> tags for HTML-Based Discovery
-        $page->addLink('openid.server openid2.provider', get_openid_url());
-        $page->addLink('openid.delegate openid2.local_id', $user->hruid);
+            return;
+        }
 
-        // Adds the global user property array to the display.
-        $page->assign_by_ref('user', $user);
+        // Interprets the form results, and updates the user whitelist.
+        S::assert_xsrf_token();
+        $trusted = $server->UpdateEndpointTrust(
+            $user,
+            Post::b('trust_accept') && !Post::b('trust_cancel'),
+            Post::b('trust_always'));
 
-        return;
+        // Finally answers the request.
+        if ($server->IsUserAuthorized($user) && $trusted) {
+            $server->AnswerRequest(true, Post::b('trust_sreg') ? $sreg_response : null);
+        } else {
+            $server->AnswerRequest(false);
+        }
     }
 
-    function render_openid_response($webresponse)
+    function handler_trusted($page, $action = 'list', $id = null)
     {
-        // Send HTTP response code
-        if ($webresponse->code != AUTH_OPENID_HTTP_OK) {
-            header(sprintf("HTTP/1.1 %d ", $webresponse->code),
-                   true, $webresponse->code);
-        }
-
-        // Send headers
-        foreach ($webresponse->headers as $k => $v) {
-            header("$k: $v");
-        }
-        header('Connection: close');
+        $page->setTitle('Sites tiers de confiance');
+        $page->assign('title', 'Mes sites tiers de confiance pour OpenId');
+        $table_editor = new PLTableEditor('openid/trusted', 'account_auth_openid', 'id');
+        $table_editor->set_where_clause(XDB::format('uid = {?}',  S::user()->id()));
+        $table_editor->vars['uid']['display_list'] = false;
+        $table_editor->vars['uid']['display_item'] = false;
+        $table_editor->describe('url', 'site tiers', true);
+        $page->assign('deleteonly', true);
+        $table_editor->apply($page, $action, $id);
+    }
 
-        // Send body
-        print $webresponse->body;
-        exit;
+    function handler_admin_trusted($page, $action = 'list', $id = null)
+    {
+        $page->setTitle('Sites tiers de confiance');
+        $page->assign('title', 'Sites tiers de confiance globaux pour OpenId');
+        $table_editor = new PLTableEditor('admin/openid/trusted', 'account_auth_openid', 'id');
+        $table_editor->set_where_clause('uid IS NULL');
+        $table_editor->vars['uid']['display_list'] = false;
+        $table_editor->vars['uid']['display_item'] = false;
+        $table_editor->describe('url', 'site tiers', true);
+        $page->assign('readonly', true);
+        $table_editor->apply($page, $action, $id);
     }
 }
 
-// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
-?>
\ No newline at end of file
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker fenc=utf-8:
+?>