MediaWiki master
UserPasswordPolicy.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Password;
10
11use DomainException;
12use InvalidArgumentException;
17use StatusValue;
18
25
29 private $policies;
30
37 private $policyCheckFunctions;
38
44 public function __construct( array $policies, array $checks ) {
45 if ( !isset( $policies['default'] ) ) {
46 throw new InvalidArgumentException(
47 'Must include a \'default\' password policy'
48 );
49 }
50 $this->policies = $policies;
51
52 foreach ( $checks as $statement => $check ) {
53 if ( !is_callable( $check ) ) {
54 throw new InvalidArgumentException(
55 "Policy check functions must be callable. '$statement' isn't callable."
56 );
57 }
58 $this->policyCheckFunctions[$statement] = $check;
59 }
60 }
61
72 public function checkUserPassword( UserIdentity $user, $password ) {
73 $effectivePolicy = $this->getPoliciesForUser( $user );
74 return $this->checkPolicies(
75 $user,
76 $password,
77 $effectivePolicy,
78 $this->policyCheckFunctions
79 );
80 }
81
95 public function checkUserPasswordForGroups( UserIdentity $user, $password, array $groups ) {
96 $effectivePolicy = self::getPoliciesForGroups(
97 $this->policies,
98 $groups,
99 $this->policies['default']
100 );
101 return $this->checkPolicies(
102 $user,
103 $password,
104 $effectivePolicy,
105 $this->policyCheckFunctions
106 );
107 }
108
116 private function checkPolicies( UserIdentity $user, $password, $policies, $policyCheckFunctions ) {
117 $status = Status::newGood( [] );
118 $forceChange = false;
119 $suggestChangeOnLogin = false;
120 $legacyUser = MediaWikiServices::getInstance()
121 ->getUserFactory()
122 ->newFromUserIdentity( $user );
123 foreach ( $policies as $policy => $settings ) {
124 if ( !isset( $policyCheckFunctions[$policy] ) ) {
125 throw new DomainException( "Invalid password policy config. No check defined for '$policy'." );
126 }
127 if ( !is_array( $settings ) ) {
128 // legacy format
129 $settings = [ 'value' => $settings ];
130 }
131 if ( !array_key_exists( 'value', $settings ) ) {
132 throw new DomainException( "Invalid password policy config. No value defined for '$policy'." );
133 }
134 $value = $settings['value'];
136 $policyStatus = $policyCheckFunctions[$policy](
137 $value,
138 $legacyUser,
139 $password
140 );
141
142 if ( !$policyStatus->isGood() ) {
143 if ( !empty( $settings['forceChange'] ) ) {
144 $forceChange = true;
145 }
146
147 if ( !empty( $settings['suggestChangeOnLogin'] ) ) {
148 $suggestChangeOnLogin = true;
149 }
150 }
151 $status->merge( $policyStatus );
152 }
153
154 if ( $status->isOK() ) {
155 if ( $forceChange ) {
156 $status->value['forceChange'] = true;
157 } elseif ( $suggestChangeOnLogin ) {
158 $status->value['suggestChangeOnLogin'] = true;
159 }
160 }
161
162 return $status;
163 }
164
171 public function getPoliciesForUser( UserIdentity $user ) {
172 $services = MediaWikiServices::getInstance();
173 $effectivePolicy = self::getPoliciesForGroups(
174 $this->policies,
175 $services->getUserGroupManager()
176 ->getUserEffectiveGroups( $user ),
177 $this->policies['default']
178 );
179
180 $legacyUser = $services->getUserFactory()
181 ->newFromUserIdentity( $user );
182 ( new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
183
184 return $effectivePolicy;
185 }
186
195 public static function getPoliciesForGroups( array $policies, array $userGroups,
196 array $defaultPolicy
197 ) {
198 $effectivePolicy = $defaultPolicy;
199 foreach ( $policies as $group => $policy ) {
200 if ( in_array( $group, $userGroups ) ) {
201 $effectivePolicy = self::maxOfPolicies(
202 $effectivePolicy,
203 $policy
204 );
205 }
206 }
207
208 return $effectivePolicy;
209 }
210
219 public static function maxOfPolicies( array $p1, array $p2 ) {
220 $ret = [];
221 $keys = array_merge( array_keys( $p1 ), array_keys( $p2 ) );
222 foreach ( $keys as $key ) {
223 if ( !isset( $p1[$key] ) ) {
224 $ret[$key] = $p2[$key];
225 } elseif ( !isset( $p2[$key] ) ) {
226 $ret[$key] = $p1[$key];
227 } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
228 $ret[$key] = max( $p1[$key], $p2[$key] );
229 } else {
230 if ( !is_array( $p1[$key] ) ) {
231 $p1[$key] = [ 'value' => $p1[$key] ];
232 } elseif ( !is_array( $p2[$key] ) ) {
233 $p2[$key] = [ 'value' => $p2[$key] ];
234 }
235 $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
236 }
237 }
238 return $ret;
239 }
240
241}
242
244class_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:44
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for objects representing user identity.