CSteipp has uploaded a new change for review.
https://gerrit.wikimedia.org/r/92037
Change subject: Password Expiration
......................................................................
Password Expiration
Add functionality to expire users' passwords:
* Adds column to the user table to keep a password expiration
* Adds a default grace period of 7 days, where if the user's password
is expired, they can still login, but are encouraged to reset their
password.
* Adds hook 'LoginPasswordResetMessage' to update reset message, in
case an extension wants to vary the message on a particular reset
event.
* Adds hook 'ResetPasswordExpiration' to allow extensions to add a
default expiration date when the user resets their password.
Also doens't do a successful reset if the user is "changing" their
password to their existing password.
Bug: 54997
Change-Id: I92a9fc63b409b182b1d7b48781d73fc7216f8061
---
M docs/hooks.txt
M includes/DefaultSettings.php
M includes/User.php
M includes/specials/SpecialChangePassword.php
M includes/specials/SpecialUserlogin.php
M languages/messages/MessagesEn.php
M languages/messages/MessagesQqq.php
7 files changed, 126 insertions(+), 16 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/37/92037/1
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 2d1001b..3e902a4 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -1553,6 +1553,12 @@
$retval: a LoginForm class constant with authenticateUserData() return
value (SUCCESS, WRONG_PASS, etc.).
+'LoginPasswordResetMessage': User is being requested to reset their password
on login.
+Use this hook to change the Message that will be output on
Special:ChangePassword.
+&$msg: Message object that will be shown to the user
+$expDate: User's password expiration date, in case you need to vary the
message on a
+particular expiration event. Will be false if the password isn't expired.
+
'LogLine': Processes a single log entry on Special:Log.
$log_type: string for the type of log entry (e.g. 'move'). Corresponds to
logging.log_type database field.
@@ -1957,6 +1963,10 @@
&$skin: A variable reference you may set a Skin instance or string key on to
override the skin that will be used for the context.
+'ResetPasswordExpiration': Allow extensions to set a default password
expiration
+$user: The user having their password expiration reset
+&$newExpire: The new expiration date
+
'ResetSessionID': Called from wfResetSessionID
$oldSessionID: old session id
$newSessionID: new session id
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 98c583b..52f2b6e 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -1359,6 +1359,12 @@
$wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60;
/**
+ * If a user's password is expired, the number of seconds when they can still
login,
+ * and cancel their password change, but are sent to the password change form
on each login.
+ */
+$wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days
+
+/**
* SMTP Mode.
*
* For using a direct (authenticated) SMTP server connection.
diff --git a/includes/User.php b/includes/User.php
index 12912e1..fa7209d 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -90,6 +90,7 @@
'mEmailAuthenticated',
'mEmailToken',
'mEmailTokenExpires',
+ 'mPasswordExpires',
'mRegistration',
'mEditCount',
// user_groups table
@@ -184,7 +185,7 @@
var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
$mEmail, $mTouched, $mToken, $mEmailAuthenticated,
$mEmailToken, $mEmailTokenExpires, $mRegistration, $mEditCount,
- $mGroups, $mOptionOverrides;
+ $mGroups, $mOptionOverrides, $mPasswordExpires;
//@}
/**
@@ -736,6 +737,60 @@
}
/**
+ * Expire a user's password
+ * @param $ts Mixed: optional timestamp to convert, default 0 for the
current time
+ */
+ public function expirePassword( $ts = 0 ) {
+ $this->load();
+ $timestamp = wfTimestamp( TS_MW, $ts );
+ $this->mPasswordExpires = $timestamp;
+ $this->saveSettings();
+ }
+
+ /**
+ * Clear the password expiration for a user
+ */
+ public function resetPasswordExpiration() {
+ $this->load();
+ $newExpire = null;
+ // Give extensions a chance to force an expiration
+ wfRunHooks( 'ResetPasswordExpiration', array( $this,
&$newExpire ) );
+ $this->mPasswordExpires = $newExpire;
+ }
+
+ /**
+ * Check if the user's password is expired.
+ * TODO: Put this and password length into a PasswordPolicy object
+ * @return string|bool The expirateion type, or false if not expired
+ * hard: A password change is required to login
+ * soft: Allow login, but encourage password change
+ * false: Password is not expired
+ */
+ public function getPasswordExpired() {
+ global $wgPasswordExpireGrace;
+ $expired = false;
+
+ $expiration = $this->getPasswordExpireDate();
+ $expUnix = wfTimestamp( TS_UNIX, $expiration );
+ $graceExpired = ( wfTimestamp() > $expUnix +
$wgPasswordExpireGrace );
+ if ( $expiration !== null && $expiration < wfTimestamp( TS_MW )
) {
+ $expired = $graceExpired ? 'hard' : 'soft';
+ }
+ return $expired;
+ }
+
+ /**
+ * Get this user's password expiration date. Since this may be using
+ * the cached User object, we assume that whatever mechanism is setting
+ * the expiration date is also expiring the User cache.
+ * @return string|false the datestamp of the expiration, or null if not
set
+ */
+ public function getPasswordExpireDate() {
+ $this->load();
+ return $this->mPasswordExpires;
+ }
+
+ /**
* Does a string look like an e-mail address?
*
* This validates an email address using an HTML5 specification found
at:
@@ -890,6 +945,7 @@
$this->mEmailAuthenticated = null;
$this->mEmailToken = '';
$this->mEmailTokenExpires = null;
+ $this->mPasswordExpires = null;
$this->mRegistration = wfTimestamp( TS_MW );
$this->mGroups = array();
@@ -1097,6 +1153,7 @@
$this->mEmailAuthenticated = wfTimestampOrNull( TS_MW,
$row->user_email_authenticated );
$this->mEmailToken = $row->user_email_token;
$this->mEmailTokenExpires = wfTimestampOrNull( TS_MW,
$row->user_email_token_expires );
+ $this->mPasswordExpires = wfTimestampOrNull( TS_MW,
$row->user_password_expires );
$this->mRegistration = wfTimestampOrNull( TS_MW,
$row->user_registration );
} else {
$all = false;
@@ -3295,6 +3352,7 @@
'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
'user_email_token_expires' =>
$dbw->timestampOrNull( $this->mEmailTokenExpires ),
+ 'user_password_expires' =>
$dbw->timestampOrNull( $this->mPasswordExpires ),
), array( /* WHERE */
'user_id' => $this->mId
), __METHOD__
@@ -4769,6 +4827,7 @@
'user_email_authenticated',
'user_email_token',
'user_email_token_expires',
+ 'user_password_expires',
'user_registration',
'user_editcount',
);
diff --git a/includes/specials/SpecialChangePassword.php
b/includes/specials/SpecialChangePassword.php
index 129e919..0a96c60 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -28,7 +28,7 @@
*/
class SpecialChangePassword extends UnlistedSpecialPage {
- protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
+ protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain,
$mMessage;
public function __construct() {
parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
@@ -119,6 +119,10 @@
$this->getOutput()->addHTML( Xml::element( 'p', array( 'class'
=> 'error' ), $msg ) );
}
+ public function setChangeMessage( $msg ) {
+ $this->mMessage = $msg;
+ }
+
function showForm() {
global $wgCookieExpiration;
@@ -126,6 +130,11 @@
if ( !$this->mUserName ) {
$this->mUserName = $user->getName();
}
+
+ if ( $this->mMessage && $this->mMessage instanceof Message ) {
+ $this->getOutput()->addHTML( $this->mMessage->parse() );
+ }
+
$rememberMe = '';
if ( !$user->isLoggedIn() ) {
$rememberMe = '<tr>' .
@@ -257,15 +266,22 @@
);
}
+ if ( !$user->checkTemporaryPassword( $this->mOldpass ) &&
!$user->checkPassword( $this->mOldpass ) ) {
+ wfRunHooks( 'PrefsPasswordAudit', array( $user,
$newpass, 'wrongpassword' ) );
+ throw new PasswordError( $this->msg(
'resetpass-wrong-oldpass' )->text() );
+ }
+
+ // User is resetting their password to their old password
+ if ( $this->mOldpass === $newpass ) {
+ throw new PasswordError( $this->msg(
'resetpass-recycled' )->text() );
+ }
+
+ // Do AbortChangePassword after checking mOldpass, so we don't
leak information
+ // by possibly aborting a new password before verifying the old
password.
$abortMsg = 'resetpass-abort-generic';
if ( !wfRunHooks( 'AbortChangePassword', array( $user,
$this->mOldpass, $newpass, &$abortMsg ) ) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user,
$newpass, 'abortreset' ) );
throw new PasswordError( $this->msg( $abortMsg
)->text() );
- }
-
- if ( !$user->checkTemporaryPassword( $this->mOldpass ) &&
!$user->checkPassword( $this->mOldpass ) ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user,
$newpass, 'wrongpassword' ) );
- throw new PasswordError( $this->msg(
'resetpass-wrong-oldpass' )->text() );
}
// Please reset throttle for successful logins, thanks!
@@ -287,7 +303,7 @@
// changing the password also modifies the user's token.
$user->setCookies();
}
-
+ $user->resetPasswordExpiration();
$user->saveSettings();
}
diff --git a/includes/specials/SpecialUserlogin.php
b/includes/specials/SpecialUserlogin.php
index 2b60ca2..b450841 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -519,7 +519,7 @@
* @return int
*/
public function authenticateUserData() {
- global $wgUser, $wgAuth;
+ global $wgUser, $wgAuth, $wgPasswordExpireGrace;
$this->load();
@@ -615,6 +615,7 @@
// At this point we just return an appropriate
code/ indicating
// that the UI should show a password reset
form; bot inter-
// faces etc will probably just fail cleanly
here.
+ $this->mAbortLoginErrorMsg =
'resetpass-temp-emailed';
$retval = self::RESET_PASS;
} else {
$retval = ( $this->mPassword == '' ) ?
self::EMPTY_PASS : self::WRONG_PASS;
@@ -622,6 +623,10 @@
} elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
// If we've enabled it, make it so that a blocked user
cannot login
$retval = self::USER_BLOCKED;
+ } elseif ( $u->getPasswordExpired() == 'hard' ) {
+ // Force reset now, without logging in
+ $retval = self::RESET_PASS;
+ $this->mAbortLoginErrorMsg = 'resetpass-expired';
} else {
$wgAuth->updateUser( $u );
$wgUser = $u;
@@ -775,7 +780,11 @@
$this->getContext()->setLanguage(
$userLang );
// Reset SessionID on Successful login
(bug 40995)
$this->renewSessionId();
- $this->successfulLogin();
+ if (
$this->getUser()->getPasswordExpired() == 'soft' ) {
+ $this->resetLoginForm(
$this->msg( 'resetpass-expired-soft' ) );
+ } else {
+ $this->successfulLogin();
+ }
} else {
$this->cookieRedirectCheck( 'login' );
}
@@ -819,7 +828,7 @@
break;
case self::RESET_PASS:
$error = $this->mAbortLoginErrorMsg ?:
'resetpass_announce';
- $this->resetLoginForm( $this->msg( $error
)->text() );
+ $this->resetLoginForm( $this->msg( $error ) );
break;
case self::CREATE_BLOCKED:
$this->userBlockedMessage(
$this->getUser()->isBlockedFromCreateAccount() );
@@ -845,11 +854,13 @@
}
/**
- * @param $error string
+ * @param $msg Message
*/
- function resetLoginForm( $error ) {
- $this->getOutput()->addHTML( Xml::element( 'p', array( 'class'
=> 'error' ), $error ) );
+ function resetLoginForm( $msg ) {
+ $expDate = $this->getUser()->getPasswordExpireDate();
+ wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $expDate
) );
$reset = new SpecialChangePassword();
+ $reset->setChangeMessage( $msg );
$reset->setContext( $this->getContext() );
$reset->execute( null );
}
diff --git a/languages/messages/MessagesEn.php
b/languages/messages/MessagesEn.php
index faaccd2..0230676 100644
--- a/languages/messages/MessagesEn.php
+++ b/languages/messages/MessagesEn.php
@@ -1266,8 +1266,7 @@
# Change password dialog
'resetpass' => 'Change password',
-'resetpass_announce' => 'You logged in with a temporary emailed code.
-To finish logging in, you must set a new password here:',
+'resetpass_announce' => 'To finish logging in, you must set a new
password here:',
'resetpass_text' => '<!-- Add text here -->', # only translate this
message to other languages if you have to change it
'resetpass_header' => 'Change account password',
'oldpassword' => 'Old password:',
@@ -1281,8 +1280,13 @@
'resetpass-submit-cancel' => 'Cancel',
'resetpass-wrong-oldpass' => 'Invalid temporary or current password.
You may have already successfully changed your password or requested a new
temporary password.',
+'resetpass-recycled' => 'Please reset your password to something other
than your current password.',
+'resetpass-temp-emailed' => 'You logged in with a temporary emailed code.
+To finish logging in, you must set a new password here:',
'resetpass-temp-password' => 'Temporary password:',
'resetpass-abort-generic' => 'Password change has been aborted by an
extension.',
+'resetpass-expired' => '<span class="error">Your password has expired. Please
set a new password to login.</span>',
+'resetpass-expired-soft' => '<span class="warning">Your password has expired,
and needs to be reset. Please choose a new password now, or click cancel to
reset it later. </span>',
# Special:PasswordReset
'passwordreset' => 'Reset password',
diff --git a/languages/messages/MessagesQqq.php
b/languages/messages/MessagesQqq.php
index a38f17c..8042fee 100644
--- a/languages/messages/MessagesQqq.php
+++ b/languages/messages/MessagesQqq.php
@@ -1604,8 +1604,12 @@
'resetpass-submit-cancel' => 'Used on [[Special:ResetPass]].
{{Identical|Cancel}}',
'resetpass-wrong-oldpass' => 'Error message shown on
[[Special:ChangePassword]] when the old password is not valid.',
+'resetpass-recycled' => 'Error message shown on [[Special:ChangePassword]]
when a user attempts to reset their password to their existing password.',
+'resetpass-temp-emailed' => 'Message shown on [[Special:ChangePassword]] when
a user logs in with a temporary password, and must set a new password.',
'resetpass-temp-password' => 'The label of the input box for the temporary
password (received by email) on the form displayed after logging in with a
temporary password.',
'resetpass-abort-generic' => 'Generic error message shown on
[[Special:ChangePassword]] when an extension aborts a password change from a
hook.',
+'resetpass-expired' => 'Generic error message shown on
[[Special:ChangePassword]] when a user\'s password is expired',
+'resetpass-expired-soft' => 'Generic marning message shown on
[[Special:ChangePassword]] when a user needs to reset their password, but they
are not prevented from logging in at this time',
# Special:PasswordReset
'passwordreset' => 'Title of [[Special:PasswordReset]].
--
To view, visit https://gerrit.wikimedia.org/r/92037
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I92a9fc63b409b182b1d7b48781d73fc7216f8061
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: CSteipp <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits