diff options
Diffstat (limited to 'MLEB/Translate/utils/TranslateSandbox.php')
-rw-r--r-- | MLEB/Translate/utils/TranslateSandbox.php | 163 |
1 files changed, 123 insertions, 40 deletions
diff --git a/MLEB/Translate/utils/TranslateSandbox.php b/MLEB/Translate/utils/TranslateSandbox.php index 12d3c0d6..999c4a3e 100644 --- a/MLEB/Translate/utils/TranslateSandbox.php +++ b/MLEB/Translate/utils/TranslateSandbox.php @@ -4,15 +4,23 @@ * * @file * @author Niklas Laxström - * @license GPL-2.0+ + * @license GPL-2.0-or-later */ +use MediaWiki\Auth\AuthManager; +use MediaWiki\Auth\AuthenticationRequest; +use MediaWiki\Auth\AuthenticationResponse; + /** - * Utility class for the sandbox feature of Translate. + * Utility class for the sandbox feature of Translate. Do not try this yourself. This code makes a + * lot of assumptions about what happens to the user account. */ class TranslateSandbox { + public static $userToCreate = null; + /** * Adds a new user without doing much validation. + * * @param string $name User name. * @param string $email Email address. * @param string $password User provided password. @@ -21,22 +29,51 @@ class TranslateSandbox { */ public static function addUser( $name, $email, $password ) { $user = User::newFromName( $name, 'creatable' ); + if ( !$user instanceof User ) { - throw new MWException( "Invalid user name" ); + throw new MWException( 'Invalid user name' ); } - $user->setEmail( $email ); - $user->setPassword( $password ); - $status = $user->addToDatabase(); - - if ( !$status->isOK() ) { - throw new MWException( $status->getWikiText() ); + $data = [ + 'username' => $user->getName(), + 'password' => $password, + 'retype' => $password, + 'email' => $email, + 'realname' => '', + ]; + + self::$userToCreate = $user; + $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_CREATE ); + $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data ); + $res = AuthManager::singleton()->beginAccountCreation( $user, $reqs, 'null:' ); + self::$userToCreate = null; + + switch ( $res->status ) { + case AuthenticationResponse::PASS: + break; + case AuthenticationResponse::FAIL: + // Unless things are misconfigured, this will handle errors such as username taken, + // invalid user name or too short password. The WebAPI is prechecking these to + // provide nicer error messages. + $reason = $res->message->inLanguage( 'en' )->useDatabase( false )->text(); + throw new MWException( "Account creation failed: $reason" ); + default: + // Just in case it was a Secondary that failed + $user->clearInstanceCache( 'name' ); + if ( $user->getId() ) { + self::deleteUser( $user, 'force' ); + } + throw new MWException( + 'AuthManager does not support such simplified account creation' + ); } - // Need to have an id first - $user->addGroup( 'translate-sandboxed' ); + // User now has an id, but we must clear the cache to see it. Without this the group + // addition below would not be saved in the database. $user->clearInstanceCache( 'name' ); - $user->sendConfirmationMail(); + + // group-translate-sandboxed group-translate-sandboxed-member + $user->addGroup( 'translate-sandboxed' ); return $user; } @@ -50,15 +87,32 @@ class TranslateSandbox { */ public static function deleteUser( User $user, $force = '' ) { $uid = $user->getId(); + $username = $user->getName(); if ( $force !== 'force' && !self::isSandboxed( $user ) ) { - throw new MWException( "Not a sandboxed user" ); + throw new MWException( 'Not a sandboxed user' ); } // Delete from database $dbw = wfGetDB( DB_MASTER ); - $dbw->delete( 'user', array( 'user_id' => $uid ), __METHOD__ ); - $dbw->delete( 'user_groups', array( 'ug_user' => $uid ), __METHOD__ ); + $dbw->delete( 'user', [ 'user_id' => $uid ], __METHOD__ ); + $dbw->delete( 'user_groups', [ 'ug_user' => $uid ], __METHOD__ ); + $dbw->delete( 'user_properties', [ 'up_user' => $uid ], __METHOD__ ); + + if ( class_exists( ActorMigration::class ) ) { + $m = ActorMigration::newMigration(); + + // Assume no joins are needed for logging or recentchanges + $dbw->delete( 'logging', $m->getWhere( $dbw, 'log_user', $user )['conds'], __METHOD__ ); + $dbw->delete( 'recentchanges', $m->getWhere( $dbw, 'rc_user', $user )['conds'], __METHOD__ ); + } else { + $dbw->delete( 'logging', [ 'log_user' => $uid ], __METHOD__ ); + $dbw->delete( + 'recentchanges', + [ 'rc_user' => $uid, 'rc_user_text' => $username ], + __METHOD__ + ); + } // If someone tries to access still object still, they will get anon user // data. @@ -66,10 +120,7 @@ class TranslateSandbox { // Nobody should access the user by id anymore, but in case they do, purge // the cache so they wont get stale data - // @todo why the bunny is this private?! - // $user->clearSharedCache(); - global $wgMemc; - $wgMemc->delete( wfMemcKey( 'user', 'id', $uid ) ); + $user->invalidateCache(); // In case we create an user with same name as was deleted during the same // request, we must also reset this cache or the User class will try to load @@ -87,15 +138,26 @@ class TranslateSandbox { * @return UserArray List of users. */ public static function getUsers() { - $dbw = wfGetDB( DB_MASTER ); - $tables = array( 'user', 'user_groups' ); - $fields = User::selectFields(); - $conds = array( + $dbw = TranslateUtils::getSafeReadDB(); + if ( is_callable( [ User::class, 'getQueryInfo' ] ) ) { + $userQuery = User::getQueryInfo(); + } else { + $userQuery = [ + 'tables' => [ 'user' ], + 'fields' => User::selectFields(), + 'joins' => [], + ]; + } + $tables = array_merge( $userQuery['tables'], [ 'user_groups' ] ); + $fields = $userQuery['fields']; + $conds = [ 'ug_group' => 'translate-sandboxed', - 'ug_user = user_id', - ); + ]; + $joins = [ + 'user_groups' => [ 'JOIN', 'ug_user = user_id' ], + ] + $userQuery['joins']; - $res = $dbw->select( $tables, $fields, $conds, __METHOD__ ); + $res = $dbw->select( $tables, $fields, $conds, __METHOD__, [], $joins ); return UserArray::newFromResult( $res ); } @@ -109,7 +171,7 @@ class TranslateSandbox { global $wgTranslateSandboxPromotedGroup; if ( !self::isSandboxed( $user ) ) { - throw new MWException( "Not a sandboxed user" ); + throw new MWException( 'Not a sandboxed user' ); } $user->removeGroup( 'translate-sandboxed' ); @@ -165,19 +227,19 @@ class TranslateSandbox { $body = wfMessage( $bodyMsg, $target->getName(), - SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalUrl(), + SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalURL(), $sender->getName() )->inLanguage( $targetLang )->text(); - $params = array( + $params = [ 'user' => $target->getId(), - 'to' => new MailAddress( $target ), - 'from' => new MailAddress( $sender ), + 'to' => MailAddress::newFromUser( $target ), + 'from' => MailAddress::newFromUser( $sender ), 'replyto' => new MailAddress( $wgNoReplyAddress ), 'subj' => $subject, 'body' => $body, 'emailType' => $type, - ); + ]; JobQueueGroup::singleton()->push( TranslateSandboxEmailJob::newJob( $params ) ); } @@ -196,7 +258,12 @@ class TranslateSandbox { return false; } - /// Hook: UserGetRights + /** + * Hook: UserGetRights + * @param User $user + * @param array &$rights + * @return true + */ public static function enforcePermissions( User $user, array &$rights ) { global $wgTranslateUseSandbox; @@ -208,7 +275,8 @@ class TranslateSandbox { return true; } - $rights = array( + // right-translate-sandboxaction action-translate-sandboxaction + $rights = [ 'editmyoptions', 'editmyprivateinfo', 'read', @@ -216,16 +284,23 @@ class TranslateSandbox { 'translate-sandboxaction', 'viewmyprivateinfo', 'writeapi', - ); + ]; // Do not let other hooks add more actions return false; } + /// Hook: UserGetRights + public static function allowAccountCreation( $user, &$rights ) { + if ( self::$userToCreate && $user->equals( self::$userToCreate ) ) { + $rights[] = 'createaccount'; + } + } + /// Hook: onGetPreferences public static function onGetPreferences( $user, &$preferences ) { $preferences['translate-sandbox'] = $preferences['translate-sandbox-reminders'] = - array( 'type' => 'api' ); + [ 'type' => 'api' ]; return true; } @@ -233,19 +308,27 @@ class TranslateSandbox { /** * Whitelisting for certain API modules. See also enforcePermissions. * Hook: ApiCheckCanExecute + * @param ApiBase $module + * @param User $user + * @param string &$message + * @return bool */ public static function onApiCheckCanExecute( ApiBase $module, User $user, &$message ) { - $whitelist = array( + $whitelist = [ // Obviously this is needed to get out of the sandbox 'ApiTranslationStash', // Used by UniversalLanguageSelector for example 'ApiOptions' - ); + ]; - if ( TranslateSandbox::isSandboxed( $user ) ) { + if ( self::isSandboxed( $user ) ) { $class = get_class( $module ); if ( $module->isWriteMode() && !in_array( $class, $whitelist, true ) ) { - $message = 'writerequired'; + $message = ApiMessage::create( 'apierror-writeapidenied' ); + if ( $message->getApiCode() === 'apierror-writeapidenied' ) { + // Backwards compatibility for pre-1.29 MediaWiki + $message = 'writerequired'; + } return false; } } |