TempUser UI tweaks
* In PermissionManager, if a user is anonymous but temporary user creation is possible, grant elevated permissions at RIGOR_QUICK rigor level. This is mostly to make skins show "edit" instead of "view source" to anonymous users in the recommended permissions configuration. * Present temporary users as if they are not logged in in various places in the interface: create/move permissions errors, login, preferences, watchlist, BotPasswords, ChangeEmail, ResetTokens. * Show a warning on login/logout about loss of access to the temp account. * On login, don't show the temporary name as a suggestion for the login username. Change-Id: Id0d5ffa46c3ca5c7b30d540cedbaa528b682aa85
This commit is contained in:
parent
d6a3b6cfa8
commit
6c1f5462f7
20 changed files with 189 additions and 50 deletions
|
|
@ -40,6 +40,7 @@ For notes on 1.38.x and older releases, see HISTORY.
|
|||
* …
|
||||
|
||||
=== New user-facing features in 1.39 ===
|
||||
* Optional automatic user creation on page save ($wgAutoCreateTempUser)
|
||||
* Administrators now have the option to delete/undelete the associated "Talk"
|
||||
page when they are (un)deleting a given page. `deletetalk` and `undeletetalk`
|
||||
options were added to the 'delete' and 'undelete' action APIs in MW 1.38.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ use MediaWiki\Page\PageIdentity;
|
|||
use MediaWiki\Page\RedirectLookup;
|
||||
use MediaWiki\Session\SessionManager;
|
||||
use MediaWiki\SpecialPage\SpecialPageFactory;
|
||||
use MediaWiki\User\TempUser\TempUserConfig;
|
||||
use MediaWiki\User\UserFactory;
|
||||
use MediaWiki\User\UserGroupManager;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use MessageSpecifier;
|
||||
|
|
@ -114,6 +116,12 @@ class PermissionManager {
|
|||
/** @var TitleFormatter */
|
||||
private $titleFormatter;
|
||||
|
||||
/** @var TempUserConfig */
|
||||
private $tempUserConfig;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/** @var string[][] Cached user rights */
|
||||
private $usersRights = [];
|
||||
|
||||
|
|
@ -228,6 +236,8 @@ class PermissionManager {
|
|||
* @param RedirectLookup $redirectLookup
|
||||
* @param RestrictionStore $restrictionStore
|
||||
* @param TitleFormatter $titleFormatter
|
||||
* @param TempUserConfig $tempUserConfig
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct(
|
||||
ServiceOptions $options,
|
||||
|
|
@ -240,7 +250,9 @@ class PermissionManager {
|
|||
UserCache $userCache,
|
||||
RedirectLookup $redirectLookup,
|
||||
RestrictionStore $restrictionStore,
|
||||
TitleFormatter $titleFormatter
|
||||
TitleFormatter $titleFormatter,
|
||||
TempUserConfig $tempUserConfig,
|
||||
UserFactory $userFactory
|
||||
) {
|
||||
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
||||
$this->options = $options;
|
||||
|
|
@ -254,6 +266,8 @@ class PermissionManager {
|
|||
$this->redirectLookup = $redirectLookup;
|
||||
$this->restrictionStore = $restrictionStore;
|
||||
$this->titleFormatter = $titleFormatter;
|
||||
$this->tempUserConfig = $tempUserConfig;
|
||||
$this->userFactory = $userFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -409,6 +423,17 @@ class PermissionManager {
|
|||
throw new Exception( "Invalid rigor parameter '$rigor'." );
|
||||
}
|
||||
|
||||
// With RIGOR_QUICK we can assume automatic account creation will
|
||||
// occur. At a higher rigor level, the caller is required to opt
|
||||
// in by either setting the create intent or actually creating
|
||||
// the account.
|
||||
if ( $rigor === self::RIGOR_QUICK
|
||||
&& !$user->isRegistered()
|
||||
&& $this->tempUserConfig->isAutoCreateAction( $action )
|
||||
) {
|
||||
$user = $this->userFactory->newTempPlaceholder();
|
||||
}
|
||||
|
||||
# Read has special handling
|
||||
if ( $action == 'read' ) {
|
||||
$checks = [
|
||||
|
|
@ -888,7 +913,7 @@ class PermissionManager {
|
|||
( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
|
||||
!$this->userHasRight( $user, 'createpage' ) )
|
||||
) {
|
||||
$errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
|
||||
$errors[] = $user->isNamed() ? [ 'nocreate-loggedin' ] : [ 'nocreatetext' ];
|
||||
}
|
||||
} elseif ( $action == 'move' ) {
|
||||
if ( !$this->userHasRight( $user, 'move-rootuserpages' )
|
||||
|
|
@ -914,11 +939,20 @@ class PermissionManager {
|
|||
|
||||
if ( !$this->userHasRight( $user, 'move' ) ) {
|
||||
// User can't move anything
|
||||
$userCanMove = $this->groupHasPermission( 'user', 'move' );
|
||||
$autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
|
||||
if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
|
||||
$userCanMove = $this->groupPermissionsLookup
|
||||
->groupHasPermission( 'user', 'move' );
|
||||
$namedCanMove = $this->groupPermissionsLookup
|
||||
->groupHasPermission( 'named', 'move' );
|
||||
$autoconfirmedCanMove = $this->groupPermissionsLookup
|
||||
->groupHasPermission( 'autoconfirmed', 'move' );
|
||||
if ( $user->isAnon()
|
||||
&& ( $userCanMove || $namedCanMove || $autoconfirmedCanMove )
|
||||
) {
|
||||
// custom message if logged-in users without any special rights can move
|
||||
$errors[] = [ 'movenologintext' ];
|
||||
} elseif ( $user->isTemp() && ( $namedCanMove || $autoconfirmedCanMove ) ) {
|
||||
// Temp user may be able to move if they log in as a proper account
|
||||
$errors[] = [ 'movenologintext' ];
|
||||
} else {
|
||||
$errors[] = [ 'movenotallowed' ];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1395,7 +1395,9 @@ return [
|
|||
$services->getUserCache(),
|
||||
$services->getRedirectLookup(),
|
||||
$services->getRestrictionStore(),
|
||||
$services->getTitleFormatter()
|
||||
$services->getTitleFormatter(),
|
||||
$services->getTempUserConfig(),
|
||||
$services->getUserFactory()
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -94,8 +94,7 @@ class WatchAction extends FormAction {
|
|||
}
|
||||
|
||||
protected function checkCanExecute( User $user ) {
|
||||
// Must be logged in
|
||||
if ( $user->isAnon() ) {
|
||||
if ( !$user->isNamed() ) {
|
||||
throw new UserNotLoggedIn( 'watchlistanontext', 'watchnologin' );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -322,7 +322,11 @@ class CookieSessionProvider extends SessionProvider {
|
|||
public function suggestLoginUsername( WebRequest $request ) {
|
||||
$name = $this->getCookie( $request, 'UserName', $this->cookieOptions['prefix'] );
|
||||
if ( $name !== null ) {
|
||||
$name = $this->userNameUtils->getCanonical( $name, UserRigorOptions::RIGOR_USABLE );
|
||||
if ( $this->userNameUtils->isTemp( $name ) ) {
|
||||
$name = false;
|
||||
} else {
|
||||
$name = $this->userNameUtils->getCanonical( $name, UserRigorOptions::RIGOR_USABLE );
|
||||
}
|
||||
}
|
||||
return $name === false ? null : $name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,12 +259,16 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
* is present). People who often switch between several accounts have grown
|
||||
* accustomed to this behavior.
|
||||
*
|
||||
* For temporary users, the form is always shown, since the UI presents
|
||||
* temporary users as not logged in and offers to discard their temporary
|
||||
* account by logging in.
|
||||
*
|
||||
* Also make an exception when force=<level> is set in the URL, which means the user must
|
||||
* reauthenticate for security reasons.
|
||||
*/
|
||||
if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
|
||||
( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
|
||||
$this->getUser()->isRegistered()
|
||||
!$this->getUser()->isTemp() && $this->getUser()->isRegistered()
|
||||
) {
|
||||
$this->successfulAction();
|
||||
return;
|
||||
|
|
@ -328,7 +332,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
switch ( $response->status ) {
|
||||
case AuthenticationResponse::PASS:
|
||||
$this->logAuthResult( true );
|
||||
$this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
|
||||
$this->proxyAccountCreation = $this->isSignup() && $this->getUser()->isNamed();
|
||||
$this->targetUser = User::newFromName( $response->username );
|
||||
|
||||
if (
|
||||
|
|
@ -566,6 +570,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
if (
|
||||
!$this->isSignup() &&
|
||||
$this->getUser()->isRegistered() &&
|
||||
!$this->getUser()->isTemp() &&
|
||||
$this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE
|
||||
) {
|
||||
$reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
|
||||
|
|
@ -728,9 +733,9 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
* @return array
|
||||
*/
|
||||
protected function getFieldDefinitions( array $fieldInfo ) {
|
||||
$isRegistered = $this->getUser()->isRegistered();
|
||||
$isLoggedIn = $this->getUser()->isRegistered();
|
||||
$continuePart = $this->isContinued() ? 'continue-' : '';
|
||||
$anotherPart = $isRegistered ? 'another-' : '';
|
||||
$anotherPart = $isLoggedIn ? 'another-' : '';
|
||||
// @phan-suppress-next-line PhanUndeclaredMethod
|
||||
$expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
|
||||
$expirationDays = ceil( $expiration / ( 3600 * 24 ) );
|
||||
|
|
@ -763,7 +768,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
'username' => [
|
||||
'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
|
||||
'id' => 'wpName2',
|
||||
'placeholder-message' => $isRegistered ? 'createacct-another-username-ph'
|
||||
'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
|
||||
: 'userlogin-yourname-ph',
|
||||
],
|
||||
'mailpassword' => [
|
||||
|
|
@ -829,7 +834,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
],
|
||||
'realname' => [
|
||||
'type' => 'text',
|
||||
'help-message' => $isRegistered ? 'createacct-another-realname-tip'
|
||||
'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
|
||||
: 'prefs-help-realname',
|
||||
'label-message' => 'createacct-realname',
|
||||
'cssclass' => 'loginText',
|
||||
|
|
@ -981,6 +986,17 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
'weight' => -100,
|
||||
];
|
||||
}
|
||||
if ( $this->isSignup() && $this->getUser()->isTemp() ) {
|
||||
$fieldDefinitions['tempWarning'] = [
|
||||
'type' => 'info',
|
||||
'default' => Html::warningBox(
|
||||
$this->msg( 'createacct-temp-warning' )->parse()
|
||||
),
|
||||
'raw' => true,
|
||||
'rawrow' => true,
|
||||
'weight' => -90,
|
||||
];
|
||||
}
|
||||
if ( !$this->showExtraInformation() ) {
|
||||
unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
|
||||
}
|
||||
|
|
@ -1025,26 +1041,27 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
if ( $this->mLanguage ) {
|
||||
$linkq .= '&uselang=' . urlencode( $this->mLanguage );
|
||||
}
|
||||
$isRegistered = $this->getUser()->isRegistered();
|
||||
$isLoggedIn = $this->getUser()->isRegistered()
|
||||
&& !$this->getUser()->isTemp();
|
||||
|
||||
$fieldDefinitions['createOrLogin'] = [
|
||||
'type' => 'info',
|
||||
'raw' => true,
|
||||
'linkQuery' => $linkq,
|
||||
'default' => function ( $params ) use ( $isRegistered, $linkTitle ) {
|
||||
'default' => function ( $params ) use ( $isLoggedIn, $linkTitle ) {
|
||||
return Html::rawElement( 'div',
|
||||
[ 'id' => 'mw-createaccount' . ( !$isRegistered ? '-cta' : '' ),
|
||||
'class' => ( $isRegistered ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
|
||||
( $isRegistered ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
|
||||
[ 'id' => 'mw-createaccount' . ( !$isLoggedIn ? '-cta' : '' ),
|
||||
'class' => ( $isLoggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
|
||||
( $isLoggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
|
||||
. Html::element( 'a',
|
||||
[
|
||||
'id' => 'mw-createaccount-join' . ( $isRegistered ? '-loggedin' : '' ),
|
||||
'id' => 'mw-createaccount-join' . ( $isLoggedIn ? '-loggedin' : '' ),
|
||||
'href' => $linkTitle->getLocalURL( $params['linkQuery'] ),
|
||||
'class' => ( $isRegistered ? '' : 'mw-ui-button' ),
|
||||
'class' => ( $isLoggedIn ? '' : 'mw-ui-button' ),
|
||||
'tabindex' => 100,
|
||||
],
|
||||
$this->msg(
|
||||
$isRegistered ? 'userlogin-createanother' : 'userlogin-joinproject'
|
||||
$isLoggedIn ? 'userlogin-createanother' : 'userlogin-joinproject'
|
||||
)->text()
|
||||
)
|
||||
);
|
||||
|
|
@ -1177,7 +1194,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
|
|||
!$this->isSignup()
|
||||
) {
|
||||
$user = $this->getUser();
|
||||
if ( $user->isRegistered() ) {
|
||||
if ( $user->isRegistered() && !$user->isTemp() ) {
|
||||
$formDescriptor['username']['default'] = $user->getName();
|
||||
} else {
|
||||
$formDescriptor['username']['default'] =
|
||||
|
|
|
|||
|
|
@ -418,6 +418,22 @@ class SpecialPage implements MessageLocalizer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is not logged in or is a temporary user, throws UserNotLoggedIn
|
||||
*
|
||||
* @since 1.39
|
||||
* @param string $reasonMsg [optional] Message key to be displayed on login page
|
||||
* @param string $titleMsg [optional] Passed on to UserNotLoggedIn constructor
|
||||
* @throws UserNotLoggedIn
|
||||
*/
|
||||
public function requireNamedUser(
|
||||
$reasonMsg = 'exception-nologin-text', $titleMsg = 'exception-nologin'
|
||||
) {
|
||||
if ( !$this->getUser()->isNamed() ) {
|
||||
throw new UserNotLoggedIn( $reasonMsg, $titleMsg );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the special page does something security-sensitive and needs extra defense against
|
||||
* a stolen account (e.g. a reauthentication). What exactly that will mean is decided by the
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class SpecialBotPasswords extends FormSpecialPage {
|
|||
*/
|
||||
public function execute( $par ) {
|
||||
$this->getOutput()->disallowUserJs();
|
||||
$this->requireLogin();
|
||||
$this->requireNamedUser();
|
||||
$this->addHelpLink( 'Manual:Bot_passwords' );
|
||||
|
||||
$par = trim( $par );
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class SpecialChangeEmail extends FormSpecialPage {
|
|||
throw new ErrorPageError( 'changeemail', 'cannotchangeemail' );
|
||||
}
|
||||
|
||||
$this->requireLogin( 'changeemail-no-info' );
|
||||
$this->requireNamedUser( 'changeemail-no-info' );
|
||||
|
||||
// This could also let someone check the current email address, so
|
||||
// require both permissions.
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
|
|||
$this->setHeaders();
|
||||
|
||||
# Anons don't get a watchlist
|
||||
$this->requireLogin( 'watchlistanontext' );
|
||||
$this->requireNamedUser( 'watchlistanontext' );
|
||||
|
||||
$out = $this->getOutput();
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class SpecialPreferences extends SpecialPage {
|
|||
$out = $this->getOutput();
|
||||
$out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
|
||||
|
||||
$this->requireLogin( 'prefsnologintext2' );
|
||||
$this->requireNamedUser( 'prefsnologintext2' );
|
||||
$this->checkReadOnly();
|
||||
|
||||
if ( $par == 'reset' ) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class SpecialResetTokens extends FormSpecialPage {
|
|||
public function execute( $par ) {
|
||||
// This is a preferences page, so no user JS for y'all.
|
||||
$this->getOutput()->disallowUserJs();
|
||||
$this->requireLogin();
|
||||
$this->requireNamedUser();
|
||||
|
||||
parent::execute( $par );
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ class SpecialUserLogout extends FormSpecialPage {
|
|||
|
||||
public function alterForm( HTMLForm $form ) {
|
||||
$form->setTokenSalt( 'logoutToken' );
|
||||
$form->addHeaderText( $this->msg( 'userlogout-continue' ) );
|
||||
$form->addHeaderHtml( $this->msg(
|
||||
$this->getUser()->isTemp() ? 'userlogout-temp' : 'userlogout-continue'
|
||||
) );
|
||||
|
||||
$form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
|
|||
*/
|
||||
public function execute( $subpage ) {
|
||||
// Anons don't get a watchlist
|
||||
$this->requireLogin( 'watchlistanontext' );
|
||||
$this->requireNamedUser( 'watchlistanontext' );
|
||||
|
||||
$output = $this->getOutput();
|
||||
$request = $this->getRequest();
|
||||
|
|
|
|||
|
|
@ -479,6 +479,7 @@
|
|||
"nocookiesfornew": "The user account was not created, as we could not confirm its source.\nEnsure you have cookies enabled, reload this page and try again.",
|
||||
"nocookiesforlogin": "{{int:nocookieslogin}}",
|
||||
"createacct-loginerror": "The account was successfully created but you could not be logged in automatically. Please proceed to [[Special:UserLogin|manual login]].",
|
||||
"createacct-temp-warning": "The edits you made with your temporary account will not be carried over to your permanent one.",
|
||||
"noname": "You have not specified a valid username.",
|
||||
"loginsuccesstitle": "Logged in",
|
||||
"loginsuccess": "<strong>You are now logged in to {{SITENAME}} as \"$1\".</strong>",
|
||||
|
|
@ -4420,6 +4421,7 @@
|
|||
"deflate-invaliddeflate": "Content provided is not properly deflated",
|
||||
"unprotected-js": "For security reasons JavaScript cannot be loaded from unprotected pages. Please only create javascript in the MediaWiki: namespace or as a User subpage",
|
||||
"userlogout-continue": "Do you want to log out?",
|
||||
"userlogout-temp": "Are you sure you want to log out? There will be no way to log back in to your temporary account.",
|
||||
"paramvalidator-baduser": "Invalid value \"$2\" for user parameter <var>$1</var>.",
|
||||
"paramvalidator-help-type-user": "Type: {{PLURAL:$1|1=user|2=list of users}}, {{PLURAL:$3|by|by any of}} $2",
|
||||
"paramvalidator-help-type-user-subtype-name": "user name",
|
||||
|
|
|
|||
|
|
@ -713,6 +713,7 @@
|
|||
"nocookiesfornew": "This message is displayed when the user tried to create a new account, but it failed the cross-site request forgery (CSRF) check. It could be blocking an attack, but most likely, the browser isn't accepting cookies.",
|
||||
"nocookiesforlogin": "{{optional}}\nThis message is displayed when someone tried to login and the CSRF failed (most likely, the browser doesn't accept cookies).\n\nDefault:\n* {{msg-mw|Nocookieslogin}}",
|
||||
"createacct-loginerror": "This message is displayed after a successful registration when there is a server-side error with logging the user in. This is not expected to happen.",
|
||||
"createacct-temp-warning": "Message shown to temporary users when creating an account.",
|
||||
"noname": "Error message.",
|
||||
"loginsuccesstitle": "The title of the page saying that you are logged in. The content of the page is the message {{msg-mw|Loginsuccess}}.\n{{Identical|Log in}}",
|
||||
"loginsuccess": "The content of the page saying that you are logged in. The title of the page is {{msg-mw|Loginsuccesstitle}}.\n\nParameters:\n* $1 - the name of the logged in user\n{{Gender}}",
|
||||
|
|
@ -4654,6 +4655,7 @@
|
|||
"deflate-invaliddeflate": "Error message if the content passed to Deflate was not deflated (compressed) properly",
|
||||
"unprotected-js": "Error message shown when trying to load javascript via action=raw that is not protected",
|
||||
"userlogout-continue": "Shown if user attempted to log out without a token specified. Probably the user clicked on an old link that hasn't been updated to use the new system. $1 - url that user should click on in order to log out.",
|
||||
"userlogout-temp": "Shown if the a temporary (cookie-only) user is attempting to log out.",
|
||||
"paramvalidator-baduser": "Error in API parameter validation. Parameters:\n* $1 - Parameter name.\n* $2 - Parameter value.",
|
||||
"paramvalidator-help-type-user": "Used to indicate that a parameter is a user or list of users. Parameters:\n* $1 - 1 if the parameter takes one value, 2 if the parameter takes any number of values\n* $2 - List of allowed ways to specify the user, as an 'and'-style list.\n* $3 - Number of items in $2.\n\nSee also:\n* {{msg-mw|paramvalidator-help-type-user-subtype-name}}\n* {{msg-mw|paramvalidator-help-type-user-subtype-ip}}\n* {{msg-mw|paramvalidator-help-type-user-subtype-cidr}}\n* {{msg-mw|paramvalidator-help-type-user-subtype-interwiki}}\n* {{msg-mw|paramvalidator-help-type-user-subtype-id}}",
|
||||
"paramvalidator-help-type-user-subtype-name": "Used with {{msg-mw|paramvalidator-help-type-user}} to indicate that users may be specified by name.",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use MediaWiki\Block\Restriction\NamespaceRestriction;
|
|||
use MediaWiki\Block\Restriction\PageRestriction;
|
||||
use MediaWiki\Block\SystemBlock;
|
||||
use MediaWiki\Cache\CacheKeyHelper;
|
||||
use MediaWiki\Permissions\PermissionManager;
|
||||
use MediaWiki\Revision\MutableRevisionRecord;
|
||||
use MediaWiki\Session\SessionId;
|
||||
use MediaWiki\Session\TestUtils;
|
||||
|
|
@ -427,6 +428,42 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
|
|||
$this->assertTrue( $permissionManager->userCan( 'edit', $this->user, $title ) );
|
||||
}
|
||||
|
||||
public function testAutocreatePermissionsHack() {
|
||||
$this->setMwGlobals( [
|
||||
'wgAutoCreateTempUser' => [
|
||||
'enabled' => true,
|
||||
'actions' => [ 'edit' ],
|
||||
'serialProvider' => [ 'type' => 'local' ],
|
||||
'serialMapping' => [ 'type' => 'plain-numeric' ],
|
||||
'matchPattern' => '*Unregistered $1',
|
||||
'genPattern' => '*Unregistered $1'
|
||||
],
|
||||
'wgGroupPermissions' => [
|
||||
'*' => [ 'edit' => false ],
|
||||
'user' => [ 'edit' => true, 'createpage' => true ],
|
||||
]
|
||||
] );
|
||||
$services = $this->getServiceContainer();
|
||||
$permissionManager = $services->getPermissionManager();
|
||||
$user = $services->getUserFactory()->newAnonymous();
|
||||
$title = $this->getNonexistingTestPage()->getTitle();
|
||||
$this->assertNotEmpty(
|
||||
$permissionManager->getPermissionErrors(
|
||||
'edit',
|
||||
$user,
|
||||
$title
|
||||
)
|
||||
);
|
||||
$this->assertEmpty(
|
||||
$permissionManager->getPermissionErrors(
|
||||
'edit',
|
||||
$user,
|
||||
$title,
|
||||
PermissionManager::RIGOR_QUICK
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestCheckUserBlockActions
|
||||
* @covers \MediaWiki\Permissions\PermissionManager::checkUserBlock
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ class WatchActionTest extends MediaWikiIntegrationTestCase {
|
|||
public function testShowUserLoggedInNoException() {
|
||||
$registeredUser = $this->createMock( User::class );
|
||||
$registeredUser->method( 'isRegistered' )->willReturn( true );
|
||||
$registeredUser->method( 'isNamed' )->willReturn( true );
|
||||
$testContext = new DerivativeContext( $this->watchAction->getContext() );
|
||||
$testContext->setUser( $registeredUser );
|
||||
$watchAction = $this->getWatchAction(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class SpecialPreferencesTest extends MediaWikiIntegrationTestCase {
|
|||
$user = $this->createMock( User::class );
|
||||
$user->method( 'isAnon' )
|
||||
->willReturn( false );
|
||||
$user->method( 'isNamed' )
|
||||
->willReturn( true );
|
||||
|
||||
# The mocked user has a long nickname
|
||||
$user->method( 'getOption' )
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ use MediaWiki\Permissions\PermissionManager;
|
|||
use MediaWiki\Permissions\RestrictionStore;
|
||||
use MediaWiki\SpecialPage\SpecialPageFactory;
|
||||
use MediaWiki\Tests\Unit\DummyServicesTrait;
|
||||
use MediaWiki\User\TempUser\RealTempUserConfig;
|
||||
use MediaWiki\User\UserFactory;
|
||||
use MediaWiki\User\UserGroupManager;
|
||||
use MediaWikiUnitTestCase;
|
||||
use Title;
|
||||
|
|
@ -71,6 +73,10 @@ class PermissionManagerTest extends MediaWikiUnitTestCase {
|
|||
$this->createMock( RestrictionStore::class );
|
||||
$titleFormatter = $options['titleFormatter'] ??
|
||||
$this->createMock( TitleFormatter::class );
|
||||
$tempUserConfig = $options['tempUserConfig'] ??
|
||||
new RealTempUserConfig( [] );
|
||||
$userFactory = $options['userFactory'] ??
|
||||
$this->createMock( UserFactory::class );
|
||||
|
||||
$permissionManager = new PermissionManager(
|
||||
new ServiceOptions( PermissionManager::CONSTRUCTOR_OPTIONS, $config ),
|
||||
|
|
@ -83,7 +89,9 @@ class PermissionManagerTest extends MediaWikiUnitTestCase {
|
|||
$userCache,
|
||||
$redirectLookup,
|
||||
$restrictionStore,
|
||||
$titleFormatter
|
||||
$titleFormatter,
|
||||
$tempUserConfig,
|
||||
$userFactory
|
||||
);
|
||||
|
||||
$accessPermissionManager = TestingAccessWrapper::newFromObject( $permissionManager );
|
||||
|
|
@ -342,7 +350,7 @@ class PermissionManagerTest extends MediaWikiUnitTestCase {
|
|||
public function testCheckQuickPermissions(
|
||||
int $namespace,
|
||||
string $pageTitle,
|
||||
bool $userIsAnon,
|
||||
string $userType,
|
||||
string $action,
|
||||
array $rights,
|
||||
string $expectedError
|
||||
|
|
@ -350,10 +358,15 @@ class PermissionManagerTest extends MediaWikiUnitTestCase {
|
|||
// Convert string single error to the array of errors PermissionManager uses
|
||||
$expectedErrors = ( $expectedError === '' ? [] : [ [ $expectedError ] ] );
|
||||
|
||||
$userIsAnon = $userType === 'anon';
|
||||
$userIsTemp = $userType === 'temp';
|
||||
$userIsNamed = $userType === 'user';
|
||||
$user = $this->createMock( User::class );
|
||||
$user->method( 'getId' )->willReturn( $userIsAnon ? 0 : 123 );
|
||||
$user->method( 'getName' )->willReturn( $userIsAnon ? '1.1.1.1' : 'NameOfActingUser' );
|
||||
$user->method( 'isAnon' )->willReturn( $userIsAnon );
|
||||
$user->method( 'isNamed' )->willReturn( $userIsNamed );
|
||||
$user->method( 'isTemp' )->willReturn( $userIsTemp );
|
||||
|
||||
// HookContainer - always return true (false tested separately)
|
||||
$hookContainer = $this->createMock( HookContainer::class );
|
||||
|
|
@ -398,62 +411,69 @@ class PermissionManagerTest extends MediaWikiUnitTestCase {
|
|||
// $namespace, $pageTitle, $userIsAnon, $action, $rights, $expectedError
|
||||
// Four different possible errors when trying to create
|
||||
yield 'Anon createtalk fail' => [
|
||||
NS_TALK, 'Example', true, 'create', [], 'nocreatetext'
|
||||
NS_TALK, 'Example', 'anon', 'create', [], 'nocreatetext'
|
||||
];
|
||||
yield 'Anon createpage fail' => [
|
||||
NS_MAIN, 'Example', true, 'create', [], 'nocreatetext'
|
||||
NS_MAIN, 'Example', 'anon', 'create', [], 'nocreatetext'
|
||||
];
|
||||
yield 'User createtalk fail' => [
|
||||
NS_TALK, 'Example', false, 'create', [], 'nocreate-loggedin'
|
||||
NS_TALK, 'Example', 'user', 'create', [], 'nocreate-loggedin'
|
||||
];
|
||||
yield 'User createpage fail' => [
|
||||
NS_MAIN, 'Example', false, 'create', [], 'nocreate-loggedin'
|
||||
NS_MAIN, 'Example', 'user', 'create', [], 'nocreate-loggedin'
|
||||
];
|
||||
yield 'Temp user createpage fail' => [
|
||||
NS_MAIN, 'Example', 'temp', 'create', [], 'nocreatetext'
|
||||
];
|
||||
|
||||
yield 'Createpage pass' => [
|
||||
NS_MAIN, 'Example', true, 'create', [ 'createpage' ], ''
|
||||
NS_MAIN, 'Example', 'anon', 'create', [ 'createpage' ], ''
|
||||
];
|
||||
|
||||
// Three different namespace specific move failures, even if user has `move` rights
|
||||
yield 'Move root user page fail' => [
|
||||
NS_USER, 'Example', true, 'move', [ 'move' ], 'cant-move-user-page'
|
||||
NS_USER, 'Example', 'anon', 'move', [ 'move' ], 'cant-move-user-page'
|
||||
];
|
||||
yield 'Move file fail' => [
|
||||
NS_FILE, 'Example', true, 'move', [ 'move' ], 'movenotallowedfile'
|
||||
NS_FILE, 'Example', 'anon', 'move', [ 'move' ], 'movenotallowedfile'
|
||||
];
|
||||
yield 'Move category fail' => [
|
||||
NS_CATEGORY, 'Example', true, 'move', [ 'move' ], 'cant-move-category-page'
|
||||
NS_CATEGORY, 'Example', 'anon', 'move', [ 'move' ], 'cant-move-category-page'
|
||||
];
|
||||
|
||||
// No move rights at all. Different failures depending on who is allowed to move.
|
||||
// Test method sets group permissions to [ 'autoconfirmed' => [ 'move' => true ] ]
|
||||
yield 'Anon move fail, autoconfirmed can move' => [
|
||||
NS_TALK, 'Example', true, 'move', [], 'movenologintext'
|
||||
NS_TALK, 'Example', 'anon', 'move', [], 'movenologintext'
|
||||
];
|
||||
yield 'User move fail, autoconfirmed can move' => [
|
||||
NS_TALK, 'Example', false, 'move', [], 'movenotallowed'
|
||||
NS_TALK, 'Example', 'user', 'move', [], 'movenotallowed'
|
||||
];
|
||||
yield 'Move pass' => [ NS_MAIN, 'Example', true, 'move', [ 'move' ], '' ];
|
||||
yield 'Temp user move fail, autoconfirmed can move' => [
|
||||
NS_TALK, 'Example', 'temp', 'move', [], 'movenologintext'
|
||||
];
|
||||
yield 'Move pass' => [ NS_MAIN, 'Example', 'anon', 'move', [ 'move' ], '' ];
|
||||
|
||||
// Three different possible failures for move target
|
||||
yield 'Move-target no rights' => [
|
||||
NS_MAIN, 'Example', false, 'move-target', [], 'movenotallowed'
|
||||
NS_MAIN, 'Example', 'user', 'move-target', [], 'movenotallowed'
|
||||
];
|
||||
yield 'Move-target to user root' => [
|
||||
NS_USER, 'Example', false, 'move-target', [ 'move' ], 'cant-move-to-user-page'
|
||||
NS_USER, 'Example', 'user', 'move-target', [ 'move' ], 'cant-move-to-user-page'
|
||||
];
|
||||
yield 'Move-target to category' => [
|
||||
NS_CATEGORY, 'Example', false, 'move-target', [ 'move' ], 'cant-move-to-category-page'
|
||||
NS_CATEGORY, 'Example', 'user', 'move-target', [ 'move' ], 'cant-move-to-category-page'
|
||||
];
|
||||
yield 'Move-target pass' => [
|
||||
NS_MAIN, 'Example', false, 'move-target', [ 'move' ], ''
|
||||
NS_MAIN, 'Example', 'user', 'move-target', [ 'move' ], ''
|
||||
];
|
||||
|
||||
// Other actions without special handling
|
||||
yield 'Missing rights for edit' => [
|
||||
NS_MAIN, 'Example', false, 'edit', [], 'badaccess-group0'
|
||||
NS_MAIN, 'Example', 'user', 'edit', [], 'badaccess-group0'
|
||||
];
|
||||
yield 'Having rights for edit' => [
|
||||
NS_MAIN, 'Example', false, 'edit', [ 'edit', ], ''
|
||||
NS_MAIN, 'Example', 'user', 'edit', [ 'edit', ], ''
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue