r76040 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r76039‎ | r76040 | r76041 >
Date:21:22, 4 November 2010
Author:happy-melon
Status:deferred (Comments)
Tags:
Comment:
Battle against bitrot
Modified paths:
  • /branches/happy-melon/phase3/includes/HTMLForm.php (modified) (history)
  • /branches/happy-melon/phase3/includes/Login.php (modified) (history)
  • /branches/happy-melon/phase3/includes/Token.php (added) (history)
  • /branches/happy-melon/phase3/includes/User.php (modified) (history)
  • /branches/happy-melon/phase3/includes/api/ApiLogin.php (modified) (history)
  • /branches/happy-melon/phase3/includes/specials/SpecialCreateAccount.php (modified) (history)
  • /branches/happy-melon/phase3/includes/specials/SpecialResetpass.php (modified) (history)
  • /branches/happy-melon/phase3/includes/specials/SpecialUserlogin.php (modified) (history)
  • /branches/happy-melon/phase3/skins/common/shared.css (modified) (history)

Diff [purge]

Index: branches/happy-melon/phase3/skins/common/shared.css
@@ -804,3 +804,28 @@
805805 }
806806
807807 #wpLoginAttempt, #wpCreateaccount { margin-right:0; }
 808+.mw-small-spinner {
 809+ padding: 10px !important;
 810+ margin-right: 0.6em;
 811+ background-image: url(images/spinner.gif);
 812+ background-position: center center;
 813+ background-repeat: no-repeat;
 814+}
 815+
 816+/* Sort arrows added by SortableTables */
 817+a.sortheader {
 818+ margin: 0 0.3em;
 819+}
 820+
 821+.grid-table {
 822+ display: table;
 823+}
 824+
 825+.grid-row {
 826+ display: table-row;
 827+}
 828+
 829+.grid-cell {
 830+ display: table-cell;
 831+ padding: 0.33ex;
 832+}
Index: branches/happy-melon/phase3/includes/User.php
@@ -5,12 +5,6 @@
66 */
77
88 /**
9 - * \int Number of characters in user_token field.
10 - * @ingroup Constants
11 - */
12 -define( 'USER_TOKEN_LENGTH', 32 );
13 -
14 -/**
159 * \int Serialized record version.
1610 * @ingroup Constants
1711 */
@@ -2968,7 +2962,7 @@
29692963 $now = time();
29702964 $expires = $now + 7 * 24 * 60 * 60;
29712965 $expiration = wfTimestamp( TS_MW, $expires );
2972 - $token = $this->generateToken( $this->mId . $this->mEmail . $expires );
 2966+ $token = wfGenerateToken( $this->mId . $this->mEmail . $expires );
29732967 $hash = md5( $token );
29742968 $this->load();
29752969 $this->mEmailToken = $hash;
Index: branches/happy-melon/phase3/includes/Token.php
@@ -0,0 +1,137 @@
 2+<?php
 3+/**
 4+ * CSRF attacks (where a malicious website uses frames, <img> tags, or
 5+ * similar, to prompt a wiki user to open a wiki page or submit a form,
 6+ * without being aware of doing so) are most easily countered by using
 7+ * tokens. For normal browsing, loading the form for a protected action
 8+ * sets two copies of a random string: one in the $_SESSION, and one as
 9+ * a hidden field in the form. When the form is submitted, it checks
 10+ * that a) the set of cookies submitted with the form *has* a copy of
 11+ * the session cookie, and b) that it matches. Since malicious websites
 12+ * don't have control over the session cookies, they can't craft a form
 13+ * that can be instantly submitted which will have the appropriate tokens.
 14+ *
 15+ * Note that these tokens are distinct from those in User::setToken(), which
 16+ * are used for persistent session authentication and are retained for as
 17+ * long as the user is logged in to the wiki. These tokens are to protect
 18+ * one individual action, and should ideally be cleared once the action is over.
 19+ */
 20+
 21+class Token {
 22+ # Some punctuation to prevent editing from broken
 23+ # text-mangling proxies.
 24+ const TOKEN_SUFFIX = '+\\';
 25+
 26+ /**
 27+ * Ensure that a token is set in cookies, by setting a new one
 28+ * if necessary.
 29+ *
 30+ * @param $action String the action that's being protected by the token
 31+ * @param $salt Mixed String, Array Optional function-specific
 32+ * data for hashing
 33+ * @return \string The token that was set
 34+ */
 35+ public static function set( $action='edit', $salt='' ) {
 36+ # The 'generic' token is EditToken, which we don't store for anons
 37+ # so they can still do things when they have cookies disabled.
 38+ # So either use this for actions which annons can't access, or
 39+ # where you don't mind an attacker being able to trigger the action
 40+ # anonymously from the user's IP. However, the token is still
 41+ # useful because it fails with some broken proxies.
 42+ global $wgUser;
 43+ if ( $action == 'edit' && $wgUser->isAnon() ) {
 44+ return self::TOKEN_SUFFIX;
 45+ }
 46+
 47+ if( !self::has( $action ) ){
 48+ $token = self::generate();
 49+ if( session_id() == '' ) {
 50+ wfSetupSession();
 51+ }
 52+ self::store( $token, $action );
 53+ } else {
 54+ $token = self::get( $action );
 55+ }
 56+
 57+ if( is_array( $salt ) ) {
 58+ $salt = implode( '|', $salt );
 59+ }
 60+
 61+ return md5( $token . $salt ) . self::TOKEN_SUFFIX;
 62+ }
 63+
 64+ /**
 65+ * Check whether the copy of the token submitted with a form
 66+ * matches the version stored in session
 67+ * @param $val String version submitted with the form. If null,
 68+ * tries to get it from $wgRequest
 69+ * @param $action String
 70+ * @param $salt String
 71+ * @return Bool, or null if no session cookie is set
 72+ */
 73+ public static function match( $val=null, $action='edit', $salt='' ){
 74+ $action = ucfirst( $action );
 75+
 76+ global $wgUser;
 77+ if( $action == 'edit' && $wgUser->isAnon() ){
 78+ return $val === self::TOKEN_SUFFIX;
 79+ }
 80+
 81+ if( !self::has( $action ) ){
 82+ return null;
 83+ }
 84+
 85+ if( $val === null ){
 86+ global $wgRequest;
 87+ $val = $wgRequest->getText( "wp{$action}Token" );
 88+ }
 89+
 90+ return md5( self::get( $action ) . $salt ) . self::TOKEN_SUFFIX === $val;
 91+ }
 92+
 93+ /**
 94+ * Whether a token is set for the given action
 95+ * @return Bool
 96+ */
 97+ public static function has( $action='edit' ){
 98+ return self::get( $action ) !== null;
 99+ }
 100+
 101+ /**
 102+ * Get the token set for a given action
 103+ */
 104+ public static function get( $action='edit' ){
 105+ global $wgRequest;
 106+ $action = ucfirst( $action );
 107+ return $wgRequest->getSessionData( "ws{$action}Token" );
 108+ }
 109+
 110+ /**
 111+ * Set the given token for the given action in the session
 112+ * @param $token String
 113+ * @param $action String
 114+ */
 115+ public static function store( $token, $action='edit' ){
 116+ global $wgRequest;
 117+ $action = ucfirst( $action );
 118+ $wgRequest->setSessionData( "ws{$action}Token", $token );
 119+ }
 120+
 121+ /**
 122+ * Delete the token after use
 123+ */
 124+ public static function clear( $action='edit' ){
 125+ self::set( null, $action );
 126+ }
 127+
 128+ /**
 129+ * Generate a random token
 130+ *
 131+ * @param $salt String Optional salt value
 132+ * @return String 32-char random token
 133+ */
 134+ public static function generate( $salt = '' ) {
 135+ $rand = dechex( mt_rand() ) . dechex( mt_rand() );
 136+ return md5( $rand . $salt );
 137+ }
 138+}
Property changes on: branches/happy-melon/phase3/includes/Token.php
___________________________________________________________________
Added: svn:eol-style
1139 + native
Index: branches/happy-melon/phase3/includes/HTMLForm.php
@@ -79,6 +79,7 @@
8080 public $mFieldData;
8181
8282 protected $mSubmitCallback;
 83+ protected $mFilterCallback;
8384 protected $mValidationErrorMessage;
8485
8586 protected $mPre = '';
@@ -97,6 +98,7 @@
9899 protected $mButtons = array();
99100
100101 protected $mWrapperLegend = false;
 102+ protected $mTokenAction = 'Edit';
