/[drupal]/drupal/modules/openid/openid.module
ViewVC logotype

Contents of /drupal/modules/openid/openid.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.65 - (show annotations) (download) (as text)
Wed Nov 4 04:56:54 2009 UTC (3 weeks, 3 days ago) by webchick
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.64: +2 -2 lines
File MIME type: text/x-php
#367567 by sun, effulgentsia, yched, and quicksketch: Use AJAX framework for 'Add more' links.
1 <?php
2 // $Id: openid.module,v 1.64 2009/10/15 16:18:45 webchick Exp $
3
4 /**
5 * @file
6 * Implement OpenID Relying Party support for Drupal
7 */
8
9 /**
10 * Implement hook_menu().
11 */
12 function openid_menu() {
13 $items['openid/authenticate'] = array(
14 'title' => 'OpenID Login',
15 'page callback' => 'openid_authentication_page',
16 'access callback' => 'user_is_anonymous',
17 'type' => MENU_CALLBACK,
18 'file' => 'openid.pages.inc',
19 );
20 $items['user/%user/openid'] = array(
21 'title' => 'OpenID identities',
22 'page callback' => 'openid_user_identities',
23 'page arguments' => array(1),
24 'access callback' => 'user_edit_access',
25 'access arguments' => array(1),
26 'type' => MENU_LOCAL_TASK,
27 'file' => 'openid.pages.inc',
28 );
29 $items['user/%user/openid/delete'] = array(
30 'title' => 'Delete OpenID',
31 'page callback' => 'drupal_get_form',
32 'page arguments' => array('openid_user_delete_form', 1),
33 'access callback' => 'user_edit_access',
34 'access arguments' => array(1),
35 'type' => MENU_CALLBACK,
36 'file' => 'openid.pages.inc',
37 );
38 return $items;
39 }
40
41 /**
42 * Implement hook_help().
43 */
44 function openid_help($path, $arg) {
45 switch ($path) {
46 case 'user/%/openid':
47 $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>';
48 $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>';
49 $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>';
50 return $output;
51
52 case 'admin/help#openid':
53 $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>';
54 $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>';
55 $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>';
56 $output .= '<p>' . t('More information on OpenID is available at <a href="@openid-net">OpenID.net</a>.', array('@openid-net' => 'http://openid.net')) . '</p>';
57 $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>';
58 return $output;
59 }
60 }
61
62 /**
63 * Implement hook_user_insert().
64 */
65 function openid_user_insert(&$edit, $account, $category) {
66 if (isset($_SESSION['openid']['values'])) {
67 // The user has registered after trying to login via OpenID.
68 if (variable_get('user_email_verification', TRUE)) {
69 drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
70 }
71 unset($_SESSION['openid']);
72 }
73 }
74
75 /**
76 * Implement hook_form_FORM_ID_alter().
77 */
78 function openid_form_user_login_block_alter(&$form, &$form_state) {
79 _openid_user_login_form_alter($form, $form_state);
80 }
81
82 /**
83 * Implement hook_form_FORM_ID_alter().
84 */
85 function openid_form_user_login_alter(&$form, &$form_state) {
86 _openid_user_login_form_alter($form, $form_state);
87 }
88
89 function _openid_user_login_form_alter(&$form, &$form_state) {
90 drupal_add_css(drupal_get_path('module', 'openid') . '/openid.css');
91 drupal_add_js(drupal_get_path('module', 'openid') . '/openid.js');
92 if (!empty($form_state['input']['openid_identifier'])) {
93 $form['name']['#required'] = FALSE;
94 $form['pass']['#required'] = FALSE;
95 unset($form['#submit']);
96 $form['#validate'] = array('openid_login_validate');
97 }
98
99 $items = array();
100 $items[] = array(
101 'data' => l(t('Log in using OpenID'), '#'),
102 'class' => array('openid-link'),
103 );
104 $items[] = array(
105 'data' => l(t('Cancel OpenID login'), '#'),
106 'class' => array('user-link'),
107 );
108
109 $form['openid_links'] = array(
110 '#markup' => theme('item_list', array('items' => $items)),
111 '#weight' => 1,
112 );
113
114 $form['links']['#weight'] = 2;
115
116 $form['openid_identifier'] = array(
117 '#type' => 'textfield',
118 '#title' => t('Log in using OpenID'),
119 '#size' => $form['name']['#size'],
120 '#maxlength' => 255,
121 '#weight' => -1,
122 '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)),
123 );
124 $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => user_login_destination())));
125 }
126
127 /**
128 * Implement hook_form_alter().
129 *
130 * Adds OpenID login to the login forms.
131 */
132 function openid_form_user_register_form_alter(&$form, &$form_state) {
133 if (isset($_SESSION['openid']['values'])) {
134 // We were unable to auto-register a new user. Prefill the registration
135 // form with the values we have.
136 $form['name']['#default_value'] = $_SESSION['openid']['values']['name'];
137 $form['mail']['#default_value'] = $_SESSION['openid']['values']['mail'];
138 // If user_email_verification is off, hide the password field and just fill
139 // with random password to avoid confusion.
140 if (!variable_get('user_email_verification', TRUE)) {
141 $form['pass']['#type'] = 'hidden';
142 $form['pass']['#value'] = user_password();
143 }
144 $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']);
145 }
146 }
147
148 /**
149 * Login form _validate hook
150 */
151 function openid_login_validate($form, &$form_state) {
152 $return_to = $form_state['values']['openid.return_to'];
153 if (empty($return_to)) {
154 $return_to = url('', array('absolute' => TRUE));
155 }
156
157 openid_begin($form_state['values']['openid_identifier'], $return_to, $form_state['values']);
158 }
159
160 /**
161 * The initial step of OpenID authentication responsible for the following:
162 * - Perform discovery on the claimed OpenID.
163 * - If possible, create an association with the Provider's endpoint.
164 * - Create the authentication request.
165 * - Perform the appropriate redirect.
166 *
167 * @param $claimed_id The OpenID to authenticate
168 * @param $return_to The endpoint to return to from the OpenID Provider
169 */
170 function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
171 module_load_include('inc', 'openid');
172
173 $claimed_id = _openid_normalize($claimed_id);
174
175 $services = openid_discovery($claimed_id);
176 if (count($services) == 0) {
177 form_set_error('openid_identifier', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
178 return;
179 }
180
181 // Store discovered information in the users' session so we don't have to rediscover.
182 $_SESSION['openid']['service'] = $services[0];
183 // Store the claimed id
184 $_SESSION['openid']['claimed_id'] = $claimed_id;
185 // Store the login form values so we can pass them to
186 // user_exteral_login later.
187 $_SESSION['openid']['user_login_values'] = $form_values;
188
189 $op_endpoint = $services[0]['uri'];
190 // If bcmath is present, then create an association
191 $assoc_handle = '';
192 if (function_exists('bcadd')) {
193 $assoc_handle = openid_association($op_endpoint);
194 }
195
196 // Now that there is an association created, move on
197 // to request authentication from the IdP
198 // First check for LocalID. If not found, check for Delegate. Fall
199 // back to $claimed_id if neither is found.
200 if (!empty($services[0]['localid'])) {
201 $identity = $services[0]['localid'];
202 }
203 elseif (!empty($services[0]['delegate'])) {
204 $identity = $services[0]['delegate'];
205 }
206 else {
207 $identity = $claimed_id;
208 }
209
210 if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 . '/server', $services[0]['types'])) {
211 $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
212 }
213 $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
214
215 if ($services[0]['version'] == 2) {
216 openid_redirect($op_endpoint, $authn_request);
217 }
218 else {
219 openid_redirect_http($op_endpoint, $authn_request);
220 }
221 }
222
223 /**
224 * Completes OpenID authentication by validating returned data from the OpenID
225 * Provider.
226 *
227 * @param $response Array of returned values from the OpenID Provider.
228 *
229 * @return $response Response values for further processing with
230 * $response['status'] set to one of 'success', 'failed' or 'cancel'.
231 */
232 function openid_complete($response = array()) {
233 module_load_include('inc', 'openid');
234
235 if (count($response) == 0) {
236 $response = _openid_response();
237 }
238
239 // Default to failed response
240 $response['status'] = 'failed';
241 if (isset($_SESSION['openid']['service']['uri']) && isset($_SESSION['openid']['claimed_id'])) {
242 $service = $_SESSION['openid']['service'];
243 $claimed_id = $_SESSION['openid']['claimed_id'];
244 unset($_SESSION['openid']['service']);
245 unset($_SESSION['openid']['claimed_id']);
246 if (isset($response['openid.mode'])) {
247 if ($response['openid.mode'] == 'cancel') {
248 $response['status'] = 'cancel';
249 }
250 else {
251 if (openid_verify_assertion($service['uri'], $response)) {
252 // If the returned claimed_id is different from the session claimed_id,
253 // then we need to do discovery and make sure the op_endpoint matches.
254 if ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) {
255 $disco = openid_discovery($response['openid.claimed_id']);
256 if ($disco[0]['uri'] != $service['uri']) {
257 return $response;
258 }
259 }
260 else {
261 $response['openid.claimed_id'] = $claimed_id;
262 }
263 $response['status'] = 'success';
264 }
265 }
266 }
267 }
268 return $response;
269 }
270
271 /**
272 * Perform discovery on a claimed ID to determine the OpenID provider endpoint.
273 *
274 * @param $claimed_id The OpenID URL to perform discovery on.
275 *
276 * @return Array of services discovered (including OpenID version, endpoint
277 * URI, etc).
278 */
279 function openid_discovery($claimed_id) {
280 module_load_include('inc', 'openid');
281 module_load_include('inc', 'openid', 'xrds');
282
283 $services = array();
284
285 $xrds_url = $claimed_id;
286 if (_openid_is_xri($claimed_id)) {
287 $xrds_url = 'http://xri.net/' . $claimed_id;
288 }
289 $scheme = @parse_url($xrds_url, PHP_URL_SCHEME);
290 if ($scheme == 'http' || $scheme == 'https') {
291 // For regular URLs, try Yadis resolution first, then HTML-based discovery
292 $headers = array('Accept' => 'application/xrds+xml');
293 $result = drupal_http_request($xrds_url, array('headers' => $headers));
294
295 if (!isset($result->error)) {
296 if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
297 // Parse XML document to find URL
298 $services = xrds_parse($result->data);
299 }
300 else {
301 $xrds_url = NULL;
302 if (isset($result->headers['X-XRDS-Location'])) {
303 $xrds_url = $result->headers['X-XRDS-Location'];
304 }
305 else {
306 // Look for meta http-equiv link in HTML head
307 $xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
308 }
309 if (!empty($xrds_url)) {
310 $headers = array('Accept' => 'application/xrds+xml');
311 $xrds_result = drupal_http_request($xrds_url, array('headers' => $headers));
312 if (!isset($xrds_result->error)) {
313 $services = xrds_parse($xrds_result->data);
314 }
315 }
316 }
317
318 // Check for HTML delegation
319 if (count($services) == 0) {
320 // Look for 2.0 links
321 $uri = _openid_link_href('openid2.provider', $result->data);
322 $delegate = _openid_link_href('openid2.local_id', $result->data);
323 $version = 2;
324
325 // 1.0 links
326 if (empty($uri)) {
327 $uri = _openid_link_href('openid.server', $result->data);
328 $delegate = _openid_link_href('openid.delegate', $result->data);
329 $version = 1;
330 }
331 if (!empty($uri)) {
332 $services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version);
333 }
334 }
335 }
336 }
337 return $services;
338 }
339
340 /**
341 * Attempt to create a shared secret with the OpenID Provider.
342 *
343 * @param $op_endpoint URL of the OpenID Provider endpoint.
344 *
345 * @return $assoc_handle The association handle.
346 */
347 function openid_association($op_endpoint) {
348 module_load_include('inc', 'openid');
349
350 // Remove Old Associations:
351 db_delete('openid_association')
352 ->condition('created + expires_in', REQUEST_TIME, '<')
353 ->execute();
354
355 // Check to see if we have an association for this IdP already
356 $assoc_handle = db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = :endpoint", array(':endpoint' => $op_endpoint))->fetchField();
357 if (empty($assoc_handle)) {
358 $mod = OPENID_DH_DEFAULT_MOD;
359 $gen = OPENID_DH_DEFAULT_GEN;
360 $r = _openid_dh_rand($mod);
361 $private = bcadd($r, 1);
362 $public = bcpowmod($gen, $private, $mod);
363
364 // If there is no existing association, then request one
365 $assoc_request = openid_association_request($public);
366 $assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
367 $assoc_options = array(
368 'headers' => array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'),
369 'method' => 'POST',
370 'data' => $assoc_message,
371 );
372 $assoc_result = drupal_http_request($op_endpoint, $assoc_options);
373 if (isset($assoc_result->error)) {
374 return FALSE;
375 }
376
377 $assoc_response = _openid_parse_message($assoc_result->data);
378 if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') {
379 return FALSE;
380 }
381
382 if ($assoc_response['session_type'] == 'DH-SHA1') {
383 $spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']);
384 $enc_mac_key = base64_decode($assoc_response['enc_mac_key']);
385 $shared = bcpowmod($spub, $private, $mod);
386 $assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key));
387 }
388 db_insert('openid_association')
389 ->fields(array(
390 'idp_endpoint_uri' => $op_endpoint,
391 'session_type' => $assoc_response['session_type'],
392 'assoc_handle' => $assoc_response['assoc_handle'],
393 'assoc_type' => $assoc_response['assoc_type'],
394 'expires_in' => $assoc_response['expires_in'],
395 'mac_key' => $assoc_response['mac_key'],
396 'created' => REQUEST_TIME,
397 ))
398 ->execute();
399 $assoc_handle = $assoc_response['assoc_handle'];
400 }
401 return $assoc_handle;
402 }
403
404 /**
405 * Authenticate a user or attempt registration.
406 *
407 * @param $response Response values from the OpenID Provider.
408 */
409 function openid_authentication($response) {
410 module_load_include('inc', 'openid');
411
412 $identity = $response['openid.claimed_id'];
413
414 $account = user_external_load($identity);
415 if (isset($account->uid)) {
416 if (!variable_get('user_email_verification', TRUE) || $account->login) {
417 // Check if user is blocked.
418 user_login_name_validate(array(), $state, (array)$account);
419 if (!form_get_errors()) {
420 // Load global $user and perform final login tasks.
421 $form_state['uid'] = $account->uid;
422 user_login_submit(array(), $form_state);
423 // Let other modules act on OpenID login
424 module_invoke_all('openid_response', $response, $account);
425 }
426 }
427 else {
428 drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
429 }
430 }
431 elseif (variable_get('user_register', 1)) {
432 // Register new user
433 $form_state['build_info']['args'] = array();
434 $form_state['redirect'] = NULL;
435 $form_state['values']['name'] = (empty($response['openid.sreg.nickname'])) ? $identity : $response['openid.sreg.nickname'];
436 $form_state['values']['mail'] = (empty($response['openid.sreg.email'])) ? '' : $response['openid.sreg.email'];
437 $form_state['values']['pass'] = user_password();
438 $form_state['values']['status'] = variable_get('user_register', 1) == 1;
439 $form_state['values']['response'] = $response;
440 $form = drupal_retrieve_form('user_register_form', $form_state);
441 drupal_prepare_form('user_register_form', $form, $form_state);
442 drupal_validate_form('user_register_form', $form, $form_state);
443 if (form_get_errors()) {
444 // We were unable to register a valid new user, redirect to standard
445 // user/register and prefill with the values we received.
446 drupal_set_message(t('OpenID registration failed for the reasons listed. You may register now, or 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'))), 'error');
447 $_SESSION['openid']['values'] = $form_state['values'];
448 // We'll want to redirect back to the same place.
449 $destination = drupal_get_destination();
450 unset($_GET['destination']);
451 drupal_goto('user/register', array('query' => $destination));
452 }
453 else {
454 unset($form_state['values']['response']);
455 $account = user_save(drupal_anonymous_user(), $form_state['values']);
456 // Terminate if an error occurred during user_save().
457 if (!$account) {
458 drupal_set_message(t("Error saving user account."), 'error');
459 drupal_goto();
460 }
461 user_set_authmaps($account, array("authname_openid" => $identity));
462 // Load global $user and perform final login tasks.
463 $form_state['uid'] = $account->uid;
464 user_login_submit(array(), $form_state);
465 // Let other modules act on OpenID login
466 module_invoke_all('openid_response', $response, $account);
467 }
468 drupal_redirect_form($form_state);
469 }
470 else {
471 drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
472 }
473 drupal_goto();
474 }
475
476 function openid_association_request($public) {
477 module_load_include('inc', 'openid');
478
479 $request = array(
480 'openid.ns' => OPENID_NS_2_0,
481 'openid.mode' => 'associate',
482 'openid.session_type' => 'DH-SHA1',
483 'openid.assoc_type' => 'HMAC-SHA1'
484 );
485
486 if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') {
487 $cpub = _openid_dh_long_to_base64($public);
488 $request['openid.dh_consumer_public'] = $cpub;
489 }
490
491 return $request;
492 }
493
494 function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
495 module_load_include('inc', 'openid');
496
497 $request = array(
498 'openid.mode' => 'checkid_setup',
499 'openid.identity' => $identity,
500 'openid.assoc_handle' => $assoc_handle,
501 'openid.return_to' => $return_to,
502 );
503
504 if ($version == 2) {
505 $request['openid.ns'] = OPENID_NS_2_0;
506 $request['openid.claimed_id'] = $claimed_id;
507 $request['openid.realm'] = url('', array('absolute' => TRUE));
508 }
509 else {
510 $request['openid.trust_root'] = url('', array('absolute' => TRUE));
511 }
512
513 // Simple Registration
514 $request['openid.sreg.required'] = 'nickname,email';
515 $request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
516
517 $request = array_merge($request, module_invoke_all('openid', 'request', $request));
518
519 return $request;
520 }
521
522 /**
523 * Attempt to verify the response received from the OpenID Provider.
524 *
525 * @param $op_endpoint The OpenID Provider URL.
526 * @param $response Array of response values from the provider.
527 *
528 * @return boolean
529 */
530 function openid_verify_assertion($op_endpoint, $response) {
531 module_load_include('inc', 'openid');
532
533 $valid = FALSE;
534
535 $association = db_query("SELECT * FROM {openid_association} WHERE assoc_handle = :assoc_handle", array(':assoc_handle' => $response['openid.assoc_handle']))->fetchObject();
536 if ($association && isset($association->session_type)) {
537 $keys_to_sign = explode(',', $response['openid.signed']);
538 $self_sig = _openid_signature($association, $response, $keys_to_sign);
539 if ($self_sig == $response['openid.sig']) {
540 $valid = TRUE;
541 }
542 else {
543 $valid = FALSE;
544 }
545 }
546 else {
547 $request = $response;
548 $request['openid.mode'] = 'check_authentication';
549 $message = _openid_create_message($request);
550 $options = array(
551 'headers' => array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'),
552 'method' => 'POST',
553 'data' => _openid_encode_message($message),
554 );
555 $result = drupal_http_request($op_endpoint, $options);
556 if (!isset($result->error)) {
557 $response = _openid_parse_message($result->data);
558 if (strtolower(trim($response['is_valid'])) == 'true') {
559 $valid = TRUE;
560 }
561 else {
562 $valid = FALSE;
563 }
564 }
565 }
566 return $valid;
567 }

  ViewVC Help
Powered by ViewVC 1.1.2