Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5979709
PermissionManagerTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
16 KB
Referenced Files
None
Subscribers
None
PermissionManagerTest.php
View Options
<?php
namespace
MediaWiki\Tests\Unit\Permissions
;
use
MediaWiki\Actions\ActionFactory
;
use
MediaWiki\Block\BlockErrorFormatter
;
use
MediaWiki\Block\BlockManager
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Page\RedirectLookup
;
use
MediaWiki\Permissions\GroupPermissionsLookup
;
use
MediaWiki\Permissions\PermissionManager
;
use
MediaWiki\Permissions\PermissionStatus
;
use
MediaWiki\Permissions\RestrictionStore
;
use
MediaWiki\SpecialPage\SpecialPageFactory
;
use
MediaWiki\Tests\Unit\DummyServicesTrait
;
use
MediaWiki\Title\Title
;
use
MediaWiki\Title\TitleFormatter
;
use
MediaWiki\User\TempUser\RealTempUserConfig
;
use
MediaWiki\User\User
;
use
MediaWiki\User\UserFactory
;
use
MediaWiki\User\UserGroupManager
;
use
MediaWiki\User\UserIdentityLookup
;
use
MediaWikiUnitTestCase
;
use
Wikimedia\TestingAccessWrapper
;
/**
* For the integration tests, see \MediaWiki\Tests\Integration\Permissions\PermissionManagerTest.
*
* @author DannyS712
* @covers \MediaWiki\Permissions\PermissionManager
*/
class
PermissionManagerTest
extends
MediaWikiUnitTestCase
{
use
DummyServicesTrait
;
private
function
getPermissionManager
(
$options
=
[]
)
{
$overrideConfig
=
$options
[
'config'
]
??
[];
$baseConfig
=
[
MainConfigNames
::
WhitelistRead
=>
false
,
MainConfigNames
::
WhitelistReadRegexp
=>
false
,
MainConfigNames
::
EmailConfirmToEdit
=>
false
,
MainConfigNames
::
BlockDisablesLogin
=>
false
,
MainConfigNames
::
EnablePartialActionBlocks
=>
false
,
MainConfigNames
::
GroupPermissions
=>
[],
MainConfigNames
::
RevokePermissions
=>
[],
MainConfigNames
::
GroupInheritsPermissions
=>
[],
MainConfigNames
::
AvailableRights
=>
[],
MainConfigNames
::
NamespaceProtection
=>
[
NS_MEDIAWIKI
=>
'editinterface'
],
MainConfigNames
::
RestrictionLevels
=>
[
''
,
'autoconfirmed'
,
'sysop'
],
MainConfigNames
::
DeleteRevisionsLimit
=>
false
,
MainConfigNames
::
RateLimits
=>
[],
MainConfigNames
::
ImplicitRights
=>
[],
];
$config
=
$overrideConfig
+
$baseConfig
;
$hookContainer
=
$options
[
'hookContainer'
]
??
$this
->
createMock
(
HookContainer
::
class
);
$redirectLookup
=
$options
[
'redirectLookup'
]
??
$this
->
createMock
(
RedirectLookup
::
class
);
$restrictionStore
=
$options
[
'restrictionStore'
]
??
$this
->
createMock
(
RestrictionStore
::
class
);
$permissionManager
=
new
PermissionManager
(
new
ServiceOptions
(
PermissionManager
::
CONSTRUCTOR_OPTIONS
,
$config
),
$this
->
createMock
(
SpecialPageFactory
::
class
),
$this
->
getDummyNamespaceInfo
(),
new
GroupPermissionsLookup
(
new
ServiceOptions
(
GroupPermissionsLookup
::
CONSTRUCTOR_OPTIONS
,
$config
)
),
$this
->
createMock
(
UserGroupManager
::
class
),
$this
->
createMock
(
BlockManager
::
class
),
$this
->
createMock
(
BlockErrorFormatter
::
class
),
$hookContainer
,
$this
->
createMock
(
UserIdentityLookup
::
class
),
$redirectLookup
,
$restrictionStore
,
$this
->
createMock
(
TitleFormatter
::
class
),
new
RealTempUserConfig
(
[]
),
$this
->
createMock
(
UserFactory
::
class
),
$this
->
createMock
(
ActionFactory
::
class
)
);
return
TestingAccessWrapper
::
newFromObject
(
$permissionManager
);
}
/**
* Does not cover the `editmyuserjsredirect` functionality, which is covered
* in testCheckUserConfigPermissionsForRedirect
*
* @dataProvider provideTestCheckUserConfigPermissions
* @param string $pageTitle Does not include the namespace prefix
* @param array $rights What rights the user should be given
* @param string $action
* @param string|bool $pageType 'css', 'js', 'json', or false for none of those
* @param array $expectedErrors
*/
public
function
testCheckUserConfigPermissions
(
string
$pageTitle
,
array
$rights
,
string
$action
,
$pageType
,
array
$expectedErrors
)
{
$user
=
$this
->
createMock
(
User
::
class
);
$user
->
method
(
'getId'
)->
willReturn
(
123
);
$user
->
method
(
'getName'
)->
willReturn
(
'NameOfActingUser'
);
$title
=
$this
->
createMock
(
Title
::
class
);
$title
->
method
(
'getText'
)->
willReturn
(
$pageTitle
);
$title
->
method
(
'isUserCssConfigPage'
)->
willReturn
(
$pageType
===
'css'
);
$title
->
method
(
'isUserJsonConfigPage'
)->
willReturn
(
$pageType
===
'json'
);
$title
->
method
(
'isUserJsConfigPage'
)->
willReturn
(
$pageType
===
'js'
);
$permissionManager
=
$this
->
getPermissionManager
();
// Override user rights
$permissionManager
->
overrideUserRightsForTesting
(
$user
,
$rights
);
$result
=
PermissionStatus
::
newEmpty
();
$permissionManager
->
checkUserConfigPermissions
(
$action
,
$user
,
$result
,
PermissionManager
::
RIGOR_QUICK
,
// unused
true
,
// $short, unused
$title
);
$this
->
assertEquals
(
$expectedErrors
,
$result
->
toLegacyErrorArray
()
);
}
public
static
function
provideTestCheckUserConfigPermissions
()
{
yield
'Patrol ignored'
=>
[
'NameOfActingUser/subpage'
,
[],
'patrol'
,
false
,
[]
];
yield
'Own non-config'
=>
[
'NameOfActingUser/subpage'
,
[],
'edit'
,
false
,
[]
];
yield
'Other non-config'
=>
[
'NameOfAnotherUser/subpage'
,
[],
'edit'
,
false
,
[]
];
yield
'Delete other subpage'
=>
[
'NameOfAnotherUser/subpage'
,
[],
'delete'
,
false
,
[]
];
foreach
(
[
'css'
,
'json'
,
'js'
]
as
$type
)
{
// User editing their own subpages, everything okay
// ensure that we don't run the checks for redirects now, those are done separately
yield
"Own $type with editmyuser*"
=>
[
'NameOfActingUser/subpage.'
.
$type
,
[
"editmyuser{$type}"
,
'editmyuserjsredirect'
],
'edit'
,
$type
,
[]
];
// Interface admin editing own subpages, everything okay
yield
"Own $type with edituser*"
=>
[
'NameOfActingUser/subpage.'
.
$type
,
[
"edituser{$type}"
],
'edit'
,
$type
,
[]
];
// User with no rights editing own subpages, problematic
yield
"Own $type with no rights"
=>
[
'NameOfActingUser/subpage.'
.
$type
,
[],
'edit'
,
$type
,
[
[
"mycustom{$type}protected"
,
'edit'
]
]
];
// Interface admin editing other user's subpages, everything okay
yield
"Other $type with edituser*"
=>
[
'NameOfAnotherUser/subpage.'
.
$type
,
[
"edituser{$type}"
],
'edit'
,
$type
,
[]
];
// Normal user editing other user's subpages, problematic
yield
"Other $type with no rights"
=>
[
'NameOfAnotherUser/subpage.'
.
$type
,
[],
'edit'
,
$type
,
[
[
"custom{$type}protected"
,
'edit'
]
]
];
}
}
/**
* @dataProvider provideTestCheckUserConfigPermissionsForRedirect
*/
public
function
testCheckUserConfigPermissionsForRedirect
(
bool
$canEditOwnRedirect
,
bool
$isRedirect
,
int
$targetNamespace
,
string
$targetText
,
bool
$expectErrors
)
{
$user
=
$this
->
createMock
(
User
::
class
);
$user
->
method
(
'getId'
)->
willReturn
(
123
);
$user
->
method
(
'getName'
)->
willReturn
(
'NameOfActingUser'
);
$title
=
$this
->
createMock
(
Title
::
class
);
$title
->
method
(
'getText'
)->
willReturn
(
'NameOfActingUser/common.js'
);
$title
->
method
(
'isUserCssConfigPage'
)->
willReturn
(
false
);
$title
->
method
(
'isUserJsonConfigPage'
)->
willReturn
(
false
);
$title
->
method
(
'isUserJsConfigPage'
)->
willReturn
(
true
);
if
(
$isRedirect
)
{
$target
=
$this
->
createMock
(
Title
::
class
);
$target
->
method
(
'inNamespace'
)
->
with
(
NS_USER
)
->
willReturn
(
$targetNamespace
===
NS_USER
);
$target
->
method
(
'getText'
)->
willReturn
(
$targetText
);
}
else
{
$target
=
null
;
}
$redirectLookup
=
$this
->
createMock
(
RedirectLookup
::
class
);
$redirectLookup
->
method
(
'getRedirectTarget'
)->
with
(
$title
)
->
willReturn
(
$target
);
$permissionManager
=
$this
->
getPermissionManager
(
[
'redirectLookup'
=>
$redirectLookup
,
]
);
// Override user rights
$rights
=
[
'editmyuserjs'
];
if
(
$canEditOwnRedirect
)
{
$rights
[]
=
'editmyuserjsredirect'
;
}
$permissionManager
->
overrideUserRightsForTesting
(
$user
,
$rights
);
$result
=
PermissionStatus
::
newEmpty
();
$permissionManager
->
checkUserConfigPermissions
(
'edit'
,
$user
,
$result
,
PermissionManager
::
RIGOR_QUICK
,
// unused
true
,
// $short, unused
$title
);
$this
->
assertEquals
(
$expectErrors
?
[
[
'mycustomjsredirectprotected'
,
'edit'
]
]
:
[],
$result
->
toLegacyErrorArray
()
);
}
public
static
function
provideTestCheckUserConfigPermissionsForRedirect
()
{
yield
'With `editmyuserjsredirect`'
=>
[
true
,
true
,
NS_USER
,
'NameOfActingUser/other.js'
,
false
];
yield
'Not a redirect'
=>
[
false
,
false
,
NS_USER
,
'NameOfActingUser/other.js'
,
false
];
yield
'Redirect out of user space'
=>
[
false
,
true
,
NS_MAIN
,
'MainPage.js'
,
true
];
yield
'Redirect to different user'
=>
[
false
,
true
,
NS_USER
,
'NameOfAnotherUser/other.js'
,
true
];
yield
'Redirect to own subpage'
=>
[
false
,
true
,
NS_USER
,
'NameOfActingUser/other.js'
,
false
];
}
/**
* @dataProvider provideTestCheckPageRestrictions
*/
public
function
testCheckPageRestrictions
(
string
$action
,
array
$restrictions
,
array
$rights
,
bool
$cascading
,
array
$expectedErrors
)
{
$user
=
$this
->
createMock
(
User
::
class
);
$user
->
method
(
'getId'
)->
willReturn
(
123
);
$user
->
method
(
'getName'
)->
willReturn
(
'NameOfActingUser'
);
$title
=
$this
->
createMock
(
Title
::
class
);
$restrictionStore
=
$this
->
createMock
(
RestrictionStore
::
class
);
$restrictionStore
->
expects
(
$this
->
any
()
)
->
method
(
'listApplicableRestrictionTypes'
)
->
with
(
$title
)
->
willReturn
(
[
'other-action'
,
$action
]
);
$restrictionStore
->
expects
(
$this
->
once
()
)
->
method
(
'getRestrictions'
)
->
with
(
$title
,
$action
)
->
willReturn
(
$restrictions
);
$restrictionStore
->
method
(
'areRestrictionsCascading'
)->
willReturn
(
$cascading
);
$permissionManager
=
$this
->
getPermissionManager
(
[
'restrictionStore'
=>
$restrictionStore
,
]
);
$permissionManager
->
overrideUserRightsForTesting
(
$user
,
$rights
);
$result
=
PermissionStatus
::
newEmpty
();
$permissionManager
->
checkPageRestrictions
(
$action
,
$user
,
$result
,
PermissionManager
::
RIGOR_QUICK
,
// unused
true
,
// $short, unused
$title
);
$this
->
assertEquals
(
$expectedErrors
,
$result
->
toLegacyErrorArray
()
);
}
public
static
function
provideTestCheckPageRestrictions
()
{
yield
'No restrictions'
=>
[
'move'
,
[],
[],
true
,
[]
];
yield
'Empty string'
=>
[
'edit'
,
[
''
],
[],
true
,
[]
];
yield
'Semi-protected, with rights'
=>
[
'edit'
,
[
'autoconfirmed'
],
[
'editsemiprotected'
],
false
,
[]
];
yield
'Sysop protected, no rights'
=>
[
'edit'
,
[
'sysop'
,
],
[],
false
,
[
[
'protectedpagetext'
,
'editprotected'
,
'edit'
]
]
];
yield
'Sysop protected and cascading, no protect'
=>
[
'edit'
,
[
'editprotected'
],
[
'editprotected'
],
true
,
[
[
'protectedpagetext'
,
'protect'
,
'edit'
]
]
];
yield
'Sysop protected and cascading, with rights'
=>
[
'edit'
,
[
'editprotected'
],
[
'editprotected'
,
'protect'
],
true
,
[]
];
}
/**
* @dataProvider provideTestCheckQuickPermissions
*/
public
function
testCheckQuickPermissions
(
int
$namespace
,
string
$pageTitle
,
string
$userType
,
string
$action
,
array
$rights
,
string
$expectedError
)
{
// 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
);
$hookContainer
->
method
(
'run'
)
->
willReturn
(
true
);
// Overrides needed in case `GroupPermissionsLookup::groupHasPermission` is called
$config
=
[
MainConfigNames
::
GroupPermissions
=>
[
'autoconfirmed'
=>
[
'move'
=>
true
]
]
];
$permissionManager
=
$this
->
getPermissionManager
(
[
'config'
=>
$config
,
'hookContainer'
=>
$hookContainer
,
]
);
$permissionManager
->
overrideUserRightsForTesting
(
$user
,
$rights
);
$title
=
$this
->
createMock
(
Title
::
class
);
$title
->
method
(
'getNamespace'
)->
willReturn
(
$namespace
);
$title
->
method
(
'getText'
)->
willReturn
(
$pageTitle
);
// Ensure that `missingPermissionError` doesn't call User::newFatalPermissionDeniedStatus
// which uses the global state
$short
=
true
;
$result
=
PermissionStatus
::
newEmpty
();
$permissionManager
->
checkQuickPermissions
(
$action
,
$user
,
$result
,
PermissionManager
::
RIGOR_QUICK
,
// unused
$short
,
$title
);
$this
->
assertEquals
(
$expectedErrors
,
$result
->
toLegacyErrorArray
()
);
}
public
static
function
provideTestCheckQuickPermissions
()
{
// $namespace, $pageTitle, $userIsAnon, $action, $rights, $expectedError
// Four different possible errors when trying to create
yield
'Anon createtalk fail'
=>
[
NS_TALK
,
'Example'
,
'anon'
,
'create'
,
[],
'nocreatetext'
];
yield
'Anon createpage fail'
=>
[
NS_MAIN
,
'Example'
,
'anon'
,
'create'
,
[],
'nocreatetext'
];
yield
'User createtalk fail'
=>
[
NS_TALK
,
'Example'
,
'user'
,
'create'
,
[],
'nocreate-loggedin'
];
yield
'User createpage fail'
=>
[
NS_MAIN
,
'Example'
,
'user'
,
'create'
,
[],
'nocreate-loggedin'
];
yield
'Temp user createpage fail'
=>
[
NS_MAIN
,
'Example'
,
'temp'
,
'create'
,
[],
'nocreatetext'
];
yield
'Createpage pass'
=>
[
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'
,
'anon'
,
'move'
,
[
'move'
],
'cant-move-user-page'
];
yield
'Move file fail'
=>
[
NS_FILE
,
'Example'
,
'anon'
,
'move'
,
[
'move'
],
'movenotallowedfile'
];
yield
'Move category fail'
=>
[
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'
,
'anon'
,
'move'
,
[],
'movenologintext'
];
yield
'User move fail, autoconfirmed can move'
=>
[
NS_TALK
,
'Example'
,
'user'
,
'move'
,
[],
'movenotallowed'
];
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'
,
'user'
,
'move-target'
,
[],
'movenotallowed'
];
yield
'Move-target to user root'
=>
[
NS_USER
,
'Example'
,
'user'
,
'move-target'
,
[
'move'
],
'cant-move-to-user-page'
];
yield
'Move-target to category'
=>
[
NS_CATEGORY
,
'Example'
,
'user'
,
'move-target'
,
[
'move'
],
'cant-move-to-category-page'
];
yield
'Move-target pass'
=>
[
NS_MAIN
,
'Example'
,
'user'
,
'move-target'
,
[
'move'
],
''
];
// Other actions without special handling
yield
'Missing rights for edit'
=>
[
NS_MAIN
,
'Example'
,
'user'
,
'edit'
,
[],
'badaccess-group0'
];
yield
'Having rights for edit'
=>
[
NS_MAIN
,
'Example'
,
'user'
,
'edit'
,
[
'edit'
,
],
''
];
}
/**
* @dataProvider provideTestCheckQuickPermissionsHook
*/
public
function
testCheckQuickPermissionsHook
(
array
$hookErrors
,
array
$expectedResult
)
{
$title
=
$this
->
createMock
(
Title
::
class
);
$user
=
$this
->
createMock
(
User
::
class
);
$action
=
'FakeActionGoesHere'
;
$hookCallback
=
function
(
$hookTitle
,
$hookUser
,
$hookAction
,
&
$errors
,
$doExpensiveQueries
,
$short
)
use
(
$user
,
$title
,
$action
,
$hookErrors
)
{
$this
->
assertSame
(
$title
,
$hookTitle
);
$this
->
assertSame
(
$user
,
$hookUser
);
$this
->
assertSame
(
$action
,
$hookAction
);
$errors
=
$hookErrors
;
return
false
;
};
$hookContainer
=
$this
->
createHookContainer
(
[
'TitleQuickPermissions'
=>
$hookCallback
]
);
$permissionManager
=
$this
->
getPermissionManager
(
[
'hookContainer'
=>
$hookContainer
,
]
);
$result
=
PermissionStatus
::
newEmpty
();
$permissionManager
->
checkQuickPermissions
(
$action
,
$user
,
$result
,
PermissionManager
::
RIGOR_QUICK
,
// unused
true
,
// $short, unused,
$title
);
$this
->
assertEquals
(
$expectedResult
,
$result
->
toLegacyErrorArray
()
);
}
public
function
provideTestCheckQuickPermissionsHook
()
{
// test name => [ $result / $errors, $status->toLegacyErrorArray() ]
yield
'Hook returns false but no errors'
=>
[
[],
[]
];
yield
'One error'
=>
[
[
'error-key'
],
[
[
'error-key'
]
]
];
yield
'One error with params as array'
=>
[
[
'error-key'
,
'param'
],
[
[
'error-key'
,
'param'
]
]
];
yield
'Multiple errors'
=>
[
[
[
'error-key'
],
[
'error-key-2'
]
],
[
[
'error-key'
],
[
'error-key-2'
]
]
];
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 21:02 (5 d, 1 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
f2/e8/c6fd66aa70b1ed2fe2c9b739d8b8
Default Alt Text
PermissionManagerTest.php (16 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment