summaryrefslogtreecommitdiff
path: root/www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php')
-rw-r--r--www/wiki/includes/auth/AuthPluginPrimaryAuthenticationProvider.php429
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!'
+ );
+ }
+ }
+}