Fixes deprecated features in PHP 5.3.x.
[platal.git] / modules / openid.php
index 1114a39..15e7565 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /***************************************************************************
- *  Copyright (C) 2003-2008 Polytechnique.org                              *
+ *  Copyright (C) 2003-2011 Polytechnique.org                              *
  *  http://opensource.polytechnique.org/                                   *
  *                                                                         *
  *  This program is free software; you can redistribute it and/or modify   *
 
 /* Definitions for the OpenId Specification
  * http://openid.net/specs/openid-authentication-2_0.html
- * 
+ *
  * OP Endpoint URL:             https://www.polytechnique.org/openid
  * OP Identifier:               https://www.polytechnique.org/openid
- * User-Supplied Identifier:    https://www.polytechnique.org/openid/{$hruid}
- *    Identity selection is not supported by this implementation
- * OP-Local Identifier:         {$hruid}
+ * User Identifier:             https://www.polytechnique.org/openid/{hruid}
+ * OP-Local Identifier:         {hruid}
  */
 
+/* This implementation supports two modes:
+ *     - entering the OP Identifier, which can simply be 'polytechnique.org'
+ *     - entering the User Identifier, or some URL that resolves there
+ * In both cases, Yadis discovery is made possible through the X-XRDS-Location
+ * header.
+ *
+ * In the former case, Yadis discovery is performed on /, or where it redirects;
+ * see platal.php. It resolves to the XRDS for this OP, and User Identifier
+ * selection is performed after forcing the user to log in. This only works for
+ * version 2.0 of the OpenId protocol.
+ *
+ * In the latter cas, Yadis discovery is performed on /openid/{hruid}. It
+ * resolves ta a user-specific XRDS. This page also features HTML-based
+ * discovery. This works with any version of the protocol.
+ */
+
+/* Testing suite is here:
+ * http://openidenabled.com/resources/openid-test/
+ * It only supports User Indentifiers.
+ *
+ * To test OP Identifiers, download the JanRain PHP library and use the
+ * consumer provided as an example (although it appears that a failure is
+ * mistakenly reported: 'Server denied check_authentication').
+ * Reading the source of the server can also help understanding the code below.
+ */
+
+
 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'                => $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_MDP),
+            'openid/trusted'        => $this->make_hook('trusted', AUTH_MDP),
+            'admin/openid/trusted'  => $this->make_hook('admin_trusted', AUTH_MDP, 'admin'),
         );
     }
 
