summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2007-06-18 16:09:39 +0000
committerDries Buytaert <dries@buytaert.net>2007-06-18 16:09:39 +0000
commitaa308028aa570d15d3abf9c7679d7de16019b78b (patch)
treec7f87e5423007f088feb99149ebf2d16752631be /modules
parent8f9298577e7db9af6d34c3d69bd633a5c7e3de74 (diff)
downloadbrdo-aa308028aa570d15d3abf9c7679d7de16019b78b.tar.gz
brdo-aa308028aa570d15d3abf9c7679d7de16019b78b.tar.bz2
- Patch #131026 by James et al: OpenID client support for Drupal!
Let this be the day where we help revolutionize the online society, and the way websites and web services interoperate. Or something.
Diffstat (limited to 'modules')
-rw-r--r--modules/locale/locale.module8
-rw-r--r--modules/node/node.module2
-rw-r--r--modules/openid/login-bg.pngbin0 -> 426 bytes
-rw-r--r--modules/openid/openid.css32
-rw-r--r--modules/openid/openid.inc420
-rw-r--r--modules/openid/openid.info6
-rw-r--r--modules/openid/openid.install18
-rw-r--r--modules/openid/openid.js29
-rw-r--r--modules/openid/openid.module530
-rw-r--r--modules/openid/openid.schema19
-rw-r--r--modules/openid/xrds.inc79
11 files changed, 1138 insertions, 5 deletions
diff --git a/modules/locale/locale.module b/modules/locale/locale.module
index 86831fa6c..e296f68e8 100644
--- a/modules/locale/locale.module
+++ b/modules/locale/locale.module
@@ -285,7 +285,7 @@ function locale_theme() {
*
* @param $string
* A string to look up translation for. If omitted, all the
- * cached strings will be returned in all languages already
+ * cached strings will be returned in all languages already
* used on the page.
* @param $langcode
* Language code to use for the lookup.
@@ -304,9 +304,9 @@ function locale($string = NULL, $langcode = NULL) {
// Store database cached translations in a static var.
if (!isset($locale_t[$langcode])) {
$locale_t[$langcode] = array();
- // Disabling the usage of string caching allows a module to watch for
- // the exact list of strings used on a page. From a performance
- // perspective that is a really bad idea, so we have no user
+ // Disabling the usage of string caching allows a module to watch for
+ // the exact list of strings used on a page. From a performance
+ // perspective that is a really bad idea, so we have no user
// interface for this. Be careful when turning this option off!
if (variable_get('locale_cache_strings', 1) == 1) {
if (!($cache = cache_get('locale:'. $langcode, 'cache'))) {
diff --git a/modules/node/node.module b/modules/node/node.module
index 091adbdc7..c253610d9 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1383,7 +1383,7 @@ function node_filters() {
}
$filters['type'] = array('title' => t('type'), 'options' => node_get_types('names'));
-
+
// The taxonomy filter
if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
$filters['category'] = array('title' => t('category'), 'options' => $taxonomy);
diff --git a/modules/openid/login-bg.png b/modules/openid/login-bg.png
new file mode 100644
index 000000000..8eca055f3
--- /dev/null
+++ b/modules/openid/login-bg.png
Binary files differ
diff --git a/modules/openid/openid.css b/modules/openid/openid.css
new file mode 100644
index 000000000..e6c30df2a
--- /dev/null
+++ b/modules/openid/openid.css
@@ -0,0 +1,32 @@
+a.openid-link, a.user-link, #edit-openid-url {
+ background-image: url("login-bg.png");
+ background-position: 0% 50%;
+ background-repeat: no-repeat;
+ padding-left: 20px;
+}
+
+div#edit-openid-url-wrapper {
+ display: block;
+}
+
+html.js #user-login-form div#edit-openid-url-wrapper,
+html.js #user-login div#edit-openid-url-wrapper {
+ display: none;
+}
+
+html.js #user-login-form a.openid-link,
+html.js #user-login a.openid-link {
+ display : block;
+}
+
+#user-login-form a.openid-link,
+#user-login-form a.user-link,
+#user-login a.openid-link,
+#user-login a.user-link {
+ display: none;
+}
+
+#user-login-form a.openid-link,
+#user-login-form a.user-link {
+ text-align : left;
+}
diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc
new file mode 100644
index 000000000..e54758a3c
--- /dev/null
+++ b/modules/openid/openid.inc
@@ -0,0 +1,420 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * OpenID utility functions.
+ */
+
+// Diffie-Hellman Key Exchange Default Value.
+define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
+ '966915404479707795314057629378541917580651227423698188993727816152646631'.
+ '438561595825688188889951272158842675419950341258706556549803580104870537'.
+ '681476726513255747040765857479291291572334510643245094715007229621094194'.
+ '349783925984760375594985848253359305585439638443');
+
+// Constants for Diffie-Hellman key exchange computations.
+define('OPENID_DH_DEFAULT_GEN', '2');
+define('OPENID_SHA1_BLOCKSIZE', 64);
+define('OPENID_RAND_SOURCE', '/dev/urandom');
+
+// OpenID namespace URLs
+define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
+define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
+define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
+
+/**
+ * Performs an HTTP 302 redirect (for the 1.x protocol).
+ */
+function openid_redirect_http($url, $message) {
+ $query = array();
+ foreach ($message as $key => $val) {
+ $query[] = $key .'='. urlencode($val);
+ }
+
+ $sep = (strpos($url, '?') === FALSE) ? '?' : '&';
+ header('Location: ' . $url . $sep . implode('&', $query), TRUE, 302);
+ exit;
+}
+
+/**
+ * Creates a js auto-submit redirect for (for the 2.x protocol)
+ */
+function openid_redirect($url, $message) {
+ $output = '<html><head><title>'.t('OpenID redirect'). "</title></head>\n<body>";
+ $output .= drupal_get_form('openid_redirect_form', $url, $message);
+ $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>';
+ $output .= "</body></html>\n";
+ print $output;
+ exit;
+}
+
+function openid_redirect_form(&$form_state, $url, $message) {
+ $form = array();
+ $form['#action'] = $url;
+ $form['#method'] = "post";
+ foreach ($message as $key => $value) {
+ $form[$key] = array(
+ '#type' => 'hidden',
+ '#name' => $key,
+ '#value' => $value,
+ );
+ }
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#prefix' => '<noscript>',
+ '#suffix' => '</noscript>',
+ '#value' => t('Send'),
+ );
+
+ return $form;
+}
+
+/**
+ * Determine if the given identifier is an XRI ID.
+ */
+function _openid_is_xri($identifier) {
+ $firstchar = substr($identifier, 0, 1);
+ if ($firstchar == "@" || $firstchar == "=")
+ return TRUE;
+
+ if (stristr($identifier, 'xri://') !== FALSE) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Normalize the given identifer as per spec.
+ */
+function _openid_normalize($identifier) {
+ if (_openid_is_xri($identifier)) {
+ return _openid_normalize_xri($identifier);
+ }
+ else {
+ return _openid_normalize_url($identifier);
+ }
+}
+
+function _openid_normalize_xri($xri) {
+ $normalized_xri = $xri;
+ if (stristr($xri, 'xri://') !== FALSE) {
+ $normalized_xri = substr($xri, 6);
+ }
+ return $normalized_xri;
+}
+
+function _openid_normalize_url($url) {
+ $normalized_url = $url;
+
+ if (stristr($url, '://') === FALSE) {
+ $normalized_url = 'http://' . $url;
+ }
+
+ if (substr_count($normalized_url, '/') < 3) {
+ $normalized_url .= '/';
+ }
+
+ return $normalized_url;
+}
+
+/**
+ * Create a serialized message packet as per spec: $key:$value\n .
+ */
+function _openid_create_message($data) {
+ $serialized = '';
+
+ foreach ($data as $key => $value) {
+ if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
+ return null;
+ }
+ $serialized .= "$key:$value\n";
+ }
+ return $serialized;
+}
+
+/**
+ * Encode a message from _openid_create_message for HTTP Post
+ */
+function _openid_encode_message($message) {
+ $encoded_message = '';
+
+ $items = explode("\n", $message);
+ foreach ($items as $item) {
+ $parts = explode(':', $item, 2);
+
+ if (count($parts) == 2) {
+ if ($encoded_message != '') {
+ $encoded_message .= '&';
+ }
+ $encoded_message .= rawurlencode(trim($parts[0])) . '=' . rawurlencode(trim($parts[1]));
+ }
+ }
+
+ return $encoded_message;
+}
+
+/**
+ * Convert a direct communication message
+ * into an associative array.
+ */
+function _openid_parse_message($message) {
+ $parsed_message = array();
+
+ $items = explode("\n", $message);
+ foreach ($items as $item) {
+ $parts = explode(':', $item, 2);
+
+ if (count($parts) == 2) {
+ $parsed_message[$parts[0]] = $parts[1];
+ }
+ }
+
+ return $parsed_message;
+}
+
+/**
+ * Return a nonce value - formatted per OpenID spec.
+ */
+function _openid_nonce() {
+ // YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
+ return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') .
+ chr(mt_rand(0, 25) + 65) .
+ chr(mt_rand(0, 25) + 65) .
+ chr(mt_rand(0, 25) + 65) .
+ chr(mt_rand(0, 25) + 65);
+}
+
+/**
+ * Pull the href attribute out of an html link element.
+ */
+function _openid_link_href($rel, $html) {
+ $rel = preg_quote($rel);
+ preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iU', $html, $matches);
+ if (isset($matches[3])) {
+ preg_match('|href=["\']([^"]+)["\']|iU', $matches[0], $href);
+ return trim($href[1]);
+ }
+ return FALSE;
+}
+
+/**
+ * Pull the http-equiv attribute out of an html meta element
+ */
+function _openid_meta_httpequiv($equiv, $html) {
+ preg_match('|<meta\s+http-equiv=["\']' . $equiv . '["\'](.*)/?>|iU', $html, $matches);
+ if (isset($matches[1])) {
+ preg_match('|content=["\']([^"]+)["\']|iU', $matches[1], $content);
+ return $content[1];
+ }
+ return FALSE;
+}
+
+/**
+ * Sign certain keys in a message
+ * @param $association - object loaded from openid_association or openid_server_association table
+ * - important fields are ->assoc_type and ->mac_key
+ * @param $message_array - array of entire message about to be sent
+ * @param $keys_to_sign - keys in the message to include in signature (without
+ * 'openid.' appended)
+ */
+function _openid_signature($association, $message_array, $keys_to_sign) {
+ $signature = '';
+ $sign_data = array();
+
+ foreach ($keys_to_sign as $key) {
+ if (isset($message_array['openid.' . $key])) {
+ $sign_data[$key] = $message_array['openid.' . $key];
+ }
+ }
+
+ $message = _openid_create_message($sign_data);
+ $secret = base64_decode($association->mac_key);
+ $signature = _openid_hmac($secret, $message);
+
+ return base64_encode($signature);
+}
+
+function _openid_hmac($key, $text) {
+ if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
+ $key = _openid_sha1($key, true);
+ }
+
+ $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
+ $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
+ $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
+ $hash1 = _openid_sha1(($key ^ $ipad) . $text, true);
+ $hmac = _openid_sha1(($key ^ $opad) . $hash1, true);
+
+ return $hmac;
+}
+
+function _openid_sha1($text) {
+ $hex = sha1($text);
+ $raw = '';
+ for ($i = 0; $i < 40; $i += 2) {
+ $hexcode = substr($hex, $i, 2);
+ $charcode = (int)base_convert($hexcode, 16, 10);
+ $raw .= chr($charcode);
+ }
+ return $raw;
+}
+
+function _openid_dh_base64_to_long($str) {
+ $b64 = base64_decode($str);
+
+ return _openid_dh_binary_to_long($b64);
+}
+
+function _openid_dh_long_to_base64($str) {
+ return base64_encode(_openid_dh_long_to_binary($str));
+}
+
+function _openid_dh_binary_to_long($str) {
+ $bytes = array_merge(unpack('C*', $str));
+
+ $n = 0;
+ foreach ($bytes as $byte) {
+ $n = bcmul($n, pow(2, 8));
+ $n = bcadd($n, $byte);
+ }
+
+ return $n;
+}
+
+function _openid_dh_long_to_binary($long) {
+ $cmp = bccomp($long, 0);
+ if ($cmp < 0) {
+ return FALSE;
+ }
+
+ if ($cmp == 0) {
+ return "\x00";
+ }
+
+ $bytes = array();
+
+ while (bccomp($long, 0) > 0) {
+ array_unshift($bytes, bcmod($long, 256));
+ $long = bcdiv($long, pow(2, 8));
+ }
+
+ if ($bytes && ($bytes[0] > 127)) {
+ array_unshift($bytes, 0);
+ }
+
+ $string = '';
+ foreach ($bytes as $byte) {
+ $string .= pack('C', $byte);
+ }
+
+ return $string;
+}
+
+function _openid_dh_xorsecret($shared, $secret) {
+ $dh_shared_str = _openid_dh_long_to_binary($shared);
+ $sha1_dh_shared = _openid_sha1($dh_shared_str);
+ $xsecret = "";
+ for ($i = 0; $i < strlen($secret); $i++) {
+ $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
+ }
+
+ return $xsecret;
+}
+
+function _openid_dh_rand($stop) {
+ static $duplicate_cache = array();
+
+ // Used as the key for the duplicate cache
+ $rbytes = _openid_dh_long_to_binary($stop);
+
+ if (array_key_exists($rbytes, $duplicate_cache)) {
+ list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+ }
+ else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = strlen($rbytes) - 1;
+ }
+ else {
+ $nbytes = strlen($rbytes);
+ }
+
+ $mxrand = bcpow(256, $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = bcmod($mxrand, $stop);
+
+ if (count($duplicate_cache) > 10) {
+ $duplicate_cache = array();
+ }
+
+ $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+ }
+
+ do {
+ $bytes = "\x00" . _openid_get_bytes($nbytes);
+ $n = _openid_dh_binary_to_long($bytes);
+ // Keep looping if this value is in the low duplicated range.
+ } while (bccomp($n, $duplicate) < 0);
+
+ return bcmod($n, $stop);
+}
+
+function _openid_get_bytes($num_bytes) {
+ static $f = null;
+ $bytes = '';
+ if (!isset($f)) {
+ $f = @fopen(OPENID_RAND_SOURCE, "r");
+ }
+ if (!isset($f)) {
+ // pseudorandom used
+ $bytes = '';
+ for ($i = 0; $i < $num_bytes; $i += 4) {
+ $bytes .= pack('L', mt_rand());
+ }
+ $bytes = substr($bytes, 0, $num_bytes);
+ }
+ else {
+ $bytes = fread($f, $num_bytes);
+ }
+ return $bytes;
+}
+
+/**
+ * Fix PHP's habit of replacing '.' by '_' in posted data.
+ */
+function _openid_fix_post(&$post) {
+ $extensions = module_invoke_all('openid', 'extension');
+ foreach ($post as $key => $value) {
+ if (strpos($key, 'openid_') === 0) {
+ $fixed_key = str_replace('openid_', 'openid.', $key);
+ $fixed_key = str_replace('openid.ns_', 'openid.ns.', $fixed_key);
+ $fixed_key = str_replace('openid.sreg_', 'openid.sreg.', $fixed_key);
+ foreach ($extensions as $ext) {
+ $fixed_key = str_replace('openid.'.$ext.'_', 'openid.'.$ext.'.', $fixed_key);
+ }
+ unset($post[$key]);
+ $post[$fixed_key] = $value;
+ }
+ }
+}
+
+/**
+ * Provide bcpowmod support for PHP4.
+ */
+if (!function_exists('bcpowmod')) {
+ function bcpowmod($base, $exp, $mod) {
+ $square = bcmod($base, $mod);
+ $result = 1;
+ while (bccomp($exp, 0) > 0) {
+ if (bcmod($exp, 2)) {
+ $result = bcmod(bcmul($result, $square), $mod);
+ }
+ $square = bcmod(bcmul($square, $square), $mod);
+ $exp = bcdiv($exp, 2);
+ }
+ return $result;
+ }
+} \ No newline at end of file
diff --git a/modules/openid/openid.info b/modules/openid/openid.info
new file mode 100644
index 000000000..724434164
--- /dev/null
+++ b/modules/openid/openid.info
@@ -0,0 +1,6 @@
+; $Id$
+name = OpenID
+description = "Allows Drupal to act as an OpenID relying party."
+version = VERSION
+package = Core - optional
+core = 6.x
diff --git a/modules/openid/openid.install b/modules/openid/openid.install
new file mode 100644
index 000000000..c98b9ea34
--- /dev/null
+++ b/modules/openid/openid.install
@@ -0,0 +1,18 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function openid_install() {
+ // Create table.
+ drupal_install_schema('openid');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function openid_uninstall() {
+ // Remove table.
+ drupal_uninstall_schema('openid');
+}
diff --git a/modules/openid/openid.js b/modules/openid/openid.js
new file mode 100644
index 000000000..5f27b66f5
--- /dev/null
+++ b/modules/openid/openid.js
@@ -0,0 +1,29 @@
+// $Id$
+
+$(document).ready(
+ function() {
+ if ($("#edit-openid-url").val()) {
+ $("#edit-name-wrapper").hide();
+ $("#edit-pass-wrapper").hide();
+ $("#edit-openid-url-wrapper").show();
+ $("a.openid-link").hide();
+ }
+ $("a.openid-link").click( function() {
+ $("#edit-pass-wrapper").hide();
+ $("#edit-name-wrapper").fadeOut('medium', function() {
+ $("#edit-openid-url-wrapper").fadeIn('medium');
+ });
+ $("a.openid-link").hide();
+ $("a.user-link").show();
+ return false;
+ });
+ $("a.user-link").click( function() {
+ $("#edit-openid-url-wrapper").hide();
+ $("#edit-pass-wrapper").show();
+ $("#edit-name-wrapper").show();
+ $("a.user-link").hide();
+ $("a.openid-link").show();
+ return false;
+ });
+ });
+
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
new file mode 100644
index 000000000..5be1fb137
--- /dev/null
+++ b/modules/openid/openid.module
@@ -0,0 +1,530 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Implement OpenID Relying Party support for Drupal
+ */
+
+/**
+ * Implementation of hook_menu.
+ */
+function openid_menu() {
+ $items['openid/authenticate'] = array(
+ 'title' => 'OpenID Login',
+ 'page callback' => 'openid_authentication_page',
+ 'access callback' => 'user_is_anonymous',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['user/%user/openid'] = array(
+ 'title' => 'OpenID identities',
+ 'page callback' => 'openid_user_identities',
+ 'page arguments' => array(1),
+ 'access callback' => 'user_edit_access',
+ 'access arguments' => array(1),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['user/%user/openid/delete'] = array(
+ 'title' => 'Delete OpenID',
+ 'page callback' => 'openid_user_delete',
+ 'page arguments' => array(1),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function openid_help($section) {
+ switch ($section) {
+ case 'user/%/openid':
+ 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.');
+ }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function openid_user($op, &$edit, &$account, $category = NULL) {
+ if ($op == 'insert' && isset($_SESSION['openid'])) {
+ // The user has registered after trying to login via OpenID.
+ if (variable_get('user_email_verification', TRUE)) {
+ drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
+ }
+ unset($_SESSION['openid']);
+ }
+}
+
+/**
+ * Implementation of hook_form_alter : adds OpenID login to the login forms.
+ */
+function openid_form_alter(&$form, $form_state, $form_id) {
+ if ($form_id == 'user_login_block' || $form_id == 'user_login') {
+ drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
+ drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js');
+ if (!empty($form_state['post']['openid_url'])) {
+ $form['name']['#required'] = FALSE;
+ $form['pass']['#required'] = FALSE;
+ unset($form['#submit']);
+ $form['#validate'] = array('openid_login_validate');
+ }
+
+ $form['openid_link'] = array('#value' => l(t('Log in using OpenID'), '#', array('attributes' => array('class' => 'openid-link'))));
+ $form['user_link'] = array('#value' => l(t('Cancel OpenID login'), '#', array('attributes' => array('class' => 'user-link'))));
+
+ $form['openid_url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Log in using OpenID'),
+ '#size' => ($form_id == 'user_login') ? 58 : 13,
+ '#maxlength' => 255,
+ '#weight' => -1,
+ '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE))
+ );
+ $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => drupal_get_destination())));
+ }
+ elseif ($form_id == 'user_register' && isset($_SESSION['openid'])) {
+ // We were unable to auto-register a new user. Prefill the registration
+ // form with the values we have.
+ $form['name']['#default_value'] = $_SESSION['openid']['name'];
+ $form['mail']['#default_value'] = $_SESSION['openid']['mail'];
+ // If user_email_verification is off, hide the password field and just fill
+ // with random password to avoid confusion.
+ if (!variable_get('user_email_verification', TRUE)) {
+ $form['pass']['#type'] = 'hidden';
+ $form['pass']['#value'] = user_password();
+ }
+ $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['auth_openid']);
+ }
+ return $form;
+}
+
+/**
+ * Login form _validate hook
+ */
+function openid_login_validate($form, &$form_state) {
+ $return_to = $form_state['values']['openid.return_to'];
+ if (empty($return_to)) {
+ $return_to = url('', array('absolute' => TRUE));
+ }
+
+ openid_begin($form_state['values']['openid_url'], $return_to);
+}
+
+/**
+ * Callbacks.
+ */
+function openid_authentication_page() {
+ $result = openid_complete($_REQUEST);
+ switch ($result['status']) {
+ case 'success':
+ return openid_authentication($result);
+ case 'failed':
+ drupal_set_message(t('OpenID login failed.'), 'error');
+ break;
+ case 'cancel':
+ drupal_set_message(t('OpenID login cancelled.'));
+ break;
+ }
+ drupal_goto();
+}
+
+function openid_user_identities($account) {
+ drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
+
+ // Check to see if we got a response
+ $result = openid_complete($_REQUEST);
+ if ($result['status'] == 'success') {
+ db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $result['openid.identity']);
+ drupal_set_message(t('Successfully added %identity', array('%identity' => $result['openid.identity'])));
+ }
+
+ $header = array(t('OpenID'), t('Operations'));
+ $rows = array();
+
+ $result = db_query("SELECT * FROM {authmap} WHERE module='openid' AND uid=%d", $account->uid);
+ while ($identity = db_fetch_object($result)) {
+ $rows[] = array($identity->authname, l(t('Delete'), 'user/'. $account->uid .'/openid/delete/'. $identity->aid));
+ }
+
+ $output = theme('table', $header, $rows);
+ $output .= drupal_get_form('openid_user_add');
+ return $output;
+}
+
+function openid_user_add() {
+ $form['openid_url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('OpenID'),
+ );
+ $form['submit'] = array('#type' => 'submit', '#value' => t('Add an OpenID'));
+ return $form;
+}
+
+function openid_user_add_validate($form, &$form_state) {
+ // Check for existing entries.
+ $claimed_id = _openid_normalize($form_state['values']['openid_url']);
+ if (db_result(db_query("SELECT authname FROM {authmap} WHERE authname='%s'", $claimed_id))) {
+ form_set_error('openid_url', t('That OpenID is already in use on this site.'));
+ }
+ else {
+ $return_to = url('user/'. arg(1) .'/openid', array('absolute' => TRUE));
+ openid_begin($form_state['values']['openid_url'], $return_to);
+ }
+}
+
+function openid_user_delete($account, $aid = 0) {
+ db_query("DELETE FROM {authmap} WHERE uid=%d AND aid=%d AND module='openid'", $account->uid, $aid);
+ if (db_affected_rows()) {
+ drupal_set_message(t('OpenID deleted.'));
+ }
+ drupal_goto('user/'. $account->uid .'/openid');
+}
+
+/**
+ * The initial step of OpenID authentication responsible for the following:
+ * - Perform discovery on the claimed OpenID.
+ * - If possible, create an association with the Provider's endpoint.
+ * - Create the authentication request.
+ * - Perform the appropriate redirect.
+ *
+ * @param $claimed_id The OpenID to authenticate
+ * @param $return_to The endpoint to return to from the OpenID Provider
+ */
+function openid_begin($claimed_id, $return_to = '') {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ $claimed_id = _openid_normalize($claimed_id);
+
+ $services = openid_discovery($claimed_id);
+ if (count($services) == 0) {
+ form_set_error('openid_url', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
+ return;
+ }
+
+ $op_endpoint = $services[0]['uri'];
+ // Store the discovered endpoint in the session (so we don't have to rediscover).
+ $_SESSION['openid_op_endpoint'] = $op_endpoint;
+ // Store the claimed_id in the session (for handling delegation).
+ $_SESSION['openid_claimed_id'] = $claimed_id;
+
+ // If bcmath is present, then create an association
+ $assoc_handle = '';
+ if (function_exists('bcadd')) {
+ $assoc_handle = openid_association($op_endpoint);
+ }
+
+ // Now that there is an association created, move on
+ // to request authentication from the IdP
+ $identity = (!empty($services[0]['delegate'])) ? $services[0]['delegate'] : $claimed_id;
+ if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
+ $identity = 'http://openid.net/identifier_select/2.0';
+ }
+ $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
+
+ if ($services[0]['version'] == 2) {
+ openid_redirect($op_endpoint, $authn_request);
+ }
+ else {
+ openid_redirect_http($op_endpoint, $authn_request);
+ }
+}
+
+/**
+ * Completes OpenID authentication by validating returned data from the OpenID
+ * Provider.
+ *
+ * @param $response Array of returned from the OpenID provider (typically $_REQUEST).
+ *
+ * @return $response Response values for further processing with
+ * $response['status'] set to one of 'success', 'failed' or 'cancel'.
+ */
+function openid_complete($response) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ // Default to failed response
+ $response['status'] = 'failed';
+ if (isset($_SESSION['openid_op_endpoint']) && isset($_SESSION['openid_claimed_id'])) {
+ _openid_fix_post($response);
+ $op_endpoint = $_SESSION['openid_op_endpoint'];
+ $claimed_id = $_SESSION['openid_claimed_id'];
+ unset($_SESSION['openid_op_endpoint']);
+ unset($_SESSION['openid_claimed_id']);
+ if (isset($response['openid.mode'])) {
+ if ($response['openid.mode'] == 'cancel') {
+ $response['status'] = 'cancel';
+ }
+ else {
+ if (openid_verify_assertion($op_endpoint, $response)) {
+ $response['openid.identity'] = $claimed_id;
+ $response['status'] = 'success';
+ }
+ }
+ }
+ }
+ return $response;
+}
+
+/**
+ * Perform discovery on a claimed ID to determine the OpenID provider endpoint.
+ *
+ * @param $claimed_id The OpenID URL to perform discovery on.
+ *
+ * @return Array of services discovered (including OpenID version, endpoint
+ * URI, etc).
+ */
+function openid_discovery($claimed_id) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+ include_once drupal_get_path('module', 'openid') .'/xrds.inc';
+
+ $services = array();
+
+ $xrds_url = $claimed_id;
+ if (_openid_is_xri($claimed_id)) {
+ $xrds_url = 'http://xri.net/'. $claimed_id;
+ }
+ $url = @parse_url($xrds_url);
+ if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
+ // For regular URLs, try Yadis resolution first, then HTML-based discovery
+ $headers = array('Accept' => 'application/xrds+xml');
+ $result = drupal_http_request($xrds_url, $headers);
+
+ if (!isset($result->error)) {
+ if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
+ // Parse XML document to find URL
+ $services = xrds_parse($result->data);
+ }
+ else {
+ $xrds_url = NULL;
+ if (isset($result->headers['X-XRDS-Location'])) {
+ $xrds_url = $result->headers['X-XRDS-Location'];
+ }
+ else {
+ // Look for meta http-equiv link in HTML head
+ $xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
+ }
+ if (!empty($xrds_url)) {
+ $headers = array('Accept' => 'application/xrds+xml');
+ $xrds_result = drupal_http_request($xrds_url, $headers);
+ if (!isset($xrds_result->error)) {
+ $services = xrds_parse($xrds_result->data);
+ }
+ }
+ }
+
+ // Check for HTML delegation
+ if (count($services) == 0) {
+ // Look for 2.0 links
+ $uri = _openid_link_href('openid2.provider', $result->data);
+ $delegate = _openid_link_href('openid2.local_id', $result->data);
+ $version = 2;
+
+ // 1.0 links
+ if (empty($uri)) {
+ $uri = _openid_link_href('openid.server', $result->data);
+ $delegate = _openid_link_href('openid.delegate', $result->data);
+ $version = 1;
+ }
+ if (!empty($uri)) {
+ $services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version);
+ }
+ }
+ }
+ }
+ return $services;
+}
+
+/**
+ * Attempt to create a shared secret with the OpenID Provider.
+ *
+ * @param $op_endpoint URL of the OpenID Provider endpoint.
+ *
+ * @return $assoc_handle The association handle.
+ */
+function openid_association($op_endpoint) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ // Remove Old Associations:
+ db_query("DELETE FROM {openid_association} WHERE created + expires_in < %d", time());
+
+ // Check to see if we have an association for this IdP already
+ $assoc_handle = db_result(db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = '%s'", $op_endpoint));
+ if (empty($assoc_handle)) {
+ $mod = OPENID_DH_DEFAULT_MOD;
+ $gen = OPENID_DH_DEFAULT_GEN;
+ $r = _openid_dh_rand($mod);
+ $private = bcadd($r, 1);
+ $public = bcpowmod($gen, $private, $mod);
+
+ // If there is no existing association, then request one
+ $assoc_request = openid_association_request($public);
+ $assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
+ $assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
+ $assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message);
+ if (isset($assoc_result->error)) {
+ return FALSE;
+ }
+
+ $assoc_response = _openid_parse_message($assoc_result->data);
+ if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') {
+ return FALSE;
+ }
+
+ if ($assoc_response['session_type'] == 'DH-SHA1') {
+ $spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']);
+ $enc_mac_key = base64_decode($assoc_response['enc_mac_key']);
+ $shared = bcpowmod($spub, $private, $mod);
+ $assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key));
+ }
+ 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)",
+ $op_endpoint, $assoc_response['session_type'], $assoc_response['assoc_handle'], $assoc_response['assoc_type'], $assoc_response['expires_in'], $assoc_response['mac_key'], time());
+
+ $assoc_handle = $assoc_response['assoc_handle'];
+ }
+
+ return $assoc_handle;
+}
+
+/**
+ * Authenticate a user or attempt registration.
+ *
+ * @param $response Response values from the OpenID Provider.
+ */
+function openid_authentication($response) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ $identity = $response['openid.identity'];
+
+ $account = user_external_load($identity);
+ if (isset($account->uid)) {
+ if (!variable_get('user_email_verification', TRUE) || $account->login) {
+ user_external_login($account);
+ }
+ else {
+ drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
+ }
+ }
+ else {
+ // Register new user
+ $form_state['redirect'] = NULL;
+ $form_state['values']['name'] = (empty($response['openid.sreg.nickname'])) ? $identity : $response['openid.sreg.nickname'];
+ $form_state['values']['mail'] = (empty($response['openid.sreg.email'])) ? '' : $response['openid.sreg.email'];
+ $form_state['values']['pass'] = user_password();
+ $form_state['values']['status'] = variable_get('user_register', 1) == 1;
+ $form_state['values']['response'] = $response;
+ $form_state['values']['auth_openid'] = $identity;
+ $form = drupal_retrieve_form('user_register', $form_state);
+ drupal_prepare_form('user_register', $form, $form_state);
+ drupal_validate_form('user_register', $form, $form_state);
+ if (form_get_errors()) {
+ // We were unable to register a valid new user, redirect to standard
+ // user/register and prefill with the values we received.
+ 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');
+ $_SESSION['openid'] = $form_state['values'];
+ // We'll want to redirect back to the same place.
+ $destination = drupal_get_destination();
+ unset($_REQUEST['destination']);
+ drupal_goto('user/register', $destination);
+ }
+ else {
+ unset($form_state['values']['response']);
+ $account = user_save('', $form_state['values']);
+ user_external_login($account);
+ }
+ drupal_redirect_form($form, $form_state['redirect']);
+ }
+ drupal_goto();
+}
+
+function openid_association_request($public) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ $request = array(
+ 'openid.ns' => OPENID_NS_2_0,
+ 'openid.mode' => 'associate',
+ 'openid.session_type' => 'DH-SHA1',
+ 'openid.assoc_type' => 'HMAC-SHA1'
+ );
+
+ if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') {
+ $cpub = _openid_dh_long_to_base64($public);
+ $request['openid.dh_consumer_public'] = $cpub;
+ }
+
+ return $request;
+}
+
+function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ $realm = ($return_to) ? $return_to : url('', array('absolute' => TRUE));
+
+ $ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0;
+ $request = array(
+ 'openid.ns' => $ns,
+ 'openid.mode' => 'checkid_setup',
+ 'openid.identity' => $identity,
+ 'openid.claimed_id' => $claimed_id,
+ 'openid.assoc_handle' => $assoc_handle,
+ 'openid.return_to' => $return_to,
+ );
+
+ if ($version == 2) {
+ $request['openid.realm'] = $realm;
+ }
+ else {
+ $request['openid.trust_root'] = $realm;
+ }
+
+ // Simple Registration
+ $request['openid.sreg.required'] = 'nickname,email';
+ $request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
+
+ $request = array_merge($request, module_invoke_all('openid', 'request', $request));
+
+ return $request;
+}
+
+/**
+ * Attempt to verify the response received from the OpenID Provider.
+ *
+ * @param $op_endpoint The OpenID Provider URL.
+ * @param $response Array of repsonse values from the provider.
+ *
+ * @return boolean
+ */
+function openid_verify_assertion($op_endpoint, $response) {
+ include_once drupal_get_path('module', 'openid') .'/openid.inc';
+
+ $valid = FALSE;
+
+ $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle']));
+ if ($association && isset($association->session_type)) {
+ $keys_to_sign = explode(',', $response['openid.signed']);
+ $self_sig = _openid_signature($association, $response, $keys_to_sign);
+ if ($self_sig == $response['openid.sig']) {
+ $valid = TRUE;
+ }
+ else {
+ $valid = FALSE;
+ }
+ }
+ else {
+ $request = $response;
+ $request['openid.mode'] = 'check_authentication';
+ $message = _openid_create_message($request);
+ $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
+ $result = drupal_http_request($op_endpoint, $headers, 'POST', _openid_encode_message($message));
+ if (!isset($result->error)) {
+ $response = _openid_parse_message($result->data);
+ if (strtolower(trim($response['is_valid'])) == 'true') {
+ $valid = TRUE;
+ }
+ else {
+ $valid = FALSE;
+ }
+ }
+ }
+
+ return $valid;
+}
diff --git a/modules/openid/openid.schema b/modules/openid/openid.schema
new file mode 100644
index 000000000..668b8b8ec
--- /dev/null
+++ b/modules/openid/openid.schema
@@ -0,0 +1,19 @@
+<?php
+// $Id$
+
+function openid_schema() {
+ $schema['openid_association'] = array(
+ 'fields' => array(
+ 'idp_endpoint_uri' => array('type' => 'varchar', 'length' => 255),
+ 'assoc_handle' => array('type' => 'varchar', 'length' => 255),
+ 'assoc_type' => array('type' => 'varchar', 'length' => 32),
+ 'session_type' => array('type' => 'varchar', 'length' => 32),
+ 'mac_key' => array('type' => 'varchar', 'length' => 255),
+ 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+ 'expires_in' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+ ),
+ 'primary key' => array('assoc_handle'),
+ );
+
+ return $schema;
+} \ No newline at end of file
diff --git a/modules/openid/xrds.inc b/modules/openid/xrds.inc
new file mode 100644
index 000000000..28fc979c4
--- /dev/null
+++ b/modules/openid/xrds.inc
@@ -0,0 +1,79 @@
+<?php
+// $Id$
+
+// Global variables to track parsing state
+$xrds_open_elements = array();
+$xrds_services = array();
+$xrds_current_service = array();
+
+/**
+ * Main entry point for parsing XRDS documents
+ */
+function xrds_parse($xml) {
+ global $xrds_services;
+
+ $parser = xml_parser_create_ns();
+ xml_set_element_handler($parser, '_xrds_element_start', '_xrds_element_end');
+ xml_set_character_data_handler($parser, '_xrds_cdata');
+
+ xml_parse($parser, $xml);
+ xml_parser_free($parser);
+
+ return $xrds_services;
+}
+
+/**
+ * Parser callback functions
+ */
+function _xrds_element_start(&$parser, $name, $attribs) {
+ global $xrds_open_elements;
+
+ $xrds_open_elements[] = _xrds_strip_namespace($name);
+}
+
+function _xrds_element_end(&$parser, $name) {
+ global $xrds_open_elements, $xrds_services, $xrds_current_service;
+
+ $name = _xrds_strip_namespace($name);
+ if ($name == 'SERVICE') {
+ if (in_array(OPENID_NS_2_0 .'/signon', $xrds_current_service['types']) ||
+ in_array(OPENID_NS_2_0 .'/server', $xrds_current_service['types'])) {
+ $xrds_current_service['version'] = 2;
+ }
+ elseif (in_array(OPENID_NS_1_1, $xrds_current_service['types']) ||
+ in_array(OPENID_NS_1_0, $xrds_current_service['types'])) {
+ $xrds_current_service['version'] = 1;
+ }
+ if (!empty($xrds_current_service['version'])) {
+ $xrds_services[] = $xrds_current_service;
+ }
+ $xrds_current_service = array();
+ }
+ array_pop($xrds_open_elements);
+}
+
+function _xrds_cdata(&$parser, $data) {
+ global $xrds_open_elements, $xrds_services, $xrds_current_service;
+ $path = strtoupper(implode('/', $xrds_open_elements));
+ switch ($path) {
+ case 'XRDS/XRD/SERVICE/TYPE':
+ $xrds_current_service['types'][] = $data;
+ break;
+ case 'XRDS/XRD/SERVICE/URI':
+ $xrds_current_service['uri'] = $data;
+ break;
+ case 'XRDS/XRD/SERVICE/DELEGATE':
+ $xrds_current_service['delegate'] = $data;
+ break;
+ }
+}
+
+function _xrds_strip_namespace($name) {
+ // Strip namespacing.
+ $pos = strrpos($name, ':');
+ if ($pos !== FALSE) {
+ $name = substr($name, $pos + 1, strlen($name));
+ }
+
+ return $name;
+} \ No newline at end of file