101103
102104 /**
103105 * Build a new HTMLForm from an array of field attributes
@@ -186,7 +188,11 @@
187189 * @return Bool whether submission was successful.
188190 */
189191 function show() {
190 - $html = '';
 192+ global $wgRequest;
 193+ // Check if we have the info we need
 194+ if ( ! $this->mTitle ) {
 195+ throw new MWException( "You must call setTitle() on an HTMLForm" );
 196+ }
191197
192198 self::addJS();
193199
@@ -194,11 +200,8 @@
195201 $this->loadData();
196202
197203 # Try a submission
198 - global $wgUser, $wgRequest;
199 - $editToken = $wgRequest->getVal( 'wpEditToken' );
200 -
201204 $result = false;
202 - if ( $wgUser->matchEditToken( $editToken ) )
 205+ if( $wgRequest->wasPosted() ){
203206 $result = $this->trySubmit();
204207
205208 if( $result === true )
@@ -217,6 +220,11 @@
218221 * display.
219222 */
220223 function trySubmit() {
 224+ # Check the session tokens
 225+ if ( !Token::match( null, $this->mTokenAction ) ) {
 226+ return array( 'sessionfailure' );
 227+ }
 228+
221229 # Check for validation
222230 foreach( $this->mFlatFields as $fieldname => $field ) {
223231 if ( !empty( $field->mParams['nodata'] ) )
@@ -362,8 +370,11 @@
363371 function getHiddenFields() {
364372 global $wgUser;
365373 $html = '';
366 -
367 - $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
 374+ $html .= Html::hidden(
 375+ "wp{$this->mTokenAction}Token",
 376+ Token::set( $this->mTokenAction ),
 377+ array( 'id' => 'wpEditToken' )
 378+ ) . "\n";
368379 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
369380
370381 foreach( $this->mHiddenFields as $name => $value ){
@@ -526,6 +537,16 @@
527538 function setMessagePrefix( $p ) {
528539 $this->mMessagePrefix = $p;
529540 }
 541+
 542+ /**
 543+ * If you want to protect the form from CSRF by a token other than
 544+ * the usual wsEditToken, set something here.
 545+ * @see Token::set()
 546+ * @param $a
 547+ */
 548+ function setTokenAction( $a ){
 549+ $this->mTokenAction = ucfirst( $a );
 550+ }
530551
531552 /**
532553 * Set the title for form submission
@@ -626,8 +647,19 @@
627648 * @return unknown_type
628649 */
629650 function filterDataForSubmit( $data ) {
 651+ if( is_callable( $this->mFilterCallback ) ){
 652+ $data = call_user_func( $this->mFilterCallback, $data );
 653+ }
630654 return $data;
631655 }
 656+
 657+ /**
 658+ * Set a filter callback.
 659+ * @param $function Callback
 660+ */
 661+ public function setFilterCallback( $callback ){
 662+ $this->mFilterCallback = $callback;
 663+ }
632664 }
633665
634666 /**
Index: branches/happy-melon/phase3/includes/api/ApiLogin.php
@@ -56,68 +56,92 @@
5757
5858 $result = array ();
5959
60 - $req = new FauxRequest(array (
61 - 'wpName' => $params['name'],
62 - 'wpPassword' => $params['password'],
63 - 'wpDomain' => $params['domain'],
64 - 'wpRemember' => ''
65 - ));
66 -
 60+ $req = array(
 61+ 'Name' => $params['name'],
 62+ 'Password' => $params['password'],
 63+ 'Domain' => $params['domain'],
 64+ 'Token' => $params['token'],
 65+ 'Remember' => ''
 66+ );
6767 // Init session if necessary
6868 if( session_id() == '' ) {
6969 wfSetupSession();
7070 }
71 -
72 - $loginForm = new LoginForm($req);
73 - switch ($authRes = $loginForm->authenticateUserData()) {
74 - case LoginForm :: SUCCESS :
75 - global $wgUser, $wgCookiePrefix;
76 -
77 - $wgUser->setOption('rememberpassword', 1);
78 - $wgUser->setCookies();
79 -
80 - // Run hooks. FIXME: split back and frontend from this hook.
81 - // FIXME: This hook should be placed in the backend
82 - $injected_html = '';
83 - wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
84 -
85 - $result['result'] = 'Success';
86 - $result['lguserid'] = intval($wgUser->getId());
87 - $result['lgusername'] = $wgUser->getName();
88 - $result['lgtoken'] = $wgUser->getToken();
89 - $result['cookieprefix'] = $wgCookiePrefix;
90 - $result['sessionid'] = session_id();
91 - break;
92 -
93 - case LoginForm :: NO_NAME :
94 - $result['result'] = 'NoName';
95 - break;
96 - case LoginForm :: ILLEGAL :
97 - $result['result'] = 'Illegal';
98 - break;
99 - case LoginForm :: WRONG_PLUGIN_PASS :
100 - $result['result'] = 'WrongPluginPass';
101 - break;
102 - case LoginForm :: NOT_EXISTS :
103 - $result['result'] = 'NotExists';
104 - break;
105 - case LoginForm :: WRONG_PASS :
106 - $result['result'] = 'WrongPass';
107 - break;
108 - case LoginForm :: EMPTY_PASS :
109 - $result['result'] = 'EmptyPass';
110 - break;
111 - case LoginForm :: CREATE_BLOCKED :
112 - $result['result'] = 'CreateBlocked';
113 - $result['details'] = 'Your IP address is blocked from account creation';
114 - break;
115 - case LoginForm :: THROTTLED :
116 - global $wgPasswordAttemptThrottle;
117 - $result['result'] = 'Throttled';
118 - $result['wait'] = intval($wgPasswordAttemptThrottle['seconds']);
119 - break;
120 - default :
121 - ApiBase :: dieDebug(__METHOD__, "Unhandled case value: {$authRes}");
 71+ if( $params['token']
 72+ && !Token::match( $params['token'], 'login' ) )
 73+ {
 74+ $result['result'] = 'WrongToken';
 75+
 76+ } elseif ( !Token::has( 'login' ) || !$params['token']
 77+ || !Token::match( $params['token'], 'login' ) )
 78+ {
 79+ global $wgCookiePrefix;
 80+ $result['result'] = 'NeedToken';
 81+ $result['token'] = Token::set( 'login' );
 82+ $result['cookieprefix'] = $wgCookiePrefix;
 83+ $result['sessionid'] = session_id();
 84+
 85+ } else {
 86+ $login = new Login( $req );
 87+ switch ( $authRes = $login->attemptLogin() ) {
 88+ case Login::SUCCESS:
 89+ Token::clear( 'login' );
 90+ global $wgUser, $wgCookiePrefix;
 91+
 92+ $result['result'] = 'Success';
 93+ $result['lguserid'] = intval( $wgUser->getId() );
 94+ $result['lgusername'] = $wgUser->getName();
 95+ $result['lgtoken'] = $wgUser->getToken();
 96+ $result['cookieprefix'] = $wgCookiePrefix;
 97+ $result['sessionid'] = session_id();
 98+ break;
 99+
 100+ case Login::NO_NAME:
 101+ $result['result'] = 'NoName';
 102+ break;
 103+
 104+ case Login::ILLEGAL:
 105+ $result['result'] = 'Illegal';
 106+ break;
 107+
 108+ case Login::WRONG_PLUGIN_PASS:
 109+ $result['result'] = 'WrongPluginPass';
 110+ break;
 111+
 112+ case Login::NOT_EXISTS:
 113+ $result['result'] = 'NotExists';
 114+ break;
 115+
 116+ # bug 20223 - Treat a temporary password as wrong. Per
 117+ # SpecialUserLogin - "The e-mailed temporary password
 118+ # should not be used for actual logins"
 119+ case Login::RESET_PASS:
 120+ case Login::WRONG_PASS:
 121+ $result['result'] = 'WrongPass';
 122+ break;
 123+
 124+ case Login::EMPTY_PASS:
 125+ $result['result'] = 'EmptyPass';
 126+ break;
 127+
 128+ case Login::CREATE_BLOCKED:
 129+ $result['result'] = 'CreateBlocked';
 130+ $result['details'] = 'Your IP address is blocked from account creation';
 131+ break;
 132+
 133+ case Login::THROTTLED:
 134+ global $wgPasswordAttemptThrottle;
 135+ $result['result'] = 'Throttled';
 136+ $result['wait'] = intval( $wgPasswordAttemptThrottle['seconds'] );
 137+ break;
 138+
 139+ case Login::USER_BLOCKED:
 140+ $result['result'] = 'Blocked';
 141+ break;
 142+
 143+ default:
 144+ ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" );
 145+ }
122146 }
123147
124148 $this->getResult()->addValue(null, 'login', $result);
Index: branches/happy-melon/phase3/includes/Login.php
@@ -15,8 +15,9 @@
1616 const RESET_PASS = 7;
1717 const ABORTED = 8;
1818 const THROTTLED = 10;
19 - const FAILED = 11;
20 - const READ_ONLY = 12;
 19+ const USER_BLOCKED = 11;
 20+ const FAILED = 12;
 21+ const READ_ONLY = 13;
2122
2223 const MAIL_PASSCHANGE_FORBIDDEN = 21;
2324 const MAIL_BLOCKED = 22;
@@ -52,29 +53,33 @@
5354
5455 /**
5556 * Constructor
56 - * @param WebRequest $request A WebRequest object passed by reference.
57 - * uses $wgRequest if not given.
 57+ * @param $data Array data object passed by reference.
5858 */
59 - public function __construct( &$request=null ) {
60 - global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
61 - if( !$request ) $request = &$wgRequest;
 59+ public function __construct( $data ) {
 60+ global $wgAuth, $wgHiddenPrefs, $wgEnableEmail;
6261
63 - $this->mName = $request->getText( 'wpName' );
64 - $this->mPassword = $request->getText( 'wpPassword' );
65 - $this->mDomain = $request->getText( 'wpDomain' );
66 - $this->mRemember = $request->getCheck( 'wpRemember' ) ? 1 : 0;
 62+ $this->mName = $data['Name'];
 63+ $this->mPassword = $data['Password'];
 64+
 65+ if( isset( $data['Remember'] ) ){
 66+ $this->mRemember = $data['Remember'] ? 1 : 0;
 67+ }
6768
68 - if( $wgEnableEmail ) {
69 - $this->mEmail = $request->getText( 'wpEmail' );
 69+ if( $wgEnableEmail && isset( $data['Email']) ) {
 70+ $this->mEmail = $data['Email'];
7071 } else {
7172 $this->mEmail = '';
7273 }
73 - if( !in_array( 'realname', $wgHiddenPrefs ) ) {
74 - $this->mRealName = $request->getText( 'wpRealName' );
 74+
 75+ if( !in_array( 'realname', $wgHiddenPrefs ) && isset( $data['RealName'] ) ) {
 76+ $this->mRealName = $data['RealName'];
7577 } else {
7678 $this->mRealName = '';
7779 }
7880
 81+ $this->mDomain = isset( $data['Domain'] )
 82+ ? $data['Domain']
 83+ : '';
7984 if( !$wgAuth->validDomain( $this->mDomain ) ) {
8085 $this->mDomain = 'invaliddomain';
8186 }
@@ -90,7 +95,8 @@
9196 * foreign database; in the latter case, a local user record may
9297 * or may not be created and initialised.
9398 * @param $password String if set, log in with this password rather
94 - * than whatever was given in the WebRequest.
 99+ * than whatever was given in the initial config. If set to
 100+ * Bool true, password always matches
95101 * @return a Login class constant representing the status.
96102 */
97103 public function attemptLogin( $password=null ){
@@ -210,8 +216,9 @@
211217 }
212218
213219 # Initialise $this->mUser
214 - if( $this->getUser() === null )
 220+ if( $this->getUser() === null ){
215221 return $this->mUserCode;
 222+ }
216223
217224 # Shortcut
218225 if( $wgUser->getName() === $this->mUser->getName() ){
@@ -224,7 +231,26 @@
225232 return self::ABORTED;
226233 }
227234
228 - if( !$this->mUser->checkPassword( $this->mPassword ) ) {
 235+ if( $this->mUser->checkPassword( $this->mPassword ) || $this->mPassword === true ) {
 236+ # If we've enabled it, make it so that a blocked user cannot login
 237+ # This is only reached if the password matches, preventing fishing
 238+ # for blocked accounts on private wikis.
 239+ global $wgBlockDisablesLogin;
 240+ if( $wgBlockDisablesLogin && $this->mUser->isBlocked() ) {
 241+ $this->mLoginResult = 'login-userblocked';
 242+ return self::USER_BLOCKED;
 243+ }
 244+
 245+ $wgAuth->updateUser( $this->mUser );
 246+ $wgUser = $this->mUser;
 247+
 248+ # Reset throttle after a successful login
 249+ if( $throttleCount ) {
 250+ $wgMemc->delete( $throttleKey );
 251+ }
 252+
 253+ $retval = self::SUCCESS;
 254+ } else {
229255 if( $this->mUser->checkTemporaryPassword( $this->mPassword ) ) {
230256 # The e-mailed temporary password should not be used for actual
231257 # logins; that's a very sloppy habit, and insecure if an
@@ -259,16 +285,6 @@
260286 $this->mLoginResult = 'wrongpassword';
261287 }
262288 }
263 - } else {
264 - $wgAuth->updateUser( $this->mUser );
265 - $wgUser = $this->mUser;
266 -
267 - # Reset throttle after a successful login
268 - if( $throttleCount ) {
269 - $wgMemc->delete( $throttleKey );
270 - }
271 -
272 - $retval = self::SUCCESS;
273289 }
274290 wfRunHooks( 'LoginAuthenticateAudit', array( &$this->mUser, $this->mPassword, $retval ) );
275291 return $retval;
@@ -281,8 +297,9 @@
282298 * @return &User, or &null on failure. Sets a status code in $this->mUserCode
283299 */
284300 public function &getUser(){
285 - if( $this->mUserCode !== null )
 301+ if( $this->mUserCode !== null ){
286302 return $this->mUser;
 303+ }
287304
288305 # Unstub $wgUser if it's not already by calling getName(). This calls
289306 # the UserLoadFromSession hook, which potentially creates the user in
@@ -309,15 +326,16 @@
310327
311328 # TODO: Allow some magic here for invalid external names, e.g., let the
312329 # user choose a different wiki name.
313 - if( is_null( $this->mUser ) || !User::isUsableName( $this->mUser->getName() ) ) {
 330+ if( !$this->mUser instanceof User || !User::isUsableName( $this->mUser->getName() ) ) {
314331 $this->mUserCode = self::ILLEGAL;
315332 $this->mUser = null;
316333 return $this->mUser;
317334 }
318335
 336+
319337 # If the user doesn't exist in the local database, our only chance
320338 # is for an external auth plugin to autocreate the local user first.
321 - if( $this->mUser->getID() == 0 ) {
 339+ if( $this->mUser->getId() == 0 ) {
322340 if( $this->canAutoCreate() == self::SUCCESS ) {
323341
324342 wfDebug( __METHOD__.": creating account\n" );
@@ -344,6 +362,15 @@
345363 return $this->mUser;
346364 }
347365 } else {
 366+ global $wgAutocreatePolicy;
 367+ if ( $this->mExtUser
 368+ && $wgAutocreatePolicy != 'never'
 369+ && $this->mExtUser->authenticate( $this->mPassword ) )
 370+ {
 371+ # The external user and local user have the same name and
 372+ # password, so we assume they're the same.
 373+ $this->mExtUser->linkToLocal( $this->mUser->getID() );
 374+ }
348375 $this->mUser->load();
349376 }
350377
@@ -369,6 +396,7 @@
370397 'name' => User::getCanonicalName( $this->mName ),
371398 'password' => $byEmail ? null : User::crypt( $this->mPassword ),
372399 'email' => $this->mEmail,
 400+ 'real_name' => $this->mRealName,
373401 'options' => array(
374402 'rememberpassword' => $this->mRemember ? 1 : 0,
375403 ),
@@ -385,7 +413,7 @@
386414
387415 # Or new ExternalUser plugins
388416 if( $this->mExtUser ) {
389 - $this->mExtUser->link( $this->mUser->getId() );
 417+ $this->mExtUser->linkToLocal( $this->mUser->getId() );
390418 $email = $this->mExtUser->getPref( 'emailaddress' );
391419 if( $email && !$this->mEmail ) {
392420 $this->mUser->setEmail( $email );
@@ -395,13 +423,14 @@
396424 # Update user count and newuser logs
397425 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
398426 $ssUpdate->doUpdate();
399 - if( $autocreate )
 427+ if( $autocreate ){
400428 $this->mUser->addNewUserLogEntryAutoCreate();
401 - elseif( $wgUser->isAnon() )
 429+ } elseif ( $wgUser->isAnon() ){
402430 # Avoid spamming IP addresses all over the newuser log
403431 $this->mUser->addNewUserLogEntry( $this->mUser, $byEmail );
404 - else
 432+ } else {
405433 $this->mUser->addNewUserLogEntry( $wgUser, $byEmail );
 434+ }
406435
407436 # Run hooks
408437 wfRunHooks( 'AddNewAccount', array( &$this->mUser, $autocreate, $byEmail ) );
@@ -421,13 +450,13 @@
422451 */
423452 public function attemptCreation( $byEmail=false ) {
424453 global $wgUser, $wgOut;
425 - global $wgEnableSorbs, $wgProxyWhitelist;
426454 global $wgMemc, $wgAccountCreationThrottle;
427455 global $wgAuth;
428456 global $wgEmailAuthentication, $wgEmailConfirmToEdit;
429457
430 - if( wfReadOnly() )
 458+ if( wfReadOnly() ) {
431459 return self::READ_ONLY;
 460+ }
432461
433462 # If the user passes an invalid domain, something is fishy
434463 if( !$wgAuth->validDomain( $this->mDomain ) ) {
@@ -451,9 +480,7 @@
452481 }
453482
454483 $ip = wfGetIP();
455 - if( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) &&
456 - $wgUser->inSorbsBlacklist( $ip ) )
457 - {
 484+ if( $wgUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
458485 $this->mCreateResult = 'sorbs_create_account_reason';
459486 return self::CREATE_SORBS;
460487 }
@@ -474,7 +501,7 @@
475502 # Check that the password is acceptable, if we're actually
476503 # going to use it
477504 if( !$byEmail ){
478 - $valid = $this->mUser->isValidPassword( $this->mPassword );
 505+ $valid = $this->mUser->getPasswordValidity( $this->mPassword );
479506 if( $valid !== true ) {
480507 $this->mCreateResult = $valid;
481508 return self::CREATE_BADPASS;
@@ -524,10 +551,11 @@
525552 }
526553
527554 $result = $this->initUser( false, $byEmail );
528 - if( $result === null )
 555+ if( $result === null ){
529556 # It's unlikely we'd get here without some exception
530557 # being thrown, but it's probably possible...
531558 return self::FAILED;
 559+ }
532560
533561 if( $byEmail ){
534562 # Send out the password by email
@@ -562,62 +590,94 @@
563591 public function mailPassword( $text='passwordremindertext', $title='passwordremindertitle' ) {
564592 global $wgUser, $wgOut, $wgAuth, $wgServer, $wgScript, $wgNewPasswordExpiry;
565593
566 - if( wfReadOnly() )
 594+ if( wfReadOnly() ) {
567595 return self::READ_ONLY;
 596+ }
568597
569598 # If we let the email go out, it will take users to a form where
570599 # they are forced to change their password, so don't let us go
571600 # there if we don't want passwords changed.
572 - if( !$wgAuth->allowPasswordChange() )
 601+ if( !$wgAuth->allowPasswordChange() ) {
573602 return self::MAIL_PASSCHANGE_FORBIDDEN;
 603+ }
574604
575605 # Check against blocked IPs
576606 # FIXME: -- should we not?
577 - if( $wgUser->isBlocked() )
 607+ if( $wgUser->isBlocked() ){
578608 return self::MAIL_BLOCKED;
 609+ }
579610
580611 # Check for hooks
581 - if( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$this->mMailResult ) ) )
 612+ if( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$this->mMailResult ) ) ){
582613 return self::ABORTED;
 614+ }
583615
584616 # Check against the rate limiter
585 - if( $wgUser->pingLimiter( 'mailpassword' ) )
 617+ if( $wgUser->pingLimiter( 'mailpassword' ) ){
586618 return self::MAIL_PING_THROTTLED;
 619+ }
587620
588621 # Initialise the user before checking data about them
589 - if( is_null( $this->getUser() ) )
 622+ if( is_null( $this->getUser() ) ){
590623 return self::NO_NAME;
 624+ }
591625
592626 # And that the resulting user actually exists
593 - if( $this->mUser->getId() === 0 )
 627+ if( $this->mUser->getId() === 0 ){
594628 return self::NOT_EXISTS;
 629+ }
595630
596631 # Check against password throttle
597 - if( $this->mUser->isPasswordReminderThrottled() )
 632+ if( $this->mUser->isPasswordReminderThrottled() ){
598633 return self::MAIL_PASS_THROTTLED;
 634+ }
599635
600636 # User doesn't have email address set
601 - if( $this->mUser->getEmail() === '' )
 637+ if( $this->mUser->getEmail() === '' ){
602638 return self::MAIL_EMPTY_EMAIL;
 639+ }
603640
604641 # Don't send to people who are acting fishily by hiding their IP
605642 $ip = wfGetIP();
606 - if( !$ip )
 643+ if( !$ip ){
607644 return self::MAIL_BAD_IP;
 645+ }
 646+
 647+ # If blocked users cannot log in, don't let them reset passwords either.
 648+ global $wgBlockDisablesLogin;
 649+ if( $wgBlockDisablesLogin && $this->mUser->isBlocked() ) {
 650+ return self::USER_BLOCKED;
 651+ }
608652
609653 # Let hooks do things with the data
610654 wfRunHooks( 'User::mailPasswordInternal', array( &$wgUser, &$ip, &$this->mUser) );
611655
612656 $newpass = $this->mUser->randomPassword();
613657 $this->mUser->setNewpassword( $newpass, true );
614 - $this->mUser->saveSettings();
615658
616 - $message = wfMsgExt( $text, array( 'parsemag' ), $ip, $this->mUser->getName(), $newpass,
617 - $wgServer . $wgScript, round( $wgNewPasswordExpiry / 86400 ) );
618 - $this->mMailResult = $this->mUser->sendMail( wfMsg( $title ), $message );
 659+ $message = wfMsgExt(
 660+ $text,
 661+ array( 'parsemag', 'language' => $this->mUser->getOption('language') ),
 662+ array(
 663+ $ip,
 664+ $this->mUser->getName(),
 665+ $newpass,
 666+ $wgServer . $wgScript,
 667+ round( $wgNewPasswordExpiry / 86400 )
 668+ )
 669+ );
 670+ $title = wfMsgExt(
 671+ $title,
 672+ array( 'parseinline', 'language' => $this->mUser->getOption('language') )
 673+ );
 674+ $this->mMailResult = $this->mUser->sendMail( $title, $message );
619675 if( WikiError::isError( $this->mMailResult ) ) {
 676+ # Discard the new password by not saving it, and hence
 677+ # also not setting the newpassword throttle
620678 return self::MAIL_ERROR;
621679 } else {
 680+ # Save the new password
 681+ $this->mUser->saveSettings();
622682 return self::SUCCESS;
623683 }
624684 }
Index: branches/happy-melon/phase3/includes/specials/SpecialUserlogin.php
@@ -6,14 +6,11 @@
77
88 class SpecialUserLogin extends SpecialPage {
99
10 - var $mUsername, $mPassword, $mReturnTo, $mCookieCheck, $mPosted;
11 - var $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
12 - var $mRemember, $mDomain, $mLanguage;
13 - var $mSkipCookieCheck, $mReturnToQuery;
 10+ var $mReturnTo, $mReturnToQuery;
1411
15 - public $mDomains = array();
16 -
 12+ public $mDomains = array(); # set by hooks
1713 public $mFormHeader = ''; # Can be filled by hooks etc
 14+
1815 public $mFormFields = array(
1916 'Name' => array(
2017 'type' => 'text',
@@ -38,87 +35,71 @@
3936 ),
4037 'Remember' => array(
4138 'type' => 'check',
42 - 'label-message' => 'remembermypassword',
 39+ 'label' => ''/* set in constructor */,
4340 'id' => 'wpRemember',
4441 )
4542 );
4643
47 - protected $mLogin; # Login object
48 -
4944 public function __construct(){
5045 parent::__construct( 'Userlogin' );
 46+
 47+ global $wgLang, $wgCookieExpiration, $wgRequest, $wgRedirectOnLogin;
 48+
 49+ $this->mFormFields['Remember']['label'] = wfMsgExt(
 50+ 'remembermypassword',
 51+ 'parseinline',
 52+ $wgLang->formatNum( ceil( $wgCookieExpiration / 86400 ) )
 53+ );
 54+
 55+ $this->mReturnTo = $wgRequest->getVal( 'returnto' );
 56+ $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
 57+
 58+ if ( $wgRedirectOnLogin ) {
 59+ $this->mReturnTo = $wgRedirectOnLogin;
 60+ $this->mReturnToQuery = '';
 61+ }
 62+
 63+ # When switching accounts, it sucks to get automatically logged out
 64+ $returnToTitle = Title::newFromText( $this->mReturnTo );
 65+ if( $returnToTitle instanceof Title && $returnToTitle->isSpecial( 'Userlogout' ) ) {
 66+ $this->mReturnTo = '';
 67+ $this->mReturnToQuery = '';
 68+ }
5169 }
5270
5371 function execute( $par ) {
54 - global $wgRequest;
 72+ global $wgRequest, $wgOut;
5573
56 - # Redirect out for account creation, for B/C
57 - $type = ( $par == 'signup' ) ? $par : $wgRequest->getText( 'type' );
58 - if( $type == 'signup' ){
 74+ # Pre-1.17, CreateAccount was at Special:UserLogin/signup
 75+ if( $par == 'signup' || $wgRequest->getText( 'type' ) == 'signup' ){
5976 $sp = new SpecialCreateAccount();
6077 $sp->execute( $par );
6178 return;
6279 }
6380
64 - # Because we're transitioning from logged-out, who might not
65 - # have a session, to logged-in, who always do, we need to make
66 - # sure that we *always* have a session...
67 - if( session_id() == '' ) {
68 - wfSetupSession();
69 - }
 81+ $wgOut->setPageTitle( wfMsg( 'login' ) );
 82+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
 83+ $wgOut->setArticleRelated( false );
 84+ $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
7085
71 - $this->loadQuery();
72 - $this->mLogin = new Login();
73 -
74 - if ( $wgRequest->getCheck( 'wpCookieCheck' ) ) {
75 - $this->onCookieRedirectCheck();
76 - return;
77 - } else if( $wgRequest->wasPosted() ) {
78 - if ( $this->mMailmypassword ) {
79 - return $this->showMailPage();
80 - } else {
81 - return $this->processLogin();
82 - }
83 - } else {
84 - $this->mainLoginForm( '' );
85 - }
 86+ $form = $this->getForm();
 87+
 88+ $form->show();
8689 }
87 -
88 - /**
89 - * Load member variables from the HTTP request data
90 - * @param $par String the fragment passed to execute()
91 - */
92 - protected function loadQuery(){
93 - global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
94 -
95 - $this->mUsername = $wgRequest->getText( 'wpName' );
96 - $this->mPassword = $wgRequest->getText( 'wpPassword' );
97 - $this->mDomain = $wgRequest->getText( 'wpDomain' );
98 - $this->mLanguage = $wgRequest->getText( 'uselang' );
99 -
100 - $this->mReturnTo = $wgRequest->getVal( 'returnto' );
101 - $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
102 -
103 - $this->mMailmypassword = $wgRequest->getCheck( 'wpMailmypassword' )
104 - && $wgEnableEmail;
105 - $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
106 - $this->mSkipCookieCheck = $wgRequest->getCheck( 'wpSkipCookieCheck' );
107 -
108 - if( !$wgAuth->validDomain( $this->mDomain ) ) {
109 - $this->mDomain = 'invaliddomain';
110 - }
111 - $wgAuth->setDomain( $this->mDomain );
11290
113 - if ( $wgRedirectOnLogin ) {
114 - $this->mReturnTo = $wgRedirectOnLogin;
115 - $this->mReturnToQuery = '';
 91+ public function formFilterCallback( $data ){
 92+ global $wgRequest, $wgEnableEmail;
 93+ $data['mailpassword'] = $wgRequest->getCheck( 'wpMailmypassword' )
 94+ && $wgEnableEmail;
 95+ return $data;
 96+ }
 97+
 98+ public function formSubmitCallback( $data ){
 99+ if ( $data['mailpassword'] ) {
 100+ return $this->showMailPage( $data );
 101+ } else {
 102+ return $this->processLogin( $data );
116103 }
117 - # When switching accounts, it sucks to get automatically logged out
118 - $returnToTitle = Title::newFromText( $this->mReturnTo );
119 - if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
120 - $this->mReturnTo = '';
121 - $this->mReturnToQuery = '';
122 - }
123104 }
124105
125106 /**
@@ -126,40 +107,26 @@
127108 * @param $msg String a message key for a warning/error message
128109 * that may have been generated on a previous iteration
129110 */
130 - protected function mainLoginForm( $msg, $msgtype = 'error' ) {
131 - global $wgUser, $wgOut, $wgEnableEmail;
 111+ protected function getForm() {
 112+ global $wgUser, $wgOut, $wgRequest, $wgEnableEmail;
132113 global $wgCookiePrefix, $wgLoginLanguageSelector;
133114 global $wgAuth, $wgCookieExpiration;
134115
135116 # Preload the name field with something if we can
136 - if ( '' == $this->mUsername ) {
137 - if ( $wgUser->isLoggedIn() ) {
138 - $this->mUsername = $wgUser->getName();
139 - } elseif( isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ) {
140 - $this->mUsername = $_COOKIE[$wgCookiePrefix.'UserName'];
141 - }
 117+ if ( $wgUser->isLoggedIn() ) {
 118+ $username = $wgUser->getName();
 119+ } elseif( isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ) {
 120+ $username = $_COOKIE[$wgCookiePrefix.'UserName'];
 121+ } else {
 122+ $username = false;
142123 }
143 - if( $this->mUsername ){
144 - $this->mFormFields['Name']['default'] = $this->mUsername;
 124+ if( $username ){
 125+ $this->mFormFields['Name']['default'] = $username;
145126 $this->mFormFields['Password']['autofocus'] = '1';
146127 } else {
147128 $this->mFormFields['Name']['autofocus'] = '1';
148129 }
149130
150 - # Parse the error message if we got one
151 - if( $msg ){
152 - if( $msgtype == 'error' ){
153 - $msg = wfMsgExt( 'loginerror', 'parseinline' ) . ' ' . $msg;
154 - }
155 - $msg = Html::rawElement(
156 - 'div',
157 - array( 'class' => $msgtype . 'box' ),
158 - $msg
159 - );
160 - } else {
161 - $msg = '';
162 - }
163 -
164131 # Make sure the returnTo strings don't get lost if the
165132 # user changes language, etc
166133 $linkq = array();
@@ -170,8 +137,8 @@
171138 }
172139
173140 # Pass any language selection on to the mode switch link
174 - if( $wgLoginLanguageSelector && $this->mLanguage )
175 - $linkq['uselang'] = $this->mLanguage;
 141+ if( $wgLoginLanguageSelector && $wgRequest->getText( 'uselang' ) )
 142+ $linkq['uselang'] = $wgRequest->getText( 'uselang' );
176143
177144 $skin = $wgUser->getSkin();
178145 $link = $skin->link(
@@ -182,7 +149,7 @@
183150
184151 # Don't show a "create account" link if the user can't
185152 $link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
186 - ? wfMsgWikiHtml( 'nologin', $link )
 153+ ? wfMsgExt( 'nologin', array('parseinline','replaceafter'), $link )
187154 : '';
188155
189156 # Prepare language selection links as needed
@@ -195,7 +162,7 @@
196163
197164 # Give authentication and captcha plugins a chance to
198165 # modify the form, by hook or by using $wgAuth
199 - $wgAuth->modifyUITemplate( $this, 'login' );
 166+ //$wgAuth->modifyUITemplate( $this, 'login' );
200167 wfRunHooks( 'UserLoginForm', array( &$this ) );
201168
202169 # The most likely use of the hook is to enable domains;
@@ -211,21 +178,30 @@
212179 if( !($wgCookieExpiration > 0) ){
213180 # Remove it altogether
214181 unset( $this->mFormFields['Remember'] );
215 - } elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
 182+ } elseif( $wgUser->getOption( 'rememberpassword' ) ){
216183 # Or check it by default
217184 # FIXME: this doesn't always work?
218 - $this->mFormFields['Remember']['checked'] = '1';
 185+ $this->mFormFields['Remember']['default'] = '1';
219186 }
220187
 188+ $this->mFormFields['Token'] = array(
 189+ 'type' => 'hidden',
 190+ 'default' => Token::get( 'login' ),
 191+ );
 192+
221193 $form = new HTMLForm( $this->mFormFields, '' );
222194 $form->setTitle( $this->getTitle() );
223195 $form->setSubmitText( wfMsg( 'login' ) );
224196 $form->setSubmitId( 'wpLoginAttempt' );
225197 $form->suppressReset();
226198 $form->setWrapperLegend( wfMsg( 'userlogin' ) );
 199+ $form->setTokenAction( 'login' );
227200
228201 $form->addHiddenField( 'returnto', $this->mReturnTo );
229202 $form->addHiddenField( 'returntoquery', $this->mReturnToQuery );
 203+ if( $wgRequest->getText( 'uselang' ) ){
 204+ $form->addHiddenField( 'uselang', $wgRequest->getText( 'uselang' ) );
 205+ }
230206
231207 $form->addHeaderText( ''
232208 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
@@ -236,7 +212,6 @@
237213 . $langSelector
238214 );
239215 $form->addPreText( ''
240 - . $msg
241216 . Html::rawElement(
242217 'div',
243218 array( 'id' => 'loginstart' ),
@@ -252,7 +227,6 @@
253228 );
254229
255230 # Add a 'mail reset' button if available
256 - $buttons = '';
257231 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
258232 $form->addButton(
259233 'wpMailmypassword',
@@ -261,61 +235,14 @@
262236 );
263237 }
264238
 239+ $form->setFilterCallback( array( $this, 'formFilterCallback' ) );
 240+ $form->setSubmitCallback( array( $this, 'formSubmitCallback' ) );
265241 $form->loadData();
266 -
267 - $wgOut->setPageTitle( wfMsg( 'login' ) );
268 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
269 - $wgOut->setArticleRelated( false );
270 - $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
271 -
272 - $form->displayForm( '' );
273 - }
274 -
275 - /**
276 - * Check if a session cookie is present.
277 - *
278 - * This will not pick up a cookie set during _this_ request, but is meant
279 - * to ensure that the client is returning the cookie which was set on a
280 - * previous pass through the system.
281 - *
282 - * @private
283 - */
284 - protected function hasSessionCookie() {
285 - global $wgDisableCookieCheck, $wgRequest;
286 - return $wgDisableCookieCheck || $wgRequest->checkSessionCookie();
 242+
 243+ return $form;
287244 }
288245
289246 /**
290 - * Do a redirect back to the same page, so we can check any
291 - * new session cookies.
292 - */
293 - protected function cookieRedirectCheck() {
294 - global $wgOut;
295 -
296 - $query = array( 'wpCookieCheck' => '1');
297 - if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
298 - $check = $this->getTitle()->getFullURL( $query );
299 -
300 - return $wgOut->redirect( $check );
301 - }
302 -
303 - /**
304 - * Check the cookies and show errors if they're not enabled.
305 - * @param $type String action being performed
306 - */
307 - protected function onCookieRedirectCheck() {
308 - if ( $this->hasSessionCookie() ) {
309 - return self::successfulLogin(
310 - 'loginsuccess',
311 - $this->mReturnTo,
312 - $this->mReturnToQuery
313 - );
314 - } else {
315 - return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
316 - }
317 - }
318 -
319 - /**
320247 * Produce a bar of links which allow the user to select another language
321248 * during login/registration but retain "returnto"
322249 * @param $title Title to use in the link
@@ -333,11 +260,13 @@
334261 $lang = trim( $lang, '* ' );
335262 $parts = explode( '|', $lang );
336263 if (count($parts) >= 2) {
337 - $links[] = SpecialUserLogin::makeLanguageSelectorLink(
 264+ $links[] = self::makeLanguageSelectorLink(
338265 $parts[0], $parts[1], $title, $returnTo );
339266 }
340267 }
341 - return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
 268+ return count( $links ) > 0
 269+ ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) )
 270+ : '';
342271 } else {
343272 return '';
344273 }
@@ -419,29 +348,67 @@
420349 }
421350 }
422351
 352+ /**
 353+ * Try and login with the data provided, and react appropriately.
 354+ * @param $data Array from HTMLForm
 355+ * @return Mixed Bool true, or String error
 356+ */
 357+ protected function processLogin( $data ){
 358+ global $wgUser;
 359+
 360+ $login = new Login( $data );
 361+ $result = $login->attemptLogin();
423362
424 - protected function processLogin(){
425 - global $wgUser, $wgAuth;
426 - $result = $this->mLogin->attemptLogin();
427363 switch ( $result ) {
428364 case Login::SUCCESS:
429 - if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
430 - # Replace the language object to provide user interface in
431 - # correct language immediately on this first page load.
432 - global $wgLang, $wgRequest;
433 - $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
434 - $wgLang = Language::factory( $code );
435 - return self::successfulLogin(
436 - 'loginsuccess',
437 - $this->mReturnTo,
438 - $this->mReturnToQuery,
439 - $this->mLogin->mLoginResult );
440 - } else {
441 - # Do a redirect check to ensure that the cookies are
442 - # being retained by the user's browser.
443 - return $this->cookieRedirectCheck();
444 - }
445 - break;
 365+ Token::clear( 'login' );
 366+ # Replace the language object to provide user interface in
 367+ # correct language immediately on this first page load. Note
 368+ # that this only has any effect if we display a login splash
 369+ # screen.
 370+ global $wgLang, $wgRequest, $wgOut;
 371+ $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
 372+ $wgLang = Language::factory( $code );
 373+ $wgOut->addHTML( self::successfulLogin(
 374+ 'loginsuccess',
 375+ $this->mReturnTo,
 376+ $this->mReturnToQuery,
 377+ $login->mLoginResult
 378+ ) );
 379+ return true;
 380+
 381+ case Login::RESET_PASS:
 382+ Token::clear( 'login' );
 383+ # 'Shell out' to Special:ResetPass to get the user to
 384+ # set a new permanent password from a temporary one.
 385+ $reset = new SpecialResetpass();
 386+ $msg = wfMsgExt( 'resetpass_announce', 'parseinline' );
 387+ $reset->getForm(true)->displayForm( $msg );
 388+ return true;
 389+
 390+ case Login::CREATE_BLOCKED:
 391+ # Be nice about this, it's likely that this feature will be used
 392+ # for blocking large numbers of innocent people, e.g. range blocks on
 393+ # schools. Don't blame it on the user. There's a small chance that it
 394+ # really is the user's fault, i.e. the username is blocked and they
 395+ # haven't bothered to log out before trying to create an account to
 396+ # evade it, but we'll leave that to their guilty conscience to figure
 397+ # out.
 398+ global $wgOut, $wgUser;
 399+ $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
 400+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
 401+ $wgOut->setArticleRelated( false );
 402+
 403+ $ip = wfGetIP();
 404+ $blocker = User::whoIs( $wgUser->mBlock->mBy );
 405+ $blockReason = $wgUser->mBlock->mReason;
 406+
 407+ if ( strval( $blockReason ) === '' ) {
 408+ $blockReason = wfMsg( 'blockednoreason' );
 409+ }
 410+ $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $blockReason, $blocker );
 411+ $wgOut->returnToMain( false );
 412+ return true;
446413
447414 case Login::NO_NAME:
448415 case Login::ILLEGAL:
@@ -449,34 +416,23 @@
450417 case Login::WRONG_PASS:
451418 case Login::EMPTY_PASS:
452419 case Login::THROTTLED:
453 - $this->mainLoginForm( wfMsgExt( $this->mLogin->mLoginResult, 'parseinline' ) );
454 - break;
 420+ return wfMsgExt( $login->mLoginResult, 'parseinline' );
455421
456422 case Login::NOT_EXISTS:
457423 if( $wgUser->isAllowed( 'createaccount' ) ){
458 - $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $this->mUsername ) ) );
 424+ return wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $data['Name'] ) );
459425 } else {
460 - $this->mainLoginForm( wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $this->mUsername ) ) );
 426+ return wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $data['Name'] ) );
461427 }
462 - break;
463428
464 - case Login::RESET_PASS:
465 - # 'Shell out' to Special:ResetPass to get the user to
466 - # set a new permanent password from a temporary one.
467 - $reset = new SpecialResetpass();
468 - $reset->mHeaderMsg = wfMsgExt( 'resetpass_announce', 'parseinline' );
469 - $reset->mHeaderMsgType = 'success';
470 - $reset->execute( null );
471 - break;
 429+ case Login::USER_BLOCKED:
 430+ return wfMsgExt( 'login-userblocked', 'parseinline', $login->getUser()->getName() );
472431
473 - case Login::CREATE_BLOCKED:
474 - $this->userBlockedMessage();
475 - break;
476 -
477432 case Login::ABORTED:
478 - $msg = $this->mLogin->mLoginResult ? $this->mLogin->mLoginResult : $this->mLogin->mCreateResult;
479 - $this->mainLoginForm( wfMsgExt( $msg, 'parseinline' ) );
480 - break;
 433+ $msg = $login->mLoginResult
 434+ ? $login->mLoginResult
 435+ : $login->mCreateResult;
 436+ return wfMsgExt( $msg, 'parseinline' );
481437
482438 default:
483439 throw new MWException( "Unhandled case value: $result" );
@@ -484,54 +440,67 @@
485441 }
486442
487443 /**
488 - * Attempt to send the user a password-reset mail, and display
 444+ * Attempt to send the user a password-reset mail, and return
489445 * the results (good, bad or ugly).
 446+ * @param $data Array from HTMLForm
 447+ * @return Mixed Bool true on success, String HTML on failure
490448 */
491 - protected function showMailPage(){
 449+ protected function showMailPage( $data ){
492450 global $wgOut;
493 - $result = $this->mLogin->mailPassword();
 451+ $login = new Login( $data );
 452+ $result = $login->mailPassword();
494453
495454 switch( $result ){
 455+
 456+ case Login::SUCCESS:
 457+ Token::clear( 'login' );
 458+ $wgOut->addWikiMsg( 'passwordsent', $login->getUser()->getName() );
 459+ return true;
 460+
496461 case Login::READ_ONLY :
497462 $wgOut->readOnlyPage();
498 - return;
499 - case Login::MAIL_PASSCHANGE_FORBIDDEN:
500 - $this->mainLoginForm( wfMsgExt( 'resetpass_forbidden', 'parseinline' ) );
501 - return;
502 - case Login::MAIL_BLOCKED:
503 - $this->mainLoginForm( wfMsgExt( 'blocked-mailpassword', 'parseinline' ) );
504 - return;
 463+ return true;
 464+
505465 case Login::MAIL_PING_THROTTLED:
506466 $wgOut->rateLimited();
507 - return;
 467+ return true;
 468+
 469+ case Login::MAIL_PASSCHANGE_FORBIDDEN:
 470+ return wfMsgExt( 'resetpass_forbidden', 'parseinline' );
 471+
 472+ case Login::MAIL_BLOCKED:
 473+ return wfMsgExt( 'blocked-mailpassword', 'parseinline' );
 474+
 475+ case Login::USER_BLOCKED:
 476+ return wfMsgExt( 'login-userblocked-reset', 'parseinline' );
 477+
508478 case Login::MAIL_PASS_THROTTLED:
509479 global $wgPasswordReminderResendTime;
510480 # Round the time in hours to 3 d.p., in case someone
511481 # is specifying minutes or seconds.
512 - $this->mainLoginForm( wfMsgExt(
 482+ return wfMsgExt(
513483 'throttled-mailpassword',
514484 array( 'parsemag' ),
515485 round( $wgPasswordReminderResendTime, 3 )
516 - ) );
517 - return;
 486+ );
 487+
518488 case Login::NO_NAME:
519 - $this->mainLoginForm( wfMsgExt( 'noname', 'parseinline' ) );
520 - return;
 489+ return wfMsgExt( 'noname', 'parseinline' );
 490+
521491 case Login::NOT_EXISTS:
522 - $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin->getUser()->getName() ) ) );
523 - return;
 492+ return wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $login->getUser()->getName() ) );
 493+
524494 case Login::MAIL_EMPTY_EMAIL:
525 - $this->mainLoginForm( wfMsgExt( 'noemail', 'parseinline', $this->mLogin->getUser()->getName() ) );
526 - return;
 495+ return wfMsgExt( 'noemail', 'parseinline', $login->getUser()->getName() );
 496+
527497 case Login::MAIL_BAD_IP:
528 - $this->mainLoginForm( wfMsgExt( 'badipaddress', 'parseinline' ) );
529 - return;
 498+ return wfMsgExt( 'badipaddress', 'parseinline' );
 499+
530500 case Login::MAIL_ERROR:
531 - $this->mainLoginForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin->mMailResult->getMessage() ) );
532 - return;
533 - case Login::SUCCESS:
534 - $this->mainLoginForm( wfMsgExt( 'passwordsent', 'parseinline', $this->mLogin->getUser()->getName() ), 'success' );
535 - return;
 501+ return wfMsgExt( 'mailerror', 'parseinline', $login->mMailResult->getMessage() );
 502+
 503+ default:
 504+ throw new MWException( "Unhandled case value: $result" );
536505 }
537506 }
538507
Index: branches/happy-melon/phase3/includes/specials/SpecialCreateAccount.php
@@ -6,16 +6,12 @@
77 class SpecialCreateAccount extends SpecialPage {
88
99 var $mUsername, $mPassword, $mRetype, $mReturnTo, $mPosted;
10 - var $mCreateaccountMail, $mRemember, $mEmail, $mDomain, $mLanguage;
 10+ var $mCreateaccountMail, $mEmail, $mDomain, $mLanguage;
1111 var $mReturnToQuery;
12 -
13 - protected $mLogin;
1412
1513 public $mDomains = array();
1614
1715 public $mUseEmail = true; # Can be switched off by AuthPlugins etc
18 - public $mUseRealname = true;
19 - public $mUseRemember = true;
2016
2117 public $mFormHeader = '';
2218 public $mFormFields = array(
@@ -52,12 +48,10 @@
5349 'type' => 'text',
5450 'label-message' => 'yourrealname',
5551 'id' => 'wpRealName',
56 - 'tabindex' => '1',
5752 'size' => '20',
5853 ),
5954 'Remember' => array(
6055 'type' => 'check',
61 - 'label-message' => 'remembermypassword',
6256 'id' => 'wpRemember',
6357 ),
6458 'Domain' => array(
@@ -71,16 +65,45 @@
7266
7367 public function __construct(){
7468 parent::__construct( 'CreateAccount', 'createaccount' );
75 - $this->mLogin = new Login();
 69+
7670 $this->mFormFields['RealName']['label-help'] = 'prefs-help-realname';
 71+ $this->mFormFields['Retype']['validation-callback'] = array( 'SpecialCreateAccount', 'formValidateRetype' );
 72+
 73+
 74+ global $wgCookieExpiration, $wgLang;
 75+ $this->mFormFields['Remember']['label'] = wfMsgExt(
 76+ 'remembermypassword',
 77+ 'parseinline',
 78+ $wgLang->formatNum( ceil( $wgCookieExpiration / 86400 ) )
 79+ );
 80+
 81+ global $wgRequest, $wgRedirectOnLogin;
 82+ $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
 83+ && $wgEnableEmail;
 84+
 85+ $this->mReturnTo = $wgRequest->getVal( 'returnto' );
 86+ $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
 87+ $this->mLanguage = $wgRequest->getText( 'uselang' );
 88+
 89+ if ( $wgRedirectOnLogin ) {
 90+ $this->mReturnTo = $wgRedirectOnLogin;
 91+ $this->mReturnToQuery = '';
 92+ }
 93+
 94+ # When switching accounts, it sucks to get automatically logged out
 95+ $returnToTitle = Title::newFromText( $this->mReturnTo );
 96+ if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
 97+ $this->mReturnTo = '';
 98+ $this->mReturnToQuery = '';
 99+ }
77100 }
78101
79102 public function execute( $par ){
80103 global $wgUser, $wgOut;
81104
82105 $this->setHeaders();
83 - $this->loadQuery();
84 -
 106+ $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
 107+
85108 # Block signup here if in readonly. Keeps user from
86109 # going through the process (filling out data, etc)
87110 # and being informed later.
@@ -98,78 +121,50 @@
99122 } elseif ( count( $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
100123 $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
101124 return;
102 - }
103 -
104 - if( $this->mPosted ) {
105 - $this->addNewAccount( $this->mCreateaccountMail );
106 - } else {
107 - $this->showMainForm('');
108125 }
 126+
 127+ $form = $this->getForm();
 128+ $form->show();
109129 }
110130
111131 /**
112 - * Load the member variables from the request parameters
 132+ * Check that the user actually managed to type the password
 133+ * in the same both times
 134+ * @param unknown_type $data
 135+ * @param unknown_type $alldata
113136 */
114 - protected function loadQuery(){
115 - global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
116 - $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
117 - && $wgEnableEmail;
118 -
119 - $this->mUsername = $wgRequest->getText( 'wpName' );
120 - $this->mPassword = $wgRequest->getText( 'wpPassword' );
121 - $this->mRetype = $wgRequest->getText( 'wpRetype' );
122 - $this->mDomain = $wgRequest->getText( 'wpDomain' );
123 - $this->mReturnTo = $wgRequest->getVal( 'returnto' );
124 - $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
125 - $this->mPosted = $wgRequest->wasPosted();
126 - $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
127 - && $wgEnableEmail;
128 - $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
129 - $this->mLanguage = $wgRequest->getText( 'uselang' );
130 -
131 - if ( $wgRedirectOnLogin ) {
132 - $this->mReturnTo = $wgRedirectOnLogin;
133 - $this->mReturnToQuery = '';
 137+ public static function formValidateRetype( $retype, $alldata ){
 138+ # blank == blank, but the 'this field is required' validation
 139+ # will catch that.
 140+ if( $retype === '' ){
 141+ return true;
134142 }
135143
136 - if( $wgEnableEmail ) {
137 - $this->mEmail = $wgRequest->getText( 'wpEmail' );
 144+ # The other password field could be 'Password' (Special:CreateAccount)
 145+ # or 'NewPassword' (Special:ResetPass).
 146+ if( isset( $alldata['Password'] ) ){
 147+ $password = $alldata['Password'];
 148+ } elseif ( isset( $alldata['NewPassword'] ) ){
 149+ $password = $alldata['NewPassword'];
138150 } else {
139 - $this->mEmail = '';
 151+ $password = null;
140152 }
141 - if( !in_array( 'realname', $wgHiddenPrefs ) ) {
142 - $this->mRealName = $wgRequest->getText( 'wpRealName' );
143 - } else {
144 - $this->mRealName = '';
145 - }
146 -
147 - if( !$wgAuth->validDomain( $this->mDomain ) ) {
148 - $this->mDomain = 'invaliddomain';
149 - }
150 - $wgAuth->setDomain( $this->mDomain );
151 -
152 - # When switching accounts, it sucks to get automatically logged out
153 - $returnToTitle = Title::newFromText( $this->mReturnTo );
154 - if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
155 - $this->mReturnTo = '';
156 - $this->mReturnToQuery = '';
157 - }
 153+
 154+ return $retype === $password
 155+ ? true
 156+ : wfMsgExt( 'badretype', 'parseinline' );
158157 }
159158
160159 /**
161160 * Create a new user account from the provided data
162161 */
163 - protected function addNewAccount( $byEmail=false ) {
 162+ public function formSubmitCallback( $data ) {
164163 global $wgUser, $wgEmailAuthentication;
165 -
166 - # Do a quick check that the user actually managed to type
167 - # the password in the same both times
168 - if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
169 - return $this->showMainForm( wfMsgExt( 'badretype', 'parseinline' ) );
170 - }
171164
172165 # Create the account and abort if there's a problem doing so
173 - $status = $this->mLogin->attemptCreation( $byEmail );
 166+ $login = new Login( $data );
 167+ $status = $login->attemptCreation( $this->mCreateaccountMail );
 168+
174169 switch( $status ){
175170 case Login::SUCCESS:
176171 case Login::MAIL_ERROR:
@@ -183,52 +178,57 @@
184179 case Login::CREATE_BADNAME:
185180 case Login::WRONG_PLUGIN_PASS:
186181 case Login::ABORTED:
187 - return $this->showMainForm( wfMsgExt( $this->mLogin->mCreateResult, 'parseinline' ) );
 182+ return wfMsgExt( $login->mCreateResult, 'parseinline' );
188183
189184 case Login::CREATE_SORBS:
190 - return $this->showMainForm( wfMsgExt( 'sorbs_create_account_reason', 'parseinline' ) . ' (' . wfGetIP() . ')' );
 185+ return wfMsgExt( 'sorbs_create_account_reason', 'parseinline' ) . ' (' . wfGetIP() . ')';
191186
192187 case Login::CREATE_BLOCKED:
193 - return $this->userBlockedMessage();
 188+ $this->userBlockedMessage();
 189+ return true;
194190
195191 case Login::CREATE_BADPASS:
196192 global $wgMinimalPasswordLength;
197 - return $this->showMainForm( wfMsgExt( $this->mLogin->mCreateResult, array( 'parsemag' ), $wgMinimalPasswordLength ) );
 193+ return wfMsgExt( $login->mCreateResult, array( 'parsemag' ), $wgMinimalPasswordLength );
198194
199195 case Login::THROTTLED:
200196 global $wgAccountCreationThrottle;
201 - return $this->showMainForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $wgAccountCreationThrottle ) );
 197+ return wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $wgAccountCreationThrottle );
202198
203199 default:
204200 throw new MWException( "Unhandled status code $status in " . __METHOD__ );
205201 }
 202+
 203+ Token::clear( 'createaccount' );
206204
207205 # If we showed up language selection links, and one was in use, be
208206 # smart (and sensible) and save that language as the user's preference
209207 global $wgLoginLanguageSelector;
210 - if( $wgLoginLanguageSelector && $this->mLanguage )
211 - $this->mLogin->getUser()->setOption( 'language', $this->mLanguage );
212 - $this->mLogin->getUser()->saveSettings();
 208+ if( $wgLoginLanguageSelector && $this->mLanguage ){
 209+ $login->getUser()->setOption( 'language', $this->mLanguage );
 210+ $login->getUser()->saveSettings();
 211+ }
213212
214 - if( $byEmail ) {
 213+ if( $this->mCreateaccountMail ) {
215214 if( $status == Login::MAIL_ERROR ){
216215 # FIXME: we are totally screwed if we end up here...
217 - $this->showMainForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin->mMailResult->getMessage() ) );
 216+ return wfMsgExt( 'mailerror', 'parseinline', $login->mMailResult->getMessage() );
218217 } else {
219218 global $wgOut;
220219 $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
221 - $wgOut->addWikiMsg( 'accmailtext', $this->mLogin->getUser()->getName(), $this->mLogin->getUser()->getEmail() );
 220+ $wgOut->addWikiMsg( 'accmailtext', $login->getUser()->getName(), $login->getUser()->getEmail() );
222221 $wgOut->returnToMain( false );
 222+ return true;
223223 }
224224
225225 } else {
226226
227227 # There might be a message stored from the confirmation mail
228228 # send, which we can display.
229 - if( $wgEmailAuthentication && $this->mLogin->mMailResult ) {
 229+ if( $wgEmailAuthentication && $login->mMailResult ) {
230230 global $wgOut;
231 - if( WikiError::isError( $this->mLogin->mMailResult ) ) {
232 - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $this->mLogin->mMailResult->getMessage() );
 231+ if( WikiError::isError( $login->mMailResult ) ) {
 232+ $wgOut->addWikiMsg( 'confirmemail_sendfailed', $login->mMailResult->getMessage() );
233233 } else {
234234 $wgOut->addWikiMsg( 'confirmemail_oncreate' );
235235 }
@@ -238,19 +238,15 @@
239239 # one and set session cookies then show a "welcome" message
240240 # or a "need cookies" message as needed
241241 if( $wgUser->isAnon() ) {
242 - $wgUser = $this->mLogin->getUser();
243 - $wgUser->setCookies();
244 - if( $this->hasSessionCookie() ) {
245 - return $this->successfulCreation();
246 - } else {
247 - return $this->cookieRedirectCheck();
248 - }
 242+ $login->attemptLogin( true );
 243+ $this->successfulCreation();
 244+ return true;
249245 } else {
250246 # Show confirmation that the account was created
251247 global $wgOut;
252248 $self = SpecialPage::getTitleFor( 'Userlogin' );
253249 $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
254 - $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $this->mLogin->getUser()->getName() ) );
 250+ $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $login->getUser()->getName() ) );
255251 $wgOut->returnToMain( false, $self );
256252 return true;
257253 }
@@ -309,24 +305,10 @@
310306 * @param $msg String HTML of message received previously
311307 * @param $msgtype String type of message, usually 'error'
312308 */
313 - protected function showMainForm( $msg, $msgtype = 'error' ) {
 309+ protected function getForm() {
314310 global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
315311 global $wgCookiePrefix, $wgLoginLanguageSelector;
316312 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
317 -
318 - # Parse the error message if we got one
319 - if( $msg ){
320 - if( $msgtype == 'error' ){
321 - $msg = wfMsgExt( 'createaccounterror', array( 'parseinline', 'replaceafter' ), $msg );
322 - }
323 - $msg = Html::rawElement(
324 - 'div',
325 - array( 'class' => $msgtype . 'box' ),
326 - $msg
327 - );
328 - } else {
329 - $msg = '';
330 - }
331313
332314 # Make sure the returnTo strings don't get lost if the
333315 # user changes language, etc
@@ -338,8 +320,9 @@
339321 }
340322
341323 # Pass any language selection on to the mode switch link
342 - if( $wgLoginLanguageSelector && $this->mLanguage )
 324+ if( $wgLoginLanguageSelector && $this->mLanguage ){
343325 $linkq['uselang'] = $this->mLanguage;
 326+ }
344327
345328 $skin = $wgUser->getSkin();
346329 $link = $skin->link(
@@ -361,7 +344,7 @@
362345
363346 # Give authentication and captcha plugins a chance to
364347 # modify the form, by hook or by using $wgAuth
365 - $wgAuth->modifyUITemplate( $this, 'new' );
 348+ //$wgAuth->modifyUITemplate( $this, 'new' );
366349 wfRunHooks( 'UserCreateForm', array( &$this ) );
367350
368351 # The most likely use of the hook is to enable domains;
@@ -386,18 +369,18 @@
387370 }
388371
389372 # Or to play with realname
390 - if( in_array( 'realname', $wgHiddenPrefs ) || !$this->mUseRealname ){
 373+ if( in_array( 'realname', $wgHiddenPrefs ) ){
391374 unset( $this->mFormFields['Realname'] );
392375 }
393376
394377 # Or to tweak the 'remember my password' checkbox
395 - if( !($wgCookieExpiration > 0) || !$this->mUseRemember ){
 378+ if( !($wgCookieExpiration > 0) ){
396379 # Remove it altogether
397380 unset( $this->mFormFields['Remember'] );
398 - } elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
 381+ } elseif( $wgUser->getOption( 'rememberpassword' ) ){
399382 # Or check it by default
400383 # FIXME: this doesn't always work?
401 - $this->mFormFields['Remember']['checked'] = '1';
 384+ $this->mFormFields['Remember']['default'] = '1';
402385 }
403386
404387 $form = new HTMLForm( $this->mFormFields );
@@ -407,9 +390,13 @@
408391 $form->setSubmitId( 'wpCreateaccount' );
409392 $form->suppressReset();
410393 $form->setWrapperLegend( wfMsg( 'createaccount' ) );
 394+ $form->setTokenAction( 'createaccount' );
411395
412396 $form->addHiddenField( 'returnto', $this->mReturnTo );
413397 $form->addHiddenField( 'returntoquery', $this->mReturnToQuery );
 398+ if( $this->mLanguage ){
 399+ $form->addHiddenField( 'uselang', $this->mLanguage );
 400+ }
414401
415402 $form->addHeaderText( ''
416403 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
@@ -418,7 +405,6 @@
419406 . $langSelector
420407 );
421408 $form->addPreText( ''
422 - . $msg
423409 . Html::rawElement(
424410 'div',
425411 array( 'id' => 'signupstart' ),
@@ -442,61 +428,12 @@
443429 );
444430 }
445431
 432+ $form->setSubmitCallback( array( $this, 'formSubmitCallback' ) );
446433 $form->loadData();
447 -
448 - $wgOut->setPageTitle( wfMsg( 'createaccount' ) );
449 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
450 - $wgOut->setArticleRelated( false );
451 - $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
452 -
453434
454 - $form->displayForm( false );
 435+ return $form;
455436
456437 }
457 -
458 - /**
459 - * Check if a session cookie is present.
460 - *
461 - * This will not pick up a cookie set during _this_ request, but is meant
462 - * to ensure that the client is returning the cookie which was set on a
463 - * previous pass through the system.
464 - *
465 - * @private
466 - */
467 - protected function hasSessionCookie() {
468 - global $wgDisableCookieCheck, $wgRequest;
469 - return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
470 - }
471 -
472 - /**
473 - * Do a redirect back to the same page, so we can check any
474 - * new session cookies.
475 - */
476 - protected function cookieRedirectCheck() {
477 - global $wgOut;
478 -
479 - $query = array( 'wpCookieCheck' => '1' );
480 - if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
481 - $check = $this->getTitle()->getFullURL( $query );
482 -
483 - return $wgOut->redirect( $check );
484 - }
485 -
486 - /**
487 - * Check the cookies and show errors if they're not enabled.
488 - * @param $type String action being performed
489 - */
490 - protected function onCookieRedirectCheck() {
491 - if ( !$this->hasSessionCookie() ) {
492 - return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
493 - } else {
494 - return SpecialUserlogin::successfulLogin(
495 - array( 'welcomecreate' ),
496 - $this->mReturnTo,
497 - $this->mReturnToQuery
498 - );
499 - }
500 - }
501438
502439 /**
503440 * Add text to the header. Only write to $mFormHeader directly
Index: branches/happy-melon/phase3/includes/specials/SpecialResetpass.php
@@ -1,5 +1,23 @@
22 <?php
33 /**
 4+ *
 5+ * This program is free software; you can redistribute it and/or modify
 6+ * it under the terms of the GNU General Public License as published by
 7+ * the Free Software Foundation; either version 2 of the License, or
 8+ * (at your option) any later version.
 9+ *
 10+ * This program is distributed in the hope that it will be useful,
 11+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 13+ * GNU General Public License for more details.
 14+ *
 15+ * You should have received a copy of the GNU General Public License along
 16+ * with this program; if not, write to the Free Software Foundation, Inc.,
 17+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 18+ * http://www.gnu.org/copyleft/gpl.html
 19+ */
 20+
 21+/**
422 * @file
523 * @ingroup SpecialPage
624 */
@@ -9,17 +27,19 @@
1028 * @ingroup SpecialPage
1129 */
1230 class SpecialResetpass extends SpecialPage {
13 - public function __construct() {
14 - parent::__construct( 'Resetpass' );
15 - }
1631
1732 public $mFormFields = array(
18 - 'Name' => array(
 33+ 'NameInfo' => array(
1934 'type' => 'info',
2035 'label-message' => 'yourname',
2136 'default' => '',
2237 ),
23 - 'Password' => array(
 38+ 'Name' => array(
 39+ 'type' => 'hidden',
 40+ 'name' => 'wpName',
 41+ 'default' => null,
 42+ ),
 43+ 'OldPassword' => array(
2444 'type' => 'password',
2545 'label-message' => 'oldpassword',
2646 'size' => '20',
@@ -42,35 +62,44 @@
4363 ),
4464 'Remember' => array(
4565 'type' => 'check',
46 - 'label-message' => 'remembermypassword',
4766 'id' => 'wpRemember',
4867 ),
4968 );
50 - public $mSubmitMsg = 'resetpass-submit-loggedin';
51 - public $mHeaderMsg = '';
52 - public $mHeaderMsgType = 'error';
5369
54 - public $mUsername;
55 - public $mOldpass;
56 - public $mNewpass;
57 - public $mRetype;
 70+ protected $mUsername;
 71+ protected $mLogin;
5872
 73+ public function __construct() {
 74+ global $wgRequest, $wgUser, $wgLang, $wgCookieExpiration;
 75+
 76+ parent::__construct( 'Resetpass' );
 77+ $this->mFormFields['Retype']['validation-callback'] = array( 'SpecialCreateAccount', 'formValidateRetype' );
 78+
 79+ $this->mUsername = $wgRequest->getVal( 'wpName', $wgUser->getName() );
 80+ $this->mReturnTo = $wgRequest->getVal( 'returnto' );
 81+ $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
 82+
 83+ $this->mFormFields['Remember']['label'] = wfMsgExt(
 84+ 'remembermypassword',
 85+ 'parseinline',
 86+ $wgLang->formatNum( ceil( $wgCookieExpiration / 86400 ) )
 87+ );
 88+ }
 89+
5990 /**
6091 * Main execution point
6192 */
62 - function execute( $par ) {
 93+ public function execute( $par ) {
6394 global $wgUser, $wgAuth, $wgOut, $wgRequest;
6495
65 - $this->mUsername = $wgRequest->getVal( 'wpName', $wgUser->getName() );
66 - $this->mOldpass = $wgRequest->getVal( 'wpPassword' );
67 - $this->mNewpass = $wgRequest->getVal( 'wpNewPassword' );
68 - $this->mRetype = $wgRequest->getVal( 'wpRetype' );
69 - $this->mRemember = $wgRequest->getVal( 'wpRemember' );
70 - $this->mReturnTo = $wgRequest->getVal( 'returnto' );
71 - $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
 96+ if ( wfReadOnly() ) {
 97+ $wgOut->readOnlyPage();
 98+ return;
 99+ }
72100
73101 $this->setHeaders();
74102 $this->outputHeader();
 103+ $wgOut->disallowUserJs();
75104
76105 if( wfReadOnly() ){
77106 $wgOut->readOnlyPage();
@@ -85,21 +114,22 @@
86115 $wgOut->showErrorPage( 'errorpagetitle', 'resetpass-no-info' );
87116 return false;
88117 }
 118+
 119+ $this->getForm()->show();
 120+
 121+ }
 122+
 123+ public function formSubmitCallback( $data ){
 124+ $data['Password'] = $data['OldPassword'];
 125+ $this->mLogin = new Login( $data );
 126+ $result = $this->attemptReset( $data );
89127
90 - $data = array(
91 - 'wpName' => $this->mUsername,
92 - 'wpPassword' => $this->mOldpass,
93 - );
94 - $this->mLogin = new Login( new FauxRequest( $data, true ) );
95 -
96 - if( $wgRequest->wasPosted()
97 - && $wgUser->matchEditToken( $wgRequest->getVal('wpEditToken') )
98 - && $this->attemptReset() )
99 - {
 128+ if( $result === true ){
100129 # Log the user in if they're not already (ie we're
101130 # coming from the e-mail-password-reset route
 131+ global $wgUser;
102132 if( !$wgUser->isLoggedIn() ) {
103 - $this->mLogin->attemptLogin( $this->mNewpass );
 133+ $this->mLogin->attemptLogin( $data['NewPassword'] );
104134 # Redirect out to the appropriate target.
105135 SpecialUserlogin::successfulLogin(
106136 'resetpass_success',
@@ -115,81 +145,82 @@
116146 $this->mReturnToQuery
117147 );
118148 }
 149+ return true;
119150 } else {
120 - $this->showForm();
 151+ return $result;
121152 }
122153 }
123154
124 - function showForm() {
125 - global $wgOut, $wgUser;
126 -
127 - $wgOut->disallowUserJs();
 155+ public function getForm( $reset=false ) {
 156+ global $wgOut, $wgUser, $wgRequest;
128157
129 - if( $wgUser->isLoggedIn() ){
130 - unset( $this->mFormFields['Remember'] );
131 - } else {
 158+ if( $reset || $wgRequest->getCheck( 'reset' ) ){
132159 # Request is coming from Special:UserLogin after it
133160 # authenticated someone with a temporary password.
134 - $this->mFormFields['Password']['label-message'] = 'resetpass-temp-password';
135 - $this->mSubmitMsg = 'resetpass_submit';
 161+ $this->mFormFields['OldPassword']['label-message'] = 'resetpass-temp-password';
 162+ $submitMsg = 'resetpass_submit';
 163+ $this->mFormFields['OldPassword']['default'] = $wgRequest->getText( 'wpPassword' );
 164+ #perpetuate
 165+ $this->mFormFields['reset'] = array(
 166+ 'type' => 'hidden',
 167+ 'default' => '1',
 168+ );
 169+ } else {
 170+ unset( $this->mFormFields['Remember'] );
 171+ $submitMsg = 'resetpass-submit-loggedin';
136172 }
137 - $this->mFormFields['Name']['default'] = $this->mUsername;
138173
139 - $header = $this->mHeaderMsg
140 - ? Html::element( 'div', array( 'class' => "{$this->mHeaderMsgType}box" ), wfMsgExt( $this->mHeaderMsg, 'parse' ) )
141 - : '';
 174+ $this->mFormFields['Name']['default'] =
 175+ $this->mFormFields['NameInfo']['default'] = $this->mUsername;
142176
143177 $form = new HTMLForm( $this->mFormFields, '' );
144178 $form->suppressReset();
145 - $form->setSubmitText( wfMsg( $this->mSubmitMsg ) );
 179+ $form->setSubmitText( wfMsg( $submitMsg ) );
146180 $form->setTitle( $this->getTitle() );
147 - $form->addHiddenField( 'wpName', $this->mUsername );
148181 $form->addHiddenField( 'returnto', $this->mReturnTo );
149182 $form->setWrapperLegend( wfMsg( 'resetpass_header' ) );
 183+
 184+ $form->setSubmitCallback( array( $this, 'formSubmitCallback' ) );
150185 $form->loadData();
151186
152 - $form->displayForm( $this->mHeaderMsg );
 187+ return $form;
153188 }
154189
155190 /**
156191 * Try to reset the user's password
157192 */
158 - protected function attemptReset() {
 193+ protected function attemptReset( $data ) {
159194
160 - if( !$this->mUsername
161 - || !$this->mNewpass
162 - || !$this->mRetype )
 195+ if( !$data['Name']
 196+ || !$data['OldPassword']
 197+ || !$data['NewPassword']
 198+ || !$data['Retype'] )
163199 {
164200 return false;
165201 }
166202
167203 $user = $this->mLogin->getUser();
168204 if( !( $user instanceof User ) ){
169 - $this->mHeaderMsg = wfMsgExt( 'nosuchuser', 'parse' );
170 - return false;
 205+ return wfMsgExt( 'nosuchuser', 'parse' );
171206 }
172207
173 - if( $this->mNewpass !== $this->mRetype ) {
174 - wfRunHooks( 'PrefsPasswordAudit', array( $user, $this->mNewpass, 'badretype' ) );
175 - $this->mHeaderMsg = wfMsgExt( 'badretype', 'parse' );
176 - return false;
 208+ if( $data['NewPassword'] !== $data['Retype'] ) {
 209+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $data['NewPassword'], 'badretype' ) );
 210+ return wfMsgExt( 'badretype', 'parse' );
177211 }
178212
179 - if( !$user->checkPassword( $this->mOldpass ) && !$user->checkTemporaryPassword( $this->mOldpass ) )
 213+ if( !$user->checkPassword( $data['OldPassword'] ) && !$user->checkTemporaryPassword( $data['OldPassword'] ) )
180214 {
181 - wfRunHooks( 'PrefsPasswordAudit', array( $user, $this->mNewpass, 'wrongpassword' ) );
182 - $this->mHeaderMsg = wfMsgExt( 'resetpass-wrong-oldpass', 'parse' );
183 - return false;
 215+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $data['NewPassword'], 'wrongpassword' ) );
 216+ return wfMsgExt( 'resetpass-wrong-oldpass', 'parse' );
184217 }
185218
186219 try {
187 - $user->setPassword( $this->mNewpass );
188 - wfRunHooks( 'PrefsPasswordAudit', array( $user, $this->mNewpass, 'success' ) );
189 - $this->mNewpass = $this->mOldpass = $this->mRetypePass = '';
 220+ $user->setPassword( $data['NewPassword'] );
 221+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $data['NewPassword'], 'success' ) );
190222 } catch( PasswordError $e ) {
191 - wfRunHooks( 'PrefsPasswordAudit', array( $user, $this->mNewpass, 'error' ) );
192 - $this->mHeaderMsg = $e->getMessage();
193 - return false;
 223+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $data['NewPassword'], 'error' ) );
 224+ return $e->getMessage();
194225 }
195226
196227 $user->setCookies();

Comments

#Comment by Bryan (talk | contribs)   08:19, 5 November 2010

Why static methods for Token? Wouldn't it be match nicer to do something like this:

$token = Token::generate();
$token->save( $wgReqest );

$reqToken = Token::newFromSession( $wgRequest, 'edit' );
$reqToken->matchString( $wgRequest->getText( 'wpEditToken' ) );

It is of course a matter of preference, but by using static methods you are essentially not doing object oriented programming, but only using the class as a namespace.

Also, passing $wgRequest explicitly would allow it to also work with FauxRequest.

Status & tagging log