Password validity by policy per group

Make password policies defined in a configurable policy, which is
defined by group. A user's password policy will be the maximum of
each group policy that the user belongs to.

Bug: T94774
Change-Id: Iad8e49ffcffed38df6293db0ef31a227d3962003
This commit is contained in:
csteipp 2015-04-22 18:48:48 -07:00
parent 77c7330172
commit 1a20dc9362
10 changed files with 744 additions and 31 deletions

View file

@ -874,6 +874,7 @@ $wgAutoloadLocalClasses = array(
'Password' => __DIR__ . '/includes/password/Password.php',
'PasswordError' => __DIR__ . '/includes/password/PasswordError.php',
'PasswordFactory' => __DIR__ . '/includes/password/PasswordFactory.php',
'PasswordPolicyChecks' => __DIR__ . '/includes/password/PasswordPolicyChecks.php',
'PatchSql' => __DIR__ . '/maintenance/patchSql.php',
'PathRouter' => __DIR__ . '/includes/PathRouter.php',
'PathRouterPatternReplacer' => __DIR__ . '/includes/PathRouter.php',
@ -1295,6 +1296,7 @@ $wgAutoloadLocalClasses = array(
'UserMailer' => __DIR__ . '/includes/mail/UserMailer.php',
'UserNotLoggedIn' => __DIR__ . '/includes/exception/UserNotLoggedIn.php',
'UserOptions' => __DIR__ . '/maintenance/userOptions.inc',
'UserPasswordPolicy' => __DIR__ . '/includes/password/UserPasswordPolicy.php',
'UserRightsProxy' => __DIR__ . '/includes/UserRightsProxy.php',
'UsercreateTemplate' => __DIR__ . '/includes/templates/Usercreate.php',
'UserloginTemplate' => __DIR__ . '/includes/templates/Userlogin.php',

View file

@ -2295,6 +2295,10 @@ run. Use when page save hooks require the presence of custom tables to ensure
that tests continue to run properly.
&$tables: array of table names
'PasswordPoliciesForUser': Alter the effective password policy for a user.
$user: User object whose policy you are modifying
&$effectivePolicy: Array of policy statements that apply to this user
'PerformRetroactiveAutoblock': Called before a retroactive autoblock is applied
to a user.
$block: Block object (which is set to be autoblocking)

View file

@ -4250,6 +4250,59 @@ $wgActiveUserDays = 30;
* @{
*/
/**
* Password policy for local wiki users. A user's effective policy
* is the superset of all policy statements from the policies for the
* groups where the user is a member. If more than one group policy
* include the same policy statement, the value is the max() of the
* values. Note true > false. The 'default' policy group is required,
* and serves as the minimum policy for all users. New statements can
* be added by appending to $wgPasswordPolicy['checks'].
* Statements:
* - MinimalPasswordLength - minimum length a user can set
* - MinimumPasswordLengthToLogin - passwords shorter than this will
* not be allowed to login, regardless if it is correct.
* - MaximalPasswordLength - maximum length password a user is allowed
* to attempt. Prevents DoS attacks with pbkdf2.
* - PasswordCannotMatchUsername - Password cannot match username to
* - PasswordCannotMatchBlacklist - Username/password combination cannot
* match a specific, hardcoded blacklist.
* @since 1.26
*/
$wgPasswordPolicy = array(
'policies' => array(
'bureaucrat' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
),
'sysop' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
),
'bot' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
),
'default' => array(
'MinimalPasswordLength' => 1,
'PasswordCannotMatchUsername' => true,
'PasswordCannotMatchBlacklist' => true,
'MaximalPasswordLength' => 4096,
),
),
'checks' => array(
'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
),
);
/**
* For compatibility with old installations set to false
* @deprecated since 1.24 will be removed in future
@ -4259,8 +4312,9 @@ $wgPasswordSalt = true;
/**
* Specifies the minimal length of a user password. If set to 0, empty pass-
* words are allowed.
* @deprecated since 1.26, use $wgPasswordPolicy's MinimalPasswordLength.
*/
$wgMinimalPasswordLength = 1;
$wgMinimalPasswordLength = false;
/**
* Specifies the maximal length of a user password (T64685).
@ -4271,8 +4325,9 @@ $wgMinimalPasswordLength = 1;
*
* @warning Unlike other password settings, user with passwords greater than
* the maximum will not be able to log in.
* @deprecated since 1.26, use $wgPasswordPolicy's MaximalPasswordLength.
*/
$wgMaximalPasswordLength = 4096;
$wgMaximalPasswordLength = false;
/**
* Specifies if users should be sent to a password-reset form on login, if their

View file

@ -470,6 +470,15 @@ if ( $wgProfileOnly ) {
$wgDebugLogFile = '';
}
// Backwards compatibility with old password limits
if ( $wgMinimalPasswordLength !== false ) {
$wgPasswordPolicy['policies']['default']['MinimalPasswordLength'] = $wgMinimalPasswordLength;
}
if ( $wgMaximalPasswordLength !== false ) {
$wgPasswordPolicy['policies']['default']['MaximalPasswordLength'] = $wgMaximalPasswordLength;
}
Profiler::instance()->scopedProfileOut( $ps_default );
// Disable MWDebug for command line mode, this prevents MWDebug from eating up

View file

@ -844,15 +844,14 @@ class User implements IDBAccessObject {
* @since 1.23
*/
public function checkPasswordValidity( $password ) {
global $wgMinimalPasswordLength, $wgMaximalPasswordLength, $wgContLang;
global $wgPasswordPolicy;
static $blockedLogins = array(
'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
$upp = new UserPasswordPolicy(
$wgPasswordPolicy['policies'],
$wgPasswordPolicy['checks']
);
$status = Status::newGood();
$result = false; //init $result to false for the internal checks
if ( !Hooks::run( 'isValidPassword', array( $password, &$result, $this ) ) ) {
@ -861,28 +860,8 @@ class User implements IDBAccessObject {
}
if ( $result === false ) {
if ( strlen( $password ) < $wgMinimalPasswordLength ) {
$status->error( 'passwordtooshort', $wgMinimalPasswordLength );
return $status;
} elseif ( strlen( $password ) > $wgMaximalPasswordLength ) {
// T64685: Password too long, might cause DoS attack
$status->fatal( 'passwordtoolong', $wgMaximalPasswordLength );
return $status;
} elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
$status->error( 'password-name-match' );
return $status;
} elseif ( isset( $blockedLogins[$this->getName()] )
&& $password == $blockedLogins[$this->getName()]
) {
$status->error( 'password-login-forbidden' );
return $status;
} else {
//it seems weird returning a Good status here, but this is because of the
//initialization of $result to false above. If the hook is never run or it
//doesn't modify $result, then we will likely get down into this if with
//a valid password.
return $status;
}
$status->merge( $upp->checkUserPassword( $this, $password ) );
return $status;
} elseif ( $result === true ) {
return $status;
} else {

View file

@ -0,0 +1,115 @@
<?php
/**
* Password policy checks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* Functions to check passwords against a policy requirement
* @since 1.26
*/
class PasswordPolicyChecks {
/**
* Check password is longer than minimum, not fatal
* @param int $policyVal minimal length
* @param User $user
* @param string $password
* @return Status error if $password is shorter than $policyVal
*/
public static function checkMinimalPasswordLength( $policyVal, User $user, $password ) {
$status = Status::newGood();
if ( $policyVal > strlen( $password ) ) {
$status->error( 'passwordtooshort', $policyVal );
}
return $status;
}
/**
* Check password is longer than minimum, fatal
* @param int $policyVal minimal length
* @param User $user
* @param string $password
* @return Status fatal if $password is shorter than $policyVal
*/
public static function checkMinimumPasswordLengthToLogin( $policyVal, User $user, $password ) {
$status = Status::newGood();
if ( $policyVal > strlen( $password ) ) {
$status->fatal( 'passwordtooshort', $policyVal );
}
return $status;
}
/**
* Check password is shorter than maximum, fatal
* @param int $policyVal maximum length
* @param User $user
* @param string $password
* @return Status fatal if $password is shorter than $policyVal
*/
public static function checkMaximalPasswordLength( $policyVal, User $user, $password ) {
$status = Status::newGood();
if ( $policyVal < strlen( $password ) ) {
$status->fatal( 'passwordtoolong', $policyVal );
}
return $status;
}
/**
* Check if username and password match
* @param bool $policyVal true to force compliance.
* @param User $user
* @param string $password
* @return Status error if username and password match, and policy is true
*/
public static function checkPasswordCannotMatchUsername( $policyVal, User $user, $password ) {
global $wgContLang;
$status = Status::newGood();
$username = $user->getName();
if ( $policyVal && $wgContLang->lc( $password ) === $wgContLang->lc( $username ) ) {
$status->error( 'password-name-match' );
}
return $status;
}
/**
* Check if username and password are on a blacklist
* @param bool $policyVal true to force compliance.
* @param User $user
* @param string $password
* @return Status error if username and password match, and policy is true
*/
public static function checkPasswordCannotMatchBlacklist( $policyVal, User $user, $password ) {
static $blockedLogins = array(
'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
);
$status = Status::newGood();
$username = $user->getName();
if ( $policyVal
&& isset( $blockedLogins[$username] )
&& $password == $blockedLogins[$username]
) {
$status->error( 'password-login-forbidden' );
}
return $status;
}
}

View file

@ -0,0 +1,158 @@
<?php
/**
* Password policy checking for a user
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* Check if a user's password complies with any password policies that apply to that
* user, based on the user's group membership.
* @since 1.26
*/
class UserPasswordPolicy {
/**
* @var array
*/
private $policies;
/**
* Mapping of statements to the function that will test the password for compliance. The
* checking functions take the policy value, the user, and password, and return a Status
* object indicating compliance.
* @var array
*/
private $policyCheckFunctions;
/**
* @param array $policies
* @param array $checks mapping statement to its checking function. Checking functions are
* called with the policy value for this user, the user object, and the password to check.
*/
public function __construct( array $policies, array $checks ) {
if ( !isset( $policies['default'] ) ) {
throw new InvalidArgumentException(
'Must include a \'default\' password policy'
);
}
$this->policies = $policies;
foreach ( $checks as $statement => $check ) {
if ( !is_callable( $check ) ) {
throw new InvalidArgumentException(
'Policy check functions must be callable'
);
}
$this->policyCheckFunctions[$statement] = $check;
}
}
/**
* Check if a passwords meets the effective password policy for a User.
* @param User $user who's policy we are checking
* @param string $password the password to check
* @return Status error to indicate the password didn't meet the policy, or fatal to
* indicate the user shouldn't be allowed to login.
*/
public function checkUserPassword( User $user, $password ) {
$effectivePolicy = $this->getPoliciesForUser( $user );
$status = Status::newGood();
foreach ( $effectivePolicy as $policy => $value ) {
if ( !isset( $this->policyCheckFunctions[$policy] ) ) {
throw new DomainException( 'Invalid password policy config' );
}
$status->merge(
call_user_func(
$this->policyCheckFunctions[$policy],
$value,
$user,
$password
)
);
}
return $status;
}
/**
* Get the policy for a user, based on their group membership. Public so
* UI elements can access and inform the user.
* @param User $user
* @return array the effective policy for $user
*/
public function getPoliciesForUser( User $user ) {
$effectivePolicy = self::getPoliciesForGroups(
$this->policies,
$user->getEffectiveGroups(),
$this->policies['default']
);
Hooks::run( 'PasswordPoliciesForUser', array( $user, &$effectivePolicy ) );
return $effectivePolicy;
}
/**
* Utility function to get the effective policy from a list of policies, based
* on a list of groups.
* @param array $policies list of policies to consider
* @param array $userGroups the groups from which we calculate the effective policy
* @param array $defaultPolicy the default policy to start from
* @return array effective policy
*/
public static function getPoliciesForGroups( array $policies, array $userGroups,
array $defaultPolicy
) {
$effectivePolicy = $defaultPolicy;
foreach ( $policies as $group => $policy ) {
if ( in_array( $group, $userGroups ) ) {
$effectivePolicy = self::maxOfPolicies(
$effectivePolicy,
$policies[$group]
);
}
}
return $effectivePolicy;
}
/**
* Utility function to get a policy that is the most restrictive of $p1 and $p2. For
* simplicity, we setup the policy values so the maximum value is always more restrictive.
* @param array $p1
* @param array $p2
* @return array containing the more restrictive values of $p1 and $p2
*/
public static function maxOfPolicies( array $p1, array $p2 ) {
$ret = array();
$keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
foreach ( $keys as $key ) {
if ( !isset( $p1[$key] ) ) {
$ret[$key] = $p2[$key];
} elseif ( !isset( $p2[$key] ) ) {
$ret[$key] = $p1[$key];
} else {
$ret[$key] = max( $p1[$key], $p2[$key] );
}
}
return $ret;
}
}

View file

@ -307,9 +307,30 @@ class UserTest extends MediaWikiTestCase {
*/
public function testCheckPasswordValidity() {
$this->setMwGlobals( array(
'wgMinimalPasswordLength' => 6,
'wgMaximalPasswordLength' => 30,
'wgPasswordPolicy' => array(
'policies' => array(
'sysop' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => 1,
),
'default' => array(
'MinimalPasswordLength' => 6,
'PasswordCannotMatchUsername' => true,
'PasswordCannotMatchBlacklist' => true,
'MaximalPasswordLength' => 30,
),
),
'checks' => array(
'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
),
),
) );
$user = User::newFromName( 'Useruser' );
// Sanity
$this->assertTrue( $user->isValidPassword( 'Password1234' ) );

View file

@ -0,0 +1,136 @@
<?php
/**
* Testing password-policy check functions
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
class PasswordPolicyChecksTest extends MediaWikiTestCase {
/**
* @covers PasswordPolicyChecks::checkMinimalPasswordLength
*/
public function testCheckMinimalPasswordLength() {
$statusOK = PasswordPolicyChecks::checkMinimalPasswordLength(
3, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' );
$statusShort = PasswordPolicyChecks::checkMinimalPasswordLength(
10, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertFalse(
$statusShort->isGood(),
'Password is shorter than minimal policy'
);
$this->assertTrue(
$statusShort->isOk(),
'Password is shorter than minimal policy, not fatal'
);
}
/**
* @covers PasswordPolicyChecks::checkMinimumPasswordLengthToLogin
*/
public function testCheckMinimumPasswordLengthToLogin() {
$statusOK = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin(
3, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' );
$statusShort = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin(
10, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertFalse(
$statusShort->isGood(),
'Password is shorter than minimum login policy'
);
$this->assertFalse(
$statusShort->isOk(),
'Password is shorter than minimum login policy, fatal'
);
}
/**
* @covers PasswordPolicyChecks::checkMaximalPasswordLength
*/
public function testCheckMaximalPasswordLength() {
$statusOK = PasswordPolicyChecks::checkMaximalPasswordLength(
100, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertTrue( $statusOK->isGood(), 'Password is shorter than maximal policy' );
$statusLong = PasswordPolicyChecks::checkMaximalPasswordLength(
4, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertFalse( $statusLong->isGood(),
'Password is longer than maximal policy'
);
$this->assertFalse( $statusLong->isOk(),
'Password is longer than maximal policy, fatal'
);
}
/**
* @covers PasswordPolicyChecks::checkPasswordCannotMatchUsername
*/
public function testCheckPasswordCannotMatchUsername() {
$statusOK = PasswordPolicyChecks::checkPasswordCannotMatchUsername(
1, // policy value
User::newFromName( 'user' ), // User
'password' // password
);
$this->assertTrue( $statusOK->isGood(), 'Password does not match username' );
$statusLong = PasswordPolicyChecks::checkPasswordCannotMatchUsername(
1, // policy value
User::newFromName( 'user' ), // User
'user' // password
);
$this->assertFalse( $statusLong->isGood(), 'Password matches username' );
$this->assertTrue( $statusLong->isOk(), 'Password matches username, not fatal' );
}
/**
* @covers PasswordPolicyChecks::checkPasswordCannotMatchBlacklist
*/
public function testCheckPasswordCannotMatchBlacklist() {
$statusOK = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist(
true, // policy value
User::newFromName( 'Username' ), // User
'AUniquePassword' // password
);
$this->assertTrue( $statusOK->isGood(), 'Password is not on blacklist' );
$statusLong = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist(
true, // policy value
User::newFromName( 'Useruser1' ), // User
'Passpass1' // password
);
$this->assertFalse( $statusLong->isGood(), 'Password matches blacklist' );
$this->assertTrue( $statusLong->isOk(), 'Password matches blacklist, not fatal' );
}
}

View file

@ -0,0 +1,234 @@
<?php
/**
* Testing for password-policy enforcement, based on a user's groups.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
class UserPasswordPolicyTest extends MediaWikiTestCase {
protected $policies = array(
'checkuser' => array(
'MinimalPasswordLength' => 10,
'MinimumPasswordLengthToLogin' => 6,
'PasswordCannotMatchUsername' => true,
),
'sysop' => array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => true,
),
'default' => array(
'MinimalPasswordLength' => 4,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchBlacklist' => true,
'MaximalPasswordLength' => 4096,
),
);
protected $checks = array(
'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
);
private function getUserPasswordPolicy() {
return new UserPasswordPolicy( $this->policies, $this->checks );
}
/**
* @covers UserPasswordPolicy::getPoliciesForUser
*/
public function testGetPoliciesForUser() {
$upp = $this->getUserPasswordPolicy();
$user = User::newFromName( 'TestUserPolicy' );
$user->addGroup( 'sysop' );
$this->assertArrayEquals(
array(
'MinimalPasswordLength' => 8,
'MinimumPasswordLengthToLogin' => 1,
'PasswordCannotMatchUsername' => 1,
'PasswordCannotMatchBlacklist' => true,
'MaximalPasswordLength' => 4096,
),
$upp->getPoliciesForUser( $user )
);
}
/**
* @covers UserPasswordPolicy::getPoliciesForGroups
*/
public function testGetPoliciesForGroups() {
$effective = UserPasswordPolicy::getPoliciesForGroups(
$this->policies,
array( 'user', 'checkuser' ),
$this->policies['default']
);
$this->assertArrayEquals(
array(
'MinimalPasswordLength' => 10,
'MinimumPasswordLengthToLogin' => 6,
'PasswordCannotMatchUsername' => true,
'PasswordCannotMatchBlacklist' => true,
'MaximalPasswordLength' => 4096,
),
$effective
);
}
/**
* @dataProvider provideCheckUserPassword
* @covers UserPasswordPolicy::checkUserPassword
*/
public function testCheckUserPassword( $username, $groups, $password, $valid, $ok, $msg ) {
$upp = $this->getUserPasswordPolicy();
$user = User::newFromName( $username );
foreach ( $groups as $group ) {
$user->addGroup( $group );
}
$status = $upp->checkUserPassword( $user, $password );
$this->assertSame( $valid, $status->isGood(), $msg . ' - password valid' );
$this->assertSame( $ok, $status->isOk(), $msg . ' - can login' );
}
public function provideCheckUserPassword() {
return array(
array(
'PassPolicyUser',
array(),
'',
false,
false,
'No groups, default policy, password too short to login'
),
array(
'PassPolicyUser',
array( 'user' ),
'aaa',
false,
true,
'Default policy, short password'
),
array(
'PassPolicyUser',
array( 'sysop' ),
'abcdabcdabcd',
true,
true,
'Sysop with good password'
),
array(
'PassPolicyUser',
array( 'sysop' ),
'abcd',
false,
true,
'Sysop with short password'
),
array(
'PassPolicyUser',
array( 'sysop', 'checkuser' ),
'abcdabcd',
false,
true,
'Checkuser with short password'
),
array(
'PassPolicyUser',
array( 'sysop', 'checkuser' ),
'abcd',
false,
false,
'Checkuser with too short password to login'
),
array(
'Useruser',
array( 'user' ),
'Passpass',
false,
true,
'Username & password on blacklist'
),
);
}
/**
* @dataProvider provideMaxOfPolicies
* @covers UserPasswordPolicy::maxOfPolicies
*/
public function testMaxOfPolicies( $p1, $p2, $max, $msg ) {
$this->assertArrayEquals(
$max,
UserPasswordPolicy::maxOfPolicies( $p1, $p2 ),
$msg
);
}
public function provideMaxOfPolicies() {
return array(
array(
array( 'MinimalPasswordLength' => 8 ), //p1
array( 'MinimalPasswordLength' => 2 ), //p2
array( 'MinimalPasswordLength' => 8 ), //max
'Basic max in p1'
),
array(
array( 'MinimalPasswordLength' => 2 ), //p1
array( 'MinimalPasswordLength' => 8 ), //p2
array( 'MinimalPasswordLength' => 8 ), //max
'Basic max in p2'
),
array(
array( 'MinimalPasswordLength' => 8 ), //p1
array(
'MinimalPasswordLength' => 2,
'PasswordCannotMatchUsername' => 1,
), //p2
array(
'MinimalPasswordLength' => 8,
'PasswordCannotMatchUsername' => 1,
), //max
'Missing items in p1'
),
array(
array(
'MinimalPasswordLength' => 8,
'PasswordCannotMatchUsername' => 1,
), //p1
array(
'MinimalPasswordLength' => 2,
), //p2
array(
'MinimalPasswordLength' => 8,
'PasswordCannotMatchUsername' => 1,
), //max
'Missing items in p2'
),
);
}
}