Nico Huber | ee52fbc | 2023-06-24 11:52:57 +0000 | [diff] [blame] | 1 | <?php |
| 2 | /* |
| 3 | * SimpleID |
| 4 | * |
| 5 | * Copyright (C) Kelvin Mo 2007-8 |
| 6 | * |
| 7 | * Includes code Drupal OpenID module (http://drupal.org/project/openid) |
| 8 | * Rowan Kerr <rowan@standardinteractive.com> |
| 9 | * James Walker <james@bryght.com> |
| 10 | * |
| 11 | * Copyright (C) Rowan Kerr and James Walker |
| 12 | * |
| 13 | * This program is free software; you can redistribute it and/or |
| 14 | * modify it under the terms of the GNU General Public |
| 15 | * License as published by the Free Software Foundation; either |
| 16 | * version 2 of the License, or (at your option) any later version. |
| 17 | * |
| 18 | * This program is distributed in the hope that it will be useful, |
| 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 21 | * General Public License for more details. |
| 22 | * |
| 23 | * You should have received a copy of the GNU General Public |
| 24 | * License along with this program; if not, write to the Free |
| 25 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| 26 | * |
| 27 | * $Id$ |
| 28 | */ |
| 29 | |
| 30 | /** |
| 31 | * Main SimpleID file. |
| 32 | * |
| 33 | * @package simpleid |
| 34 | * @filesource |
| 35 | */ |
| 36 | |
| 37 | include_once "version.inc.php"; |
| 38 | include_once "locale.inc.php"; |
| 39 | |
| 40 | // Check if the configuration file has been defined |
| 41 | if (file_exists('conf/config.php')) { |
| 42 | include_once 'conf/config.php'; |
| 43 | } elseif (file_exists('config.php')) { |
| 44 | include_once 'config.php'; |
| 45 | } else { |
| 46 | die(t('No configuration file found. See the <a href="!url">manual</a> for instructions on how to set up a configuration file.', array('!url' => 'http://simpleid.org/docs/1/installing/'))); |
| 47 | } |
| 48 | |
| 49 | include_once "config.default.php"; |
| 50 | include_once "log.inc.php"; |
| 51 | include_once "common.inc.php"; |
| 52 | include_once "simpleweb.inc.php"; |
| 53 | include_once "openid.inc.php"; |
| 54 | include_once "discovery.inc.php"; |
| 55 | include_once "user.inc.php"; |
| 56 | include_once "cache.inc.php"; |
| 57 | include_once SIMPLEID_STORE . ".store.php"; |
| 58 | include_once "page.inc.php"; |
| 59 | include_once "lib/xtemplate.class.php"; |
| 60 | |
| 61 | define('CACHE_DIR', SIMPLEID_CACHE_DIR); |
| 62 | |
| 63 | /** |
| 64 | */ |
| 65 | define('CHECKID_OK', 127); |
| 66 | define('CHECKID_RETURN_TO_SUSPECT', 3); |
| 67 | define('CHECKID_APPROVAL_REQUIRED', 2); |
| 68 | define('CHECKID_LOGIN_REQUIRED', -1); |
| 69 | define('CHECKID_IDENTITIES_NOT_MATCHING', -2); |
| 70 | define('CHECKID_IDENTITY_NOT_EXIST', -3); |
| 71 | define('CHECKID_PROTOCOL_ERROR', -127); |
| 72 | |
| 73 | define('ASSOCIATION_PRIVATE', 2); |
| 74 | define('ASSOCIATION_SHARED', 1); |
| 75 | |
| 76 | |
| 77 | /** |
| 78 | * This variable holds the version of the OpenID specification associated with |
| 79 | * the current OpenID request. This can be either {@link OPENID_VERSION_1_1} |
| 80 | * or {@link OPENID_VERSION_2}. |
| 81 | * |
| 82 | * @global float $version |
| 83 | */ |
| 84 | $version = OPENID_VERSION_1_1; |
| 85 | |
| 86 | /** |
| 87 | * This variable holds an instance of the XTemplate engine. |
| 88 | * |
| 89 | * @global object $xtpl |
| 90 | */ |
| 91 | $xtpl = NULL; |
| 92 | |
| 93 | /** |
| 94 | * This variable holds the combined $_GET and $_POST superglobal arrays. |
| 95 | * This is then passed through {@link fix_http_request()}. |
| 96 | * |
| 97 | * @global array $GETPOST |
| 98 | */ |
| 99 | $GETPOST = array(); |
| 100 | |
| 101 | simpleid_start(); |
| 102 | |
| 103 | /** |
| 104 | * Entry point for SimpleID. |
| 105 | * |
| 106 | * @see user_init() |
| 107 | */ |
| 108 | function simpleid_start() { |
| 109 | global $xtpl, $GETPOST; |
| 110 | |
| 111 | locale_init(SIMPLEID_LOCALE); |
| 112 | |
| 113 | $xtpl = new XTemplate('html/template.xtpl'); |
| 114 | $xtpl->assign('version', SIMPLEID_VERSION); |
| 115 | $xtpl->assign('base_path', get_base_path()); |
| 116 | $xtpl->assign('footer_doc', t('Documentation')); |
| 117 | $xtpl->assign('footer_support', t('Support')); |
| 118 | |
| 119 | if (!is_dir(SIMPLEID_IDENTITIES_DIR)) { |
| 120 | log_fatal('Identities directory not found.'); |
| 121 | indirect_fatal_error(t('Identities directory not found. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/documentation/getting-started'))); |
| 122 | } |
| 123 | |
| 124 | if (!is_dir(SIMPLEID_CACHE_DIR) || !is_writeable(SIMPLEID_CACHE_DIR)) { |
| 125 | log_fatal('Cache directory not found or not writeable.'); |
| 126 | indirect_fatal_error(t('Cache directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/'))); |
| 127 | } |
| 128 | |
| 129 | |
| 130 | if (!is_dir(SIMPLEID_STORE_DIR) || !is_writeable(SIMPLEID_STORE_DIR)) { |
| 131 | log_fatal('Store directory not found or not writeable.'); |
| 132 | indirect_fatal_error(t('Store directory not found or not writeable. See the <a href="!url">manual</a> for instructions on how to set up SimpleID.', array('!url' => 'http://simpleid.org/docs/1/installing/'))); |
| 133 | } |
| 134 | |
| 135 | if ((@ini_get('register_globals') === 1) || (@ini_get('register_globals') === '1') || (strtolower(@ini_get('register_globals')) == 'on')) { |
| 136 | log_fatal('register_globals is enabled in PHP configuration.'); |
| 137 | indirect_fatal_error(t('register_globals is enabled in PHP configuration, which is not supported by SimpleID. See the <a href="!url">manual</a> for further information.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 138 | } |
| 139 | |
| 140 | if (!bignum_loaded()) { |
| 141 | log_fatal('gmp/bcmath PHP extension not loaded.'); |
| 142 | indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'gmp/bcmath', '!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 143 | } |
| 144 | if (!function_exists('preg_match')) { |
| 145 | log_fatal('pcre PHP extension not loaded.'); |
| 146 | indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'pcre', '!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 147 | } |
| 148 | if (!function_exists('session_start')) { |
| 149 | log_fatal('session PHP extension not loaded.'); |
| 150 | indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'session', '!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 151 | } |
| 152 | if (!function_exists('xml_parser_create_ns')) { |
| 153 | log_fatal('xml PHP extension not loaded.'); |
| 154 | indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'xml', '!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 155 | } |
| 156 | if (!function_exists('hash')) { |
| 157 | log_fatal('hash PHP extension not loaded.'); |
| 158 | indirect_fatal_error(t('One or more required PHP extensions (%extension) is not loaded. See the <a href="!url">manual</a> for further information on system requirements.', array('%extension' => 'hash', '!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 159 | } |
| 160 | if (is_numeric(@ini_get('suhosin.get.max_value_length')) && (@ini_get('suhosin.get.max_value_length') < 1024)) { |
| 161 | log_fatal('suhosin.get.max_value_length < 1024'); |
| 162 | indirect_fatal_error(t('suhosin.get.max_value_length is less than 1024, which will lead to problems. See the <a href="!url">manual</a> for further information on system requirements.', array('!url' => 'http://simpleid.org/docs/1/system-requirements/'))); |
| 163 | } |
| 164 | |
| 165 | fix_http_request(); |
| 166 | $GETPOST = array_merge($_GET, $_POST); |
| 167 | openid_parse_request($GETPOST); |
| 168 | |
| 169 | $q = (isset($GETPOST['q'])) ? $GETPOST['q'] : ''; |
| 170 | |
| 171 | $uaid = get_user_agent_id(); |
| 172 | extension_init(); |
| 173 | user_init($q); |
| 174 | log_info('Session opened for "' . $q . '" from ' . $uaid . ' [' . $_SERVER['REMOTE_ADDR'] . ', ' . gethostbyaddr($_SERVER['REMOTE_ADDR']) . ']'); |
| 175 | |
| 176 | // Clean stale assocations |
| 177 | cache_expire(array( |
| 178 | 'association' => SIMPLEID_ASSOC_EXPIRES_IN, |
| 179 | 'stateless' => 300) |
| 180 | ); |
| 181 | |
| 182 | simpleid_route($q); |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * Dispatches to the correct SimpleID function based on the request path. The |
| 187 | * request path usually comes from the q parameter in the query string (which may |
| 188 | * be inserted by mod_rewrite), but can come from other functions as well. |
| 189 | * |
| 190 | * @param string $q the request path |
| 191 | */ |
| 192 | function simpleid_route($q) { |
| 193 | $routes = array( |
| 194 | 'continue' => 'simpleid_continue', |
| 195 | 'login' => 'user_login', |
| 196 | 'logout' => 'user_logout', |
| 197 | 'my/dashboard' => 'page_dashboard', |
| 198 | 'my/sites' => 'page_sites', |
| 199 | 'my/profile' => 'page_profile', |
| 200 | 'otp' => 'user_otp_page', |
| 201 | 'openid/consent' => 'simpleid_openid_consent', |
| 202 | 'ppid/(.*)' => 'user_ppid_page', |
| 203 | 'user' => 'user_public_page', |
| 204 | 'user/(.+)' => 'user_public_page', |
| 205 | 'xrds/(.*)' => 'user_xrds', |
| 206 | 'xrds' => 'simpleid_xrds', |
| 207 | ); |
| 208 | $routes = array_merge($routes, extension_invoke_all('routes'), array('.*' => 'simpleid_index')); |
| 209 | |
| 210 | simpleweb_run($routes, $q); |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * The default route, called when the q parameter is missing or is invalid. |
| 215 | * |
| 216 | * This function performs the following: |
| 217 | * |
| 218 | * - If openid.mode is present, then the request is an OpenID request. This |
| 219 | * is passed to {@link simpleid_process_openid()} |
| 220 | * - If the Accept HTTP header contains the expression application/xrds+xml, then |
| 221 | * the request is a YADIS discovery request for SimpleID as an OpenID provider. Thi |
| 222 | * is passed to {@link simpleid_xrds()} |
| 223 | * - Otherwise, the dashboard or the login page is displayed to the user as |
| 224 | * appropriate |
| 225 | * |
| 226 | */ |
| 227 | function simpleid_index() { |
| 228 | global $GETPOST; |
| 229 | |
| 230 | log_debug('simpleid_index'); |
| 231 | |
| 232 | $content_type = negotiate_content_type(array('text/html', 'application/xml', 'application/xhtml+xml', 'application/xrds+xml')); |
| 233 | |
| 234 | header('Vary: Accept'); |
| 235 | if (isset($GETPOST['openid.mode'])) { |
| 236 | simpleid_process_openid($GETPOST); |
| 237 | return; |
| 238 | } elseif ($content_type == 'application/xrds+xml') { |
| 239 | simpleid_xrds(); |
| 240 | } else { |
| 241 | // Point to SimpleID's XRDS document |
| 242 | header('X-XRDS-Location: ' . simpleid_url('xrds')); |
| 243 | page_dashboard(); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | |
| 248 | /** |
| 249 | * Process an OpenID request. |
| 250 | * |
| 251 | * This function determines the version of the OpenID specification that is |
| 252 | * relevant to this request, checks openid.mode and passes the |
| 253 | * request on to the function required to process the request. |
| 254 | * |
| 255 | * The OpenID request expressed as an array contain key-value pairs corresponding |
| 256 | * to the HTTP request. This is usually contained in the <code>$_REQUEST</code> |
| 257 | * variable. |
| 258 | * |
| 259 | * @param array $request the OpenID request |
| 260 | */ |
| 261 | function simpleid_process_openid($request) { |
| 262 | global $version; |
| 263 | |
| 264 | $version = openid_get_version($request); |
| 265 | |
| 266 | switch ($request['openid.mode']) { |
| 267 | case 'associate': |
| 268 | simpleid_associate($request); |
| 269 | return; |
| 270 | case 'checkid_immediate': |
| 271 | case 'checkid_setup': |
| 272 | check_https('redirect', true, simpleid_url('continue', 's=' . rawurlencode(pickle($request)), false, 'https')); |
| 273 | |
| 274 | return simpleid_checkid($request); |
| 275 | case 'check_authentication': |
| 276 | simpleid_check_authentication($request); |
| 277 | break; |
| 278 | default: |
| 279 | if (isset($request['openid.return_to'])) { |
| 280 | // Indirect communication - send error via indirect communication. |
| 281 | header_response_code('400 Bad Request'); |
| 282 | set_message(t('Invalid OpenID message.')); |
| 283 | page_dashboard(); |
| 284 | } else { |
| 285 | // Direct communication |
| 286 | openid_direct_error('Invalid OpenID message.'); |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Processes an association request from a relying party. |
| 293 | * |
| 294 | * An association request has an openid.mode value of |
| 295 | * associate. This function checks whether the association request |
| 296 | * is valid, and if so, creates an association and sends the response to |
| 297 | * the relying party. |
| 298 | * |
| 299 | * @see _simpleid_create_association() |
| 300 | * @param array $request the OpenID request |
| 301 | * @link http://openid.net/specs/openid-authentication-1_1.html#mode_associate, http://openid.net/specs/openid-authentication-2_0.html#associations |
| 302 | * |
| 303 | */ |
| 304 | function simpleid_associate($request) { |
| 305 | global $version; |
| 306 | |
| 307 | log_info('OpenID association request: ' . log_array($request)); |
| 308 | |
| 309 | $assoc_types = openid_association_types(); |
| 310 | $session_types = openid_session_types(is_https(), $version); |
| 311 | |
| 312 | // Common Request Parameters [8.1.1] |
| 313 | if (($version == OPENID_VERSION_1_1) && !isset($request['openid.session_type'])) $request['openid.session_type'] = ''; |
| 314 | $assoc_type = $request['openid.assoc_type']; |
| 315 | $session_type = $request['openid.session_type']; |
| 316 | |
| 317 | // Diffie-Hellman Request Parameters [8.1.2] |
| 318 | $dh_modulus = (isset($request['openid.dh_modulus'])) ? $request['openid.dh_modulus'] : NULL; |
| 319 | $dh_gen = (isset($request['openid.dh_gen'])) ? $request['openid.dh_gen'] : NULL; |
| 320 | $dh_consumer_public = $request['openid.dh_consumer_public']; |
| 321 | |
| 322 | if (!isset($request['openid.session_type']) || !isset($request['openid.assoc_type'])) { |
| 323 | log_error('Association failed: openid.session_type or openid.assoc_type not set'); |
| 324 | openid_direct_error('openid.session_type or openid.assoc_type not set'); |
| 325 | return; |
| 326 | } |
| 327 | |
| 328 | // Check if the assoc_type is supported |
| 329 | if (!array_key_exists($assoc_type, $assoc_types)) { |
| 330 | $error = array( |
| 331 | 'error_code' => 'unsupported-type', |
| 332 | 'session_type' => 'DH-SHA1', |
| 333 | 'assoc_type' => 'HMAC-SHA1' |
| 334 | ); |
| 335 | log_error('Association failed: The association type is not supported by SimpleID.'); |
| 336 | openid_direct_error('The association type is not supported by SimpleID.', $error, $version); |
| 337 | return; |
| 338 | } |
| 339 | // Check if the session_type is supported |
| 340 | if (!array_key_exists($session_type, $session_types)) { |
| 341 | $error = array( |
| 342 | 'error_code' => 'unsupported-type', |
| 343 | 'session_type' => 'DH-SHA1', |
| 344 | 'assoc_type' => 'HMAC-SHA1' |
| 345 | ); |
| 346 | log_error('Association failed: The session type is not supported by SimpleID.'); |
| 347 | openid_direct_error('The session type is not supported by SimpleID.', $error, $version); |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | if ($session_type == 'DH-SHA1' || $session_type == 'DH-SHA256') { |
| 352 | if (!$dh_consumer_public) { |
| 353 | log_error('Association failed: openid.dh_consumer_public not set'); |
| 354 | openid_direct_error('openid.dh_consumer_public not set'); |
| 355 | return; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | $response = _simpleid_create_association(ASSOCIATION_SHARED, $assoc_type, $session_type, $dh_modulus, $dh_gen, $dh_consumer_public); |
| 360 | |
| 361 | openid_direct_response(openid_direct_message($response, $version)); |
| 362 | } |
| 363 | |
| 364 | |
| 365 | /** |
| 366 | * Creates an association. |
| 367 | * |
| 368 | * This function calls {@link openid_dh_server_assoc()} where required, to |
| 369 | * generate the cryptographic values required for an association response. |
| 370 | * |
| 371 | * @param int $mode either ASSOCIATION_SHARED or ASSOCIATION_PRIVATE |
| 372 | * @param string $assoc_type a valid OpenID association type |
| 373 | * @param string $session_type a valid OpenID session type |
| 374 | * @param string $dh_modulus for Diffie-Hellman key exchange, the modulus encoded in Base64 |
| 375 | * @param string $dh_gen for Diffie-Hellman key exchange, g encoded in Base64 |
| 376 | * @param string $dh_consumer_public for Diffie-Hellman key exchange, the public key of the relying party encoded in Base64 |
| 377 | * @return mixed if $mode is ASSOCIATION_SHARED, an OpenID response |
| 378 | * to the association request, if $mode is ASSOCIATION_PRIVATE, the |
| 379 | * association data for storage. |
| 380 | * @link http://openid.net/specs/openid-authentication-1_1.html#anchor14, http://openid.net/specs/openid-authentication-2_0.html#anchor20 |
| 381 | */ |
| 382 | function _simpleid_create_association($mode = ASSOCIATION_SHARED, $assoc_type = 'HMAC-SHA1', $session_type = 'no-encryption', $dh_modulus = NULL, $dh_gen = NULL, $dh_consumer_public = NULL) { |
| 383 | global $version; |
| 384 | |
| 385 | $assoc_types = openid_association_types(); |
| 386 | $session_types = openid_session_types(is_https(), $version); |
| 387 | |
| 388 | $mac_size = $assoc_types[$assoc_type]['mac_size']; |
| 389 | $hmac_func = $assoc_types[$assoc_type]['hmac_func']; |
| 390 | |
| 391 | $assoc_handle = random_id(); |
| 392 | $expires_in = SIMPLEID_ASSOC_EXPIRES_IN; |
| 393 | |
| 394 | $secret = random_bytes($mac_size); |
| 395 | |
| 396 | $response = array( |
| 397 | 'assoc_handle' => $assoc_handle, |
| 398 | 'assoc_type' => $assoc_type, |
| 399 | 'expires_in' => $expires_in |
| 400 | ); |
| 401 | |
| 402 | // If $session_type is '', then it must be using OpenID 1.1 (blank parameter |
| 403 | // is not allowed for OpenID 2.0. For OpenID 1.1 blank requests, we don't |
| 404 | // put a session_type in the response. |
| 405 | if ($session_type != '') $response['session_type'] = $session_type; |
| 406 | |
| 407 | if (($session_type == 'no-encryption') || ($session_type == '')) { |
| 408 | $mac_key = base64_encode(call_user_func($hmac_func, $secret, $response['assoc_handle'])); |
| 409 | $response['mac_key'] = $mac_key; |
| 410 | } elseif ($session_type == 'DH-SHA1' || $session_type == 'DH-SHA256') { |
| 411 | $hash_func = $session_types[$session_type]['hash_func']; |
| 412 | |
| 413 | $dh_assoc = openid_dh_server_assoc($secret, $dh_consumer_public, $dh_modulus, $dh_gen, $hash_func); |
| 414 | $mac_key = base64_encode($secret); |
| 415 | $response['dh_server_public'] = $dh_assoc['dh_server_public']; |
| 416 | $response['enc_mac_key'] = $dh_assoc['enc_mac_key']; |
| 417 | } |
| 418 | |
| 419 | $association = array('assoc_handle' => $assoc_handle, 'assoc_type' => $assoc_type, 'mac_key' => $mac_key, 'created' => time()); |
| 420 | if ($mode == ASSOCIATION_PRIVATE) $association['private'] = 1; |
| 421 | cache_set('association', $assoc_handle, $association); |
| 422 | |
| 423 | if ($mode == ASSOCIATION_SHARED) { |
| 424 | log_info('Created association: ' . log_array($response)); |
| 425 | log_debug('***** MAC key: ' . $association['mac_key']); |
| 426 | return $response; |
| 427 | } else { |
| 428 | log_info('Created association: private; ' . log_array($association, array('assoc_handle', 'assoc_type'))); |
| 429 | log_debug('***** MAC key: ' . $association['mac_key']); |
| 430 | return $association; |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | |
| 435 | /** |
| 436 | * Processes an authentication request from a relying party. |
| 437 | * |
| 438 | * An authentication request has an openid.mode value of |
| 439 | * checkid_setup or checkid_immediate. |
| 440 | * |
| 441 | * If the authentication request is a standard OpenID request about an identity |
| 442 | * (i.e. contains the key openid.identity), this function calls |
| 443 | * {@link simpleid_checkid_identity()} to see whether the user logged on into SimpleID |
| 444 | * matches the identity supplied in the OpenID request. |
| 445 | * |
| 446 | * If the authentication request is not about an identity, this function calls |
| 447 | * the {@link hook_checkid() checkid hook} of the loaded extensions. |
| 448 | * |
| 449 | * Depending on the OpenID version, this function will supply an appropriate |
| 450 | * assertion. |
| 451 | * |
| 452 | * @param array $request the OpenID request |
| 453 | * |
| 454 | */ |
| 455 | function simpleid_checkid($request) { |
| 456 | global $version; |
| 457 | |
| 458 | $immediate = ($request['openid.mode'] == 'checkid_immediate'); |
| 459 | |
| 460 | log_info('OpenID authentication request: ' . (($immediate) ? 'immediate' : 'setup') . '; '. log_array($request)); |
| 461 | |
| 462 | // Check for protocol correctness |
| 463 | if ($version == OPENID_VERSION_1_1) { |
| 464 | if (!isset($request['openid.return_to'])) { |
| 465 | log_error('Protocol Error: openid.return_to not set.'); |
| 466 | indirect_fatal_error(t('Protocol Error: openid.return_to not set.')); |
| 467 | return; |
| 468 | } |
| 469 | if (!isset($request['openid.identity'])) { |
| 470 | log_error('Protocol Error: openid.identity not set.'); |
| 471 | indirect_fatal_error(t('Protocol Error: openid.identity not set.')); |
| 472 | return; |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | if ($version >= OPENID_VERSION_2) { |
| 477 | if (isset($request['openid.identity']) && !isset($request['openid.claimed_id'])) { |
| 478 | log_error('Protocol Error: openid.identity set, but not openid.claimed_id.'); |
| 479 | indirect_fatal_error(t('Protocol Error: openid.identity set, but not openid.claimed_id.')); |
| 480 | return; |
| 481 | } |
| 482 | |
| 483 | if (!isset($request['openid.realm']) && !isset($request['openid.return_to'])) { |
| 484 | log_error('Protocol Error: openid.return_to not set when openid.realm is not set.'); |
| 485 | indirect_fatal_error(t('Protocol Error: openid.return_to not set when openid.realm is not set.')); |
| 486 | return; |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | if (isset($request['openid.return_to'])) { |
| 491 | $realm = openid_get_realm($request, $version); |
| 492 | |
| 493 | if (!openid_url_matches_realm($request['openid.return_to'], $realm)) { |
| 494 | log_error('Protocol Error: openid.return_to does not match realm.'); |
| 495 | openid_indirect_error($request['openid.return_to'], 'Protocol Error: openid.return_to does not match realm.'); |
| 496 | return; |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | if (isset($request['openid.identity'])) { |
| 501 | // Standard request |
| 502 | log_debug('openid.identity found, use simpleid_checkid_identity'); |
| 503 | $result = simpleid_checkid_identity($request, $immediate); |
| 504 | } else { |
| 505 | log_debug('openid.identity not found, trying extensions'); |
| 506 | // Extension request |
| 507 | $results = extension_invoke_all('checkid', $request, $immediate); |
| 508 | |
| 509 | // Filter out nulls |
| 510 | $results = array_merge(array_diff($results, array(NULL))); |
| 511 | |
| 512 | // If there are still results, it is the lowest value, otherwise, it is CHECKID_PROTOCOL_ERROR |
| 513 | $result = ($results) ? min($results) : CHECKID_PROTOCOL_ERROR; |
| 514 | } |
| 515 | |
| 516 | switch ($result) { |
| 517 | case CHECKID_APPROVAL_REQUIRED: |
| 518 | log_info('CHECKID_APPROVAL_REQUIRED'); |
| 519 | if ($immediate) { |
| 520 | $response = simpleid_checkid_approval_required($request); |
| 521 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 522 | } else { |
| 523 | $response = simpleid_checkid_ok($request); |
| 524 | simpleid_openid_consent_form($request, $response, $result); |
| 525 | } |
| 526 | break; |
| 527 | case CHECKID_RETURN_TO_SUSPECT: |
| 528 | log_info('CHECKID_RETURN_TO_SUSPECT'); |
| 529 | if ($immediate) { |
| 530 | $response = simpleid_checkid_error($request, $immediate); |
| 531 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 532 | } else { |
| 533 | $response = simpleid_checkid_ok($request); |
| 534 | simpleid_openid_consent_form($request, $response, $result); |
| 535 | } |
| 536 | break; |
| 537 | case CHECKID_OK: |
| 538 | log_info('CHECKID_OK'); |
| 539 | $response = simpleid_checkid_ok($request); |
| 540 | $response = simpleid_sign($response, isset($request['openid.assoc_handle']) ? $request['openid.assoc_handle'] : NULL); |
| 541 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 542 | break; |
| 543 | case CHECKID_LOGIN_REQUIRED: |
| 544 | log_info('CHECKID_LOGIN_REQUIRED'); |
| 545 | if ($immediate) { |
| 546 | $response = simpleid_checkid_login_required($request); |
| 547 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 548 | } else { |
| 549 | user_login_form('continue', pickle($request)); |
| 550 | exit; |
| 551 | } |
| 552 | break; |
| 553 | case CHECKID_IDENTITIES_NOT_MATCHING: |
| 554 | case CHECKID_IDENTITY_NOT_EXIST: |
| 555 | log_info('CHECKID_IDENTITIES_NOT_MATCHING | CHECKID_IDENTITY_NOT_EXIST'); |
| 556 | $response = simpleid_checkid_error($request, $immediate); |
| 557 | if ($immediate) { |
| 558 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 559 | } else { |
| 560 | simpleid_openid_consent_form($request, $response, $result); |
| 561 | } |
| 562 | break; |
| 563 | case CHECKID_PROTOCOL_ERROR: |
| 564 | if (isset($request['openid.return_to'])) { |
| 565 | $response = simpleid_checkid_error($request, $immediate); |
| 566 | simpleid_assertion_response($response, $request['openid.return_to']); |
| 567 | } else { |
| 568 | indirect_fatal_error('Unrecognised request.'); |
| 569 | } |
| 570 | break; |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | /** |
| 575 | * Processes a standard OpenID authentication request about an identity. |
| 576 | * |
| 577 | * Checks whether the current user logged into SimpleID matches the identity |
| 578 | * supplied in an OpenID request. |
| 579 | * |
| 580 | * @param array &$request the OpenID request |
| 581 | * @param bool $immediate whether checkid_immediate was used |
| 582 | * @return int one of CHECKID_OK, CHECKID_APPROVAL_REQUIRED, CHECKID_RETURN_TO_SUSPECT, CHECKID_IDENTITY_NOT_EXIST, |
| 583 | * CHECKID_IDENTITIES_NOT_MATCHING, CHECKID_LOGIN_REQUIRED or CHECKID_PROTOCOL_ERROR |
| 584 | * @global array the current logged in user |
| 585 | */ |
| 586 | function simpleid_checkid_identity(&$request, $immediate) { |
| 587 | global $user, $version; |
| 588 | |
| 589 | $realm = openid_get_realm($request, $version); |
| 590 | |
| 591 | // Check 1: Is the user logged into SimpleID as any user? |
| 592 | if ($user == NULL) { |
| 593 | return CHECKID_LOGIN_REQUIRED; |
| 594 | } else { |
| 595 | $uid = $user['uid']; |
| 596 | } |
| 597 | |
| 598 | // Check 2: Is the user logged in as the same identity as the identity requested? |
| 599 | // Choose the identity URL for the user automatically |
| 600 | if ($request['openid.identity'] == OPENID_IDENTIFIER_SELECT) { |
| 601 | $test_user = user_load($uid); |
| 602 | $identity = $test_user['identity']; |
| 603 | |
| 604 | log_info('OpenID identifier selection: Selected ' . $uid . ' [' . $identity . ']'); |
| 605 | } else { |
| 606 | $identity = $request['openid.identity']; |
| 607 | $test_user = user_load_from_identity($identity); |
| 608 | } |
| 609 | if ($test_user == NULL) return CHECKID_IDENTITY_NOT_EXIST; |
| 610 | if ($test_user['uid'] != $user['uid']) { |
| 611 | log_notice('Requested user ' . $test_user['uid'] . ' does not match logged in user ' . $user['uid']); |
| 612 | return CHECKID_IDENTITIES_NOT_MATCHING; |
| 613 | } |
| 614 | |
| 615 | // Pass the assertion to extensions |
| 616 | $assertion_results = extension_invoke_all('checkid_identity', $request, $identity, $immediate); |
| 617 | $assertion_results = array_merge(array_diff($assertion_results, array(NULL))); |
| 618 | |
| 619 | // Populate the request with the selected identity |
| 620 | if ($request['openid.identity'] == OPENID_IDENTIFIER_SELECT) { |
| 621 | $request['openid.claimed_id'] = $identity; |
| 622 | $request['openid.identity'] = $identity; |
| 623 | } |
| 624 | |
| 625 | // Check 3: Discover the realm and match its return_to |
| 626 | $user_rp = (isset($user['rp'][$realm])) ? $user['rp'][$realm] : NULL; |
| 627 | |
| 628 | if (($version >= OPENID_VERSION_2) && SIMPLEID_VERIFY_RETURN_URL_USING_REALM) { |
| 629 | $verified = FALSE; |
| 630 | |
| 631 | $rp_info = simpleid_get_rp_info($realm); |
| 632 | $services = discovery_xrds_services_by_type($rp_info['services'], OPENID_RETURN_TO); |
| 633 | |
| 634 | log_info('OpenID 2 discovery: ' . count($services) . ' matching services'); |
| 635 | |
| 636 | if ($services) { |
| 637 | $return_to_uris = array(); |
| 638 | |
| 639 | foreach ($services as $service) { |
| 640 | $return_to_uris = array_merge($return_to_uris, $service['uri']); |
| 641 | } |
| 642 | foreach ($return_to_uris as $return_to) { |
| 643 | if (openid_url_matches_realm($request['openid.return_to'], $return_to)) { |
| 644 | log_info('OpenID 2 discovery: verified'); |
| 645 | $verified = TRUE; |
| 646 | break; |
| 647 | } |
| 648 | } |
| 649 | } |
| 650 | |
| 651 | $rp_info['return_to_verified'] = $verified; |
| 652 | simpleid_set_rp_info($realm, $rp_info); |
| 653 | |
| 654 | if (!$verified) { |
| 655 | if (($user_rp != NULL) && ($user_rp['auto_release'] == 1)) { |
| 656 | log_notice('OpenID 2 discovery: not verified, but overridden by user preference'); |
| 657 | } else { |
| 658 | log_notice('OpenID 2 discovery: not verified'); |
| 659 | $assertion_results[] = CHECKID_RETURN_TO_SUSPECT; |
| 660 | } |
| 661 | } |
| 662 | } |
| 663 | |
| 664 | // Check 4: For checkid_immediate, the user must already have given |
| 665 | // permission to log in automatically. |
| 666 | if (($user_rp != NULL) && ($user_rp['auto_release'] == 1)) { |
| 667 | log_info('Automatic set for realm ' . $realm); |
| 668 | $assertion_results[] = CHECKID_OK; |
| 669 | return min($assertion_results); |
| 670 | } else { |
| 671 | $assertion_results[] = CHECKID_APPROVAL_REQUIRED; |
| 672 | return min($assertion_results); |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | /** |
| 677 | * Obtains information on a relying party by performing discovery on them. Information |
| 678 | * obtained includes the discovery URL, the parsed XRDS document, and any other |
| 679 | * information saved by SimpleID extensions |
| 680 | * |
| 681 | * The results are cached for 1 hour. For performance reasons, stale results may |
| 682 | * be obtained by using the $allow_stale parameter |
| 683 | * |
| 684 | * @param string $realm the openid.realm parameter |
| 685 | * @param bool $allow_stale allow stale results to be returned, otherwise discovery |
| 686 | * will occur |
| 687 | * @return array containing information on a relying party. |
| 688 | * @link http://openid.net/specs/openid-authentication-2_0.html#rp_discovery |
| 689 | * @since 0.8 |
| 690 | */ |
| 691 | function simpleid_get_rp_info($realm, $allow_stale = FALSE) { |
| 692 | $url = openid_realm_discovery_url($realm); |
| 693 | |
| 694 | log_info('simpleid_get_rp_info'); |
| 695 | |
| 696 | $rp_info = cache_get('rp-info', $realm); |
| 697 | |
| 698 | if (($rp_info == NULL) || (!isset($rp_info['updated'])) || (!$allow_stale && ($rp_info['updated'] < time() - 3600))) { |
| 699 | log_info('OpenID 2 RP discovery: realm: ' . $realm . '; url: ' . $url); |
| 700 | |
| 701 | $rp_info = array( |
| 702 | 'url' => $url, |
| 703 | 'services' => discovery_xrds_discover($url), |
| 704 | 'updated' => time() |
| 705 | ); |
| 706 | |
| 707 | cache_set('rp-info', $realm, $rp_info); |
| 708 | } |
| 709 | |
| 710 | return $rp_info; |
| 711 | } |
| 712 | |
| 713 | /** |
| 714 | * Saves information on a relying party to disk. |
| 715 | * |
| 716 | * @param string $realm the openid.realm parameter |
| 717 | * @param array $rp_info containing information on a relying party. |
| 718 | * |
| 719 | * @since 0.8 |
| 720 | */ |
| 721 | function simpleid_set_rp_info($realm, $rp_info) { |
| 722 | if (!isset($rp_info['updated'])) $rp_info['updated'] = time(); |
| 723 | cache_set('rp-info', $realm, $rp_info, $rp_info['updated']); |
| 724 | } |
| 725 | |
| 726 | /** |
| 727 | * Returns an OpenID response indicating a positive assertion. |
| 728 | * |
| 729 | * @param array $request the OpenID request |
| 730 | * @return array an OpenID response with a positive assertion |
| 731 | * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#positive_assertions |
| 732 | */ |
| 733 | function simpleid_checkid_ok($request) { |
| 734 | global $version; |
| 735 | |
| 736 | $message = array( |
| 737 | 'openid.mode' => 'id_res', |
| 738 | 'openid.op_endpoint' => simpleid_url(), |
| 739 | 'openid.response_nonce' => openid_nonce() |
| 740 | ); |
| 741 | |
| 742 | if (isset($request['openid.assoc_handle'])) $message['openid.assoc_handle'] = $request['openid.assoc_handle']; |
| 743 | if (isset($request['openid.identity'])) $message['openid.identity'] = $request['openid.identity']; |
| 744 | if (isset($request['openid.return_to'])) $message['openid.return_to'] = $request['openid.return_to']; |
| 745 | |
| 746 | if (($version >= OPENID_VERSION_2) && isset($request['openid.claimed_id'])) { |
| 747 | $message['openid.claimed_id'] = $request['openid.claimed_id']; |
| 748 | } |
| 749 | |
| 750 | $message = array_merge($message, extension_invoke_all('response', TRUE, $request)); |
| 751 | |
| 752 | log_info('OpenID authentication response: ' . log_array($message)); |
| 753 | return openid_indirect_message($message, $version); |
| 754 | } |
| 755 | |
| 756 | /** |
| 757 | * Returns an OpenID response indicating a negative assertion to a |
| 758 | * checkid_immediate request, where an approval of the relying party by the |
| 759 | * user is required |
| 760 | * |
| 761 | * @param mixed $request the OpenID request |
| 762 | * @return mixed an OpenID response with a negative assertion |
| 763 | * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions |
| 764 | */ |
| 765 | function simpleid_checkid_approval_required($request) { |
| 766 | global $version; |
| 767 | |
| 768 | if ($version >= OPENID_VERSION_2) { |
| 769 | $message = array('openid.mode' => 'setup_needed'); |
| 770 | } else { |
| 771 | $request['openid.mode'] = 'checkid_setup'; |
| 772 | $message = array( |
| 773 | 'openid.mode' => 'id_res', |
| 774 | 'openid.user_setup_url' => simpleid_url('continue', 's=' . rawurlencode(pickle($request))) |
| 775 | ); |
| 776 | } |
| 777 | |
| 778 | $message = array_merge($message, extension_invoke_all('response', FALSE, $request)); |
| 779 | |
| 780 | log_info('OpenID authentication response: ' . log_array($message)); |
| 781 | return openid_indirect_message($message, $version); |
| 782 | } |
| 783 | |
| 784 | /** |
| 785 | * Returns an OpenID response indicating a negative assertion to a |
| 786 | * checkid_immediate request, where the user has not logged in. |
| 787 | * |
| 788 | * @param array $request the OpenID request |
| 789 | * @return array an OpenID response with a negative assertion |
| 790 | * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions |
| 791 | */ |
| 792 | function simpleid_checkid_login_required($request) { |
| 793 | global $version; |
| 794 | |
| 795 | if ($version >= OPENID_VERSION_2) { |
| 796 | $message = array('openid.mode' => 'setup_needed'); |
| 797 | } else { |
| 798 | $message = array( |
| 799 | 'openid.mode' => 'id_res', |
| 800 | 'openid.user_setup_url' => simpleid_url('login', 'destination=continue&s=' . rawurlencode(pickle($request))) |
| 801 | ); |
| 802 | } |
| 803 | |
| 804 | $message = array_merge($message, extension_invoke_all('response', FALSE, $request)); |
| 805 | |
| 806 | log_info('OpenID authentication response: ' . log_array($message)); |
| 807 | return openid_indirect_message($message, $version); |
| 808 | } |
| 809 | |
| 810 | /** |
| 811 | * Returns an OpenID response indicating a generic negative assertion. |
| 812 | * |
| 813 | * The content of the negative version depends on the OpenID version, and whether |
| 814 | * the openid.mode of the request was checkid_immediate |
| 815 | * |
| 816 | * @param array $request the OpenID request |
| 817 | * @param bool $immediate true if openid.mode of the request was checkid_immediate |
| 818 | * @return array an OpenID response with a negative assertion |
| 819 | * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#negative_assertions |
| 820 | */ |
| 821 | function simpleid_checkid_error($request, $immediate = false) { |
| 822 | global $version; |
| 823 | |
| 824 | $message = array(); |
| 825 | if ($immediate) { |
| 826 | if ($version >= OPENID_VERSION_2) { |
| 827 | $message['openid.mode'] = 'setup_needed'; |
| 828 | } else { |
| 829 | $message['openid.mode'] = 'id_res'; |
| 830 | } |
| 831 | } else { |
| 832 | $message['openid.mode'] = 'cancel'; |
| 833 | } |
| 834 | |
| 835 | $message = array_merge($message, extension_invoke_all('response', FALSE, $request)); |
| 836 | |
| 837 | log_info('OpenID authentication response: ' . log_array($message)); |
| 838 | return openid_indirect_message($message, $version); |
| 839 | } |
| 840 | |
| 841 | /** |
| 842 | * Signs an OpenID response, using signature information from an association |
| 843 | * handle. |
| 844 | * |
| 845 | * @param array &$response the OpenID response |
| 846 | * @param array $assoc_handle the association handle containing key information |
| 847 | * for the signature. If $assoc_handle is not specified, a private association |
| 848 | * is created |
| 849 | * @return array the signed OpenID response |
| 850 | * |
| 851 | */ |
| 852 | function simpleid_sign(&$response, $assoc_handle = NULL) { |
| 853 | global $version; |
| 854 | |
| 855 | if (!$assoc_handle) { |
| 856 | $assoc = _simpleid_create_association(ASSOCIATION_PRIVATE); |
| 857 | $response['openid.assoc_handle'] = $assoc['assoc_handle']; |
| 858 | } else { |
| 859 | $assoc = cache_get('association', $assoc_handle); |
| 860 | |
| 861 | if (!is_array($assoc) || ($assoc['created'] + SIMPLEID_ASSOC_EXPIRES_IN < time())) { |
| 862 | // Association has expired, need to create a new one |
| 863 | log_notice('Association handle ' . ($assoc['assoc_handle'] ?? '(none)') . ' expired. Using stateless mode.'); |
| 864 | $response['openid.invalidate_handle'] = $assoc_handle; |
| 865 | $assoc = _simpleid_create_association(ASSOCIATION_PRIVATE); |
| 866 | $response['openid.assoc_handle'] = $assoc['assoc_handle']; |
| 867 | } |
| 868 | } |
| 869 | |
| 870 | // If we are using stateless mode, then we need to cache the response_nonce |
| 871 | // so that the RP can only verify once |
| 872 | if (isset($assoc['private']) && ($assoc['private'] == 1) && isset($response['openid.response_nonce'])) { |
| 873 | cache_set('stateless', $response['openid.response_nonce'], array( |
| 874 | 'response_nonce' => $response['openid.response_nonce'], |
| 875 | 'assoc_handle' => $response['openid.assoc_handle'])); |
| 876 | } |
| 877 | |
| 878 | // Get all the signed fields [10.1] |
| 879 | openid_parse_request($response); // Fill the namespace array |
| 880 | $signed_fields = array('op_endpoint', 'return_to', 'response_nonce', 'assoc_handle', 'identity', 'claimed_id'); |
| 881 | $signed_fields = array_merge($signed_fields, extension_invoke_all('signed_fields', $response)); |
| 882 | |
| 883 | // Check if the signed keys are actually present |
| 884 | $to_sign = array(); |
| 885 | foreach ($signed_fields as $field) { |
| 886 | if (isset($response['openid.' . $field])) $to_sign[] = $field; |
| 887 | } |
| 888 | |
| 889 | $response['openid.signed'] = implode(',', $to_sign); |
| 890 | |
| 891 | // Generate signature for this message |
| 892 | $mac_key = $assoc['mac_key']; |
| 893 | $assoc_types = openid_association_types(); |
| 894 | $hmac_func = $assoc_types[$assoc['assoc_type']]['hmac_func']; |
| 895 | |
| 896 | $response['openid.sig'] = openid_sign($response, $to_sign, $mac_key, $hmac_func, $version); |
| 897 | |
| 898 | log_info('OpenID signed authentication response: ' . log_array($response)); |
| 899 | |
| 900 | return $response; |
| 901 | } |
| 902 | |
| 903 | /** |
| 904 | * Processes a direct verification request. This is used in the OpenID specification |
| 905 | * to verify signatures generated using stateless mode. |
| 906 | * |
| 907 | * @param array $request the OpenID request |
| 908 | * @see http://openid.net/specs/openid-authentication-1_1.html#mode_check_authentication, http://openid.net/specs/openid-authentication-2_0.html#verifying_signatures |
| 909 | */ |
| 910 | function simpleid_check_authentication($request) { |
| 911 | global $version; |
| 912 | |
| 913 | log_info('OpenID direct verification: ' . log_array($request)); |
| 914 | |
| 915 | $is_valid = simpleid_verify_signatures($request); |
| 916 | |
| 917 | if ($is_valid) { |
| 918 | $response = array('is_valid' => 'true'); |
| 919 | } else { |
| 920 | $response = array('is_valid' => 'false'); |
| 921 | } |
| 922 | |
| 923 | // RP wants to check whether a handle is invalid |
| 924 | if (isset($request['openid.invalidate_handle'])) { |
| 925 | $invalid_assoc = cache_get('association', $request['openid.invalidate_handle']); |
| 926 | |
| 927 | if (!$invalid_assoc || ($invalid_assoc['created'] + SIMPLEID_ASSOC_EXPIRES_IN < time())) { |
| 928 | // Yes, it's invalid |
| 929 | $response['invalidate_handle'] = $request['openid.invalidate_handle']; |
| 930 | } |
| 931 | } |
| 932 | |
| 933 | log_info('OpenID direct verification response: ' . log_array($response)); |
| 934 | |
| 935 | openid_direct_response(openid_direct_message($response, $version)); |
| 936 | } |
| 937 | |
| 938 | /** |
| 939 | * Verifies the signature of a signed OpenID request/response. |
| 940 | * |
| 941 | * @param array $request the OpenID request/response |
| 942 | * @return bool true if the signature is verified |
| 943 | * @since 0.8 |
| 944 | */ |
| 945 | function simpleid_verify_signatures($request) { |
| 946 | global $version; |
| 947 | |
| 948 | log_info('simpleid_verify_signatures'); |
| 949 | |
| 950 | $is_valid = TRUE; |
| 951 | |
| 952 | $assoc = (isset($request['openid.assoc_handle'])) ? cache_get('association', $request['openid.assoc_handle']) : NULL; |
| 953 | $stateless = (isset($request['openid.response_nonce'])) ? cache_get('stateless', $request['openid.response_nonce']) : NULL; |
| 954 | |
| 955 | if (!$assoc) { |
| 956 | log_notice('simpleid_verify_signatures: Association not found.'); |
| 957 | $is_valid = FALSE; |
| 958 | } elseif (!$assoc['assoc_type']) { |
| 959 | log_error('simpleid_verify_signatures: Association does not contain valid assoc_type.'); |
| 960 | $is_valid = FALSE; |
| 961 | } elseif (!isset($assoc['private']) || ($assoc['private'] != 1)) { |
| 962 | log_warn('simpleid_verify_signatures: Attempting to verify an association with a shared key.'); |
| 963 | $is_valid = FALSE; |
| 964 | } elseif (!$stateless || ($stateless['assoc_handle'] != $request['openid.assoc_handle'])) { |
| 965 | log_warn('simpleid_verify_signatures: Attempting to verify a response_nonce more than once, or private association expired.'); |
| 966 | $is_valid = FALSE; |
| 967 | } else { |
| 968 | $mac_key = $assoc['mac_key']; |
| 969 | $assoc_types = openid_association_types(); |
| 970 | $hmac_func = $assoc_types[$assoc['assoc_type']]['hmac_func']; |
| 971 | |
| 972 | $signed_keys = explode(',', $request['openid.signed']); |
| 973 | $signature = openid_sign($request, $signed_keys, $mac_key, $hmac_func, $version); |
| 974 | log_debug('***** Signature: ' . $signature); |
| 975 | |
| 976 | if ($signature != $request['openid.sig']) { |
| 977 | log_warn('simpleid_verify_signatures: Signature supplied in request does not match the signatured generated.'); |
| 978 | $is_valid = FALSE; |
| 979 | } |
| 980 | |
| 981 | cache_delete('stateless', $request['openid.response_nonce']); |
| 982 | } |
| 983 | |
| 984 | return $is_valid; |
| 985 | } |
| 986 | |
| 987 | |
| 988 | /** |
| 989 | * Continues an OpenID authentication request. |
| 990 | * |
| 991 | * This function decodes an OpenID authentication request specified in the |
| 992 | * s request parameter and feeds it to the |
| 993 | * {@link simpleid_process_openid} function. This allows SimpleID to preserve |
| 994 | * the state of an OpenID request. |
| 995 | */ |
| 996 | function simpleid_continue() { |
| 997 | global $GETPOST; |
| 998 | |
| 999 | $request = unpickle($GETPOST['s']); |
| 1000 | openid_parse_request($request); |
| 1001 | simpleid_process_openid($request); |
| 1002 | } |
| 1003 | |
| 1004 | /** |
| 1005 | * Provides a form for user consent of an OpenID relying party, where the |
| 1006 | * {@link simpleid_checkid_identity()} function returns a CHECKID_APPROVAL_REQUIRED |
| 1007 | * or CHECKID_RETURN_TO_SUSPECT. |
| 1008 | * |
| 1009 | * Alternatively, provide a form for the user to rectify the situation where |
| 1010 | * {@link simpleid_checkid_identity()} function returns a CHECKID_IDENTITIES_NOT_MATCHING |
| 1011 | * or CHECKID_IDENTITY_NOT_EXIST |
| 1012 | * |
| 1013 | * @param array $request the original OpenID request |
| 1014 | * @param array $response the proposed OpenID response, subject to user |
| 1015 | * verification |
| 1016 | * @param int $reason either CHECKID_APPROVAL_REQUIRED, CHECKID_RETURN_TO_SUSPECT, |
| 1017 | * CHECKID_IDENTITIES_NOT_MATCHING or CHECKID_IDENTITY_NOT_EXIST |
| 1018 | */ |
| 1019 | function simpleid_openid_consent_form($request, $response, $reason = CHECKID_APPROVAL_REQUIRED) { |
| 1020 | global $user; |
| 1021 | global $xtpl; |
| 1022 | global $version; |
| 1023 | |
| 1024 | $request_state = pickle($request); |
| 1025 | |
| 1026 | user_header($request_state); |
| 1027 | |
| 1028 | $realm = openid_get_realm($request, $version); |
| 1029 | |
| 1030 | $xtpl->assign('token', get_form_token('rp')); |
| 1031 | $xtpl->assign('state', pickle($response)); |
| 1032 | $xtpl->assign('realm', htmlspecialchars($realm, ENT_QUOTES, 'UTF-8')); |
| 1033 | |
| 1034 | $xtpl->assign('cancel_button', t('Cancel')); |
| 1035 | |
| 1036 | if ($response['openid.mode'] == 'cancel') { |
| 1037 | $xtpl->assign('return_to', htmlspecialchars($request['openid.return_to'], ENT_QUOTES, 'UTF-8')); |
| 1038 | |
| 1039 | $xtpl->assign('unable_label', t('Unable to log into <strong class="realm">@realm</strong>.', array('@realm' => $realm))); |
| 1040 | $xtpl->assign('identity_not_matching_label', t('Your current identity does not match the requested identity %identity.', array('%identity' => $request['openid.identity']))); |
| 1041 | $xtpl->assign('switch_user_label', t('<a href="!url">Switch to a different user</a> and try again.', array('!url' => simpleid_url('logout', 'destination=continue&s=' . rawurlencode($request_state), true)))); |
| 1042 | |
| 1043 | $xtpl->parse('main.openid_consent.cancel'); |
| 1044 | } else { |
| 1045 | $xtpl->assign('javascript', '<script src="' . get_base_path() . 'html/openid-consent.js" type="text/javascript"></script>'); |
| 1046 | |
| 1047 | $rp = (isset($user['rp'][$realm])) ? $user['rp'][$realm] : NULL; |
| 1048 | |
| 1049 | $extensions = extension_invoke_all('consent_form', $request, $response, $rp); |
| 1050 | $xtpl->assign('extensions', implode($extensions)); |
| 1051 | |
| 1052 | if ($reason == CHECKID_RETURN_TO_SUSPECT) { |
| 1053 | $xtpl->assign('suspect_label', t('Warning: This web site has not confirmed its identity and might be fraudulent. Do not share any personal information with this web site unless you are sure it is legitimate. See the <a href="!url" class="popup">SimpleID documentation for details</a> (OpenID version 2.0 return_to discovery failure)', |
| 1054 | array('!url' => 'http://simpleid.org/docs/1/return_to/'))); |
| 1055 | |
| 1056 | $xtpl->parse('main.openid_consent.setup.suspect'); |
| 1057 | $xtpl->assign('realm_class', 'return-to-suspect'); |
| 1058 | } |
| 1059 | |
| 1060 | $xtpl->assign('realm_label', t('You are being logged into <strong class="realm">@realm</strong>.', array('@realm' => $realm))); |
| 1061 | $xtpl->assign('auto_release_label', t('Automatically send my information to this site for any future requests.')); |
| 1062 | $xtpl->assign('ok_button', t('OK')); |
| 1063 | |
| 1064 | $xtpl->parse('main.openid_consent.setup'); |
| 1065 | } |
| 1066 | |
| 1067 | $xtpl->parse('main.openid_consent'); |
| 1068 | |
| 1069 | $xtpl->assign(array('js_locale_label' => 'openid_suspect', 'js_locale_text' => addslashes(t('This web site has not confirmed its identity and might be fraudulent.')) . '\n\n' . addslashes(t('Are you sure you wish to automatically send your information to this site for any future requests?')))); |
| 1070 | $xtpl->parse('main.js_locale'); |
| 1071 | |
| 1072 | $xtpl->parse('main.framekiller'); |
| 1073 | |
| 1074 | header('X-Frame-Options: DENY'); |
| 1075 | |
| 1076 | $xtpl->assign('title', t('OpenID Login')); |
| 1077 | $xtpl->assign('page_class', 'dialog-page'); |
| 1078 | $xtpl->parse('main'); |
| 1079 | |
| 1080 | $xtpl->out('main'); |
| 1081 | } |
| 1082 | |
| 1083 | |
| 1084 | /** |
| 1085 | * Processes a user response from the {@link simpleid_openid_consent_form()} function. |
| 1086 | * |
| 1087 | * If the user verifies the relying party, an OpenID response will be sent to |
| 1088 | * the relying party. Otherwise, the dashboard will be displayed to the user. |
| 1089 | * |
| 1090 | */ |
| 1091 | function simpleid_openid_consent() { |
| 1092 | global $xtpl, $user, $version, $GETPOST; |
| 1093 | |
| 1094 | if ($user == NULL) { |
| 1095 | user_login_form(''); |
| 1096 | return; |
| 1097 | } |
| 1098 | |
| 1099 | if (!validate_form_token($GETPOST['tk'], 'rp')) { |
| 1100 | set_message(t('SimpleID detected a potential security attack. Please try again.')); |
| 1101 | $xtpl->assign('title', t('OpenID Login')); |
| 1102 | $xtpl->parse('main'); |
| 1103 | $xtpl->out('main'); |
| 1104 | return; |
| 1105 | } |
| 1106 | |
| 1107 | $uid = $user['uid']; |
| 1108 | |
| 1109 | $response = unpickle($GETPOST['s']); |
| 1110 | $version = openid_get_version($response); |
| 1111 | openid_parse_request($response); |
| 1112 | $return_to = $response['openid.return_to']; |
| 1113 | if (!$return_to) $return_to = $GETPOST['openid.return_to']; |
| 1114 | |
| 1115 | if ($GETPOST['op'] == t('Cancel')) { |
| 1116 | $response = simpleid_checkid_error(false); |
| 1117 | if (!$return_to) set_message(t('Log in cancelled.')); |
| 1118 | } else { |
| 1119 | $now = time(); |
| 1120 | $realm = $GETPOST['openid.realm']; |
| 1121 | |
| 1122 | if (isset($user['rp'][$realm])) { |
| 1123 | $rp = $user['rp'][$realm]; |
| 1124 | } else { |
| 1125 | $rp = array('realm' => $realm, 'first_time' => $now); |
| 1126 | } |
| 1127 | $rp['last_time'] = $now; |
| 1128 | $rp['auto_release'] = (isset($GETPOST['autorelease']) && $GETPOST['autorelease']) ? 1 : 0; |
| 1129 | |
| 1130 | // Mimic extension_invoke_all, but allow for passing by reference {{ |
| 1131 | global $simpleid_extensions; |
| 1132 | |
| 1133 | foreach ($simpleid_extensions as $extension) { |
| 1134 | $consent_function = $extension . '_consent'; |
| 1135 | if (function_exists($consent_function)) { |
| 1136 | $consent_function($GETPOST, $response, $rp); |
| 1137 | } |
| 1138 | } |
| 1139 | // }} |
| 1140 | |
| 1141 | $user['rp'][$realm] = $rp; |
| 1142 | user_save($user); |
| 1143 | |
| 1144 | $response = simpleid_sign($response, isset($response['openid.assoc_handle']) ? $response['openid.assoc_handle'] : NULL); |
| 1145 | if (!$return_to) set_message(t('You were logged in successfully.')); |
| 1146 | } |
| 1147 | |
| 1148 | if ($return_to) { |
| 1149 | simpleid_assertion_response($response, $return_to); |
| 1150 | } else { |
| 1151 | page_dashboard(); |
| 1152 | } |
| 1153 | } |
| 1154 | |
| 1155 | /** |
| 1156 | * Sends an OpenID assertion response. |
| 1157 | * |
| 1158 | * The OpenID specification version 2.0 provides for the sending of assertions |
| 1159 | * via indirect communication. However, future versions of the OpenID |
| 1160 | * specification may provide for sending of assertions via direct communication. |
| 1161 | * |
| 1162 | * @param array $response the signed OpenID assertion response to send |
| 1163 | * @param string $indirect_url the URL to which the OpenID response is sent. If |
| 1164 | * this is an empty string, the response is sent via direct communication |
| 1165 | */ |
| 1166 | function simpleid_assertion_response($response, $indirect_url = NULL) { |
| 1167 | global $xtpl, $version; |
| 1168 | |
| 1169 | if ($indirect_url) { |
| 1170 | // We want to see if the extensions want to change the way indirect responses are made |
| 1171 | $results = extension_invoke_all('indirect_response', $indirect_url, $response); |
| 1172 | $results = array_filter($results, 'is_null'); |
| 1173 | $component = ($results) ? max($results) : OPENID_RESPONSE_QUERY; |
| 1174 | |
| 1175 | openid_indirect_response($indirect_url, $response, $component); |
| 1176 | } else { |
| 1177 | openid_direct_response(openid_direct_message($response, $version)); |
| 1178 | } |
| 1179 | } |
| 1180 | |
| 1181 | /** |
| 1182 | * Displays the XRDS document for this SimpleID installation. |
| 1183 | * |
| 1184 | */ |
| 1185 | function simpleid_xrds() { |
| 1186 | global $xtpl; |
| 1187 | |
| 1188 | log_debug('Providing XRDS.'); |
| 1189 | |
| 1190 | header('Content-Type: application/xrds+xml'); |
| 1191 | header('Content-Disposition: inline; filename=yadis.xml'); |
| 1192 | |
| 1193 | $types = extension_invoke_all('xrds_types'); |
| 1194 | foreach ($types as $type) { |
| 1195 | $xtpl->assign('uri', htmlspecialchars($type, ENT_QUOTES, 'UTF-8')); |
| 1196 | $xtpl->parse('xrds.op_xrds.type'); |
| 1197 | } |
| 1198 | |
| 1199 | $xtpl->assign('simpleid_base_url', htmlspecialchars(simpleid_url(), ENT_QUOTES, 'UTF-8'), false, 'detect'); |
| 1200 | $xtpl->parse('xrds.op_xrds'); |
| 1201 | $xtpl->parse('xrds'); |
| 1202 | $xtpl->out('xrds'); |
| 1203 | } |
| 1204 | |
| 1205 | |
| 1206 | ?> |