idSitesAccess = [ 'view' => array(1), 'write' => array(), 'admin' => array(), 'superuser' => array(), ]; } protected static function beforeTableDataCached() { parent::beforeTableDataCached(); Fixture::createWebsite('2018-02-03 00:00:00'); } public function testProcessShouldNotReloadAccessIfNoTokenAuthIsGiven() { $this->assertAccessNotReloaded(); $request = new Request(array('method' => 'API.getPiwikVersion')); $request->process(); $this->assertSameUserAsBeforeIsAuthenticated(); } public function testProcessShouldNotReloadAccessIfSameAuthTokenIsAlreadyLoaded() { $this->assertAccessNotReloaded(); $request = new Request(array('method' => 'API.getPiwikVersion', 'token_auth' => $this->access->getTokenAuth())); $request->process(); $this->assertSameUserAsBeforeIsAuthenticated(); } public function testProcessShouldReloadAccessIfAuthTokenIsDifferent() { // make sure tokenAuth is different then set 'AnYTOkEN' token $this->assertEquals('token', $this->access->getTokenAuth()); $this->assertAccessReloadedAndRestored('AnYTOkEN'); $request = new Request(array('method' => 'API.getPiwikVersion', 'token_auth' => 'AnYTOkEN')); $request->process(); // make sure token auth was restored after it was loaded with AnYTOkEN $this->assertSameUserAsBeforeIsAuthenticated(); } public function testProcessShouldReloadAccessIfAuthTokenIsDifferentButEmpty() { $this->assertEquals('token', $this->access->getTokenAuth()); $this->assertAccessReloadedAndRestored(''); $request = new Request(array('method' => 'API.getPiwikVersion', 'token_auth' => '')); $request->process(); $this->assertSameUserAsBeforeIsAuthenticated(); } public function testProcessShouldKeepSuperUserPermissionIfAccessWasManuallySet() { $this->access->setSuperUserAccess(true); $this->assertAccessReloadedAndRestored('difFenrenT'); $request = new Request(array('method' => 'API.getPiwikVersion', 'token_auth' => 'difFenrenT')); $request->process(); // make sure token auth was restored after it was loaded with difFenrenT $this->assertSameUserAsBeforeIsAuthenticated(); $this->assertTrue($this->access->hasSuperUserAccess()); } public function testIsApiRequestShouldDetectIfItIsApiRequestOrNot() { $this->assertFalse(Request::isApiRequest(array())); $this->assertFalse(Request::isApiRequest(array('module' => '', 'method' => ''))); $this->assertFalse(Request::isApiRequest(array('module' => 'API'))); // no method $this->assertFalse(Request::isApiRequest(array('module' => 'CoreHome', 'method' => 'index.test'))); // not api $this->assertFalse(Request::isApiRequest(array('module' => 'API', 'method' => 'testmethod'))); // no valid action $this->assertTrue(Request::isApiRequest(array('module' => 'API', 'method' => 'test.method'))); } public function testCheckTokenAuthIsNotLimitedAllowsSuperUserTokenAuthIfCurrentRequestIsForAPI() { $this->expectNotToPerformAssertions(); Common::$isCliMode = false; $this->access->setSuperUserAccess(true); Request::checkTokenAuthIsNotLimited('API', 'index'); } public function testCheckTokenAuthIsNotLimitedAllowsSuperUserTokenAuthIfCurrentlyInCliMode() { $this->expectNotToPerformAssertions(); Common::$isCliMode = true; $this->access->setSuperUserAccess(true); Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowSuperUserTokenAuthIfCurrentlyInUiRequest() { $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_TooHighAccessLevel'); Common::$isCliMode = false; $this->access->setSuperUserAccess(true); Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowSuperUserTokenAuthIfCurrentlyInUiRequestAndEnableConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 1; $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_TooHighAccessLevel'); Common::$isCliMode = false; $this->access->setSuperUserAccess(true); Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowWriteTokenAuthIfConfigNotSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_ViewAccessRequired'); $this->idSitesAccess['view'] = []; $this->idSitesAccess['write'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowAdminTokenAuthIfConfigNotSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_ViewAccessRequired'); $this->idSitesAccess['view'] = []; $this->idSitesAccess['admin'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeAdminAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsWriteTokenAuthIfConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 1; $this->idSitesAccess['view'] = []; $this->idSitesAccess['write'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsAdminTokenAuthIfConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 1; $this->idSitesAccess['view'] = []; $this->idSitesAccess['admin'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeAdminAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsWriteTokenAuthIfModuleActionConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->setAllowedModuleActions(['SomePlugin.someMethod']); $this->idSitesAccess['view'] = []; $this->idSitesAccess['write'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsAdminTokenAuthIfModuleActionConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->setAllowedModuleActions(['SomePlugin.someMethod']); $this->idSitesAccess['view'] = []; $this->idSitesAccess['admin'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeAdminAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowWriteTokenAuthIfModuleActionConfigCaseDoesNotMatch() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->setAllowedModuleActions(['someplugin.someMethod']); $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_ViewAccessRequired'); $this->idSitesAccess['view'] = []; $this->idSitesAccess['write'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsWriteTokenAuthIfCurrentModuleActionIsInMultipleConfiguredValues() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->setAllowedModuleActions([ 'OtherPlugin.otherMethod', 'SomePlugin.someMethod', 'ThirdPlugin.thirdMethod', ]); $this->idSitesAccess['view'] = []; $this->idSitesAccess['write'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertTrue($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedDoesNotAllowSuperUserTokenAuthIfModuleActionConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->setAllowedModuleActions(['SomePlugin.someMethod']); $this->expectException(\Exception::class); $this->expectExceptionMessage('Widgetize_TooHighAccessLevel'); Common::$isCliMode = false; $this->access->setSuperUserAccess(true); Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsViewTokenAuthIfConfigSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 1; $this->idSitesAccess['view'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertFalse($this->access->isUserHasSomeAdminAccess()); $this->assertFalse($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testCheckTokenAuthIsNotLimitedAllowsViewTokenAuthIfConfigNotSet() { Config::getInstance()->General['enable_framed_allow_write_admin_token_auth'] = 0; $this->idSitesAccess['view'] = [1]; $this->access->reloadAccess($this->auth); $this->access->setSuperUserAccess(false); $this->assertFalse($this->access->hasSuperUserAccess()); $this->assertFalse($this->access->isUserHasSomeAdminAccess()); $this->assertFalse($this->access->isUserHasSomeWriteAccess()); Common::$isCliMode = false; Request::checkTokenAuthIsNotLimited('SomePlugin', 'someMethod'); } public function testProcessThrowsIfForceApiSessionConflictsBetweenGetAndPost() { $this->expectException(\Exception::class); $this->expectExceptionMessage(Piwik::translate('General_ConflictingAuthenticationParametersProvided')); $_GET['force_api_session'] = 1; $_POST['force_api_session'] = 0; try { $request = new Request(['method' => 'API.getPiwikVersion', 'format' => 'original']); $request->process(); } finally { unset($_GET['force_api_session']); unset($_POST['force_api_session']); } } public function testProcessThrowsIfTokenAuthConflictsBetweenGetAndPost() { $this->expectException(\Exception::class); $this->expectExceptionMessage(Piwik::translate('General_ConflictingAuthenticationParametersProvided')); $_GET['token_auth'] = 'tokenGet'; $_POST['token_auth'] = 'tokenPost'; try { $request = new Request(['method' => 'API.getPiwikVersion', 'format' => 'original']); $request->process(); } finally { unset($_GET['token_auth']); unset($_POST['token_auth']); } } public function testProcessDoesNotThrowConflictingForceApiSessionWhenNestedApiInvocation() { $_GET['force_api_session'] = 1; $_POST['force_api_session'] = 0; $this->setNestedApiInvocationCount(2); try { $request = new Request(['method' => 'API.getPiwikVersion', 'format' => 'original']); $result = $request->process(); $this->assertNotEmpty($result); } finally { unset($_GET['force_api_session']); unset($_POST['force_api_session']); $this->setNestedApiInvocationCount(0); } } private function assertSameUserAsBeforeIsAuthenticated() { $this->assertEquals($this->userAuthToken, $this->access->getTokenAuth()); } private function assertAccessNotReloaded() { $this->access->expects($this->never())->method('reloadAccess'); } private function assertAccessReloadedAndRestored($expectedTokenToBeReloaded) { $this->access->expects($this->exactly(2))->method('reloadAccess'); // verify access reloaded $this->auth->expects($this->at(0))->method('setLogin')->with($this->equalTo(null)); $this->auth->expects($this->at(1))->method('setTokenAuth')->with($this->equalTo($expectedTokenToBeReloaded)); $this->auth->expects($this->at(2))->method('authenticate')->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login1', $expectedTokenToBeReloaded))); // verify access restored $this->auth->expects($this->at(3))->method('setLogin')->with($this->equalTo(null)); $this->auth->expects($this->at(4))->method('setTokenAuth')->with($this->equalTo($tokenRestored = $this->userAuthToken)); $this->auth->expects($this->at(5))->method('authenticate')->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', $this->userAuthToken))); } private function createAuthMock() { $authMock = $this->getMockBuilder('Piwik\Plugins\Login\Auth') ->setMethods(array('authenticate', 'setTokenAuth', 'setLogin')) ->getMock(); $authMock->expects($this->any()) ->method('authenticate') ->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', $this->userAuthToken))); return $authMock; } private function createAccessMock($auth) { $mock = $this->getMockBuilder('Piwik\Access') ->onlyMethods(array('loadSitesIfNeeded', 'reloadAccess', 'getTokenAuth')) ->enableProxyingToOriginalMethods() ->getMock(); $mock->method('loadSitesIfNeeded')->willReturnCallback(function () use ($mock) { // setting the property directly since enableProxyingToOriginalMethods() will just proxy to the original // method after this mock method is called. (we can't not call enableProxyingToOriginalMethods() because // some tests require it) $reflection = new ReflectionClass(Access::class); $reflectionProperty = $reflection->getProperty('idsitesByAccess'); if (PHP_VERSION_ID < 80100) { $reflectionProperty->setAccessible(true); } $reflectionProperty->setValue($mock, $this->idSitesAccess); }); $mock->reloadAccess($auth); return $mock; } private function setAllowedModuleActions(array $moduleActions): void { StaticContainer::getContainer()->set('token_auth.write_admin_allowed_module_actions', $moduleActions); } private function setNestedApiInvocationCount(int $count): void { $reflection = new ReflectionClass(Request::class); $reflectionProperty = $reflection->getProperty('nestedApiInvocationCount'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue(null, $count); if ($count > 0) { Request::setIsRootRequestApiRequest('API.getPiwikVersion'); } else { Cache::getTransientCache()->delete('API.setIsRootRequestApiRequest'); } } public function provideContainerConfig() { $this->auth = $this->createAuthMock(); $this->access = $this->createAccessMock($this->auth); return array( 'Piwik\Auth' => $this->auth, 'Piwik\Access' => $this->access, ); } }