| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @file 5 * Implement OpenID Relying Party support for Drupal 6 */ 7 8 /** 9 * Implementation of hook_menu. 10 */ 11 function openid_menu() { 12 $items['openid/authenticate'] = array( 13 'title' => 'OpenID Login', 14 'page callback' => 'openid_authentication_page', 15 'access callback' => 'user_is_anonymous', 16 'type' => MENU_CALLBACK, 17 'file' => 'openid.pages.inc', 18 ); 19 $items['user/%user/openid'] = array( 20 'title' => 'OpenID identities', 21 'page callback' => 'openid_user_identities', 22 'page arguments' => array(1), 23 'access callback' => 'user_edit_access', 24 'access arguments' => array(1), 25 'type' => MENU_LOCAL_TASK, 26 'file' => 'openid.pages.inc', 27 ); 28 $items['user/%user/openid/delete'] = array( 29 'title' => 'Delete OpenID', 30 'page callback' => 'drupal_get_form', 31 'page arguments' => array('openid_user_delete_form', 1), 32 'access callback' => 'user_edit_access', 33 'access arguments' => array(1), 34 'type' => MENU_CALLBACK, 35 'file' => 'openid.pages.inc', 36 ); 37 return $items; 38 } 39 40 /** 41 * Implementation of hook_help(). 42 */ 43 function openid_help($path, $arg) { 44 switch ($path) { 45 case 'user/%/openid': 46 $output = '<p>'. t('This site supports <a href="@openid-net">OpenID</a>, a secure way to log into many websites using a single username and password. OpenID can reduce the necessity of managing many usernames and passwords for many websites.', array('@openid-net' => 'http://openid.net')) .'</p>'; 47 $output .= '<p>'. t('To use OpenID you must first establish an identity on a public or private OpenID server. If you do not have an OpenID and would like one, look into one of the <a href="@openid-providers">free public providers</a>. You can find out more about OpenID at <a href="@openid-net">this website</a>.', array('@openid-providers' => 'http://openid.net/get/', '@openid-net' => 'http://openid.net')) .'</p>'; 48 $output .= '<p>'. t('If you already have an OpenID, enter the URL to your OpenID server below (e.g. myusername.openidprovider.com). Next time you login, you will be able to use this URL instead of a regular username and password. You can have multiple OpenID servers if you like; just keep adding them here.') .'</p>'; 49 return $output; 50 51 case 'admin/help#openid': 52 $output = '<p>'. t('OpenID is a secure method for logging into many websites with a single username and password. It does not require special software, and it does not share passwords with any site to which it is associated; including your site.') .'</p>'; 53 $output .= '<p>'. t('Users can create accounts using their OpenID, assign one or more OpenIDs to an existing account, and log in using an OpenID. This lowers the barrier to registration, which is good for the site, and offers convenience and security to the users. OpenID is not a trust system, so email verification is still necessary. The benefit stems from the fact that users can have a single password that they can use on many websites. This means they can easily update their single password from a centralized location, rather than having to change dozens of passwords individually.') .'</p>'; 54 $output .= '<p>'. t('The basic concept is as follows: A user has an account on an OpenID server. This account provides them with a unique URL (such as myusername.openidprovider.com). When the user comes to your site, they are presented with the option of entering this URL. Your site then communicates with the OpenID server, asking it to verify the identity of the user. If the user is logged into their OpenID server, the server communicates back to your site, verifying the user. If they are not logged in, the OpenID server will ask the user for their password. At no point does your site record, or need to record the user\'s password.') .'</p>'; 55 $output .= '<p>'. t('More information on OpenID is available at <a href="@openid-net">OpenID.net</a>.', array('@openid-net' => url('http://openid.net'))) .'</p>'; 56 $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@handbook">OpenID module</a>.', array('@handbook' => 'http://drupal.org/handbook/modules/openid')) .'</p>'; 57 return $output; 58 } 59 } 60 61 /** 62 * Implementation of hook_user(). 63 */ 64 function openid_user($op, &$edit, &$account, $category = NULL) { 65 if ($op == 'insert' && isset($_SESSION['openid']['values'])) { 66 // The user has registered after trying to login via OpenID. 67 if (variable_get('user_email_verification', TRUE)) { 68 drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.')); 69 } 70 unset($_SESSION['openid']); 71 } 72 } 73 74 /** 75 * Implementation of hook_form_alter : adds OpenID login to the login forms. 76 */ 77 function openid_form_alter(&$form, $form_state, $form_id) { 78 if ($form_id == 'user_login_block' || $form_id == 'user_login') { 79 drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module'); 80 drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js'); 81 if (!empty($form_state['post']['openid_identifier'])) { 82 $form['name']['#required'] = FALSE; 83 $form['pass']['#required'] = FALSE; 84 unset($form['#submit']); 85 $form['#validate'] = array('openid_login_validate'); 86 } 87 88 $items = array(); 89 $items[] = array( 90 'data' => l(t('Log in using OpenID'), '#'), 91 'class' => 'openid-link', 92 ); 93 $items[] = array( 94 'data' => l(t('Cancel OpenID login'), '#'), 95 'class' => 'user-link', 96 ); 97 98 $form['openid_links'] = array( 99 '#value' => theme('item_list', $items), 100 '#weight' => 1, 101 ); 102 103 $form['links']['#weight'] = 2; 104 105 $form['openid_identifier'] = array( 106 '#type' => 'textfield', 107 '#title' => t('Log in using OpenID'), 108 '#size' => ($form_id == 'user_login') ? 58 : 13, 109 '#maxlength' => 255, 110 '#weight' => -1, 111 '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)), 112 ); 113 $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => user_login_destination()))); 114 } 115 elseif ($form_id == 'user_register' && isset($_SESSION['openid']['values'])) { 116 // We were unable to auto-register a new user. Prefill the registration 117 // form with the values we have. 118 $form['name']['#default_value'] = $_SESSION['openid']['values']['name']; 119 $form['mail']['#default_value'] = $_SESSION['openid']['values']['mail']; 120 // If user_email_verification is off, hide the password field and just fill 121 // with random password to avoid confusion. 122 if (!variable_get('user_email_verification', TRUE)) { 123 $form['pass']['#type'] = 'hidden'; 124 $form['pass']['#value'] = user_password(); 125 } 126 $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']); 127 $form['openid_display'] = array( 128 '#type' => 'item', 129 '#title' => t('Your OpenID'), 130 '#description' => t('This OpenID will be attached to your account after registration.'), 131 '#value' => check_plain($_SESSION['openid']['values']['auth_openid']), 132 ); 133 } 134 return $form; 135 } 136 137 /** 138 * Login form _validate hook 139 */ 140 function openid_login_validate($form, &$form_state) { 141 $return_to = $form_state['values']['openid.return_to']; 142 if (empty($return_to)) { 143 $return_to = url('', array('absolute' => TRUE)); 144 } 145 146 openid_begin($form_state['values']['openid_identifier'], $return_to, $form_state['values']); 147 } 148 149 /** 150 * The initial step of OpenID authentication responsible for the following: 151 * - Perform discovery on the claimed OpenID. 152 * - If possible, create an association with the Provider's endpoint. 153 * - Create the authentication request. 154 * - Perform the appropriate redirect. 155 * 156 * @param $claimed_id The OpenID to authenticate 157 * @param $return_to The endpoint to return to from the OpenID Provider 158 */ 159 function openid_begin($claimed_id, $return_to = '', $form_values = array()) { 160 module_load_include('inc', 'openid'); 161 162 $claimed_id = _openid_normalize($claimed_id); 163 164 $services = openid_discovery($claimed_id); 165 if (count($services) == 0) { 166 form_set_error('openid_identifier', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.')); 167 return; 168 } 169 170 // Store discovered information in the users' session so we don't have to rediscover. 171 $_SESSION['openid']['service'] = $services[0]; 172 // Store the claimed id 173 $_SESSION['openid']['claimed_id'] = $claimed_id; 174 // Store the login form values so we can pass them to 175 // user_exteral_login later. 176 $_SESSION['openid']['user_login_values'] = $form_values; 177 178 $op_endpoint = $services[0]['uri']; 179 // If bcmath is present, then create an association 180 $assoc_handle = ''; 181 if (function_exists('bcadd')) { 182 $assoc_handle = openid_association($op_endpoint); 183 } 184 185 // Now that there is an association created, move on 186 // to request authentication from the IdP 187 // First check for LocalID. If not found, check for Delegate. Fall 188 // back to $claimed_id if neither is found. 189 if (!empty($services[0]['localid'])) { 190 $identity = $services[0]['localid']; 191 } 192 else if (!empty($services[0]['delegate'])) { 193 $identity = $services[0]['delegate']; 194 } 195 else { 196 $identity = $claimed_id; 197 } 198 199 if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) { 200 $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select'; 201 } 202 $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']); 203 204 if ($services[0]['version'] == 2) { 205 openid_redirect($op_endpoint, $authn_request); 206 } 207 else { 208 openid_redirect_http($op_endpoint, $authn_request); 209 } 210 } 211 212 /** 213 * Completes OpenID authentication by validating returned data from the OpenID 214 * Provider. 215 * 216 * @param $response Array of returned values from the OpenID Provider. 217 * 218 * @return $response Response values for further processing with 219 * $response['status'] set to one of 'success', 'failed' or 'cancel'. 220 */ 221 function openid_complete($response = array()) { 222 global $base_url; 223 module_load_include('inc', 'openid'); 224 225 if (count($response) == 0) { 226 $response = _openid_response(); 227 } 228 229 // Default to failed response 230 $response['status'] = 'failed'; 231 if (isset($_SESSION['openid']['service']['uri']) && isset($_SESSION['openid']['claimed_id'])) { 232 $service = $_SESSION['openid']['service']; 233 $claimed_id = $_SESSION['openid']['claimed_id']; 234 unset($_SESSION['openid']['service']); 235 unset($_SESSION['openid']['claimed_id']); 236 if (isset($response['openid.mode'])) { 237 if ($response['openid.mode'] == 'cancel') { 238 $response['status'] = 'cancel'; 239 } 240 else { 241 if (openid_verify_assertion($service, $response)) { 242 // If the returned claimed_id is different from the session claimed_id, 243 // then we need to do discovery and make sure the op_endpoint matches. 244 if ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) { 245 $disco = openid_discovery($response['openid.claimed_id']); 246 if ($disco[0]['uri'] != $service['uri']) { 247 return $response; 248 } 249 } 250 else { 251 $response['openid.claimed_id'] = $claimed_id; 252 } 253 // Verify that openid.return_to matches the current URL (see OpenID 254 // Authentication 2.0, section 11.1). 255 // While OpenID Authentication 1.1, section 4.3 does not mandate 256 // return_to verification, the received return_to should still 257 // match these constraints. 258 $return_to_parts = parse_url($response['openid.return_to']); 259 260 $base_url_parts = parse_url($base_url); 261 $current_parts = parse_url($base_url_parts['scheme'] .'://'. $base_url_parts['host'] . request_uri()); 262 263 if ($return_to_parts['scheme'] != $current_parts['scheme'] || 264 $return_to_parts['host'] != $current_parts['host'] || 265 $return_to_parts['path'] != $current_parts['path']) { 266 267 return $response; 268 } 269 // Verify that all query parameters in the openid.return_to URL have 270 // the same value in the current URL. In addition, the current URL 271 // contains a number of other parameters added by the OpenID Provider. 272 parse_str(isset($return_to_parts['query']) ? $return_to_parts['query'] : '', $return_to_query_parameters); 273 foreach ($return_to_query_parameters as $name => $value) { 274 if (!array_key_exists($name, $_GET) || $_GET[$name] != $value) { 275 return $response; 276 } 277 } 278 $response['status'] = 'success'; 279 } 280 } 281 } 282 } 283 return $response; 284 } 285 286 /** 287 * Perform discovery on a claimed ID to determine the OpenID provider endpoint. 288 * 289 * @param $claimed_id The OpenID URL to perform discovery on. 290 * 291 * @return Array of services discovered (including OpenID version, endpoint 292 * URI, etc). 293 */ 294 function openid_discovery($claimed_id) { 295 module_load_include('inc', 'openid'); 296 module_load_include('inc', 'openid', 'xrds'); 297 298 $services = array(); 299 300 $xrds_url = $claimed_id; 301 if (_openid_is_xri($claimed_id)) { 302 $xrds_url = 'http://xri.net/'. $claimed_id; 303 } 304 $url = @parse_url($xrds_url); 305 if ($url['scheme'] == 'http' || $url['scheme'] == 'https') { 306 // For regular URLs, try Yadis resolution first, then HTML-based discovery 307 $headers = array('Accept' => 'application/xrds+xml'); 308 $result = drupal_http_request($xrds_url, $headers); 309 310 if (!isset($result->error)) { 311 if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) { 312 // Parse XML document to find URL 313 $services = xrds_parse($result->data); 314 } 315 else { 316 $xrds_url = NULL; 317 if (isset($result->headers['X-XRDS-Location'])) { 318 $xrds_url = $result->headers['X-XRDS-Location']; 319 } 320 else { 321 // Look for meta http-equiv link in HTML head 322 $xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data); 323 } 324 if (!empty($xrds_url)) { 325 $headers = array('Accept' => 'application/xrds+xml'); 326 $xrds_result = drupal_http_request($xrds_url, $headers); 327 if (!isset($xrds_result->error)) { 328 $services = xrds_parse($xrds_result->data); 329 } 330 } 331 } 332 333 // Check for HTML delegation 334 if (count($services) == 0) { 335 // Look for 2.0 links 336 $uri = _openid_link_href('openid2.provider', $result->data); 337 $delegate = _openid_link_href('openid2.local_id', $result->data); 338 $version = 2; 339 340 // 1.0 links 341 if (empty($uri)) { 342 $uri = _openid_link_href('openid.server', $result->data); 343 $delegate = _openid_link_href('openid.delegate', $result->data); 344 $version = 1; 345 } 346 if (!empty($uri)) { 347 $services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version); 348 } 349 } 350 } 351 } 352 return $services; 353 } 354 355 /** 356 * Attempt to create a shared secret with the OpenID Provider. 357 * 358 * @param $op_endpoint URL of the OpenID Provider endpoint. 359 * 360 * @return $assoc_handle The association handle. 361 */ 362 function openid_association($op_endpoint) { 363 module_load_include('inc', 'openid'); 364 365 // Remove Old Associations: 366 db_query("DELETE FROM {openid_association} WHERE created + expires_in < %d", time()); 367 368 // Check to see if we have an association for this IdP already 369 $assoc_handle = db_result(db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = '%s'", $op_endpoint)); 370 if (empty($assoc_handle)) { 371 $mod = OPENID_DH_DEFAULT_MOD; 372 $gen = OPENID_DH_DEFAULT_GEN; 373 $r = _openid_dh_rand($mod); 374 $private = bcadd($r, 1); 375 $public = bcpowmod($gen, $private, $mod); 376 377 // If there is no existing association, then request one 378 $assoc_request = openid_association_request($public); 379 $assoc_message = _openid_encode_message(_openid_create_message($assoc_request)); 380 $assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'); 381 $assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message); 382 if (isset($assoc_result->error)) { 383 return FALSE; 384 } 385 386 $assoc_response = _openid_parse_message($assoc_result->data); 387 if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') { 388 return FALSE; 389 } 390 391 if ($assoc_response['session_type'] == 'DH-SHA1') { 392 $spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']); 393 $enc_mac_key = base64_decode($assoc_response['enc_mac_key']); 394 $shared = bcpowmod($spub, $private, $mod); 395 $assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key)); 396 } 397 db_query("INSERT INTO {openid_association} (idp_endpoint_uri, session_type, assoc_handle, assoc_type, expires_in, mac_key, created) VALUES('%s', '%s', '%s', '%s', %d, '%s', %d)", 398 $op_endpoint, $assoc_response['session_type'], $assoc_response['assoc_handle'], $assoc_response['assoc_type'], $assoc_response['expires_in'], $assoc_response['mac_key'], time()); 399 400 $assoc_handle = $assoc_response['assoc_handle']; 401 } 402 403 return $assoc_handle; 404 } 405 406 /** 407 * Authenticate a user or attempt registration. 408 * 409 * @param $response Response values from the OpenID Provider. 410 */ 411 function openid_authentication($response) { 412 module_load_include('inc', 'openid'); 413 414 $identity = $response['openid.claimed_id']; 415 416 $account = user_external_load($identity); 417 if (isset($account->uid)) { 418 if (!variable_get('user_email_verification', TRUE) || $account->login) { 419 user_external_login($account, $_SESSION['openid']['user_login_values']); 420 } 421 else { 422 drupal_set_message(t('You must validate your email address for this account before logging in via OpenID')); 423 } 424 } 425 elseif (variable_get('user_register', 1)) { 426 // Register new user 427 $form_state['redirect'] = NULL; 428 // Only signed SREG keys are included as required by OpenID Simple 429 // Registration Extension 1.0, section 4. 430 $signed_keys = explode(',', $response['openid.signed']); 431 $form_state['values']['name'] = in_array('sreg.nickname', $signed_keys) ? $response['openid.sreg.nickname'] : ''; 432 $form_state['values']['mail'] = in_array('sreg.email', $signed_keys) ? $response['openid.sreg.email'] : ''; 433 $form_state['values']['pass'] = user_password(); 434 $form_state['values']['status'] = variable_get('user_register', 1) == 1; 435 $form_state['values']['response'] = $response; 436 $form_state['values']['auth_openid'] = $identity; 437 438 if (empty($form_state['values']['name']) && empty($form_state['values']['mail'])) { 439 drupal_set_message(t('Please complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'warning'); 440 $success = FALSE; 441 } 442 else { 443 $form = drupal_retrieve_form('user_register', $form_state); 444 drupal_prepare_form('user_register', $form, $form_state); 445 drupal_validate_form('user_register', $form, $form_state); 446 $success = !form_get_errors(); 447 if (!$success) { 448 drupal_set_message(t('Account registration using the information provided by your OpenID provider failed due to the reasons listed below. Please complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'warning'); 449 // Append form validation errors below the above warning. 450 $messages = drupal_get_messages('error'); 451 foreach ($messages['error'] as $message) { 452 drupal_set_message( $message, 'error'); 453 } 454 } 455 } 456 if (!$success) { 457 // We were unable to register a valid new user, redirect to standard 458 // user/register and prefill with the values we received. 459 $_SESSION['openid']['values'] = $form_state['values']; 460 // We'll want to redirect back to the same place. 461 $destination = drupal_get_destination(); 462 unset($_REQUEST['destination']); 463 drupal_goto('user/register', $destination); 464 } 465 else { 466 unset($form_state['values']['response']); 467 $account = user_save('', $form_state['values']); 468 // Terminate if an error occured during user_save(). 469 if (!$account) { 470 drupal_set_message(t("Error saving user account."), 'error'); 471 drupal_goto(); 472 } 473 user_external_login($account); 474 } 475 drupal_redirect_form($form, $form_state['redirect']); 476 } 477 else { 478 drupal_set_message(t('Only site administrators can create new user accounts.'), 'error'); 479 } 480 drupal_goto(); 481 } 482 483 function openid_association_request($public) { 484 module_load_include('inc', 'openid'); 485 486 $request = array( 487 'openid.ns' => OPENID_NS_2_0, 488 'openid.mode' => 'associate', 489 'openid.session_type' => 'DH-SHA1', 490 'openid.assoc_type' => 'HMAC-SHA1' 491 ); 492 493 if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') { 494 $cpub = _openid_dh_long_to_base64($public); 495 $request['openid.dh_consumer_public'] = $cpub; 496 } 497 498 return $request; 499 } 500 501 function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) { 502 module_load_include('inc', 'openid'); 503 504 $ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0; 505 $request = array( 506 'openid.ns' => $ns, 507 'openid.mode' => 'checkid_setup', 508 'openid.identity' => $identity, 509 'openid.claimed_id' => $claimed_id, 510 'openid.assoc_handle' => $assoc_handle, 511 'openid.return_to' => $return_to, 512 ); 513 514 if ($version == 2) { 515 $request['openid.realm'] = url('', array('absolute' => TRUE)); 516 } 517 else { 518 $request['openid.trust_root'] = url('', array('absolute' => TRUE)); 519 } 520 521 // Simple Registration 522 $request['openid.sreg.required'] = 'nickname,email'; 523 $request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1"; 524 525 $request = array_merge($request, module_invoke_all('openid', 'request', $request)); 526 527 return $request; 528 } 529 530 /** 531 * Attempt to verify the response received from the OpenID Provider. 532 * 533 * @param $service 534 * Array describing the OpenID provider. 535 * @param $response 536 * Array of response values from the provider. 537 * 538 * @return boolean 539 */ 540 function openid_verify_assertion($service, $response) { 541 module_load_include('inc', 'openid'); 542 543 // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.3 544 // Check the Nonce to protect against replay attacks. 545 if (!openid_verify_assertion_nonce($service, $response)) { 546 return FALSE; 547 } 548 549 // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4 550 // Verify the signatures. 551 $valid = FALSE; 552 $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle'])); 553 if ($association && isset($association->session_type)) { 554 // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.2 555 // Verification using an association. 556 $valid = openid_verify_assertion_signature($service, $association, $response); 557 } 558 else { 559 // http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.3 560 // Direct verification. 561 $request = $response; 562 $request['openid.mode'] = 'check_authentication'; 563 $message = _openid_create_message($request); 564 $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'); 565 $result = drupal_http_request($service['uri'], $headers, 'POST', _openid_encode_message($message)); 566 if (!isset($result->error)) { 567 $response = _openid_parse_message($result->data); 568 if (strtolower(trim($response['is_valid'])) == 'true') { 569 $valid = TRUE; 570 } 571 else { 572 $valid = FALSE; 573 } 574 } 575 } 576 return $valid; 577 } 578 579 /** 580 * Verify the signature of the response received from the OpenID provider. 581 * 582 * @param $service 583 * Array describing the OpenID provider. 584 * @param $association 585 * Information on the association with the OpenID provider. 586 * @param $response 587 * Array of response values from the provider. 588 * 589 * @return 590 * TRUE if the signature is valid and covers all fields required to be signed. 591 * @see http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4 592 */ 593 function openid_verify_assertion_signature($service, $association, $response) { 594 if ($service['version'] == 2) { 595 // OpenID Authentication 2.0, section 10.1: 596 // These keys must always be signed. 597 $mandatory_keys = array('op_endpoint', 'return_to', 'response_nonce', 'assoc_handle'); 598 if (isset($response['openid.claimed_id'])) { 599 // If present, these two keys must also be signed. According to the spec, 600 // they are either both present or both absent. 601 $mandatory_keys[] = 'claimed_id'; 602 $mandatory_keys[] = 'identity'; 603 } 604 } 605 else { 606 // OpenID Authentication 1.1. section 4.3.3. 607 $mandatory_keys = array('identity', 'return_to'); 608 } 609 610 $keys_to_sign = explode(',', $response['openid.signed']); 611 612 if (count(array_diff($mandatory_keys, $keys_to_sign)) > 0) { 613 return FALSE; 614 } 615 616 return _openid_signature($association, $response, $keys_to_sign) == $response['openid.sig']; 617 } 618 619 /** 620 * Verify that the nonce has not been used in earlier assertions from the same OpenID provider. 621 * 622 * @param $service 623 * Array describing the OpenID provider. 624 * @param $response 625 * Array of response values from the provider. 626 * 627 * @return 628 * TRUE if the nonce has not expired and has not been used earlier. 629 */ 630 function openid_verify_assertion_nonce($service, $response) { 631 if ($service['version'] != 2) { 632 return TRUE; 633 } 634 635 if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/', $response['openid.response_nonce'], $matches)) { 636 list(, $year, $month, $day, $hour, $minutes, $seconds) = $matches; 637 $nonce_timestamp = gmmktime($hour, $minutes, $seconds, $month, $day, $year); 638 } 639 else { 640 watchdog('openid', 'Nonce from @endpoint rejected because it is not correctly formatted, nonce: @nonce.', array('@endpoint' => $service['uri'], '@nonce' => $response['openid.response_nonce']), WATCHDOG_WARNING); 641 return FALSE; 642 } 643 644 // A nonce with a timestamp to far in the past or future will already have 645 // been removed and cannot be checked for single use anymore. 646 $time = time(); 647 $expiry = 900; 648 if ($nonce_timestamp <= $time - $expiry || $nonce_timestamp >= $time + $expiry) { 649 watchdog('openid', 'Nonce received from @endpoint is out of range (time difference: @intervals). Check possible clock skew.', array('@endpoint' => $service['uri'], '@interval' => $time - $nonce_timestamp), WATCHDOG_WARNING); 650 return FALSE; 651 } 652 653 // Record that this nonce was used. 654 db_query("INSERT INTO {openid_nonce} (idp_endpoint_uri, nonce, expires) VALUES ('%s', '%s', %d)", $service['uri'], $response['openid.response_nonce'], $nonce_timestamp + $expiry); 655 656 // Count the number of times this nonce was used. 657 $count_used = db_result(db_query("SELECT COUNT(*) FROM {openid_nonce} WHERE nonce = '%s' AND idp_endpoint_uri = '%s'", $response['openid.response_nonce'], $service['uri'])); 658 659 if ($count_used == 1) { 660 return TRUE; 661 } 662 else { 663 watchdog('openid', 'Nonce replay attempt blocked from @ip, nonce: @nonce.', array('@ip' => ip_address(), '@nonce' => $response['openid.response_nonce']), WATCHDOG_CRITICAL); 664 return FALSE; 665 } 666 } 667 668 /** 669 * Remove expired nonces from the database. 670 * 671 * Implementation of hook_cron(). 672 */ 673 function openid_cron() { 674 db_query("DELETE FROM {openid_nonce} WHERE expires < %d", time()); 675 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |