9declare( strict_types = 1 );
14use InvalidArgumentException;
31 private array $policies;
39 private array $policyCheckFunctions = [];
46 public function __construct( array $policies, array $checks ) {
47 if ( !isset( $policies[
'default'] ) ) {
48 throw new InvalidArgumentException(
49 'Must include a \'default\' password policy'
52 $this->policies = $policies;
54 foreach ( $checks as $statement => $check ) {
55 if ( !is_callable( $check ) ) {
56 throw new InvalidArgumentException(
57 "Policy check functions must be callable. '$statement' isn't callable."
60 $this->policyCheckFunctions[$statement] = $check;
76 return $this->checkPolicies(
80 $this->policyCheckFunctions
98 $effectivePolicy = self::getPoliciesForGroups(
101 $this->policies[
'default']
103 return $this->checkPolicies(
107 $this->policyCheckFunctions
118 private function checkPolicies(
122 array $policyCheckFunctions
124 $status =
Status::newGood( [] );
125 $forceChange =
false;
126 $suggestChangeOnLogin =
false;
127 $legacyUser = MediaWikiServices::getInstance()
129 ->newFromUserIdentity( $user );
130 foreach ( $policies as $policy => $settings ) {
131 if ( !isset( $policyCheckFunctions[$policy] ) ) {
132 throw new DomainException(
"Invalid password policy config. No check defined for '$policy'." );
134 if ( !is_array( $settings ) ) {
136 $settings = [
'value' => $settings ];
138 if ( !array_key_exists(
'value', $settings ) ) {
139 throw new DomainException(
"Invalid password policy config. No value defined for '$policy'." );
141 $value = $settings[
'value'];
143 $policyStatus = $policyCheckFunctions[$policy](
149 if ( !$policyStatus->isGood() ) {
150 if ( !empty( $settings[
'forceChange'] ) ) {
154 if ( !empty( $settings[
'suggestChangeOnLogin'] ) ) {
155 $suggestChangeOnLogin =
true;
158 $status->merge( $policyStatus );
161 if ( $status->isOK() ) {
162 if ( $forceChange ) {
163 $status->value[
'forceChange'] =
true;
164 } elseif ( $suggestChangeOnLogin ) {
165 $status->value[
'suggestChangeOnLogin'] =
true;
180 $effectivePolicy = self::getPoliciesForGroups(
182 $services->getUserGroupManager()
183 ->getUserEffectiveGroups( $user ),
184 $this->policies[
'default']
187 $legacyUser = $services->getUserFactory()
188 ->newFromUserIdentity( $user );
189 (
new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
191 return $effectivePolicy;
205 $effectivePolicy = $defaultPolicy;
206 foreach ( $policies as $group => $policy ) {
207 if ( in_array( $group, $userGroups ) ) {
208 $effectivePolicy = self::maxOfPolicies(
215 return $effectivePolicy;
228 $keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
229 foreach ( $keys as $key ) {
230 if ( !isset( $p1[$key] ) ) {
231 $ret[$key] = $p2[$key];
232 } elseif ( !isset( $p2[$key] ) ) {
233 $ret[$key] = $p1[$key];
234 } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
235 $ret[$key] = max( $p1[$key], $p2[$key] );
237 if ( !is_array( $p1[$key] ) ) {
238 $p1[$key] = [
'value' => $p1[$key] ];
239 } elseif ( !is_array( $p2[$key] ) ) {
240 $p2[$key] = [
'value' => $p2[$key] ];
242 $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
251class_alias( UserPasswordPolicy::class,
'UserPasswordPolicy' );
Generic operation result class Has warning/error list, boolean status and arbitrary value.