<?php namespace MediaWiki\Minerva; use MediaWiki\Content\ContentHandler; use MediaWiki\Content\IContentHandlerFactory; use MediaWiki\Context\RequestContext; use MediaWiki\HookContainer\HookContainer; use MediaWiki\MainConfigNames; use MediaWiki\Minerva\Permissions\IMinervaPagePermissions; use MediaWiki\Minerva\Permissions\MinervaPagePermissions; use MediaWiki\Minerva\Skins\SkinUserPageHelper; use MediaWiki\Permissions\Authority; use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait; use MediaWiki\Title\Title; use MediaWiki\User\UserFactory; use MediaWiki\Watchlist\WatchlistManager; use MediaWikiIntegrationTestCase; /** * @group MinervaNeue * @coversDefaultClass \MediaWiki\Minerva\Permissions\MinervaPagePermissions */ class MinervaPagePermissionsTest extends MediaWikiIntegrationTestCase { use MockAuthorityTrait; protected function setUp(): void { $this->overrideConfigValues( [ MainConfigNames::HideInterlanguageLinks => false ] ); } private function buildPermissionsObject( Title $title, array $options = [], ?ContentHandler $contentHandler = null, ?Authority $user = null, $hasOtherLanguagesOrVariants = false ): MinervaPagePermissions { $languageHelper = $this->createMock( LanguagesHelper::class ); $languageHelper->method( 'doesTitleHasLanguagesOrVariants' ) ->willReturn( $hasOtherLanguagesOrVariants ); $user ??= $this->mockRegisteredNullAuthority(); $contentHandler = $contentHandler ?? $this->getMockForAbstractClass( ContentHandler::class, [], '', false ); $skinOptions = new SkinOptions( $this->createMock( HookContainer::class ), $this->createMock( SkinUserPageHelper::class ) ); if ( $options ) { $skinOptions->setMultiple( $options ); } $context = new RequestContext(); // Force a content model to avoid DB queries. $title->setContentModel( CONTENT_MODEL_WIKITEXT ); $context->setTitle( $title ); $context->setAuthority( $user ); $permissionManager = $this->getServiceContainer()->getPermissionManager(); $contentHandlerFactory = $this->createMock( IContentHandlerFactory::class ); $contentHandlerFactory->expects( $this->once() ) ->method( 'getContentHandler' ) ->willReturn( $contentHandler ); return ( new MinervaPagePermissions( $skinOptions, $languageHelper, $permissionManager, $contentHandlerFactory, $this->createMock( UserFactory::class ), $this->getServiceContainer()->getWatchlistManager() ) )->setContext( $context ); } /** * @covers ::isAllowed */ public function testWatchAndEditNotAllowedOnMainPage() { $user = $this->mockAnonNullAuthority(); $permsAnon = $this->buildPermissionsObject( Title::newMainPage(), [], null, $user ); $perms = $this->buildPermissionsObject( Title::newMainPage() ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::WATCH ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::CONTENT_EDIT ) ); $this->assertFalse( $permsAnon->isAllowed( IMinervaPagePermissions::TALK ) ); // Check to make sure 'talk' and 'switch-language' are enabled on the Main page. $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::TALK ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::SWITCH_LANGUAGE ) ); } /** * @covers ::isAllowed */ public function testInvalidPageActionsArentAllowed() { $perms = $this->buildPermissionsObject( Title::makeTitle( NS_MAIN, 'Test' ) ); $this->assertFalse( $perms->isAllowed( 'blah' ) ); $this->assertFalse( $perms->isAllowed( 'wah' ) ); } /** * @covers ::isAllowed */ public function testValidPageActionsAreAllowed() { $perms = $this->buildPermissionsObject( Title::makeTitle( NS_MAIN, 'Test' ), [], null, $this->mockRegisteredUltimateAuthority() ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::TALK ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::WATCH ) ); } public static function editPageActionProvider() { return [ [ false, false, false ], [ true, false, false ], [ true, true, true ] ]; } /** * The "edit" page action is allowed when the page doesn't support direct editing via the API. * * @dataProvider editPageActionProvider * @covers ::isAllowed */ public function testEditPageAction( $supportsDirectEditing, $supportsDirectApiEditing, $expected ) { $contentHandler = $this->getMockBuilder( ContentHandler::class ) ->disableOriginalConstructor() ->getMock(); $contentHandler->method( 'supportsDirectEditing' ) ->willReturn( $supportsDirectEditing ); $contentHandler->method( 'supportsDirectApiEditing' ) ->willReturn( $supportsDirectApiEditing ); $perms = $this->buildPermissionsObject( Title::makeTitle( NS_MAIN, 'Test' ), [], $contentHandler ); $this->assertEquals( $expected, $perms->isAllowed( IMinervaPagePermissions::CONTENT_EDIT ) ); } /** * @covers ::isAllowed */ public function testPageActionsWhenOnUserPage() { $perms = $this->buildPermissionsObject( Title::makeTitle( NS_USER, 'Admin' ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::TALK ) ); } /** * @covers ::isAllowed */ public function testPageActionsWhenOnAnonUserPage() { $perms = $this->buildPermissionsObject( Title::makeTitle( NS_USER, '1.1.1.1' ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::TALK ) ); } public static function switchLanguagePageActionProvider() { return [ [ true, false, true ], [ false, true, true ], [ false, false, false ], ]; } /** * MediaWiki defines wgHideInterlanguageLinks which is default set to false, but some wikis * can set this config to true. Minerva page permissions must respect that * @covers ::isAllowed */ public function testGlobalHideLanguageLinksTakesPrecedenceOnMainPage() { $this->overrideConfigValues( [ MainConfigNames::HideInterlanguageLinks => true ] ); $perms = $this->buildPermissionsObject( Title::newMainPage() ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::SWITCH_LANGUAGE ) ); } /** * MediaWiki defines wgHideInterlanguageLinks which is default set to false, but some wikis * can set this config to true. Minerva page permissions must respect that * @covers ::isAllowed */ public function testGlobalHideLanguageLinksTakesPrecedence() { $this->overrideConfigValues( [ MainConfigNames::HideInterlanguageLinks => true ] ); $perms = $this->buildPermissionsObject( Title::makeTitle( NS_MAIN, 'Test' ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::SWITCH_LANGUAGE ) ); } /** * The "switch-language" page action is allowed when: v2 of the page action bar is enabled and * if the page has interlanguage links or if the <code>$wgMinervaAlwaysShowLanguageButton</code> * configuration variable is set to truthy. * * @dataProvider switchLanguagePageActionProvider * @covers ::isAllowed */ public function testSwitchLanguagePageAction( $hasLanguagesOrVariants, $minervaAlwaysShowLanguageButton, $expected ) { $title = $this->createMock( Title::class ); $title->expects( $this->once() ) ->method( 'isMainPage' ) ->willReturn( false ); $title->expects( $this->once() ) ->method( 'getContentModel' ) ->willReturn( CONTENT_MODEL_WIKITEXT ); $this->overrideConfigValues( [ 'MinervaAlwaysShowLanguageButton' => $minervaAlwaysShowLanguageButton ] ); $permissions = $this->buildPermissionsObject( $title, [], null, null, $hasLanguagesOrVariants ); $actual = $permissions->isAllowed( IMinervaPagePermissions::SWITCH_LANGUAGE ); $this->assertEquals( $expected, $actual ); } /** * Watch action requires 'viewmywatchlist' and 'editmywatchlist' permissions * to be grated. Verify that isAllowedAction('watch') returns false when user * do not have those permissions granted * @covers ::isAllowed */ public function testWatchIsAllowedOnlyWhenWatchlistPermissionsAreGranted() { $title = Title::makeTitle( NS_MAIN, 'Test_watchstar_permissions' ); $perms = $this->buildPermissionsObject( $title ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::TALK ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::WATCH ) ); } /** * If Title is not watchable, it cannot be watched * @covers ::isAllowed */ public function testCannotWatchNotWatchableTitle() { $title = $this->createMock( Title::class ); $title->expects( $this->once() ) ->method( 'isMainPage' ) ->willReturn( false ); $title->expects( $this->once() ) ->method( 'getContentModel' ) ->willReturn( CONTENT_MODEL_UNKNOWN ); $watchlistManager = $this->createMock( WatchlistManager::class ); $watchlistManager->expects( $this->once() ) ->method( 'isWatchable' ) ->willReturn( false ); $this->setService( 'WatchlistManager', $watchlistManager ); $permissions = $this->buildPermissionsObject( $title ); $this->assertFalse( $permissions->isAllowed( IMinervaPagePermissions::WATCH ) ); } /** * @covers ::isAllowed */ public function testMoveAndDeleteAndProtectNotAllowedByDefault() { $perms = $this->buildPermissionsObject( Title::makeTitle( NS_MAIN, 'Test' ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::MOVE ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::DELETE ) ); $this->assertFalse( $perms->isAllowed( IMinervaPagePermissions::PROTECT ) ); } /** * @covers ::isAllowed */ public function testMoveAndDeleteAndProtectAllowedForUserWithPermissions() { $title = $this->createMock( Title::class ); $title ->method( 'exists' ) ->willReturn( true ); $title->expects( $this->once() ) ->method( 'getContentModel' ) ->willReturn( CONTENT_MODEL_WIKITEXT ); $perms = $this->buildPermissionsObject( $title, [], null, $this->mockRegisteredUltimateAuthority() ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::MOVE ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::DELETE ) ); $this->assertTrue( $perms->isAllowed( IMinervaPagePermissions::PROTECT ) ); } }