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

Contents of /contributions/modules/openid/openid.module

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


Revision 1.29 - (show annotations) (download) (as text)
Mon Jun 18 15:29:04 2007 UTC (2 years, 5 months ago) by walkah
Branch: MAIN
CVS Tags: HEAD
Changes since 1.28: +7 -5 lines
File MIME type: text/x-php
A couple of minor clean-ups in delegation detection (to better
accomodate the new rel="openid2.local_id openid.delegate" syntax.
1 <?php
2 // $Id: openid.module,v 1.28 2007/06/15 20:44:18 walkah Exp $
3
4 /**
5 * @file
6 * Implement OpenID Relying Party support for Drupal
7 */
8
9 /**
10 * Implementation of 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 );
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 );
27 $items['user/%user/openid/delete'] = array(
28 'title' => 'Delete OpenID',
29 'page callback' => 'openid_user_delete',
30 'page arguments' => array(1),
31 'type' => MENU_CALLBACK,
32 );
33 return $items;
34 }
35
36 /**
37 * Implementation of hook_help().
38 */
39 function openid_help($section) {
40 switch ($section) {
41 case 'user/%/openid':
42 return t('You may login to this site using an OpenID. You may add your OpenId URLs below, and also see a list of any OpenIDs which have already been added.');
43 }
44 }
45
46 /**
47 * Implementation of hook_user().
48 */
49 function openid_user($op, &$edit, &$account, $category = NULL) {
50 if ($op == 'insert' && isset($_SESSION['openid'])) {
51 // The user has registered after trying to login via OpenID.
52 if (variable_get('user_email_verification', TRUE)) {
53 drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
54 }
55 unset($_SESSION['openid']);
56 }
57 }
58
59 /**
60 * Implementation of hook_form_alter : adds OpenID login to the login forms.
61 */
62 function openid_form_alter(&$form, $form_state, $form_id) {
63 if ($form_id == 'user_login_block' || $form_id == 'user_login') {
64 drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
65 drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js');
66 if (!empty($form_state['post']['openid_url'])) {
67 $form['name']['#required'] = FALSE;
68 $form['pass']['#required'] = FALSE;
69 unset($form['#submit']);
70 $form['#validate'] = array('openid_login_validate');
71 }
72
73 $form['openid_link'] = array('#value' => l(t('Log in using OpenID'), '#', array('attributes' => array('class' => 'openid-link'))));
74 $form['user_link'] = array('#value' => l(t('Cancel OpenID login'), '#', array('attributes' => array('class' => 'user-link'))));
75
76 $form['openid_url'] = array(
77 '#type' => 'textfield',
78 '#title' => t('Log in using OpenID'),
79 '#size' => ($form_id == 'user_login') ? 58 : 13,
80 '#maxlength' => 255,
81 '#weight' => -1,
82 '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE))
83 );
84 $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => drupal_get_destination())));
85 }
86 elseif ($form_id == 'user_register' && isset($_SESSION['openid'])) {
87 // We were unable to auto-register a new user. Prefill the registration
88 // form with the values we have.
89 $form['name']['#default_value'] = $_SESSION['openid']['name'];
90 $form['mail']['#default_value'] = $_SESSION['openid']['mail'];
91 // If user_email_verification is off, hide the password field and just fill
92 // with random password to avoid confusion.
93 if (!variable_get('user_email_verification', TRUE)) {
94 $form['pass']['#type'] = 'hidden';
95 $form['pass']['#value'] = user_password();
96 }
97 $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['auth_openid']);
98 }
99 return $form;
100 }
101
102 /**
103 * Login form _validate hook
104 */
105 function openid_login_validate($form, &$form_state) {
106 $return_to = $form_state['values']['openid.return_to'];
107 if (empty($return_to)) {
108 $return_to = url('', array('absolute' => TRUE));
109 }
110
111 openid_begin($form_state['values']['openid_url'], $return_to);
112 }
113
114 /**
115 * Callbacks.
116 */
117 function openid_authentication_page() {
118 $result = openid_complete($_REQUEST);
119 switch ($result['status']) {
120 case 'success':
121 return openid_authentication($result);
122 case 'failed':
123 drupal_set_message(t('OpenID login failed.'));
124 break;
125 case 'cancel':
126 drupal_set_message(t('OpenID login cancelled.'));
127 break;
128 }
129 drupal_goto();
130 }
131
132 function openid_user_identities($account) {
133 drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
134
135 // Check to see if we got a response
136 $result = openid_complete($_REQUEST);
137 if ($result['status'] == 'success') {
138 db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $result['openid.identity']);
139 drupal_set_message(t('Successfully added %identity', array('%identity' => $result['openid.identity'])));
140 }
141
142 $header = array(t('OpenID'), '');
143 $rows = array();
144
145 $result = db_query("SELECT * FROM {authmap} WHERE module='openid' AND uid=%d", $account->uid);
146 while ($identity = db_fetch_object($result)) {
147 $rows[] = array($identity->authname, l(t('Delete'), 'user/'. $account->uid .'/openid/delete/'. $identity->aid));
148 }
149
150 $output = theme('table', $header, $rows);
151 $output .= drupal_get_form('openid_user_add');
152 return $output;
153 }
154
155 function openid_user_add() {
156 $form['openid_url'] = array(
157 '#type' => 'textfield',
158 '#title' => t('Add an OpenID'),
159 );
160 $form['submit'] = array('#type' => 'submit', '#value' => t('Add'));
161 return $form;
162 }
163
164 function openid_user_add_validate($form, &$form_state) {
165 // Check for existing entries.
166 $claimed_id = _openid_normalize($form_state['values']['openid_url']);
167 if (db_result(db_query("SELECT authname FROM {authmap} WHERE authname='%s'", $claimed_id))) {
168 form_set_error('openid_url', t('That OpenID is already in use on this site.'));
169 }
170 else {
171 $return_to = url('user/'. arg(1) .'/openid', array('absolute' => TRUE));
172 openid_begin($form_state['values']['openid_url'], $return_to);
173 }
174 }
175
176 function openid_user_delete($account, $aid = 0) {
177 db_query("DELETE FROM {authmap} WHERE uid=%d AND aid=%d AND module='openid'", $account->uid, $aid);
178 if (db_affected_rows()) {
179 drupal_set_message(t('OpenID deleted.'));
180 }
181 drupal_goto('user/'. $account->uid .'/openid');
182 }
183
184 /**
185 * The initial step of OpenID authentication responsible for the following:
186 * - Perform discovery on the claimed OpenID.
187 * - If possible, create an association with the Provider's endpoint.
188 * - Create the authentication request.
189 * - Perform the appropriate redirect.
190 *
191 * @param $claimed_id The OpenID to authenticate
192 * @param $return_to The endpoint to return to from the OpenID Provider
193 */
194 function openid_begin($claimed_id, $return_to = '') {
195 include_once drupal_get_path('module', 'openid') .'/openid.inc';
196
197 $claimed_id = _openid_normalize($claimed_id);
198
199 $services = openid_discovery($claimed_id);
200 if (count($services) == 0) {
201 form_set_error('openid_url', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
202 return;
203 }
204
205 $op_endpoint = $services[0]['uri'];
206 // Store the discovered endpoint in the session (so we don't have to rediscover).
207 $_SESSION['openid_op_endpoint'] = $op_endpoint;
208 // Store the claimed_id in the session (for handling delegation).
209 $_SESSION['openid_claimed_id'] = $claimed_id;
210
211 // If bcmath is present, then create an association
212 $assoc_handle = '';
213 if (function_exists('bcadd')) {
214 $assoc_handle = openid_association($op_endpoint);
215 }
216
217 // Now that there is an association created, move on
218 // to request authentication from the IdP
219 $identity = (!empty($services[0]['delegate'])) ? $services[0]['delegate'] : $claimed_id;
220 if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
221 $identity = 'http://openid.net/identifier_select/2.0';
222 }
223 $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
224
225 if ($services[0]['version'] == 2) {
226 openid_redirect($op_endpoint, $authn_request);
227 }
228 else {
229 openid_redirect_http($op_endpoint, $authn_request);
230 }
231 }
232
233 /**
234 * Completes OpenID authentication by validating returned data from the OpenID
235 * Provider.
236 *
237 * @param $response Array of returned from the OpenID provider (typically $_REQUEST).
238 *
239 * @return $response Response values for further processing with
240 * $response['status'] set to one of 'success', 'failed' or 'cancel'.
241 */
242 function openid_complete($response) {
243 include_once drupal_get_path('module', 'openid') .'/openid.inc';
244
245 // Default to failed response
246 $response['status'] = 'failed';
247 if (isset($_SESSION['openid_op_endpoint']) && isset($_SESSION['openid_claimed_id'])) {
248 _openid_fix_post($response);
249 $op_endpoint = $_SESSION['openid_op_endpoint'];
250 $claimed_id = $_SESSION['openid_claimed_id'];
251 unset($_SESSION['openid_op_endpoint']);
252 unset($_SESSION['openid_claimed_id']);
253 if (isset($response['openid.mode'])) {
254 if ($response['openid.mode'] == 'cancel') {
255 $response['status'] = 'cancel';
256 }
257 else {
258 if (openid_verify_assertion($op_endpoint, $response)) {
259 $response['openid.identity'] = $claimed_id;
260 $response['status'] = 'success';
261 }
262 }
263 }
264 }
265 return $response;
266 }
267
268 /**
269 * Perform discovery on a claimed ID to determine the OpenID provider endpoint.
270 *
271 * @param $claimed_id The OpenID URL to perform discovery on.
272 *
273 * @return Array of services discovered (including OpenID version, endpoint
274 * URI, etc).
275 */
276 function openid_discovery($claimed_id) {
277 include_once drupal_get_path('module', 'openid') .'/openid.inc';
278 include_once drupal_get_path('module', 'openid') .'/xrds.inc';
279
280 $services = array();
281
282 $xrds_url = $claimed_id;
283 if (_openid_is_xri($claimed_id)) {
284 $xrds_url = 'http://xri.net/'. $claimed_id;
285 }
286 $url = @parse_url($xrds_url);
287 if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
288 // For regular URLs, try Yadis resolution first, then HTML-based discovery
289 $headers = array('Accept' => 'application/xrds+xml');
290 $result = drupal_http_request($xrds_url, $headers);
291
292 if (!isset($result->error)) {
293 if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
294 // Parse XML document to find URL
295 $services = xrds_parse($result->data);
296 }
297 else {
298 $xrds_url = NULL;
299 if (isset($result->headers['X-XRDS-Location'])) {
300 $xrds_url = $result->headers['X-XRDS-Location'];
301 }
302 else {
303 // Look for meta http-equiv link in HTML head
304 $xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
305 }
306 if (!empty($xrds_url)) {
307 $headers = array('Accept' => 'application/xrds+xml');
308 $xrds_result = drupal_http_request($xrds_url, $headers);
309 if (!isset($xrds_result->error)) {
310 $services = xrds_parse($xrds_result->data);
311 }
312 }
313 }
314
315 // Check for HTML delegation
316 if (count($services) == 0) {
317 // Look for 2.0 links
318 $uri = _openid_link_href('openid2.provider', $result->data);
319 $delegate = _openid_link_href('openid2.local_id', $result->data);
320 $version = 2;
321
322 // 1.0 links
323 if (empty($uri)) {
324 $uri = _openid_link_href('openid.server', $result->data);
325 $delegate = _openid_link_href('openid.delegate', $result->data);
326 $version = 1;
327 }
328 if (!empty($uri)) {
329 $services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version);
330 }
331 }
332 }
333 }
334 return $services;
335 }
336
337 /**
338 * Attempt to create a shared secret with the OpenID Provider.
339 *
340 * @param $op_endpoint URL of the OpenID Provider endpoint.
341 *
342 * @return $assoc_handle The association handle.
343 */
344 function openid_association($op_endpoint) {
345 include_once drupal_get_path('module', 'openid') .'/openid.inc';
346
347 // Remove Old Associations:
348 db_query("DELETE FROM {openid_association} WHERE created + expires_in < %d", time());
349
350 // Check to see if we have an association for this IdP already
351 $assoc_handle = db_result(db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = '%s'", $op_endpoint));
352 if (empty($assoc_handle)) {
353 $mod = OPENID_DH_DEFAULT_MOD;
354 $gen = OPENID_DH_DEFAULT_GEN;
355 $r = _openid_dh_rand($mod);
356 $private = bcadd($r, 1);
357 $public = bcpowmod($gen, $private, $mod);
358
359 // If there is no existing association, then request one
360 $assoc_request = openid_association_request($public);
361 $assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
362 $assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
363 $assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message);
364 if (isset($assoc_result->error)) {
365 return FALSE;
366 }
367
368 $assoc_response = _openid_parse_message($assoc_result->data);
369 if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') {
370 return FALSE;
371 }
372
373 if ($assoc_response['session_type'] == 'DH-SHA1') {
374 $spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']);
375 $enc_mac_key = base64_decode($assoc_response['enc_mac_key']);
376 $shared = bcpowmod($spub, $private, $mod);
377 $assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key));
378 }
379 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)",
380 $op_endpoint, $assoc_response['session_type'], $assoc_response['assoc_handle'], $assoc_response['assoc_type'], $assoc_response['expires_in'], $assoc_response['mac_key'], time());
381
382 $assoc_handle = $assoc_response['assoc_handle'];
383 }
384
385 return $assoc_handle;
386 }
387
388 /**
389 * Authenticate a user or attempt registration.
390 *
391 * @param $response Response values from the OpenID Provider.
392 */
393 function openid_authentication($response) {
394 include_once drupal_get_path('module', 'openid') .'/openid.inc';
395
396 $identity = $response['openid.identity'];
397
398 $account = user_external_load($identity);
399 if (isset($account->uid)) {
400 if (!variable_get('user_email_verification', TRUE) || $account->login) {
401 user_external_login($account);
402 }
403 else {
404 drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
405 }
406 }
407 else {
408 // Register new user
409 $form_state['redirect'] = NULL;
410 $form_state['values']['name'] = (empty($response['openid.sreg.nickname'])) ? $identity : $response['openid.sreg.nickname'];
411 $form_state['values']['mail'] = (empty($response['openid.sreg.email'])) ? '' : $response['openid.sreg.email'];
412 $form_state['values']['pass'] = user_password();
413 $form_state['values']['status'] = variable_get('user_register', 1) == 1;
414 $form_state['values']['response'] = $response;
415 $form_state['values']['auth_openid'] = $identity;
416 $form = drupal_retrieve_form('user_register', $form_state);
417 drupal_prepare_form('user_register', $form, $form_state);
418 drupal_validate_form('user_register', $form, $form_state);
419 if (form_get_errors()) {
420 // We were unable to register a valid new user, redirect to standard
421 // user/register and prefill with the values we received.
422 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');
423 $_SESSION['openid'] = $form_state['values'];
424 // We'll want to redirect back to the same place.
425 $destination = drupal_get_destination();
426 unset($_REQUEST['destination']);
427 drupal_goto('user/register', $destination);
428 }
429 else {
430 unset($form_state['values']['response']);
431 $account = user_save('', $form_state['values']);
432 user_external_login($account);
433 }
434 drupal_redirect_form($form, $form_state['redirect']);
435 }
436 drupal_goto();
437 }
438
439 function openid_association_request($public) {
440 include_once drupal_get_path('module', 'openid') .'/openid.inc';
441
442 $request = array(
443 'openid.ns' => OPENID_NS_2_0,
444 'openid.mode' => 'associate',
445 'openid.session_type' => 'DH-SHA1',
446 'openid.assoc_type' => 'HMAC-SHA1'
447 );
448
449 if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') {
450 $cpub = _openid_dh_long_to_base64($public);
451 $request['openid.dh_consumer_public'] = $cpub;
452 }
453
454 return $request;
455 }
456
457 function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
458 include_once drupal_get_path('module', 'openid') .'/openid.inc';
459
460 $realm = ($return_to) ? $return_to : url('', array('absolute' => TRUE));
461
462 $ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0;
463 $request = array(
464 'openid.ns' => $ns,
465 'openid.mode' => 'checkid_setup',
466 'openid.identity' => $identity,
467 'openid.claimed_id' => $claimed_id,
468 'openid.assoc_handle' => $assoc_handle,
469 'openid.return_to' => $return_to,
470 );
471
472 if ($version == 2) {
473 $request['openid.realm'] = $realm;
474 }
475 else {
476 $request['openid.trust_root'] = $realm;
477 }
478
479 // Simple Registration
480 $request['openid.sreg.required'] = 'nickname,email';
481 $request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
482
483 $request = array_merge($request, module_invoke_all('openid', 'request', $request));
484
485 return $request;
486 }
487
488 /**
489 * Attempt to verify the response received from the OpenID Provider.
490 *
491 * @param $op_endpoint The OpenID Provider URL.
492 * @param $response Array of repsonse values from the provider.
493 *
494 * @return boolean
495 */
496 function openid_verify_assertion($op_endpoint, $response) {
497 include_once drupal_get_path('module', 'openid') .'/openid.inc';
498
499 $valid = FALSE;
500
501 $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle']));
502 if ($association && isset($association->session_type)) {
503 $keys_to_sign = explode(',', $response['openid.signed']);
504 $self_sig = _openid_signature($association, $response, $keys_to_sign);
505 if ($self_sig == $response['openid.sig']) {
506 $valid = TRUE;
507 }
508 else {
509 $valid = FALSE;
510 }
511 }
512 else {
513 $request = $response;
514 $request['openid.mode'] = 'check_authentication';
515 $message = _openid_create_message($request);
516 $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
517 $result = drupal_http_request($op_endpoint, $headers, 'POST', _openid_encode_message($message));
518 if (!isset($result->error)) {
519 $response = _openid_parse_message($result->data);
520 if (strtolower(trim($response['is_valid'])) == 'true') {
521 $valid = TRUE;
522 }
523 else {
524 $valid = FALSE;
525 }
526 }
527 }
528
529 return $valid;
530 }

  ViewVC Help
Powered by ViewVC 1.1.2