3 * Primary authentication provider wrapper for AuthPlugin
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 namespace MediaWiki\Auth
;
30 * Primary authentication provider wrapper for AuthPlugin
31 * @warning If anything depends on the wrapped AuthPlugin being $wgAuth, it won't work with this!
34 * @deprecated since 1.27
36 class AuthPluginPrimaryAuthenticationProvider
37 extends AbstractPasswordPrimaryAuthenticationProvider
41 private $requestType = null;
44 * @param AuthPlugin $auth AuthPlugin to wrap
45 * @param string|null $requestType Class name of the
46 * PasswordAuthenticationRequest to use. If $auth->domainList() returns
47 * more than one domain, this must be a PasswordDomainAuthenticationRequest.
49 public function __construct( AuthPlugin
$auth, $requestType = null ) {
50 parent
::__construct();
52 if ( $auth instanceof AuthManagerAuthPlugin
) {
53 throw new \
InvalidArgumentException(
54 'Trying to wrap AuthManagerAuthPlugin in AuthPluginPrimaryAuthenticationProvider ' .
59 $need = count( $auth->domainList() ) > 1
60 ? PasswordDomainAuthenticationRequest
::class
61 : PasswordAuthenticationRequest
::class;
62 if ( $requestType === null ) {
64 } elseif ( $requestType !== $need && !is_subclass_of( $requestType, $need ) ) {
65 throw new \
InvalidArgumentException( "$requestType is not a $need" );
69 $this->requestType
= $requestType;
71 $requestType === PasswordDomainAuthenticationRequest
::class ||
72 is_subclass_of( $requestType, PasswordDomainAuthenticationRequest
::class )
74 $this->authoritative
= $auth->strict();
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' ] );
85 * Create an appropriate AuthenticationRequest
86 * @return PasswordAuthenticationRequest
88 protected function makeAuthReq() {
89 $class = $this->requestType
;
90 if ( $this->hasDomain
) {
91 return new $class( $this->auth
->domainList() );
98 * Call $this->auth->setDomain()
99 * @param PasswordAuthenticationRequest $req
101 protected function setDomain( $req ) {
102 if ( $this->hasDomain
) {
103 $domain = $req->domain
;
105 // Just grab the first one.
106 $domainList = $this->auth
->domainList();
107 $domain = reset( $domainList );
110 // Special:UserLogin does this. Strange.
111 if ( !$this->auth
->validDomain( $domain ) ) {
112 $domain = $this->auth
->getDomain();
114 $this->auth
->setDomain( $domain );
118 * Hook function to call AuthPlugin::updateExternalDB()
120 * @codeCoverageIgnore
122 public function onUserSaveSettings( $user ) {
123 // No way to know the domain, just hope the provider handles that.
124 $this->auth
->updateExternalDB( $user );
128 * Hook function to call AuthPlugin::updateExternalDBGroups()
130 * @param array $added
131 * @param array $removed
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 );
139 * Hook function to call AuthPlugin::updateUser()
142 public function onUserLoggedIn( $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!'
154 * Hook function to call AuthPlugin::initUser()
156 * @param bool $autocreated
158 public function onLocalUserCreated( $user, $autocreated ) {
159 // For $autocreated, see self::autoCreatedAccount()
160 if ( !$autocreated ) {
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!'
172 public function getUniqueId() {
173 return parent
::getUniqueId() . ':' . get_class( $this->auth
);
176 public function getAuthenticationRequests( $action, array $options ) {
178 case AuthManager
::ACTION_LOGIN
:
179 case AuthManager
::ACTION_CREATE
:
180 return [ $this->makeAuthReq() ];
182 case AuthManager
::ACTION_CHANGE
:
183 case AuthManager
::ACTION_REMOVE
:
184 // No way to know the domain, just hope the provider handles that.
185 return $this->auth
->allowPasswordChange() ?
[ $this->makeAuthReq() ] : [];
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 )
197 return AuthenticationResponse
::newAbstain();
200 $username = User
::getCanonicalName( $req->username
, 'usable' );
201 if ( $username === false ) {
202 return AuthenticationResponse
::newAbstain();
205 $this->setDomain( $req );
206 if ( $this->testUserCanAuthenticateInternal( User
::newFromName( $username ) ) &&
207 $this->auth
->authenticate( $username, $req->password
)
209 return AuthenticationResponse
::newPass( $username );
211 $this->authoritative
= $this->auth
->strict() ||
$this->auth
->strictUserAuth( $username );
212 return $this->failResponse( $req );
216 public function testUserCanAuthenticate( $username ) {
217 $username = User
::getCanonicalName( $username, 'usable' );
218 if ( $username === false ) {
222 // We have to check every domain, because at least LdapAuthentication
223 // interprets AuthPlugin::userExists() as applying only to the current
225 $curDomain = $this->auth
->getDomain();
226 $domains = $this->auth
->domainList() ?
: [ '' ];
227 foreach ( $domains as $domain ) {
228 $this->auth
->setDomain( $domain );
229 if ( $this->testUserCanAuthenticateInternal( User
::newFromName( $username ) ) ) {
230 $this->auth
->setDomain( $curDomain );
234 $this->auth
->setDomain( $curDomain );
239 * @see self::testUserCanAuthenticate
240 * @note The caller is responsible for calling $this->auth->setDomain()
244 private function testUserCanAuthenticateInternal( $user ) {
245 if ( $this->auth
->userExists( $user->getName() ) ) {
246 return !$this->auth
->getUserInstance( $user )->isLocked();
252 public function providerRevokeAccessForUser( $username ) {
253 $username = User
::getCanonicalName( $username, 'usable' );
254 if ( $username === false ) {
257 $user = User
::newFromName( $username );
259 // Reset the password on every domain.
260 $curDomain = $this->auth
->getDomain();
261 $domains = $this->auth
->domainList() ?
: [ '' ];
263 foreach ( $domains as $domain ) {
264 $this->auth
->setDomain( $domain );
265 if ( $this->testUserCanAuthenticateInternal( $user ) &&
266 !$this->auth
->setPassword( $user, null )
268 $failed[] = $domain === '' ?
'(default)' : $domain;
271 $this->auth
->setDomain( $curDomain );
273 throw new \
UnexpectedValueException(
274 "AuthPlugin failed to reset password for $username in the following domains: "
275 . join( ' ', $failed )
281 public function testUserExists( $username, $flags = User
::READ_NORMAL
) {
282 $username = User
::getCanonicalName( $username, 'usable' );
283 if ( $username === false ) {
287 // We have to check every domain, because at least LdapAuthentication
288 // interprets AuthPlugin::userExists() as applying only to the current
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 );
299 $this->auth
->setDomain( $curDomain );
303 public function providerAllowsPropertyChange( $property ) {
304 // No way to know the domain, just hope the provider handles that.
305 return $this->auth
->allowPropChange( $property );
308 public function providerAllowsAuthenticationDataChange(
309 AuthenticationRequest
$req, $checkData = true
311 if ( get_class( $req ) !== $this->requestType
) {
312 return \StatusValue
::newGood( 'ignored' );
315 // Hope it works, AuthPlugin gives us no way to do this.
316 $curDomain = $this->auth
->getDomain();
317 $this->setDomain( $req );
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' );
325 return \StatusValue
::newGood();
328 if ( $this->hasDomain
) {
329 if ( $req->domain
=== null ) {
330 return \StatusValue
::newGood( 'ignored' );
332 if ( !$this->auth
->validDomain( $req->domain
) ) {
333 return \StatusValue
::newFatal( 'authmanager-authplugin-setpass-bad-domain' );
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' );
344 $sv->merge( $this->checkPasswordValidity( $username, $req->password
) );
349 return \StatusValue
::newGood( 'ignored' );
352 $this->auth
->setDomain( $curDomain );
356 public function providerChangeAuthenticationData( AuthenticationRequest
$req ) {
357 if ( get_class( $req ) === $this->requestType
) {
358 $username = $req->username
!== null ? User
::getCanonicalName( $req->username
, 'usable' ) : false;
359 if ( $username === false ) {
363 if ( $this->hasDomain
&& $req->domain
=== null ) {
367 $this->setDomain( $req );
368 $user = User
::newFromName( $username );
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
373 throw new \
ErrorPageError(
374 'authmanager-authplugin-setpass-failed-title',
375 'authmanager-authplugin-setpass-failed-message'
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
;
386 public function testForAccountCreation( $user, $creator, array $reqs ) {
387 return \StatusValue
::newGood();
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' );
395 $req = AuthenticationRequest
::getRequestByClass( $reqs, $this->requestType
);
396 if ( !$req ||
$req->username
=== null ||
$req->password
=== null ||
397 ( $this->hasDomain
&& $req->domain
=== null )
399 return AuthenticationResponse
::newAbstain();
402 $username = User
::getCanonicalName( $req->username
, 'usable' );
403 if ( $username === false ) {
404 return AuthenticationResponse
::newAbstain();
407 $this->setDomain( $req );
408 if ( $this->auth
->addUser(
409 $user, $req->password
, $user->getEmail(), $user->getRealName()
411 return AuthenticationResponse
::newPass();
413 return AuthenticationResponse
::newFail(
414 new \
Message( 'authmanager-authplugin-create-fail' )
419 public function autoCreatedAccount( $user, $source ) {
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!'