3
* SpecialOpenIDFinish.body.php -- Finish logging into an OpenID site
4
* Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
5
* Copyright 2007,2008 Evan Prodromou <evan@prodromou.name>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
* @author Evan Prodromou <evan@prodromou.name>
22
* @addtogroup Extensions
25
if (!defined('MEDIAWIKI'))
28
require_once("Auth/OpenID/Consumer.php");
29
require_once("Auth/OpenID/SReg.php");
30
require_once("Auth/Yadis/XRI.php");
32
class SpecialOpenIDFinish extends SpecialOpenID {
34
function SpecialOpenIDFinish() {
35
SpecialPage::SpecialPage("OpenIDFinish", '', false);
38
function execute( $par) {
40
global $wgUser, $wgOut, $wgRequest;
42
wfLoadExtensionMessages( 'OpenID' );
46
# Shouldn't work if you're already logged in.
48
if ( $wgUser->getID() != 0 ) {
49
$this->alreadyLoggedIn();
53
$consumer = $this->getConsumer();
57
list( $openid, $sreg) = $this->fetchValues();
58
if ( !isset( $openid ) ) {
59
wfDebug( "OpenID: aborting in ChooseName because identity_url is missing\n" );
61
# No messing around, here
62
$wgOut->showErrorPage( 'openiderror', 'openiderrortext' );
66
if ( $wgRequest->getCheck( 'wpCancel' ) ) {
68
$wgOut->showErrorPage( 'openidcancel', 'openidcanceltext' );
72
$choice = $wgRequest->getText( 'wpNameChoice' );
73
$nameValue = $wgRequest->getText( 'wpNameValue' );
75
if ($choice == 'existing') {
76
$user = $this->attachUser( $openid, $sreg,
77
$wgRequest->getText( 'wpExistingName' ),
78
$wgRequest->getText( 'wpExistingPassword' )
82
$this->chooseNameForm( $openid, $sreg, 'wrongpassword' );
86
if ($wgRequest->getText( 'wpUpdateUserInfo' ))
88
$this->updateUser( $user, $sreg );
91
$name = $this->getUserName( $openid, $sreg, $choice, $nameValue);
93
if ( !$name || !$this->userNameOK( $name ) ) {
94
wfDebug( "OpenID: Name not OK: '$name'\n" );
95
$this->chooseNameForm( $openid, $sreg );
99
$user = $this->createUser( $openid, $sreg, $name );
102
if ( !isset( $user ) ) {
103
wfDebug( "OpenID: aborting in ChooseName because we could not create user object\n" );
104
$this->clearValues();
105
$wgOut->showErrorPage( 'openiderror', 'openiderrortext' );
111
$this->clearValues();
113
$this->finishLogin( $response->identity_url );
116
default: # No parameter, returning from a server
118
$response = $consumer->complete( $this->scriptUrl( 'OpenIDFinish' ) );
120
if ( !isset( $response ) ) {
121
wfDebug( "OpenID: aborting in auth because no response was recieved\n" );
122
$wgOut->showErrorPage( 'openiderror', 'openiderrortext' );
126
switch ( $response->status ) {
127
case Auth_OpenID_CANCEL:
128
// This means the authentication was cancelled.
129
$wgOut->showErrorPage( 'openidcancel', 'openidcanceltext' );
131
case Auth_OpenID_FAILURE:
132
wfDebug( "OpenID: error message '" . $response->message . "'\n" );
133
$wgOut->showErrorPage( 'openidfailure', 'openidfailuretext',
134
array( ( $response->message ) ? $response->message : '' ) );
136
case Auth_OpenID_SUCCESS:
137
// This means the authentication succeeded.
138
$openid = $response->getDisplayIdentifier();
139
$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse( $response );
140
$sreg = $sreg_resp->contents();
142
if (!isset($openid)) {
143
wfDebug( "OpenID: aborting in auth success because display identifier is missing\n" );
144
$wgOut->showErrorPage( 'openiderror', 'openiderrortext' );
148
$user = $this->getUser( $openid );
150
if ( isset( $user ) )
152
if ($user->getOption('openid-update-userinfo-on-login'))
154
$this->updateUser( $user, $sreg); # update from server
158
$name = $this->createName( $openid, $sreg );
160
$user = $this->createUser( $openid, $sreg, $name );
163
$this->saveValues( $openid, $sreg );
164
$this->chooseNameForm( $openid, $sreg );
169
if ( !isset( $user ) ) {
170
wfDebug( "OpenID: aborting in auth success because we could not create user object\n" );
171
$wgOut->showErrorPage( 'openiderror', 'openiderrortext' );
174
$this->finishLogin( $openid );
180
function finishLogin( $openid ) {
181
global $wgUser, $wgOut;
183
$wgUser->SetupSession();
184
$wgUser->SetCookies();
186
# Run any hooks; ignore results
188
wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$inject_html ) );
190
# Set a cookie for later check-immediate use
192
$this->loginSetCookie($openid);
194
$wgOut->setPageTitle( wfMsg( 'openidsuccess' ) );
195
$wgOut->setRobotPolicy( 'noindex,nofollow' );
196
$wgOut->setArticleRelated( false );
197
$wgOut->addWikiText( wfMsg( 'openidsuccess', $wgUser->getName(), $openid ) );
198
$wgOut->addHtml( $inject_html );
199
$wgOut->returnToMain( false, $this->returnTo() );
202
function loginSetCookie( $openid ) {
203
global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
204
global $wgOpenIDCookieExpiration;
206
$exp = time() + $wgOpenIDCookieExpiration;
208
setcookie( $wgCookiePrefix.'OpenID', $openid, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
211
function chooseNameForm( $openid, $sreg, $messagekey = NULL ) {
213
global $wgOut, $wgUser, $wgOpenIDOnly;
215
$sk = $wgUser->getSkin();
217
$message = wfMsg( $messagekey );
218
} else if ( array_key_exists( 'nickname', $sreg ) ) {
219
$message = wfMsg( 'openidnotavailable', $sreg['nickname'] );
221
$message = wfMsg( 'openidnotprovided' );
223
$instructions = wfMsg( 'openidchooseinstructions' );
224
$wgOut->addHTML( "<p>{$message}</p>" .
225
"<p>{$instructions}</p>" .
226
'<form action="' . $sk->makeSpecialUrl( 'OpenIDFinish/ChooseName' ) . '" method="POST">' );
229
if ( !$wgOpenIDOnly ) {
230
# Let them attach it to an existing user
232
# Grab the UserName in the cookie if it exists
234
global $wgCookiePrefix;
236
if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
237
$name = trim( $_COOKIE["{$wgCookiePrefix}UserName"] );
240
# show OpenID Attributes
241
$oidAttributesToAccept = array('fullname', 'nickname', 'email', 'language');
242
$oidAttributes = array();
244
foreach ($oidAttributesToAccept as $oidAttr)
246
if ($oidAttr == 'fullname' && !$wgAllowRealName)
251
if ( array_key_exists( $oidAttr, $sreg )) {
252
$oidAttributes[] = '<div>'.wfMsg( "openid$oidAttr" ).': <i>'.$sreg[$oidAttr].'</i></div>';
256
$oidAttributesUpdate = '';
257
if (count($oidAttributes) > 0)
259
$oidAttributesUpdate = '<div style="margin-left: 25px">'.
260
'<input type="checkbox" name="wpUpdateUserInfo" id="wpUpdateUserInfo">' .
261
'<label for="wpUpdateUserInfo">' . wfMsg( "openidupdateuserinfo" ) .
262
'<div style="margin-left: 25px">'.implode('', $oidAttributes).'</div>'.
266
$wgOut->addHTML("<div><input type='radio' name='wpNameChoice' id='wpNameChoiceExisting' value='existing' />" .
267
"<label for='wpNameChoiceExisting'>" . wfMsg( "openidchooseexisting" ) . "</label> " .
268
"<input type='text' name='wpExistingName' id='wpExistingName' size='16' value='{$name}'> " .
269
"<label for='wpExistingPassword'>" . wfMsg( "openidchoosepassword" ) . "</label> " .
270
"<input type='password' name='wpExistingPassword' size='8' /> " .
271
$oidAttributesUpdate . "</div>\n" );
274
# These options won't exist if we can't get them.
276
if ( array_key_exists( 'fullname', $sreg ) && $this->userNameOK( $sreg['fullname'] ) ) {
277
$wgOut->addHTML( "<div><input type='radio' name='wpNameChoice' id='wpNameChoiceFull' value='full' " .
278
( ( !$def ) ? "checked = 'checked'" : "" ) . " />" .
279
"<label for='wpNameChoiceFull'>" . wfMsg( "openidchoosefull", $sreg['fullname'] ) . "</label></div>\n" );
283
$idname = $this->toUserName( $openid );
285
if ( $idname && $this->userNameOK( $idname ) ) {
286
$wgOut->addHTML( "<div><input type='radio' name='wpNameChoice' id='wpNameChoiceUrl' value='url' " .
287
( ( !$def ) ? "checked = 'checked'" : "" ) . " />" .
288
"<label for='wpNameChoiceUrl'>" . wfMsg( "openidchooseurl", $idname ) . "</label></div>\n" );
292
# These are always available
294
$wgOut->addHTML( "<div><input type='radio' name='wpNameChoice' id='wpNameChoiceAuto' value='auto' " .
295
( ( !$def ) ? "checked = 'checked'" : "" ) . " />" .
296
"<label for='wpNameChoiceAuto'>" . wfMsg( "openidchooseauto", $this->automaticName( $sreg ) ) . "</label></div>\n");
300
$wgOut->addHTML("<div><input type='radio' name='wpNameChoice' id='wpNameChoiceManual' value='manual' " .
301
" checked='off' />" .
302
"<label for='wpNameChoiceManual'>" . wfMsg( "openidchoosemanual" ) . "</label> " .
303
"<input type='text' name='wpNameValue' id='wpNameValue' size='16' /></div>\n");
305
$ok = wfMsg( 'login' );
306
$cancel = wfMsg( 'cancel' );
308
$wgOut->addHTML( "<input type='submit' name='wpOK' value='{$ok}' /> <input type='submit' name='wpCancel' value='{$cancel}' />" );
309
$wgOut->addHTML( "</form>" );
312
function canLogin( $openid_url ) {
313
global $wgOpenIDConsumerDenyByDefault, $wgOpenIDConsumerAllow, $wgOpenIDConsumerDeny;
315
if ( $this->isLocalUrl( $openid_url ) ) {
319
if ( $wgOpenIDConsumerDenyByDefault ) {
321
foreach ( $wgOpenIDConsumerAllow as $allow ) {
322
if ( preg_match( $allow, $openid_url ) ) {
323
wfDebug( "OpenID: $openid_url matched allow pattern $allow.\n" );
325
foreach ( $wgOpenIDConsumerDeny as $deny ) {
326
if ( preg_match( $deny, $openid_url ) ) {
327
wfDebug( "OpenID: $openid_url matched deny pattern $deny.\n" );
337
foreach ( $wgOpenIDConsumerDeny as $deny ) {
338
if ( preg_match( $deny, $openid_url ) ) {
339
wfDebug( "OpenID: $openid_url matched deny pattern $deny.\n" );
341
foreach ( $wgOpenIDConsumerAllow as $allow ) {
342
if ( preg_match( $allow, $openid_url ) ) {
343
wfDebug( "OpenID: $openid_url matched allow pattern $allow.\n" );
355
function isLocalUrl( $url ) {
356
global $wgServer, $wgArticlePath;
358
$pattern = $wgServer . $wgArticlePath;
359
$pattern = str_replace( '$1', '(.*)', $pattern );
360
$pattern = str_replace( '?', '\?', $pattern );
362
return preg_match( '|^' . $pattern . '$|', $url );
365
# Find the user with the given openid, if any
367
function getUser( $openid ) {
368
global $wgSharedDB, $wgDBprefix;
370
if ( isset( $wgSharedDB ) ) {
371
$tableName = "`$wgSharedDB`.${wgDBprefix}user_openid";
373
$tableName = 'user_openid';
376
$dbr = wfGetDB( DB_SLAVE );
377
$id = $dbr->selectField(
380
array( 'uoi_openid' => $openid ),
384
return User::newFromID( $id );
390
function updateUser( $user, $sreg ) {
391
global $wgAllowRealName;
393
# FIXME: only update if there's been a change
394
$user->setOption('nickname', array_key_exists( 'nickname', $sreg )
395
? $sreg['nickname'] : '');
397
$user->setEmail( array_key_exists( 'email', $sreg ) ? $sreg['email'] : '' );
399
$user->setRealName( (array_key_exists( 'fullname', $sreg ) && $wgAllowRealName)
400
? $sreg['fullname'] : '');
402
if ( array_key_exists( 'language', $sreg ) ) {
403
# FIXME: check and make sure the language exists
404
$user->setOption( 'language', $sreg['language'] );
406
$user->setOption( 'language', NULL );
409
if (array_key_exists( 'timezone', $sreg ) ) {
410
# FIXME: do something with it.
411
# $offset = OpenIDTimezoneToTzoffset($sreg['timezone']);
412
# $user->setOption('timecorrection', $offset);
414
# $user->setOption('timecorrection', NULL);
417
$user->saveSettings();
420
function createUser($openid, $sreg, $name) {
422
global $wgAuth, $wgAllowRealName;
424
$user = User::newFromName($name);
427
wfDebug("OpenID: Error adding new user.\n");
431
$user->addToDatabase();
433
if (!$user->getId()) {
434
wfDebug("OpenID: Error adding new user.\n");
437
$this->insertUserUrl($user, $openid);
439
if (array_key_exists('nickname', $sreg)) {
440
$user->setOption('nickname', $sreg['nickname']);
442
if (array_key_exists('email', $sreg)) {
443
$user->setEmail( $sreg['email'] );
445
if ($wgAllowRealName && array_key_exists('fullname', $sreg)) {
446
$user->setRealName($sreg['fullname']);
448
if (array_key_exists('language', $sreg)) {
449
# FIXME: check and make sure the language exists
450
$user->setOption('language', $sreg['language']);
452
if (array_key_exists('timezone', $sreg)) {
453
# FIXME: do something with it.
454
# $offset = OpenIDTimezoneToTzoffset($sreg['timezone']);
455
# $user->setOption('timecorrection', $offset);
457
$user->saveSettings();
462
function createName($openid, $sreg) {
464
if (array_key_exists('nickname', $sreg) && # try nickname
465
$this->userNameOK($sreg['nickname']))
467
return $sreg['nickname'];
471
function toUserName($openid) {
472
if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
473
return $this->toUserNameXri($openid);
475
return $this->toUserNameUrl($openid);
479
# We try to use an OpenID URL as a legal MediaWiki user name in this order
480
# 1. Plain hostname, like http://evanp.myopenid.com/
481
# 2. One element in path, like http://profile.typekey.com/EvanProdromou/
482
# or http://getopenid.com/evanprodromou
484
function toUserNameUrl($openid) {
485
static $bad = array('query', 'user', 'password', 'port', 'fragment');
487
$parts = parse_url($openid);
489
# If any of these parts exist, this won't work
491
foreach ($bad as $badpart) {
492
if (array_key_exists($badpart, $parts)) {
497
# We just have host and/or path
499
# If it's just a host...
500
if (array_key_exists('host', $parts) &&
501
(!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
503
$hostparts = explode('.', $parts['host']);
505
# Try to catch common idiom of nickname.service.tld
507
if ((count($hostparts) > 2) &&
508
(strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
509
(strcmp($hostparts[0], 'www') != 0))
511
return $hostparts[0];
513
# Do the whole hostname
514
return $parts['host'];
517
if (array_key_exists('path', $parts)) {
518
# Strip starting, ending slashes
519
$path = preg_replace('@/$@', '', $parts['path']);
520
$path = preg_replace('@^/@', '', $path);
521
if (strpos($path, '/') === false) {
530
function toUserNameXri($xri) {
531
$base = $this->xriBase($xri);
537
# or @gratis*evan.prodromou
538
$parts = explode('*', substr($base, 1));
539
return array_pop($parts);
543
# Is this name OK to use as a user name?
545
function userNameOK($name) {
546
global $wgReservedUsernames;
547
return (0 == User::idFromName($name) &&
548
!in_array( $name, $wgReservedUsernames ));
551
# Get an auto-incremented name
553
function firstAvailable($prefix) {
554
for ($i = 2; ; $i++) { # FIXME: this is the DUMB WAY to do this
556
if ($this->userNameOK($name)) {
562
function alreadyLoggedIn() {
564
global $wgUser, $wgOut;
566
$wgOut->setPageTitle( wfMsg( 'openiderror' ) );
567
$wgOut->setRobotPolicy( 'noindex,nofollow' );
568
$wgOut->setArticleRelated( false );
569
$wgOut->addWikiText( wfMsg( 'openidalreadyloggedin', $wgUser->getName() ) );
570
$wgOut->returnToMain( false, $this->returnTo() );
573
static function getUserUrl( $user ) {
576
if ( isset( $user ) && $user->getId() != 0 ) {
577
global $wgSharedDB, $wgDBprefix;
578
if ( isset( $wgSharedDB ) ) {
579
$tableName = "`${wgSharedDB}`.${wgDBprefix}user_openid";
581
$tableName = 'user_openid';
584
$dbr = wfGetDB( DB_SLAVE );
587
array( 'uoi_openid' ),
588
array( 'uoi_user' => $user->getId() ),
592
# This should return 0 or 1 result, since user is unique
595
while ( $row = $res->fetchObject() ) {
596
$openid_url = $row->uoi_openid;
603
function saveValues($openid, $sreg) {
604
global $wgSessionStarted, $wgUser;
606
if (!$wgSessionStarted) {
607
$wgUser->SetupSession();
610
$_SESSION['openid_consumer_identity'] = $openid;
611
$_SESSION['openid_consumer_sreg'] = $sreg;
616
function clearValues() {
617
unset( $_SESSION['openid_consumer_identity'] );
618
unset( $_SESSION['openid_consumer_sreg'] );
622
function fetchValues() {
623
return array( $_SESSION['openid_consumer_identity'], $_SESSION['openid_consumer_sreg'] );
626
function returnTo() {
627
return $_SESSION['openid_consumer_returnto'];
630
function setReturnTo($returnto) {
631
$_SESSION['openid_consumer_returnto'] = $returnto;
634
function getUserName($openid, $sreg, $choice, $nameValue) {
637
return ( ( array_key_exists( 'fullname', $sreg ) ) ? $sreg['fullname'] : null );
640
return $this->toUserName( $openid );
643
return $this->automaticName( $sreg );
652
function automaticName( $sreg ) {
653
if ( array_key_exists( 'nickname', $sreg ) && # try auto-generated from nickname
654
strlen( $sreg['nickname']) > 0 ) {
655
return $this->firstAvailable( $sreg['nickname'] );
656
} else { # try auto-generated
657
return $this->firstAvailable( wfMsg( 'openidusernameprefix' ) );
661
function attachUser( $openid, $sreg, $name, $password ) {
663
$user = User::newFromName( $name );
669
if ( !$user->checkPassword( $password ) ) {
673
$this->setUserUrl( $user, $openid );