MediaWiki master
UserPasswordPolicy.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Password;
24
25use DomainException;
26use InvalidArgumentException;
31use StatusValue;
32
39
43 private $policies;
44
51 private $policyCheckFunctions;
52
58 public function __construct( array $policies, array $checks ) {
59 if ( !isset( $policies['default'] ) ) {
60 throw new InvalidArgumentException(
61 'Must include a \'default\' password policy'
62 );
63 }
64 $this->policies = $policies;
65
66 foreach ( $checks as $statement => $check ) {
67 if ( !is_callable( $check ) ) {
68 throw new InvalidArgumentException(
69 "Policy check functions must be callable. '$statement' isn't callable."
70 );
71 }
72 $this->policyCheckFunctions[$statement] = $check;
73 }
74 }
75
86 public function checkUserPassword( UserIdentity $user, $password ) {
87 $effectivePolicy = $this->getPoliciesForUser( $user );
88 return $this->checkPolicies(
89 $user,
90 $password,
91 $effectivePolicy,
92 $this->policyCheckFunctions
93 );
94 }
95
109 public function checkUserPasswordForGroups( UserIdentity $user, $password, array $groups ) {
110 $effectivePolicy = self::getPoliciesForGroups(
111 $this->policies,
112 $groups,
113 $this->policies['default']
114 );
115 return $this->checkPolicies(
116 $user,
117 $password,
118 $effectivePolicy,
119 $this->policyCheckFunctions
120 );
121 }
122
130 private function checkPolicies( UserIdentity $user, $password, $policies, $policyCheckFunctions ) {
131 $status = Status::newGood( [] );
132 $forceChange = false;
133 $suggestChangeOnLogin = false;
134 $legacyUser = MediaWikiServices::getInstance()
135 ->getUserFactory()
136 ->newFromUserIdentity( $user );
137 foreach ( $policies as $policy => $settings ) {
138 if ( !isset( $policyCheckFunctions[$policy] ) ) {
139 throw new DomainException( "Invalid password policy config. No check defined for '$policy'." );
140 }
141 if ( !is_array( $settings ) ) {
142 // legacy format
143 $settings = [ 'value' => $settings ];
144 }
145 if ( !array_key_exists( 'value', $settings ) ) {
146 throw new DomainException( "Invalid password policy config. No value defined for '$policy'." );
147 }
148 $value = $settings['value'];
150 $policyStatus = $policyCheckFunctions[$policy](
151 $value,
152 $legacyUser,
153 $password
154 );
155
156 if ( !$policyStatus->isGood() ) {
157 if ( !empty( $settings['forceChange'] ) ) {
158 $forceChange = true;
159 }
160
161 if ( !empty( $settings['suggestChangeOnLogin'] ) ) {
162 $suggestChangeOnLogin = true;
163 }
164 }
165 $status->merge( $policyStatus );
166 }
167
168 if ( $status->isOK() ) {
169 if ( $forceChange ) {
170 $status->value['forceChange'] = true;
171 } elseif ( $suggestChangeOnLogin ) {
172 $status->value['suggestChangeOnLogin'] = true;
173 }
174 }
175
176 return $status;
177 }
178
185 public function getPoliciesForUser( UserIdentity $user ) {
186 $services = MediaWikiServices::getInstance();
187 $effectivePolicy = self::getPoliciesForGroups(
188 $this->policies,
189 $services->getUserGroupManager()
190 ->getUserEffectiveGroups( $user ),
191 $this->policies['default']
192 );
193
194 $legacyUser = $services->getUserFactory()
195 ->newFromUserIdentity( $user );
196 ( new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
197
198 return $effectivePolicy;
199 }
200
209 public static function getPoliciesForGroups( array $policies, array $userGroups,
210 array $defaultPolicy
211 ) {
212 $effectivePolicy = $defaultPolicy;
213 foreach ( $policies as $group => $policy ) {
214 if ( in_array( $group, $userGroups ) ) {
215 $effectivePolicy = self::maxOfPolicies(
216 $effectivePolicy,
217 $policy
218 );
219 }
220 }
221
222 return $effectivePolicy;
223 }
224
233 public static function maxOfPolicies( array $p1, array $p2 ) {
234 $ret = [];
235 $keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
236 foreach ( $keys as $key ) {
237 if ( !isset( $p1[$key] ) ) {
238 $ret[$key] = $p2[$key];
239 } elseif ( !isset( $p2[$key] ) ) {
240 $ret[$key] = $p1[$key];
241 } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
242 $ret[$key] = max( $p1[$key], $p2[$key] );
243 } else {
244 if ( !is_array( $p1[$key] ) ) {
245 $p1[$key] = [ 'value' => $p1[$key] ];
246 } elseif ( !is_array( $p2[$key] ) ) {
247 $p2[$key] = [ 'value' => $p2[$key] ];
248 }
249 $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
250 }
251 }
252 return $ret;
253 }
254
255}
256
258class_alias( UserPasswordPolicy::class, 'UserPasswordPolicy' );
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Check if a user's password complies with any password policies that apply to that user,...
static getPoliciesForGroups(array $policies, array $userGroups, array $defaultPolicy)
Utility function to get the effective policy from a list of policies, based on a list of groups.
__construct(array $policies, array $checks)
getPoliciesForUser(UserIdentity $user)
Get the policy for a user, based on their group membership.
checkUserPassword(UserIdentity $user, $password)
Check if a password meets the effective password policy for a User.
checkUserPasswordForGroups(UserIdentity $user, $password, array $groups)
Check if a password meets the effective password policy for a User, using a set of groups they may or...
static maxOfPolicies(array $p1, array $p2)
Utility function to get a policy that is the most restrictive of $p1 and $p2.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for objects representing user identity.