-    function handler_openid(&$page, $x = null)
+    function handler_openid($page, $login = null)
     {
         $this->load('openid.inc.php');
-        $user = get_user($x);
-
-        // Display the discovery page
-        if ($_SERVER['REQUEST_METHOD'] != 'POST' && !array_key_exists('openid_mode', $_GET)) {
-            return $this->render_discovery_page($page, $user);
-        }
-
-        // Create a server and decode the request
-        $server = init_openid_server();
-        $request = $server->decodeRequest();
-
-        if (in_array($request->mode,
-                     array('checkid_immediate', 'checkid_setup'))) {
-
-            // Each user has only one identity to choose from
-            // So we can make automatically the identity selection
-            if ($request->idSelect()) {
-                $request->identity = get_user_openid_url($user);
-            }
-
-            // If we still don't have an identifier (used or desired), give up
-            if (!$request->identity) {
-                $this->render_no_identifier_page($page, $request);
+        $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;
+        }
 
-            // We always require confirmation before sending information
-            // to third-party websites
-            if ($request->immediate) {
-                $response =& $request->answer(false, get_openid_url());
+        // 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 {
-                // Save request in session and jump to confirmation page
-                S::set('request', serialize($request));
-                pl_redirect('openid/trust');
-                return;
+                // 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 { // Other $request->mode
-            $response =& $server->handleRequest($request);
+        } else {
+            $server->HandleRequest();
         }
 
-        // Render response
-        $webresponse =& $server->encodeResponse($response);
-        $this->render_openid_response($webresponse, true);
+        // 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('request');
-        if (is_null($request)) {
-            // There is no authentication information, something went wrong
-            pl_redirect('/');
-            return;
-        } else {
-            // 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();
-
-        // Check that the identity matches the user currently logged in
-        if ($request->identity != get_user_openid_url($user)) {
-            $response =& $request->answer(false);
-            $webresponse =& $server->encodeResponse($response);
-            $this->render_openid_response($webresponse);
-            return;
-        }
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $page->changeTpl('openid/trust.tpl');
-            $page->assign('relying_party', $request->trust_root);
-            return;
-        }
-
-        if (isset($_POST['trust'])) { // $_SERVER['REQUEST_METHOD'] == 'POST'
-            unset($_SESSION['request']);
-            $response =& $request->answer(true, null, $request->identity);
-
-            // Answer with some sample Simple Registration data.
-            // TODO USE REAL USER DATA
-            // $user = S::user();
-            $sreg_data = array(
-                               'fullname' => 'Example User',
-                               'nickname' => 'example',
-                               'dob' => '1970-01-01',
-                               'email' => 'invalid@example.com',
-                               'gender' => 'F',
-                               'postcode' => '12345',
-                               'country' => 'ES',
-                               'language' => 'eu',
-                               'timezone' => 'America/New_York');
-
-            // Add the simple registration response values to the OpenID
-            // response message.
-            require_once 'Auth/OpenID/SReg.php';
-            $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
-            $sreg_response = Auth_OpenID_SRegResponse::extractResponse($sreg_request, $sreg_data);
-            $sreg_response->toMessage($response->fields);
-
-            // Generate a response to send to the user agent.
-            $webresponse =& $server->encodeResponse($response);
-            $this->render_openid_response($webresponse);
+        if ($melix && ($requested_user = User::getSilent($melix))) {
+            $server = new OpenId();
+            $server->RenderDiscoveryPage($page, $requested_user);
         } else {
-            pl_redirect('');
-            return;
+            pl_redirect('Xorg/OpenId');
         }
     }
 
-    function handler_idp_xrds(&$page)
+    function handler_xrds($page, $login = null)
     {
-        // Load constants
         $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->changeTpl('openid/idp_xrds.tpl', NO_SKIN);
-        $page->assign('type', Auth_OpenID_TYPE_2_0_IDP);
-        $page->assign('uri', get_openid_url());
+        if (!$login) {
+            $server->RenderMainXrdsPage($page);
+        } else if ($requested_user) {
+            $server->RenderUserXrdsPage($page, $requested_user);
+        } else {
+            return PL_NOT_FOUND;
+        }
     }
 
-    function handler_user_xrds(&$page, $x = null)
+    function handler_trust($page)
     {
-        // Load constants
         $this->load('openid.inc.php');
+        $server = new OpenId();
+        $user = S::user();
 
-        // Set XRDS content-type and template
-        header('Content-type: application/xrds+xml');
-        $page->changeTpl('openid/user_xrds.tpl', NO_SKIN);
-
-        // Set variables
-        $page->assign('type1', Auth_OpenID_TYPE_2_0);
-        $page->assign('type2', Auth_OpenID_TYPE_1_1);
-        $page->assign('uri', get_openid_url());
-    }
-
-    //--------------------------------------------------------------------//
-
-    function render_discovery_page(&$page, $user)
-    {
-
-        if (is_null($user)) {
-            return PL_NOT_FOUND;
+        // 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));
+        // Prepares the SREG data, if any is required.
+        $sreg_response = $server->GetSRegDataForRequest($user);
 
-        // Select template
-        $page->changeTpl('openid/openid.tpl');
-
-        // 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_no_identifier_page($page, $request)
+    function handler_trusted($page, $action = 'list', $id = null)
     {
-        $page->changeTpl('openid/no_identifier.tpl');
+        $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'] = false;
+        $table_editor->describe('url', 'site tiers', true);
+        $page->assign('deleteonly', true);
+        $table_editor->apply($page, $action, $id);
     }
 
-    // TODO determine when to close the connection or not
-    // TODO i don't why it was done that way in the example
-    function render_openid_response($webresponse, $close = false)
+    function handler_admin_trusted($page, $action = 'list', $id = null)
     {
-        if ($webresponse->code != AUTH_OPENID_HTTP_OK) {
-            header(sprintf("HTTP/1.1 %d ", $webresponse->code),
-                   true, $webresponse->code);
-        }
-
-        foreach ($webresponse->headers as $k => $v) {
-            header("$k: $v");
-        }
-
-        if ($close) {
-            header('Connection: close');
-        }
-        print $webresponse->body;
-        exit;
+        $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'] = false;
+        $table_editor->describe('url', 'site tiers', true);
+        $page->assign('readonly', true);
+        $table_editor->apply($page, $action, $id);
     }
 }