diff options
Diffstat (limited to 'www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php')
-rw-r--r-- | www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php b/www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php new file mode 100644 index 00000000..cd0734d8 --- /dev/null +++ b/www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php @@ -0,0 +1,429 @@ +<?php +/** + * Primary authentication provider wrapper for AuthPlugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Auth + */ + +namespace MediaWiki\Auth; + +use AuthPlugin; +use User; + +/** + * Primary authentication provider wrapper for AuthPlugin + * @warning If anything depends on the wrapped AuthPlugin being $wgAuth, it won't work with this! + * @ingroup Auth + * @since 1.27 + * @deprecated since 1.27 + */ +class AuthPluginPrimaryAuthenticationProvider + extends AbstractPasswordPrimaryAuthenticationProvider +{ + private $auth; + private $hasDomain; + private $requestType = null; + + /** + * @param AuthPlugin $auth AuthPlugin to wrap + * @param string|null $requestType Class name of the + * PasswordAuthenticationRequest to use. If $auth->domainList() returns + * more than one domain, this must be a PasswordDomainAuthenticationRequest. + */ + public function __construct( AuthPlugin $auth, $requestType = null ) { + parent::__construct(); + + if ( $auth instanceof AuthManagerAuthPlugin ) { + throw new \InvalidArgumentException( + 'Trying to wrap AuthManagerAuthPlugin in AuthPluginPrimaryAuthenticationProvider ' . + 'makes no sense.' + ); + } + + $need = count( $auth->domainList() ) > 1 + ? PasswordDomainAuthenticationRequest::class + : PasswordAuthenticationRequest::class; + if ( $requestType === null ) { + $requestType = $need; + } elseif ( $requestType !== $need && !is_subclass_of( $requestType, $need ) ) { + throw new \InvalidArgumentException( "$requestType is not a $need" ); + } + + $this->auth = $auth; + $this->requestType = $requestType; + $this->hasDomain = ( + $requestType === PasswordDomainAuthenticationRequest::class || + is_subclass_of( $requestType, PasswordDomainAuthenticationRequest::class ) + ); + $this->authoritative = $auth->strict(); + + // Registering hooks from core is unusual, but is needed here to be + // able to call the AuthPlugin methods those hooks replace. + \Hooks::register( 'UserSaveSettings', [ $this, 'onUserSaveSettings' ] ); + \Hooks::register( 'UserGroupsChanged', [ $this, 'onUserGroupsChanged' ] ); + \Hooks::register( 'UserLoggedIn', [ $this, 'onUserLoggedIn' ] ); + \Hooks::register( 'LocalUserCreated', [ $this, 'onLocalUserCreated' ] ); + } + + /** + * Create an appropriate AuthenticationRequest + * @return PasswordAuthenticationRequest + */ + protected function makeAuthReq() { + $class = $this->requestType; + if ( $this->hasDomain ) { + return new $class( $this->auth->domainList() ); + } else { + return new $class(); + } + } + + /** + * Call $this->auth->setDomain() + * @param PasswordAuthenticationRequest $req + */ + protected function setDomain( $req ) { + if ( $this->hasDomain ) { + $domain = $req->domain; + } else { + // Just grab the first one. + $domainList = $this->auth->domainList(); + $domain = reset( $domainList ); + } + + // Special:UserLogin does this. Strange. + if ( !$this->auth->validDomain( $domain ) ) { + $domain = $this->auth->getDomain(); + } + $this->auth->setDomain( $domain ); + } + + /** + * Hook function to call AuthPlugin::updateExternalDB() + * @param User $user + * @codeCoverageIgnore + */ + public function onUserSaveSettings( $user ) { + // No way to know the domain, just hope the provider handles that. + $this->auth->updateExternalDB( $user ); + } + + /** + * Hook function to call AuthPlugin::updateExternalDBGroups() + * @param User $user + * @param array $added + * @param array $removed + */ + public function onUserGroupsChanged( $user, $added, $removed ) { + // No way to know the domain, just hope the provider handles that. + $this->auth->updateExternalDBGroups( $user, $added, $removed ); + } + + /** + * Hook function to call AuthPlugin::updateUser() + * @param User $user + */ + public function onUserLoggedIn( $user ) { + $hookUser = $user; + // No way to know the domain, just hope the provider handles that. + $this->auth->updateUser( $hookUser ); + if ( $hookUser !== $user ) { + throw new \UnexpectedValueException( + get_class( $this->auth ) . '::updateUser() tried to replace $user!' + ); + } + } + + /** + * Hook function to call AuthPlugin::initUser() + * @param User $user + * @param bool $autocreated + */ + public function onLocalUserCreated( $user, $autocreated ) { + // For $autocreated, see self::autoCreatedAccount() + if ( !$autocreated ) { + $hookUser = $user; + // No way to know the domain, just hope the provider handles that. + $this->auth->initUser( $hookUser, $autocreated ); + if ( $hookUser !== $user ) { + throw new \UnexpectedValueException( + get_class( $this->auth ) . '::initUser() tried to replace $user!' + ); + } + } + } + + public function getUniqueId() { + return parent::getUniqueId() . ':' . get_class( $this->auth ); + } + + public function getAuthenticationRequests( $action, array $options ) { + switch ( $action ) { + case AuthManager::ACTION_LOGIN: + case AuthManager::ACTION_CREATE: + return [ $this->makeAuthReq() ]; + + case AuthManager::ACTION_CHANGE: + case AuthManager::ACTION_REMOVE: + // No way to know the domain, just hope the provider handles that. + return $this->auth->allowPasswordChange() ? [ $this->makeAuthReq() ] : []; + + default: + return []; + } + } + + public function beginPrimaryAuthentication( array $reqs ) { + $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType ); + if ( !$req || $req->username === null || $req->password === null || + ( $this->hasDomain && $req->domain === null ) + ) { + return AuthenticationResponse::newAbstain(); + } + + $username = User::getCanonicalName( $req->username, 'usable' ); + if ( $username === false ) { + return AuthenticationResponse::newAbstain(); + } + + $this->setDomain( $req ); + if ( $this->testUserCanAuthenticateInternal( User::newFromName( $username ) ) && + $this->auth->authenticate( $username, $req->password ) + ) { + return AuthenticationResponse::newPass( $username ); + } else { + $this->authoritative = $this->auth->strict() || $this->auth->strictUserAuth( $username ); + return $this->failResponse( $req ); + } + } + + public function testUserCanAuthenticate( $username ) { + $username = User::getCanonicalName( $username, 'usable' ); + if ( $username === false ) { + return false; + } + + // We have to check every domain, because at least LdapAuthentication + // interprets AuthPlugin::userExists() as applying only to the current + // domain. + $curDomain = $this->auth->getDomain(); + $domains = $this->auth->domainList() ?: [ '' ]; + foreach ( $domains as $domain ) { + $this->auth->setDomain( $domain ); + if ( $this->testUserCanAuthenticateInternal( User::newFromName( $username ) ) ) { + $this->auth->setDomain( $curDomain ); + return true; + } + } + $this->auth->setDomain( $curDomain ); + return false; + } + + /** + * @see self::testUserCanAuthenticate + * @note The caller is responsible for calling $this->auth->setDomain() + * @param User $user + * @return bool + */ + private function testUserCanAuthenticateInternal( $user ) { + if ( $this->auth->userExists( $user->getName() ) ) { + return !$this->auth->getUserInstance( $user )->isLocked(); + } else { + return false; + } + } + + public function providerRevokeAccessForUser( $username ) { + $username = User::getCanonicalName( $username, 'usable' ); + if ( $username === false ) { + return; + } + $user = User::newFromName( $username ); + if ( $user ) { + // Reset the password on every domain. + $curDomain = $this->auth->getDomain(); + $domains = $this->auth->domainList() ?: [ '' ]; + $failed = []; + foreach ( $domains as $domain ) { + $this->auth->setDomain( $domain ); + if ( $this->testUserCanAuthenticateInternal( $user ) && + !$this->auth->setPassword( $user, null ) + ) { + $failed[] = $domain === '' ? '(default)' : $domain; + } + } + $this->auth->setDomain( $curDomain ); + if ( $failed ) { + throw new \UnexpectedValueException( + "AuthPlugin failed to reset password for $username in the following domains: " + . implode( ' ', $failed ) + ); + } + } + } + + public function testUserExists( $username, $flags = User::READ_NORMAL ) { + $username = User::getCanonicalName( $username, 'usable' ); + if ( $username === false ) { + return false; + } + + // We have to check every domain, because at least LdapAuthentication + // interprets AuthPlugin::userExists() as applying only to the current + // domain. + $curDomain = $this->auth->getDomain(); + $domains = $this->auth->domainList() ?: [ '' ]; + foreach ( $domains as $domain ) { + $this->auth->setDomain( $domain ); + if ( $this->auth->userExists( $username ) ) { + $this->auth->setDomain( $curDomain ); + return true; + } + } + $this->auth->setDomain( $curDomain ); + return false; + } + + public function providerAllowsPropertyChange( $property ) { + // No way to know the domain, just hope the provider handles that. + return $this->auth->allowPropChange( $property ); + } + + public function providerAllowsAuthenticationDataChange( + AuthenticationRequest $req, $checkData = true + ) { + if ( get_class( $req ) !== $this->requestType ) { + return \StatusValue::newGood( 'ignored' ); + } + + // Hope it works, AuthPlugin gives us no way to do this. + $curDomain = $this->auth->getDomain(); + $this->setDomain( $req ); + try { + // If !$checkData the domain might be wrong. Nothing we can do about that. + if ( !$this->auth->allowPasswordChange() ) { + return \StatusValue::newFatal( 'authmanager-authplugin-setpass-denied' ); + } + + if ( !$checkData ) { + return \StatusValue::newGood(); + } + + if ( $this->hasDomain ) { + if ( $req->domain === null ) { + return \StatusValue::newGood( 'ignored' ); + } + if ( !$this->auth->validDomain( $req->domain ) ) { + return \StatusValue::newFatal( 'authmanager-authplugin-setpass-bad-domain' ); + } + } + + $username = User::getCanonicalName( $req->username, 'usable' ); + if ( $username !== false ) { + $sv = \StatusValue::newGood(); + if ( $req->password !== null ) { + if ( $req->password !== $req->retype ) { + $sv->fatal( 'badretype' ); + } else { + $sv->merge( $this->checkPasswordValidity( $username, $req->password ) ); + } + } + return $sv; + } else { + return \StatusValue::newGood( 'ignored' ); + } + } finally { + $this->auth->setDomain( $curDomain ); + } + } + + public function providerChangeAuthenticationData( AuthenticationRequest $req ) { + if ( get_class( $req ) === $this->requestType ) { + $username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false; + if ( $username === false ) { + return; + } + + if ( $this->hasDomain && $req->domain === null ) { + return; + } + + $this->setDomain( $req ); + $user = User::newFromName( $username ); + if ( !$this->auth->setPassword( $user, $req->password ) ) { + // This is totally unfriendly and leaves other + // AuthenticationProviders in an uncertain state, but what else + // can we do? + throw new \ErrorPageError( + 'authmanager-authplugin-setpass-failed-title', + 'authmanager-authplugin-setpass-failed-message' + ); + } + } + } + + public function accountCreationType() { + // No way to know the domain, just hope the provider handles that. + return $this->auth->canCreateAccounts() ? self::TYPE_CREATE : self::TYPE_NONE; + } + + public function testForAccountCreation( $user, $creator, array $reqs ) { + return \StatusValue::newGood(); + } + + public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) { + if ( $this->accountCreationType() === self::TYPE_NONE ) { + throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' ); + } + + $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType ); + if ( !$req || $req->username === null || $req->password === null || + ( $this->hasDomain && $req->domain === null ) + ) { + return AuthenticationResponse::newAbstain(); + } + + $username = User::getCanonicalName( $req->username, 'usable' ); + if ( $username === false ) { + return AuthenticationResponse::newAbstain(); + } + + $this->setDomain( $req ); + if ( $this->auth->addUser( + $user, $req->password, $user->getEmail(), $user->getRealName() + ) ) { + return AuthenticationResponse::newPass(); + } else { + return AuthenticationResponse::newFail( + new \Message( 'authmanager-authplugin-create-fail' ) + ); + } + } + + public function autoCreatedAccount( $user, $source ) { + $hookUser = $user; + // No way to know the domain, just hope the provider handles that. + $this->auth->initUser( $hookUser, true ); + if ( $hookUser !== $user ) { + throw new \UnexpectedValueException( + get_class( $this->auth ) . '::initUser() tried to replace $user!' + ); + } + } +} |