PageRenderTime 98ms CodeModel.GetById 38ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 1ms

/wp-content/plugins/openid/logic.php

https://github.com/alx/alexgirard.com-blog
PHP | 1300 lines | 766 code | 244 blank | 290 comment | 156 complexity | 1454b6d6eb60df5beea975015a8923aa MD5 | raw file
   1<?php
   2/**
   3 * logic.php
   4 *
   5 * Dual License: GPLv2 & Modified BSD
   6 */
   7if (!class_exists('WordPressOpenID_Logic')):
   8
   9/**
  10 * Basic logic for wp-openid plugin.
  11 */
  12class WordPressOpenID_Logic {
  13
  14	/**
  15	 * Soft verification of plugin activation
  16	 *
  17	 * @return boolean if the plugin is okay
  18	 */
  19	function uptodate() {
  20		global $openid;
  21
  22		$openid->log->debug('checking if database is up to date');
  23		if( get_option('oid_db_revision') != WPOPENID_DB_REVISION ) {
  24			$openid->enabled = false;
  25			$openid->log->warning('Plugin database is out of date: ' . get_option('oid_db_revision') . ' != ' . WPOPENID_DB_REVISION);
  26			update_option('oid_plugin_enabled', false);
  27			return false;
  28		}
  29		$openid->enabled = (get_option('oid_plugin_enabled') == true );
  30		return $openid->enabled;
  31	}
  32
  33
  34	/**
  35	 * Get the internal SQL Store.  If it is not already initialized, do so.
  36	 *
  37	 * @return WordPressOpenID_Store internal SQL store
  38	 */
  39	function getStore() {
  40		global $openid;
  41
  42		if (!isset($openid->store)) {
  43			set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
  44			require_once 'store.php';
  45
  46			$openid->store = new WordPressOpenID_Store($openid);
  47			if (null === $openid->store) {
  48				$openid->log->err('OpenID store could not be created properly.');
  49				$openid->enabled = false;
  50			}
  51		}
  52
  53		return $openid->store;
  54	}
  55
  56
  57	/**
  58	 * Get the internal OpenID Consumer object.  If it is not already initialized, do so.
  59	 *
  60	 * @return Auth_OpenID_Consumer OpenID consumer object
  61	 */
  62	function getConsumer() {
  63		global $openid;
  64
  65		if (!isset($openid->consumer)) {
  66			set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
  67			require_once 'Auth/OpenID/Consumer.php';
  68
  69			$store = WordPressOpenID_Logic::getStore();
  70			$openid->consumer = new Auth_OpenID_Consumer($store);
  71			if( null === $openid->consumer ) {
  72				$openid->log->err('OpenID consumer could not be created properly.');
  73				$openid->enabled = false;
  74			}
  75		}
  76
  77		return $openid->consumer;
  78	}
  79
  80
  81	/**
  82	 * Initialize required store and consumer and make a few sanity checks.  This method
  83	 * does a lot of the heavy lifting to get everything initialized, so we don't call it
  84	 * until we actually need it.
  85	 */
  86	function late_bind($reload = false) {
  87		global $wpdb, $openid;
  88		openid_init();
  89
  90		$openid->log->debug('beginning late binding');
  91
  92		$openid->enabled = true; // Be Optimistic
  93		if( $openid->bind_done && !$reload ) {
  94			$openid->log->debug('we\'ve already done the late bind... moving on');
  95			return WordPressOpenID_Logic::uptodate();
  96		}
  97		$openid->bind_done = true;
  98
  99		$f = @fopen( '/dev/urandom', 'r');
 100		if ($f === false) {
 101			define( 'Auth_OpenID_RAND_SOURCE', null );
 102		}
 103			
 104		// include required JanRain OpenID library files
 105		set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
 106		$openid->log->debug('temporary include path for importing = ' . get_include_path());
 107		require_once('Auth/OpenID/Discover.php');
 108		require_once('Auth/OpenID/DatabaseConnection.php');
 109		require_once('Auth/OpenID/MySQLStore.php');
 110		require_once('Auth/OpenID/Consumer.php');
 111		require_once('Auth/OpenID/SReg.php');
 112		restore_include_path();
 113
 114		$openid->log->debug("Bootstrap -- checking tables");
 115		if( $openid->enabled ) {
 116
 117			$store =& WordPressOpenID_Logic::getStore();
 118			if (!$store) return; 	// something broke
 119			$openid->enabled = $store->check_tables();
 120
 121			if( !WordPressOpenID_Logic::uptodate() ) {
 122				update_option('oid_plugin_enabled', true);
 123				update_option('oid_plugin_revision', WPOPENID_PLUGIN_REVISION );
 124				update_option('oid_db_revision', WPOPENID_DB_REVISION );
 125				WordPressOpenID_Logic::uptodate();
 126			}
 127		} else {
 128			$openid->message = 'WPOpenID Core is Disabled!';
 129			update_option('oid_plugin_enabled', false);
 130		}
 131
 132		return $openid->enabled;
 133	}
 134
 135
 136	/**
 137	 * Called on plugin activation.
 138	 *
 139	 * @see register_activation_hook
 140	 */
 141	function activate_plugin() {
 142		$start_mem = memory_get_usage();
 143		global $openid;
 144		openid_init();
 145
 146		$store =& WordPressOpenID_Logic::getStore();
 147		$store->create_tables();
 148
 149		wp_schedule_event(time(), 'hourly', 'cleanup_openid');
 150		//$openid->log->warning("activation memory usage: " . (int)((memory_get_usage() - $start_mem) / 1000));
 151	}
 152
 153	function cleanup_nonces() {
 154		global $openid;
 155		openid_init();
 156		$store =& WordPressOpenID_Logic::getStore();
 157		$store->cleanupNonces();
 158	}
 159
 160
 161	/**
 162	 * Called on plugin deactivation.  Cleanup all transient tables.
 163	 *
 164	 * @see register_deactivation_hook
 165	 */
 166	function deactivate_plugin() {
 167		set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
 168		require_once 'store.php';
 169		WordPressOpenID_Store::destroy_tables();
 170	}
 171
 172
 173	/*
 174	 * Customer error handler for calls into the JanRain library
 175	 */
 176	function customer_error_handler($errno, $errmsg, $filename, $linenum, $vars) {
 177		global $openid;
 178
 179		if( (2048 & $errno) == 2048 ) return;
 180		$openid->log->notice( "Library Error $errno: $errmsg in $filename :$linenum");
 181	}
 182
 183
 184	/**
 185	 * If we're doing openid authentication ($_POST['openid_url'] is set), start the consumer & redirect
 186	 * Otherwise, return and let WordPress handle the login and/or draw the form.
 187	 *
 188	 * @param string $username username provided in login form
 189	 */
 190	function wp_authenticate( &$username ) {
 191		global $openid;
 192
 193		if( !empty( $_POST['openid_url'] ) ) {
 194			if( !WordPressOpenID_Logic::late_bind() ) return; // something is broken
 195			$redirect_to = '';
 196			if( !empty( $_REQUEST['redirect_to'] ) ) $redirect_to = $_REQUEST['redirect_to'];
 197			WordPressOpenID_Logic::start_login( $_POST['openid_url'], 'login', array('redirect_to' => $redirect_to) );
 198		}
 199		if( !empty( $openid->message ) ) {
 200			global $error;
 201			$error = $openid->message;
 202		}
 203	}
 204
 205
 206	/**
 207	 * Handle OpenID profile management.
 208	 */
 209	function openid_profile_management() {
 210		global $wp_version, $openid;
 211		openid_init();
 212		
 213		if( !isset( $_REQUEST['action'] )) return;
 214			
 215		$openid->action = $_REQUEST['action'];
 216			
 217		require_once(ABSPATH . 'wp-admin/admin-functions.php');
 218
 219		if ($wp_version < '2.3') {
 220			require_once(ABSPATH . 'wp-admin/admin-db.php');
 221			require_once(ABSPATH . 'wp-admin/upgrade-functions.php');
 222		}
 223
 224		auth_redirect();
 225		nocache_headers();
 226		get_currentuserinfo();
 227
 228		if( !WordPressOpenID_Logic::late_bind() ) return; // something is broken
 229			
 230		switch( $openid->action ) {
 231			case 'add_identity':
 232				check_admin_referer('wp-openid-add_identity');
 233
 234				$user = wp_get_current_user();
 235
 236				$store =& WordPressOpenID_Logic::getStore();
 237				$auth_request = WordPressOpenID_Logic::begin_consumer($_POST['openid_url']);
 238
 239				$userid = $store->get_user_by_identity($auth_request->endpoint->claimed_id);
 240
 241				if ($userid) {
 242					global $error;
 243					if ($user->ID == $userid) {
 244						$error = 'You already have this Identity URL!';
 245					} else {
 246						$error = 'This Identity URL is already connected to another user.';
 247					}
 248					return;
 249				}
 250
 251				WordPressOpenID_Logic::start_login($_POST['openid_url'], 'verify');
 252				break;
 253
 254			case 'drop_identity':  // Remove a binding.
 255				WordPressOpenID_Logic::_profile_drop_identity($_REQUEST['id']);
 256				break;
 257		}
 258	}
 259
 260
 261	/**
 262	 * Remove identity URL from current user account.
 263	 *
 264	 * @param int $id id of identity URL to remove
 265	 */
 266	function _profile_drop_identity($id) {
 267		global $openid;
 268
 269		$user = wp_get_current_user();
 270
 271		if( !isset($id)) {
 272			$openid->message = 'Identity url delete failed: ID paramater missing.';
 273			$openid->action = 'error';
 274			return;
 275		}
 276
 277		$store =& WordPressOpenID_Logic::getStore();
 278		$deleted_identity_url = $store->get_identities($user->ID, $id);
 279		if( FALSE === $deleted_identity_url ) {
 280			$openid->message = 'Identity url delete failed: Specified identity does not exist.';
 281			$openid->action = 'error';
 282			return;
 283		}
 284
 285		$identity_urls = $store->get_identities($user->ID);
 286		if (sizeof($identity_urls) == 1 && !$_REQUEST['confirm']) {
 287			$openid->message = 'This is your last identity URL.  Are you sure you want to delete it? Doing so may interfere with your ability to login.<br /><br /> '
 288			. '<a href="?confirm=true&'.$_SERVER['QUERY_STRING'].'">Yes I\'m sure.  Delete it</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
 289			. '<a href="?page=openid">No, don\'t delete it.</a>';
 290			$openid->action = 'warning';
 291			return;
 292		}
 293
 294		check_admin_referer('wp-openid-drop-identity_'.$deleted_identity_url);
 295			
 296
 297		if( $store->drop_identity($user->ID, $id) ) {
 298			$openid->message = 'Identity url delete successful. <b>' . $deleted_identity_url
 299			. '</b> removed.';
 300			$openid->action = 'success';
 301
 302			// ensure that profile URL is still a verified Identity URL
 303			set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
 304			require_once 'Auth/OpenID.php';
 305			if ($GLOBALS['wp_version'] >= '2.3') {
 306				require_once(ABSPATH . 'wp-admin/includes/admin.php');
 307			} else {
 308				require_once(ABSPATH . WPINC . '/registration.php');
 309			}
 310			$identities = $store->get_identities($user->ID);
 311			$current_url = Auth_OpenID::normalizeUrl($user->user_url);
 312
 313			$verified_url = false;
 314			if (!empty($identities)) {
 315				foreach ($identities as $id) {
 316					if ($id['url'] == $current_url) {
 317						$verified_url = true;
 318						break;
 319					}
 320				}
 321
 322				if (!$verified_url) {
 323					$user->user_url = $identities[0]['url'];
 324					wp_update_user( get_object_vars( $user ));
 325					$openid->message .= '<br /><strong>Note:</strong> For security reasons, your profile URL has been updated to match your Identity URL.';
 326				}
 327			}
 328			return;
 329		}
 330			
 331		$openid->message = 'Identity url delete failed: Unknown reason.';
 332		$openid->action = 'error';
 333	}
 334
 335
 336	/**
 337	 * Send the user to their OpenID provider to authenticate.
 338	 *
 339	 * @param Auth_OpenID_AuthRequest $auth_request OpenID authentication request object
 340	 * @param string $trust_root OpenID trust root
 341	 * @param string $return_to URL where the OpenID provider should return the user
 342	 */
 343	function doRedirect($auth_request, $trust_root, $return_to) {
 344		global $openid;
 345
 346		if ($auth_request->shouldSendRedirect()) {
 347			$trust_root = trailingslashit($trust_root);
 348			$redirect_url = $auth_request->redirectURL($trust_root, $return_to);
 349
 350			if (Auth_OpenID::isFailure($redirect_url)) {
 351				$openid->log->error('Could not redirect to server: '.$redirect_url->message);
 352			} else {
 353				wp_redirect( $redirect_url );
 354			}
 355		} else {
 356			// Generate form markup and render it
 357			$request_message = $auth_request->getMessage($trust_root, $return_to, false);
 358
 359			if (Auth_OpenID::isFailure($request_message)) {
 360				$openid->log->error('Could not redirect to server: '.$request_message->message);
 361			} else {
 362				WordPressOpenID_Interface::repost($auth_request->endpoint->server_url, $request_message->toPostArgs());
 363			}
 364		}
 365	}
 366
 367
 368	/**
 369	 * Finish OpenID Authentication.
 370	 *
 371	 * @return String authenticated identity URL, or null if authentication failed.
 372	 */
 373	function finish_openid_auth() {
 374		global $openid;
 375
 376		//set_error_handler( array('WordPressOpenID_Logic', 'customer_error_handler'));
 377		$consumer = WordPressOpenID_Logic::getConsumer();
 378		$openid->response = $consumer->complete($_SESSION['oid_return_to']);
 379		//restore_error_handler();
 380			
 381		switch( $openid->response->status ) {
 382			case Auth_OpenID_CANCEL:
 383				$openid->message = 'OpenID assertion cancelled';
 384				$openid->action = 'error';
 385				break;
 386
 387			case Auth_OpenID_FAILURE:
 388				$openid->message = 'OpenID assertion failed: ' . $openid->response->message;
 389				$openid->action = 'error';
 390				break;
 391
 392			case Auth_OpenID_SUCCESS:
 393				$openid->message = 'OpenID assertion successful';
 394				$openid->action = 'success';
 395
 396				$identity_url = $openid->response->identity_url;
 397				$escaped_url = htmlspecialchars($identity_url, ENT_QUOTES);
 398				$openid->log->notice('Got back identity URL ' . $escaped_url);
 399
 400				if ($openid->response->endpoint->canonicalID) {
 401					$openid->log->notice('XRI CanonicalID: ' . $openid->response->endpoint->canonicalID);
 402				}
 403
 404				return $escaped_url;
 405
 406			default:
 407				$openid->message = 'Unknown Status. Bind not successful. This is probably a bug';
 408				$openid->action = 'error';
 409		}
 410
 411		return null;
 412	}
 413
 414
 415	/**
 416	 * Generate a unique WordPress username for the given OpenID URL.
 417	 *
 418	 * @param string $url OpenID URL to generate username for
 419	 * @return string generated username
 420	 */
 421	function generate_new_username($url) {
 422		global $openid;
 423
 424		$base = WordPressOpenID_Logic::normalize_username($url);
 425		$i='';
 426		while(true) {
 427			$username = WordPressOpenID_Logic::normalize_username( $base . $i );
 428			$user = get_userdatabylogin($username);
 429			if ( $user ) {
 430				$i++;
 431				continue;
 432			}
 433			return $username;
 434		}
 435	}
 436
 437	/**
 438	 * Normalize the OpenID URL into a username.  This includes rules like:
 439	 *  - remove protocol prefixes like 'http://' and 'xri://'
 440	 *  - remove the 'xri.net' domain for i-names
 441	 *  - substitute certain characters which are not allowed by WordPress
 442	 *
 443	 * @param string $username username to be normalized
 444	 * @return string normalized username
 445	 */
 446	function normalize_username($username) {
 447		$username = preg_replace('|^https?://(xri.net/([^@]!?)?)?|', '', $username);
 448		$username = preg_replace('|^xri://([^@]!?)?|', '', $username);
 449		$username = preg_replace('|/$|', '', $username);
 450		$username = sanitize_user( $username );
 451		$username = preg_replace('|[^a-z0-9 _.\-@]+|i', '-', $username);
 452		return $username;
 453	}
 454
 455	function begin_consumer($url) {
 456		global $openid_auth_request;
 457
 458		if ($openid_auth_request == NULL) {
 459			set_error_handler( array('WordPressOpenID_Logic', 'customer_error_handler'));
 460
 461			if (WordPressOpenID_Logic::isValidEmail($url)) {
 462				$_SESSION['openid_login_email'] = $url;
 463				set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
 464				require_once 'Auth/Yadis/Email.php';
 465				$mapped_url = Auth_Yadis_Email_getID($url, trailingslashit(get_option('home')));
 466				if ($mapped_url) {
 467					$url = $mapped_url;
 468				}
 469			}
 470
 471			$consumer = WordPressOpenID_Logic::getConsumer();
 472			$openid_auth_request = $consumer->begin($url);
 473
 474			restore_error_handler();
 475		}
 476
 477		return $openid_auth_request;;
 478	}
 479
 480	function isValidEmail($email) {
 481	    return eregi("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$", $email);
 482	}
 483
 484
 485	/**
 486	 * Start the OpenID authentication process.
 487	 *
 488	 * @param string $claimed_url claimed OpenID URL
 489	 * @param action $action OpenID action being performed
 490	 * @param array $arguments array of additional arguments to be included in the 'return_to' URL
 491	 */
 492	function start_login( $claimed_url, $action, $arguments = null) {
 493		global $openid;
 494
 495		if ( empty($claimed_url) ) return; // do nothing.
 496			
 497		if( !WordPressOpenID_Logic::late_bind() ) return; // something is broken
 498
 499		$auth_request = WordPressOpenID_Logic::begin_consumer( $claimed_url );
 500
 501		if ( null === $auth_request ) {
 502			$openid->action = 'error';
 503			$openid->message = 'Could not discover an OpenID identity server endpoint at the url: '
 504			. htmlentities( $claimed_url );
 505			if( strpos( $claimed_url, '@' ) ) {
 506				$openid->message .= '<br />It looks like you entered an email address, but it '
 507					. 'was not able to be transformed into a valid OpenID.';
 508			}
 509			$openid->log->debug('OpenIDConsumer: ' . $openid->message );
 510			return;
 511		}
 512			
 513		$openid->log->debug('OpenIDConsumer: Is an OpenID url. Starting redirect.');
 514
 515
 516		// build return_to URL
 517		$return_to = trailingslashit(get_option('home'));
 518		$auth_request->return_to_args['openid_consumer'] = '1';
 519		$auth_request->return_to_args['action'] = $action;
 520		if (is_array($arguments) && !empty($arguments)) {
 521			foreach ($arguments as $k => $v) {
 522				if ($k && $v) {
 523					$auth_request->return_to_args[urlencode($k)] = urlencode($v);
 524				}
 525			}
 526		}
 527			
 528
 529		/* If we've never heard of this url before, do attribute query */
 530		$store =& WordPressOpenID_Logic::getStore();
 531		if( $store->get_user_by_identity( $auth_request->endpoint->identity_url ) == NULL ) {
 532			$attribute_query = true;
 533		}
 534		if ($attribute_query) {
 535			// SREG
 536			$sreg_request = Auth_OpenID_SRegRequest::build(array(),array('nickname','email','fullname'));
 537			if ($sreg_request) $auth_request->addExtension($sreg_request);
 538
 539			// AX
 540		}
 541			
 542		$_SESSION['oid_return_to'] = $return_to;
 543		WordPressOpenID_Logic::doRedirect($auth_request, get_option('home'), $return_to);
 544		exit(0);
 545	}
 546
 547
 548	/**
 549	 * Intercept login requests on wp-login.php if they include an 'openid_url' 
 550	 * value and start OpenID authentication.  This hook is only necessary in 
 551	 * WordPress 2.5.x because it has the 'wp_authenticate' action call in the 
 552	 * wrong place.
 553	 */
 554	function wp_login_openid() {
 555		global $wp_version;
 556
 557		// this is only needed in WordPress 2.5.x
 558		if (strpos($wp_version, '2.5') != 0) {
 559			return;
 560		}
 561
 562		$self = basename( $GLOBALS['pagenow'] );
 563			
 564		if ($self == 'wp-login.php' && !empty($_POST['openid_url'])) {
 565			if (function_exists('wp_signon')) {
 566				wp_signon(array('user_login'=>'openid', 'user_password'=>'openid'));
 567			}
 568		}
 569	}
 570
 571
 572	/**
 573	 * Login user with specified identity URL.  This will find the WordPress user account connected to this
 574	 * OpenID and set it as the current user.  Only call this function AFTER you've verified the identity URL.
 575	 *
 576	 * @param string $identity userID or OpenID to set as current user
 577	 * @param boolean $remember should we set the "remember me" cookie
 578	 * @return void
 579	 */
 580	function set_current_user($identity, $remember = true) {
 581		global $openid;
 582
 583		if (is_numeric($identity)) {
 584			$user_id = $identity;
 585		} else {
 586			$store =& WordPressOpenID_Logic::getStore();
 587			$user_id = $store->get_user_by_identity( $identity );
 588		}
 589
 590		if (!$user_id) return;
 591
 592		$user = set_current_user($user_id);
 593			
 594		if (function_exists('wp_set_auth_cookie')) {
 595			wp_set_auth_cookie($user->ID, $remember);
 596		} else {
 597			wp_setcookie($user->user_login, md5($user->user_pass), true, '', '', $remember);
 598		}
 599
 600		do_action('wp_login', $user->user_login);
 601	}
 602
 603
 604	/**
 605	 * Finish OpenID authentication.  After doing the basic stuff, the action method is called to complete the
 606	 * process.  Action methods are based on the action name passed in and are of the form
 607	 * '_finish_openid_$action'.  Action methods are passed the verified identity URL, or null if OpenID
 608	 * authentication failed.
 609	 *
 610	 * @param string $action login action that is being performed
 611	 */
 612	function finish_openid($action) {
 613		global $openid;
 614
 615		if( !WordPressOpenID_Logic::late_bind() ) return; // something is broken
 616			
 617		$identity_url = WordPressOpenID_Logic::finish_openid_auth();
 618			
 619		if (!empty($action) && method_exists('WordPressOpenID_Logic', '_finish_openid_' . $action)) {
 620			call_user_func(array('WordPressOpenID_Logic', '_finish_openid_' . $action), $identity_url);
 621		}
 622			
 623		global $action;
 624		$action = $openid->action;
 625	}
 626
 627
 628	/**
 629	 * Action method for completing the 'login' action.  This action is used when a user is logging in from
 630	 * wp-login.php.
 631	 *
 632	 * @param string $identity_url verified OpenID URL
 633	 */
 634	function _finish_openid_login($identity_url) {
 635		global $openid;
 636
 637		$redirect_to = urldecode($_REQUEST['redirect_to']);
 638			
 639		if (empty($identity_url)) {
 640			WordPressOpenID_Logic::set_error('Unable to authenticate OpenID.');
 641			wp_safe_redirect(get_option('siteurl') . '/wp-login.php');
 642			exit;
 643		}
 644			
 645		WordPressOpenID_Logic::set_current_user($identity_url);
 646
 647		if (!is_user_logged_in()) {
 648			if ( get_option('users_can_register') ) {
 649				$user_data =& WordPressOpenID_Logic::get_user_data($identity_url);
 650				$user = WordPressOpenID_Logic::create_new_user($identity_url, $user_data);
 651				WordPressOpenID_Logic::set_current_user($user->ID);
 652			} else {
 653				// TODO - Start a registration loop in WPMU.
 654				WordPressOpenID_Logic::set_error('OpenID authentication valid, but unable '
 655				. 'to find a WordPress account associated with this OpenID.<br /><br />'
 656				. 'Enable "Anyone can register" to allow creation of new accounts via OpenID.');
 657				wp_safe_redirect(get_option('siteurl') . '/wp-login.php');
 658				exit;
 659			}
 660
 661		}
 662			
 663		if (empty($redirect_to)) {
 664			$redirect_to = 'wp-admin/';
 665		}
 666		if ($redirect_to == 'wp-admin/') {
 667			if (!current_user_can('edit_posts')) {
 668				$redirect_to .= 'profile.php';
 669			}
 670		}
 671		if (!preg_match('#^(http|\/)#', $redirect_to)) {
 672			$wpp = parse_url(get_option('siteurl'));
 673			$redirect_to = $wpp['path'] . '/' . $redirect_to;
 674		}
 675
 676		if (function_exists('wp_safe_redirect')) {
 677			wp_safe_redirect( $redirect_to );
 678		} else {
 679			wp_redirect( $redirect_to );
 680		}
 681			
 682		exit;
 683	}
 684
 685
 686	/**
 687	 * Action method for completing the 'comment' action.  This action is used when leaving a comment.
 688	 *
 689	 * @param string $identity_url verified OpenID URL
 690	 */
 691	function _finish_openid_comment($identity_url) {
 692		global $openid;
 693
 694		if (empty($identity_url)) {
 695			WordPressOpenID_Interface::repost_comment_anonymously($_SESSION['oid_comment_post']);
 696		}
 697			
 698		WordPressOpenID_Logic::set_current_user($identity_url);
 699			
 700		if (is_user_logged_in()) {
 701			// simulate an authenticated comment submission
 702			$_SESSION['oid_comment_post']['author'] = null;
 703			$_SESSION['oid_comment_post']['email'] = null;
 704			$_SESSION['oid_comment_post']['url'] = null;
 705		} else {
 706			// try to get user data from the verified OpenID
 707			$user_data =& WordPressOpenID_Logic::get_user_data($identity_url);
 708
 709			if (!empty($user_data['display_name'])) {
 710				$_SESSION['oid_comment_post']['author'] = $user_data['display_name'];
 711			}
 712			if (!empty($user_data['user_email'])) {
 713				$_SESSION['oid_comment_post']['email'] = $user_data['user_email'];
 714			}
 715			$_SESSION['oid_comment_post']['url'] = $identity_url;
 716		}
 717			
 718		// record that we're about to post an OpenID authenticated comment.
 719		// We can't actually record it in the database until after the repost below.
 720		$_SESSION['oid_posted_comment'] = true;
 721
 722		$wpp = parse_url(get_option('siteurl'));
 723		WordPressOpenID_Interface::repost($wpp['path'] . '/wp-comments-post.php',
 724		array_filter($_SESSION['oid_comment_post']));
 725	}
 726
 727
 728	/**
 729	 * Action method for completing the 'verify' action.  This action is used adding an identity URL to a
 730	 * WordPress user through the admin interface.
 731	 *
 732	 * @param string $identity_url verified OpenID URL
 733	 */
 734	function _finish_openid_verify($identity_url) {
 735		global $openid;
 736
 737		$user = wp_get_current_user();
 738		if (empty($identity_url)) {
 739			WordPressOpenID_Logic::set_error('Unable to authenticate OpenID.');
 740		} else {
 741			$store =& WordPressOpenID_Logic::getStore();
 742			if( !$store->insert_identity($user->ID, $identity_url) ) {
 743				WordPressOpenID_Logic::set_error('OpenID assertion successful, but this URL is already claimed by '
 744				. 'another user on this blog. This is probably a bug. ' . $identity_url);
 745			} else {
 746				$openid->action = 'success';
 747				$openid->message = "Successfully added Identity URL: $identity_url.";
 748				
 749				// ensure that profile URL is a verified Identity URL
 750				set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
 751				require_once 'Auth/OpenID.php';
 752				if ($GLOBALS['wp_version'] >= '2.3') {
 753					require_once(ABSPATH . 'wp-admin/includes/admin.php');
 754				} else {
 755					require_once(ABSPATH . WPINC . '/registration.php');
 756				}
 757				$identities = $store->get_identities($user->ID);
 758				$current_url = Auth_OpenID::normalizeUrl($user->user_url);
 759
 760				$verified_url = false;
 761				if (!empty($identities)) {
 762					foreach ($identities as $id) {
 763						if ($id['url'] == $current_url) {
 764							$verified_url = true;
 765							break;
 766						}
 767					}
 768
 769					if (!$verified_url) {
 770						$user->user_url = $identity_url;
 771						wp_update_user( get_object_vars( $user ));
 772						$openid->message .= '<br /><strong>Note:</strong> For security reasons, your profile URL has been updated to match your Identity URL.';
 773					}
 774				}
 775			}
 776		}
 777
 778		$_SESSION['oid_message'] = $openid->message;
 779		$_SESSION['oid_action'] = $openid->action;	
 780		$wpp = parse_url(get_option('siteurl'));
 781		$redirect_to = $wpp['path'] . '/wp-admin/' . (current_user_can('edit_users') ? 'users.php' : 'profile.php') . '?page=openid';
 782		if (function_exists('wp_safe_redirect')) {
 783			wp_safe_redirect( $redirect_to );
 784		} else {
 785			wp_redirect( $redirect_to );
 786		}
 787		exit;
 788	}
 789
 790
 791	/**
 792	 * If last comment was authenticated by an OpenID, record that in the database.
 793	 *
 794	 * @param string $location redirect location
 795	 * @param object $comment comment that was just left
 796	 * @return string redirect location
 797	 */
 798	function comment_post_redirect($location, $comment) {
 799		global $openid;
 800
 801		if ($_SESSION['oid_posted_comment']) {
 802			WordPressOpenID_Logic::set_comment_openid($comment->comment_ID);
 803			$_SESSION['oid_posted_comment'] = null;
 804		}
 805			
 806		return $location;
 807	}
 808
 809
 810	/**
 811	 * Create a new WordPress user with the specified identity URL and user data.
 812	 *
 813	 * @param string $identity_url OpenID to associate with the newly
 814	 * created account
 815	 * @param array $user_data array of user data
 816	 */
 817	function create_new_user($identity_url, &$user_data) {
 818		global $wpdb, $openid;
 819
 820		// Identity URL is new, so create a user
 821		@include_once( ABSPATH . 'wp-admin/upgrade-functions.php');	// 2.1
 822		@include_once( ABSPATH . WPINC . '/registration-functions.php'); // 2.0.4
 823
 824		// use email address for username if URL is from emailtoid.net
 825		$username = $identity_url;
 826		if (null != $_SESSION['openid_login_email'] and strpos($username, 'http://emailtoid.net/') == 0) {
 827			if($user_data['user_email'] == NULL) {
 828				$user_data['user_email'] = $_SESSION['openid_login_email'];
 829			}
 830			$username = $_SESSION['openid_login_email'];
 831			unset($_SESSION['openid_login_email']);
 832		}
 833
 834		$user_data['user_login'] = $wpdb->escape( WordPressOpenID_Logic::generate_new_username($username) );
 835		$user_data['user_pass'] = substr( md5( uniqid( microtime() ) ), 0, 7);
 836		$user_id = wp_insert_user( $user_data );
 837			
 838		$openid->log->debug("wp_create_user( $user_data )  returned $user_id ");
 839
 840		if( $user_id ) { // created ok
 841
 842			$user_data['ID'] = $user_id;
 843			// XXX this all looks redundant, see WordPressOpenID_Logic::set_current_user
 844
 845			$openid->log->debug("OpenIDConsumer: Created new user $user_id : " . $user_data['user_login'] . " and metadata: "
 846			. var_export( $user_data, true ) );
 847
 848			$user = new WP_User( $user_id );
 849
 850			if( ! wp_login( $user->user_login, $user_data['user_pass'] ) ) {
 851				$openid->message = 'User was created fine, but wp_login() for the new user failed. '
 852				. 'This is probably a bug.';
 853				$openid->action= 'error';
 854				$openid->log->err( $openid->message );
 855				return;
 856			}
 857
 858			// notify of user creation
 859			wp_new_user_notification( $user->user_login );
 860
 861			wp_clearcookie();
 862			wp_setcookie( $user->user_login, md5($user->user_pass), true, '', '', true );
 863
 864			// Bind the provided identity to the just-created user
 865			global $userdata;
 866			$userdata = get_userdata( $user_id );
 867			$store = WordPressOpenID_Logic::getStore();
 868			$store->insert_identity($user_id, $identity_url);
 869
 870			$openid->action = 'redirect';
 871
 872			if ( !$user->has_cap('edit_posts') ) $redirect_to = '/wp-admin/profile.php';
 873
 874		} else {
 875			// failed to create user for some reason.
 876			$openid->message = 'OpenID authentication successful, but failed to create WordPress user. '
 877			. 'This is probably a bug.';
 878			$openid->action= 'error';
 879			$openid->log->error( $openid->message );
 880		}
 881
 882	}
 883
 884
 885	/**
 886	 * Get user data for the given identity URL.  Data is returned as an associative array with the keys:
 887	 *   ID, user_url, user_nicename, display_name
 888	 *
 889	 * Multiple soures of data may be available and are attempted in the following order:
 890	 *   - OpenID Attribute Exchange      !! not yet implemented
 891	 * 	 - OpenID Simple Registration
 892	 * 	 - hCard discovery                !! not yet implemented
 893	 * 	 - default to identity URL
 894	 *
 895	 * @param string $identity_url OpenID to get user data about
 896	 * @return array user data
 897	 */
 898	function get_user_data($identity_url) {
 899		global $openid;
 900
 901		$data = array(
 902				'ID' => null,
 903				'user_url' => $identity_url,
 904				'user_nicename' => $identity_url,
 905				'display_name' => $identity_url 
 906		);
 907
 908		// create proper website URL if OpenID is an i-name
 909		if (preg_match('/^[\=\@\+].+$/', $identity_url)) {
 910			$data['user_url'] = 'http://xri.net/' . $identity_url;
 911		}
 912
 913		$data = apply_filters('openid_user_data', $identity_url, $data);
 914
 915		return $data;
 916	}
 917
 918
 919	/**
 920	 * Retrieve user data from OpenID Attribute Exchange.
 921	 *
 922	 * @param string $identity_url OpenID to get user data about
 923	 * @param reference $data reference to user data array
 924	 * @see get_user_data
 925	 */
 926	function get_user_data_ax($identity_url, $data) {
 927		// TODO implement attribute exchange
 928		return $data;
 929	}
 930
 931
 932	/**
 933	 * Retrieve user data from OpenID Simple Registration.
 934	 *
 935	 * @param string $identity_url OpenID to get user data about
 936	 * @param reference $data reference to user data array
 937	 * @see get_user_data
 938	 */
 939	function get_user_data_sreg($identity_url, $data) {
 940		global $openid;
 941
 942		$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($openid->response);
 943		$sreg = $sreg_resp->contents();
 944
 945		$openid->log->debug(var_export($sreg, true));
 946		if (!$sreg) return $data;
 947
 948		if (array_key_exists('email', $sreg) && $sreg['email']) {
 949			$data['user_email'] = $sreg['email'];
 950		}
 951
 952		if (array_key_exists('nickname', $sreg) && $sreg['nickname']) {
 953			$data['nickname'] = $sreg['nickname'];
 954			$data['user_nicename'] = $sreg['nickname'];
 955			$data['display_name'] = $sreg['nickname'];
 956		}
 957
 958		if (array_key_exists('fullname', $sreg) && $sreg['fullname']) {
 959			$namechunks = explode( ' ', $sreg['fullname'], 2 );
 960			if( isset($namechunks[0]) ) $data['first_name'] = $namechunks[0];
 961			if( isset($namechunks[1]) ) $data['last_name'] = $namechunks[1];
 962			$data['display_name'] = $sreg['fullname'];
 963		}
 964
 965		return $data;;
 966	}
 967
 968
 969	/**
 970	 * Retrieve user data from hCard discovery.
 971	 *
 972	 * @param string $identity_url OpenID to get user data about
 973	 * @param reference $data reference to user data array
 974	 * @see get_user_data
 975	 */
 976	function get_user_data_hcard($identity_url, $data) {
 977		// TODO implement hcard discovery
 978		return $data;
 979	}
 980
 981	/**
 982	 * Retrieve user data from comment form.
 983	 *
 984	 * @param string $identity_url OpenID to get user data about
 985	 * @param reference $data reference to user data array
 986	 * @see get_user_data
 987	 */
 988	function get_user_data_form($identity_url, $data) {
 989		$comment = $_SESSION['oid_comment_post'];
 990
 991		if (!$comment) {
 992			return $data;
 993		}
 994
 995		if ($comment['email']) {
 996			$data['user_email'] = $comment['email'];
 997		}
 998
 999		if ($comment['author']) {
1000			$data['nickname'] = $comment['author'];
1001			$data['user_nicename'] = $comment['author'];
1002			$data['display_name'] = $comment['author'];
1003		}
1004
1005		return $data;
1006	}
1007
1008
1009	/**
1010	 * For comments that were handled by WordPress normally (not our code), check if the author
1011	 * registered with OpenID and set comment openid flag if so.
1012	 *
1013	 * @action post_comment
1014	 */
1015	function check_author_openid($comment_ID) {
1016		global $openid;
1017
1018		$comment = get_comment($comment_ID);
1019		if ( $comment->user_id && !$comment->openid && is_user_openid($comment->user_id) ) {
1020			WordPressOpenID_Logic::set_comment_openid($comment_ID);
1021		}
1022	}
1023
1024	/**
1025	 * hook in and call when user is updating their profile URL... make sure it is an OpenID they control.
1026	 */
1027	function personal_options_update() {
1028		set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
1029		require_once 'Auth/OpenID.php';
1030		$claimed = Auth_OpenID::normalizeUrl($_POST['url']);
1031
1032		$user = wp_get_current_user();
1033
1034		openid_init();
1035		$store =& WordPressOpenID_Logic::getStore();
1036		$identities = $store->get_identities($user->ID);
1037
1038		if (!empty($identities)) {
1039			$urls = array();
1040			foreach ($identities as $id) {
1041				if ($id['url'] == $claimed) {
1042					return; 
1043				} else {
1044					$urls[] = $id['url'];
1045				}
1046			}
1047
1048			wp_die('For security reasons, your profile URL must be one of your claimed '
1049			   . 'Identity URLs: <ul><li>' . join('</li><li>', $urls) . '</li></ul>');
1050		}
1051	}
1052
1053
1054	/**
1055	 * Mark the provided comment as an OpenID comment
1056	 *
1057	 * @param int $comment_ID id of comment to set as OpenID
1058	 */
1059	function set_comment_openid($comment_ID) {
1060		global $wpdb, $openid;
1061
1062		$comments_table = WordPressOpenID::comments_table_name();
1063		$wpdb->query("UPDATE $comments_table SET openid='1' WHERE comment_ID='$comment_ID' LIMIT 1");
1064	}
1065
1066
1067	/**
1068	 * If the comment contains a valid OpenID, skip the check for requiring a name and email address.  Even if
1069	 * this data is provided in the form, we may get it through other methods, so we don't want to bail out
1070	 * prematurely.  After OpenID authentication has completed (and $_SESSION['oid_skip'] is set), we don't
1071	 * interfere so that this data can be required if desired.
1072	 *
1073	 * @param boolean $value existing value of flag, whether to require name and email
1074	 * @return boolean new value of flag, whether to require name and email
1075	 * @see get_user_data
1076	 */
1077	function bypass_option_require_name_email( $value ) {
1078		global $openid;
1079			
1080		if ($_REQUEST['oid_skip']) {
1081			return $value;
1082		}
1083
1084		if (array_key_exists('openid_url', $_POST)) {
1085			if( !empty( $_POST['openid_url'] ) ) {
1086				return false;
1087			}
1088		} else {
1089			if (!empty($_POST['url'])) {
1090				if (WordPressOpenID_Logic::late_bind()) {
1091					// check if url is valid OpenID by forming an auth request
1092					$auth_request = WordPressOpenID_Logic::begin_consumer($_POST['url']);
1093
1094					if (null !== $auth_request) {
1095						return false;
1096					}
1097				}
1098			}
1099		}
1100
1101		return $value;
1102	}
1103
1104
1105	/**
1106	 * Intercept comment submission and check if it includes a valid OpenID.  If it does, save the entire POST
1107	 * array and begin the OpenID authentication process.
1108	 *
1109	 * regarding comment_type: http://trac.wordpress.org/ticket/2659
1110	 *
1111	 * @param object $comment comment object
1112	 * @return object comment object
1113	 */
1114	function comment_tagging( $comment ) {
1115		global $openid;
1116
1117		if ($_REQUEST['oid_skip']) return $comment;
1118			
1119		$openid_url = (array_key_exists('openid_url', $_POST) ? $_POST['openid_url'] : $_POST['url']);
1120
1121		if( !empty($openid_url) ) {  // Comment form's OpenID url is filled in.
1122			$_SESSION['oid_comment_post'] = $_POST;
1123			$_SESSION['oid_comment_post']['comment_author_openid'] = $openid_url;
1124			$_SESSION['oid_comment_post']['oid_skip'] = 1;
1125
1126			WordPressOpenID_Logic::start_login( $openid_url, 'comment');
1127
1128			// Failure to redirect at all, the URL is malformed or unreachable.
1129
1130			// Display an error message only if an explicit OpenID field was used.  Otherwise,
1131			// just ignore the error... it just means the user entered a normal URL.
1132			if (array_key_exists('openid_url', $_POST)) {
1133				WordPressOpenID_Interface::repost_comment_anonymously($_SESSION['oid_comment_post']);
1134			}
1135		}
1136
1137		/*
1138		if (get_option('oid_enable_email_mapping') && !empty($_POST['email'])) {
1139			$_SESSION['oid_comment_post'] = $_POST;
1140			$_SESSION['oid_comment_post']['comment_author_openid'] = $openid_url;
1141			$_SESSION['oid_comment_post']['oid_skip'] = 1;
1142
1143			set_include_path( dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
1144			require_once 'Auth/Yadis/Email.php';
1145			$id = Auth_Yadis_Email_getID($_POST['email'], trailingslashit(get_option('home')));
1146			WordPressOpenID_Logic::start_login( $id, 'comment');
1147		}
1148		*/
1149			
1150		return $comment;
1151	}
1152
1153
1154	/**
1155	 * This filter callback simply approves all OpenID comments, but later it could do more complicated logic
1156	 * like whitelists.
1157	 *
1158	 * @param string $approved comment approval status
1159	 * @return string new comment approval status
1160	 */
1161	function comment_approval($approved) {
1162		if ($_SESSION['oid_posted_comment']) {
1163			return 1;
1164		}
1165			
1166		return $approved;
1167	}
1168
1169
1170	/**
1171	 * Get any additional comments awaiting moderation by this user.  WordPress
1172	 * core has been udpated to grab most, but we still do one last check for
1173	 * OpenID comments that have a URL match with the current user.
1174	 *
1175	 * @param array $comments array of comments to display
1176	 * @param int $post_id id of the post to display comments for
1177	 * @return array new array of comments to display
1178	 */
1179	function comments_awaiting_moderation(&$comments, $post_id) {
1180		global $wpdb, $openid;
1181		$user = wp_get_current_user();
1182
1183		$commenter = wp_get_current_commenter();
1184		extract($commenter);
1185
1186		$author_db = $wpdb->escape($comment_author);
1187		$email_db  = $wpdb->escape($comment_author_email);
1188		$url_db  = $wpdb->escape($comment_author_url);
1189
1190		if ($url_db) {
1191			$comments_table = WordPressOpenID::comments_table_name();
1192			$additional = $wpdb->get_results(
1193					"SELECT * FROM $comments_table"
1194			. " WHERE comment_post_ID = '$post_id'"
1195			. " AND openid = '1'"             // get OpenID comments
1196			. " AND comment_author_url = '$url_db'"      // where only the URL matches
1197			. ($user ? " AND user_id != '$user->ID'" : '')
1198			. ($author_db ? " AND comment_author != '$author_db'" : '')
1199			. ($email_db ? " AND comment_author_email != '$email_db'" : '')
1200			. " AND comment_approved = '0'"
1201			. " ORDER BY comment_date");
1202
1203			if ($additional) {
1204				$comments = array_merge($comments, $additional);
1205				usort($comments, create_function('$a,$b',
1206						'return strcmp($a->comment_date_gmt, $b->comment_date_gmt);'));
1207			}
1208		}
1209
1210		return $comments;
1211	}
1212
1213	/**
1214	 * Delete user.
1215	 */
1216	function delete_user($userid) {
1217		openid_init();
1218		$store = WordPressOpenID_Logic::getStore();
1219		$store->drop_all_identities_for_user($userid);
1220	}
1221
1222
1223	/**
1224	 * Make sure that a user's OpenID is stored and retrieved properly.  This is important because the OpenID
1225	 * may be an i-name, but WordPress is expecting the comment URL cookie to be a valid URL.
1226	 *
1227	 * @wordpress-action sanitize_comment_cookies
1228	 */
1229	function sanitize_comment_cookies() {
1230		if ( isset($_COOKIE['comment_author_openid_'.COOKIEHASH]) ) {
1231
1232			// this might be an i-name, so we don't want to run clean_url()
1233			remove_filter('pre_comment_author_url', 'clean_url');
1234
1235			$comment_author_url = apply_filters('pre_comment_author_url',
1236			$_COOKIE['comment_author_openid_'.COOKIEHASH]);
1237			$comment_author_url = stripslashes($comment_author_url);
1238			$_COOKIE['comment_author_url_'.COOKIEHASH] = $comment_author_url;
1239		}
1240	}
1241
1242
1243	function xrds_simple($xrds) {
1244		$xrds = xrds_add_service($xrds, 'main', 'OpenID Consumer Service', 
1245			array(
1246				'Type' => array(array('content' => 'http://specs.openid.net/auth/2.0/return_to') ),
1247				'URI' => array(array('content' => trailingslashit(get_option('home'))) ),
1248			)
1249		);
1250
1251		$siteurl = function_exists('site_url') ? site_url('/wp-login.php', 'login_post') : get_option('siteurl').'/wp-login.php';
1252		$xrds = xrds_add_service($xrds, 'main', 'Identity in the Browser Login Service', 
1253			array(
1254				'Type' => array(array('content' => 'http://specs.openid.net/idib/1.0/login') ),
1255				'URI' => array(
1256					array(
1257						'simple:httpMethod' => 'POST',
1258						'content' => $siteurl,
1259					),
1260				),
1261			)
1262		);
1263
1264		$xrds = xrds_add_service($xrds, 'main', 'Identity in the Browser Indicator Service', 
1265			array(
1266				'Type' => array(array('content' => 'http://specs.openid.net/idib/1.0/indicator') ),
1267				'URI' => array(array('content' => trailingslashit(get_option('home')) . '?openid_check_login')),
1268			)
1269		);
1270
1271		return $xrds;
1272	}
1273
1274	/**
1275	 * Parse the WordPress request.  If the pagename is 'openid_consumer', then the request
1276	 * is an OpenID response and should be handled accordingly.
1277	 *
1278	 * @param WP $wp WP instance for the current request
1279	 */
1280	function parse_request($wp) {
1281		if (array_key_exists('openid_check_login', $_REQUEST)) {
1282			echo is_user_logged_in() ? 'true' : 'false';
1283			exit;
1284		}
1285
1286		if (array_key_exists('openid_consumer', $_REQUEST) && $_REQUEST['action']) {
1287			openid_init();
1288			WordPressOpenID_Logic::finish_openid($_REQUEST['action']);
1289		}
1290	}
1291
1292	function set_error($error) {
1293		$_SESSION['oid_error'] = $error;
1294		return;
1295	}
1296
1297} // end class definition
1298endif; // end if-class-exists test
1299
1300?>