MediaWiki  master
AuthPluginPrimaryAuthenticationProvider.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
27 use User;
28 
38 {
39  private $auth;
40  private $hasDomain;
41  private $requestType = null;
42 
49  public function __construct( AuthPlugin $auth, $requestType = null ) {
50  parent::__construct();
51 
52  if ( $auth instanceof AuthManagerAuthPlugin ) {
53  throw new \InvalidArgumentException(
54  'Trying to wrap AuthManagerAuthPlugin in AuthPluginPrimaryAuthenticationProvider ' .
55  'makes no sense.'
56  );
57  }
58 
59  $need = count( $auth->domainList() ) > 1
62  if ( $requestType === null ) {
63  $requestType = $need;
64  } elseif ( $requestType !== $need && !is_subclass_of( $requestType, $need ) ) {
65  throw new \InvalidArgumentException( "$requestType is not a $need" );
66  }
67 
68  $this->auth = $auth;
69  $this->requestType = $requestType;
70  $this->hasDomain = (
73  );
74  $this->authoritative = $auth->strict();
75 
76  // Registering hooks from core is unusual, but is needed here to be
77  // able to call the AuthPlugin methods those hooks replace.
78  \Hooks::register( 'UserSaveSettings', [ $this, 'onUserSaveSettings' ] );
79  \Hooks::register( 'UserGroupsChanged', [ $this, 'onUserGroupsChanged' ] );
80  \Hooks::register( 'UserLoggedIn', [ $this, 'onUserLoggedIn' ] );
81  \Hooks::register( 'LocalUserCreated', [ $this, 'onLocalUserCreated' ] );
82  }
83 
88  protected function makeAuthReq() {
89  $class = $this->requestType;
90  if ( $this->hasDomain ) {
91  return new $class( $this->auth->domainList() );
92  } else {
93  return new $class();
94  }
95  }
96 
101  protected function setDomain( $req ) {
102  if ( $this->hasDomain ) {
103  $domain = $req->domain;
104  } else {
105  // Just grab the first one.
106  $domainList = $this->auth->domainList();
107  $domain = reset( $domainList );
108  }
109 
110  // Special:UserLogin does this. Strange.
111  if ( !$this->auth->validDomain( $domain ) ) {
112  $domain = $this->auth->getDomain();
113  }
114  $this->auth->setDomain( $domain );
115  }
116 
122  public function onUserSaveSettings( $user ) {
123  // No way to know the domain, just hope the provider handles that.
124  $this->auth->updateExternalDB( $user );
125  }
126 
133  public function onUserGroupsChanged( $user, $added, $removed ) {
134  // No way to know the domain, just hope the provider handles that.
135  $this->auth->updateExternalDBGroups( $user, $added, $removed );
136  }
137 
142  public function onUserLoggedIn( $user ) {
143  $hookUser = $user;
144  // No way to know the domain, just hope the provider handles that.
145  $this->auth->updateUser( $hookUser );
146  if ( $hookUser !== $user ) {
147  throw new \UnexpectedValueException(
148  get_class( $this->auth ) . '::updateUser() tried to replace $user!'
149  );
150  }
151  }
152 
158  public function onLocalUserCreated( $user, $autocreated ) {
159  // For $autocreated, see self::autoCreatedAccount()
160  if ( !$autocreated ) {
161  $hookUser = $user;
162  // No way to know the domain, just hope the provider handles that.
163  $this->auth->initUser( $hookUser, $autocreated );
164  if ( $hookUser !== $user ) {
165  throw new \UnexpectedValueException(
166  get_class( $this->auth ) . '::initUser() tried to replace $user!'
167  );
168  }
169  }
170  }
171 
172  public function getUniqueId() {
173  return parent::getUniqueId() . ':' . get_class( $this->auth );
174  }
175 
177  switch ( $action ) {
180  return [ $this->makeAuthReq() ];
181 
184  // No way to know the domain, just hope the provider handles that.
185  return $this->auth->allowPasswordChange() ? [ $this->makeAuthReq() ] : [];
186 
187  default:
188  return [];
189  }
190  }
191 
192  public function beginPrimaryAuthentication( array $reqs ) {
193  $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
194  if ( !$req || $req->username === null || $req->password === null ||
195  ( $this->hasDomain && $req->domain === null )
196  ) {
198  }
199 
200  $username = User::getCanonicalName( $req->username, 'usable' );
201  if ( $username === false ) {
203  }
204 
205  $this->setDomain( $req );
207  $this->auth->authenticate( $username, $req->password )
208  ) {
210  } else {
211  $this->authoritative = $this->auth->strict() || $this->auth->strictUserAuth( $username );
212  return $this->failResponse( $req );
213  }
214  }
215 
216  public function testUserCanAuthenticate( $username ) {
218  if ( $username === false ) {
219  return false;
220  }
221 
222  // We have to check every domain, because at least LdapAuthentication
223  // interprets AuthPlugin::userExists() as applying only to the current
224  // domain.
225  $curDomain = $this->auth->getDomain();
226  $domains = $this->auth->domainList() ?: [ '' ];
227  foreach ( $domains as $domain ) {
228  $this->auth->setDomain( $domain );
230  $this->auth->setDomain( $curDomain );
231  return true;
232  }
233  }
234  $this->auth->setDomain( $curDomain );
235  return false;
236  }
237 
245  if ( $this->auth->userExists( $user->getName() ) ) {
246  return !$this->auth->getUserInstance( $user )->isLocked();
247  } else {
248  return false;
249  }
250  }
251 
254  if ( $username === false ) {
255  return;
256  }
258  if ( $user ) {
259  // Reset the password on every domain.
260  $curDomain = $this->auth->getDomain();
261  $domains = $this->auth->domainList() ?: [ '' ];
262  $failed = [];
263  foreach ( $domains as $domain ) {
264  $this->auth->setDomain( $domain );
265  if ( $this->testUserCanAuthenticateInternal( $user ) &&
266  !$this->auth->setPassword( $user, null )
267  ) {
268  $failed[] = $domain === '' ? '(default)' : $domain;
269  }
270  }
271  $this->auth->setDomain( $curDomain );
272  if ( $failed ) {
273  throw new \UnexpectedValueException(
274  "AuthPlugin failed to reset password for $username in the following domains: "
275  . implode( ' ', $failed )
276  );
277  }
278  }
279  }
280 
281  public function testUserExists( $username, $flags = User::READ_NORMAL ) {
283  if ( $username === false ) {
284  return false;
285  }
286 
287  // We have to check every domain, because at least LdapAuthentication
288  // interprets AuthPlugin::userExists() as applying only to the current
289  // domain.
290  $curDomain = $this->auth->getDomain();
291  $domains = $this->auth->domainList() ?: [ '' ];
292  foreach ( $domains as $domain ) {
293  $this->auth->setDomain( $domain );
294  if ( $this->auth->userExists( $username ) ) {
295  $this->auth->setDomain( $curDomain );
296  return true;
297  }
298  }
299  $this->auth->setDomain( $curDomain );
300  return false;
301  }
302 
304  // No way to know the domain, just hope the provider handles that.
305  return $this->auth->allowPropChange( $property );
306  }
307 
309  AuthenticationRequest $req, $checkData = true
310  ) {
311  if ( get_class( $req ) !== $this->requestType ) {
312  return \StatusValue::newGood( 'ignored' );
313  }
314 
315  // Hope it works, AuthPlugin gives us no way to do this.
316  $curDomain = $this->auth->getDomain();
317  $this->setDomain( $req );
318  try {
319  // If !$checkData the domain might be wrong. Nothing we can do about that.
320  if ( !$this->auth->allowPasswordChange() ) {
321  return \StatusValue::newFatal( 'authmanager-authplugin-setpass-denied' );
322  }
323 
324  if ( !$checkData ) {
325  return \StatusValue::newGood();
326  }
327 
328  if ( $this->hasDomain ) {
329  if ( $req->domain === null ) {
330  return \StatusValue::newGood( 'ignored' );
331  }
332  if ( !$this->auth->validDomain( $req->domain ) ) {
333  return \StatusValue::newFatal( 'authmanager-authplugin-setpass-bad-domain' );
334  }
335  }
336 
337  $username = User::getCanonicalName( $req->username, 'usable' );
338  if ( $username !== false ) {
339  $sv = \StatusValue::newGood();
340  if ( $req->password !== null ) {
341  if ( $req->password !== $req->retype ) {
342  $sv->fatal( 'badretype' );
343  } else {
344  $sv->merge( $this->checkPasswordValidity( $username, $req->password ) );
345  }
346  }
347  return $sv;
348  } else {
349  return \StatusValue::newGood( 'ignored' );
350  }
351  } finally {
352  $this->auth->setDomain( $curDomain );
353  }
354  }
355 
357  if ( get_class( $req ) === $this->requestType ) {
358  $username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false;
359  if ( $username === false ) {
360  return;
361  }
362 
363  if ( $this->hasDomain && $req->domain === null ) {
364  return;
365  }
366 
367  $this->setDomain( $req );
369  if ( !$this->auth->setPassword( $user, $req->password ) ) {
370  // This is totally unfriendly and leaves other
371  // AuthenticationProviders in an uncertain state, but what else
372  // can we do?
373  throw new \ErrorPageError(
374  'authmanager-authplugin-setpass-failed-title',
375  'authmanager-authplugin-setpass-failed-message'
376  );
377  }
378  }
379  }
380 
381  public function accountCreationType() {
382  // No way to know the domain, just hope the provider handles that.
383  return $this->auth->canCreateAccounts() ? self::TYPE_CREATE : self::TYPE_NONE;
384  }
385 
386  public function testForAccountCreation( $user, $creator, array $reqs ) {
387  return \StatusValue::newGood();
388  }
389 
390  public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
391  if ( $this->accountCreationType() === self::TYPE_NONE ) {
392  throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
393  }
394 
395  $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
396  if ( !$req || $req->username === null || $req->password === null ||
397  ( $this->hasDomain && $req->domain === null )
398  ) {
400  }
401 
402  $username = User::getCanonicalName( $req->username, 'usable' );
403  if ( $username === false ) {
405  }
406 
407  $this->setDomain( $req );
408  if ( $this->auth->addUser(
409  $user, $req->password, $user->getEmail(), $user->getRealName()
410  ) ) {
412  } else {
414  new \Message( 'authmanager-authplugin-create-fail' )
415  );
416  }
417  }
418 
419  public function autoCreatedAccount( $user, $source ) {
420  $hookUser = $user;
421  // No way to know the domain, just hope the provider handles that.
422  $this->auth->initUser( $hookUser, true );
423  if ( $hookUser !== $user ) {
424  throw new \UnexpectedValueException(
425  get_class( $this->auth ) . '::initUser() tried to replace $user!'
426  );
427  }
428  }
429 }
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
beginPrimaryAccountCreation( $user, $creator, array $reqs)
Start an account creation flow.
checkPasswordValidity( $username, $password)
Check that the password is valid.
onUserSaveSettings( $user)
Hook function to call AuthPlugin::updateExternalDB()
$property
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$source
strict()
Return true to prevent logins that don&#39;t authenticate here from being checked against the local datab...
Definition: AuthPlugin.php:260
string $action
Cache what action this request is.
Definition: MediaWiki.php:48
onUserGroupsChanged( $user, $added, $removed)
Hook function to call AuthPlugin::updateExternalDBGroups()
providerRevokeAccessForUser( $username)
Revoke the user&#39;s credentials.
providerChangeAuthenticationData(AuthenticationRequest $req)
Change or remove authentication data (e.g.
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1233
testUserExists( $username, $flags=User::READ_NORMAL)
Test whether the named user exists.
Backwards-compatibility wrapper for AuthManager via $wgAuth.
Primary authentication provider wrapper for AuthPlugin.
static register( $name, $callback)
Attach an event handler to a given hook.
Definition: Hooks.php:49
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
const ACTION_CHANGE
Change a user&#39;s credentials.
onUserLoggedIn( $user)
Hook function to call AuthPlugin::updateUser()
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1996
Basic framework for a primary authentication provider that uses passwords.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
providerAllowsPropertyChange( $property)
Determine whether a property can change.
domainList()
Get a list of domains (in HTMLForm options format) used.
Definition: AuthPlugin.php:318
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
this hook is for auditing only $req
Definition: hooks.txt:990
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:785
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
testForAccountCreation( $user, $creator, array $reqs)
Determine whether an account creation may begin.
const ACTION_REMOVE
Remove a user&#39;s credentials.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
const ACTION_CREATE
Create a new user.
Definition: AuthManager.php:90
testUserCanAuthenticate( $username)
Test whether the named user can authenticate with this provider.
failResponse(PasswordAuthenticationRequest $req)
Return the appropriate response for failure.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
Definition: AuthManager.php:85
providerAllowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1487
This is a value object for authentication requests.
onLocalUserCreated( $user, $autocreated)
Hook function to call AuthPlugin::initUser()