MediaWiki master
UserPasswordPolicy.php
Go to the documentation of this file.
1<?php
9declare( strict_types = 1 );
10
11namespace MediaWiki\Password;
12
13use DomainException;
14use InvalidArgumentException;
19use StatusValue;
20
27
31 private array $policies;
32
39 private array $policyCheckFunctions = [];
40
46 public function __construct( array $policies, array $checks ) {
47 if ( !isset( $policies['default'] ) ) {
48 throw new InvalidArgumentException(
49 'Must include a \'default\' password policy'
50 );
51 }
52 $this->policies = $policies;
53
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."
58 );
59 }
60 $this->policyCheckFunctions[$statement] = $check;
61 }
62 }
63
74 public function checkUserPassword( UserIdentity $user, string $password ): Status {
75 $effectivePolicy = $this->getPoliciesForUser( $user );
76 return $this->checkPolicies(
77 $user,
78 $password,
79 $effectivePolicy,
80 $this->policyCheckFunctions
81 );
82 }
83
97 public function checkUserPasswordForGroups( UserIdentity $user, string $password, array $groups ): Status {
98 $effectivePolicy = self::getPoliciesForGroups(
99 $this->policies,
100 $groups,
101 $this->policies['default']
102 );
103 return $this->checkPolicies(
104 $user,
105 $password,
106 $effectivePolicy,
107 $this->policyCheckFunctions
108 );
109 }
110
118 private function checkPolicies(
119 UserIdentity $user,
120 string $password,
121 array $policies,
122 array $policyCheckFunctions
123 ): Status {
124 $status = Status::newGood( [] );
125 $forceChange = false;
126 $suggestChangeOnLogin = false;
127 $legacyUser = MediaWikiServices::getInstance()
128 ->getUserFactory()
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'." );
133 }
134 if ( !is_array( $settings ) ) {
135 // legacy format
136 $settings = [ 'value' => $settings ];
137 }
138 if ( !array_key_exists( 'value', $settings ) ) {
139 throw new DomainException( "Invalid password policy config. No value defined for '$policy'." );
140 }
141 $value = $settings['value'];
143 $policyStatus = $policyCheckFunctions[$policy](
144 $value,
145 $legacyUser,
146 $password
147 );
148
149 if ( !$policyStatus->isGood() ) {
150 if ( !empty( $settings['forceChange'] ) ) {
151 $forceChange = true;
152 }
153
154 if ( !empty( $settings['suggestChangeOnLogin'] ) ) {
155 $suggestChangeOnLogin = true;
156 }
157 }
158 $status->merge( $policyStatus );
159 }
160
161 if ( $status->isOK() ) {
162 if ( $forceChange ) {
163 $status->value['forceChange'] = true;
164 } elseif ( $suggestChangeOnLogin ) {
165 $status->value['suggestChangeOnLogin'] = true;
166 }
167 }
168
169 return $status;
170 }
171
178 public function getPoliciesForUser( UserIdentity $user ): array {
179 $services = MediaWikiServices::getInstance();
180 $effectivePolicy = self::getPoliciesForGroups(
181 $this->policies,
182 $services->getUserGroupManager()
183 ->getUserEffectiveGroups( $user ),
184 $this->policies['default']
185 );
186
187 $legacyUser = $services->getUserFactory()
188 ->newFromUserIdentity( $user );
189 ( new HookRunner( $services->getHookContainer() ) )->onPasswordPoliciesForUser( $legacyUser, $effectivePolicy );
190
191 return $effectivePolicy;
192 }
193
202 public static function getPoliciesForGroups( array $policies, array $userGroups,
203 array $defaultPolicy
204 ): array {
205 $effectivePolicy = $defaultPolicy;
206 foreach ( $policies as $group => $policy ) {
207 if ( in_array( $group, $userGroups ) ) {
208 $effectivePolicy = self::maxOfPolicies(
209 $effectivePolicy,
210 $policy
211 );
212 }
213 }
214
215 return $effectivePolicy;
216 }
217
226 public static function maxOfPolicies( array $p1, array $p2 ): array {
227 $ret = [];
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] );
236 } else {
237 if ( !is_array( $p1[$key] ) ) {
238 $p1[$key] = [ 'value' => $p1[$key] ];
239 } elseif ( !is_array( $p2[$key] ) ) {
240 $p2[$key] = [ 'value' => $p2[$key] ];
241 }
242 $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
243 }
244 }
245 return $ret;
246 }
247
248}
249
251class_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.
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.
checkUserPassword(UserIdentity $user, string $password)
Check if a password meets the effective password policy for a User.
__construct(array $policies, array $checks)
getPoliciesForUser(UserIdentity $user)
Get the policy for a user, based on their group membership.
checkUserPasswordForGroups(UserIdentity $user, string $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.