[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/modules/openid/ -> openid.module (source)

   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  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7