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 = call_user_func(
151 $policyCheckFunctions[$policy],
152 $value,
153 $legacyUser,
154 $password
155 );
156
157 if ( !$policyStatus->isGood() ) {
158 if ( !empty( $settings['forceChange'] ) ) {
159 $forceChange = true;
160 }
161
162 if ( !empty( $settings['suggestChangeOnLogin'] ) ) {
163 $suggestChangeOnLogin = true;
164 }
165 }
166 $status->merge( $policyStatus );
167 }
168
169 if ( $status->isOK() ) {
170 if ( $forceChange ) {
171 $status->value['forceChange'] = true;
172 } elseif ( $suggestChangeOnLogin ) {
173 $status->value['suggestChangeOnLogin'] = true;
174 }
175 }
176
177 return $status;
178 }
179
186 public function getPoliciesForUser( UserIdentity $user ) {
187 $services = MediaWikiServices::getInstance();
188 $effectivePolicy = self::getPoliciesForGroups(
189 $this->policies,
190 $services->getUserGroupManager()
191 ->getUserEffectiveGroups( $user ),
192 $this->policies['default']
193 );
194
195 $legacyUser = $services->getUserFactory()
196 ->newFromUserIdentity( $user );
197 ( new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
198
199 return $effectivePolicy;
200 }
201
210 public static function getPoliciesForGroups( array $policies, array $userGroups,
211 array $defaultPolicy
212 ) {
213 $effectivePolicy = $defaultPolicy;
214 foreach ( $policies as $group => $policy ) {
215 if ( in_array( $group, $userGroups ) ) {
216 $effectivePolicy = self::maxOfPolicies(
217 $effectivePolicy,
218 $policy
219 );
220 }
221 }
222
223 return $effectivePolicy;
224 }
225
234 public static function maxOfPolicies( array $p1, array $p2 ) {
235 $ret = [];
236 $keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
237 foreach ( $keys as $key ) {
238 if ( !isset( $p1[$key] ) ) {
239 $ret[$key] = $p2[$key];
240 } elseif ( !isset( $p2[$key] ) ) {
241 $ret[$key] = $p1[$key];
242 } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
243 $ret[$key] = max( $p1[$key], $p2[$key] );
244 } else {
245 if ( !is_array( $p1[$key] ) ) {
246 $p1[$key] = [ 'value' => $p1[$key] ];
247 } elseif ( !is_array( $p2[$key] ) ) {
248 $p2[$key] = [ 'value' => $p2[$key] ];
249 }
250 $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
251 }
252 }
253 return $ret;
254 }
255
256}
257
259class_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.