MediaWiki  master
UserPasswordPolicy.php
Go to the documentation of this file.
1 <?php
27 
34 
38  private $policies;
39 
46  private $policyCheckFunctions;
47 
53  public function __construct( array $policies, array $checks ) {
54  if ( !isset( $policies['default'] ) ) {
55  throw new InvalidArgumentException(
56  'Must include a \'default\' password policy'
57  );
58  }
59  $this->policies = $policies;
60 
61  foreach ( $checks as $statement => $check ) {
62  if ( !is_callable( $check ) ) {
63  throw new InvalidArgumentException(
64  "Policy check functions must be callable. '$statement' isn't callable."
65  );
66  }
67  $this->policyCheckFunctions[$statement] = $check;
68  }
69  }
70 
81  public function checkUserPassword( UserIdentity $user, $password ) {
82  $effectivePolicy = $this->getPoliciesForUser( $user );
83  return $this->checkPolicies(
84  $user,
85  $password,
86  $effectivePolicy,
87  $this->policyCheckFunctions
88  );
89  }
90 
104  public function checkUserPasswordForGroups( UserIdentity $user, $password, array $groups ) {
105  $effectivePolicy = self::getPoliciesForGroups(
106  $this->policies,
107  $groups,
108  $this->policies['default']
109  );
110  return $this->checkPolicies(
111  $user,
112  $password,
113  $effectivePolicy,
114  $this->policyCheckFunctions
115  );
116  }
117 
125  private function checkPolicies( UserIdentity $user, $password, $policies, $policyCheckFunctions ) {
126  $status = Status::newGood( [] );
127  $forceChange = false;
128  $suggestChangeOnLogin = false;
129  $legacyUser = MediaWikiServices::getInstance()
130  ->getUserFactory()
131  ->newFromUserIdentity( $user );
132  foreach ( $policies as $policy => $settings ) {
133  if ( !isset( $policyCheckFunctions[$policy] ) ) {
134  throw new DomainException( "Invalid password policy config. No check defined for '$policy'." );
135  }
136  if ( !is_array( $settings ) ) {
137  // legacy format
138  $settings = [ 'value' => $settings ];
139  }
140  if ( !array_key_exists( 'value', $settings ) ) {
141  throw new DomainException( "Invalid password policy config. No value defined for '$policy'." );
142  }
143  $value = $settings['value'];
145  $policyStatus = call_user_func(
146  $policyCheckFunctions[$policy],
147  $value,
148  $legacyUser,
149  $password
150  );
151 
152  if ( !$policyStatus->isGood() ) {
153  if ( !empty( $settings['forceChange'] ) ) {
154  $forceChange = true;
155  }
156 
157  if ( !empty( $settings['suggestChangeOnLogin'] ) ) {
158  $suggestChangeOnLogin = true;
159  }
160  }
161  $status->merge( $policyStatus );
162  }
163 
164  if ( $status->isOK() ) {
165  if ( $forceChange ) {
166  $status->value['forceChange'] = true;
167  } elseif ( $suggestChangeOnLogin ) {
168  $status->value['suggestChangeOnLogin'] = true;
169  }
170  }
171 
172  return $status;
173  }
174 
181  public function getPoliciesForUser( UserIdentity $user ) {
182  $services = MediaWikiServices::getInstance();
183  $effectivePolicy = self::getPoliciesForGroups(
184  $this->policies,
185  $services->getUserGroupManager()
186  ->getUserEffectiveGroups( $user ),
187  $this->policies['default']
188  );
189 
190  $legacyUser = $services->getUserFactory()
191  ->newFromUserIdentity( $user );
192  ( new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
193 
194  return $effectivePolicy;
195  }
196 
205  public static function getPoliciesForGroups( array $policies, array $userGroups,
206  array $defaultPolicy
207  ) {
208  $effectivePolicy = $defaultPolicy;
209  foreach ( $policies as $group => $policy ) {
210  if ( in_array( $group, $userGroups ) ) {
211  $effectivePolicy = self::maxOfPolicies(
212  $effectivePolicy,
213  $policy
214  );
215  }
216  }
217 
218  return $effectivePolicy;
219  }
220 
229  public static function maxOfPolicies( array $p1, array $p2 ) {
230  $ret = [];
231  $keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
232  foreach ( $keys as $key ) {
233  if ( !isset( $p1[$key] ) ) {
234  $ret[$key] = $p2[$key];
235  } elseif ( !isset( $p2[$key] ) ) {
236  $ret[$key] = $p1[$key];
237  } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
238  $ret[$key] = max( $p1[$key], $p2[$key] );
239  } else {
240  if ( !is_array( $p1[$key] ) ) {
241  $p1[$key] = [ 'value' => $p1[$key] ];
242  } elseif ( !is_array( $p2[$key] ) ) {
243  $p2[$key] = [ 'value' => $p2[$key] ];
244  }
245  $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
246  }
247  }
248  return $ret;
249  }
250 
251 }
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
Service locator for MediaWiki core services.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
Check if a user's password complies with any password policies that apply to that user,...
getPoliciesForUser(UserIdentity $user)
Get the policy for a user, based on their group membership.
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...
checkUserPassword(UserIdentity $user, $password)
Check if a password meets the effective password policy for a 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)
static maxOfPolicies(array $p1, array $p2)
Utility function to get a policy that is the most restrictive of $p1 and $p2.
Interface for objects representing user identity.