J. King
f902346b6c
- RuntimeData has now been replaced by a single static Data class - The Data class has a load() method which fills the same role as the constructor of RuntimeData - The static Lang class is now an instantiable class and is a member of Data - All tests have been adjusted and pass - The Exception tests no longer require convoluted workarounds: a simple mock for Data::$l suffices; Lang tests also use a mock to prevent loops now instead of using a workaround
301 lines
No EOL
15 KiB
PHP
301 lines
No EOL
15 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace JKingWeb\Arsse;
|
|
|
|
|
|
class TestAuthorization extends \PHPUnit\Framework\TestCase {
|
|
use Test\Tools;
|
|
|
|
const USERS = [
|
|
'user@example.com' => User\Driver::RIGHTS_NONE,
|
|
'user@example.org' => User\Driver::RIGHTS_NONE,
|
|
'dman@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER,
|
|
'dman@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER,
|
|
'dadm@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN,
|
|
'dadm@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN,
|
|
'gman@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER,
|
|
'gman@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER,
|
|
'gadm@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN,
|
|
'gadm@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN,
|
|
// invalid rights levels
|
|
'bad1@example.com' => User\Driver::RIGHTS_NONE+1,
|
|
'bad1@example.org' => User\Driver::RIGHTS_NONE+1,
|
|
'bad2@example.com' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
|
|
'bad2@example.org' => User\Driver::RIGHTS_DOMAIN_MANAGER+1,
|
|
'bad3@example.com' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
|
|
'bad3@example.org' => User\Driver::RIGHTS_DOMAIN_ADMIN+1,
|
|
'bad4@example.com' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
|
|
'bad4@example.org' => User\Driver::RIGHTS_GLOBAL_MANAGER+1,
|
|
'bad5@example.com' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
|
|
'bad5@example.org' => User\Driver::RIGHTS_GLOBAL_ADMIN+1,
|
|
|
|
];
|
|
const LEVELS = [
|
|
User\Driver::RIGHTS_NONE,
|
|
User\Driver::RIGHTS_DOMAIN_MANAGER,
|
|
User\Driver::RIGHTS_DOMAIN_ADMIN,
|
|
User\Driver::RIGHTS_GLOBAL_MANAGER,
|
|
User\Driver::RIGHTS_GLOBAL_ADMIN,
|
|
];
|
|
const DOMAINS = [
|
|
'@example.com',
|
|
'@example.org',
|
|
"",
|
|
];
|
|
|
|
protected $data;
|
|
|
|
function setUp(string $drv = Test\User\DriverInternalMock::class, string $db = null) {
|
|
$this->clearData();
|
|
$conf = new Conf();
|
|
$conf->userDriver = $drv;
|
|
$conf->userAuthPreferHTTP = true;
|
|
$conf->userComposeNames = true;
|
|
Data::$conf = $conf;
|
|
if($db !== null) {
|
|
Data::$db = new $db();
|
|
}
|
|
Data::$user = new User();
|
|
Data::$user->authorizationEnabled(false);
|
|
foreach(self::USERS as $user => $level) {
|
|
Data::$user->add($user, "");
|
|
Data::$user->rightsSet($user, $level);
|
|
}
|
|
Data::$user->authorizationEnabled(true);
|
|
}
|
|
|
|
function tearDown() {
|
|
$this->clearData();
|
|
}
|
|
|
|
function testSelfActionLogic() {
|
|
foreach(array_keys(self::USERS) as $user) {
|
|
Data::$user->auth($user, "");
|
|
// users should be able to do basic actions for themselves
|
|
$this->assertTrue(Data::$user->authorize($user, "userExists"), "User $user could not act for themselves.");
|
|
$this->assertTrue(Data::$user->authorize($user, "userRemove"), "User $user could not act for themselves.");
|
|
}
|
|
}
|
|
|
|
function testRegularUserLogic() {
|
|
foreach(self::USERS as $actor => $rights) {
|
|
if($rights != User\Driver::RIGHTS_NONE) continue;
|
|
Data::$user->auth($actor, "");
|
|
foreach(array_keys(self::USERS) as $affected) {
|
|
// regular users should only be able to act for themselves
|
|
if($actor==$affected) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// they should never be able to set rights
|
|
foreach(self::LEVELS as $level) {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
|
|
}
|
|
}
|
|
// they should not be able to list users
|
|
foreach(self::DOMAINS as $domain) {
|
|
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function testDomainManagerLogic() {
|
|
foreach(self::USERS as $actor => $actorRights) {
|
|
if($actorRights != User\Driver::RIGHTS_DOMAIN_MANAGER) continue;
|
|
$actorDomain = substr($actor,strrpos($actor,"@")+1);
|
|
Data::$user->auth($actor, "");
|
|
foreach(self::USERS as $affected => $affectedRights) {
|
|
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
|
|
// domain managers should be able to check any user on the same domain
|
|
if($actorDomain==$affectedDomain) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// they should only be able to act for regular users on the same domain
|
|
if($actor==$affected || ($actorDomain==$affectedDomain && $affectedRights==User\Driver::RIGHTS_NONE)) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// and they should only be able to set their own rights to regular user
|
|
foreach(self::LEVELS as $level) {
|
|
if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_DOMAIN_MANAGER])) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
// they should also be able to list all users on their own domain
|
|
foreach(self::DOMAINS as $domain) {
|
|
if($domain=="@".$actorDomain) {
|
|
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function testDomainAdministratorLogic() {
|
|
foreach(self::USERS as $actor => $actorRights) {
|
|
if($actorRights != User\Driver::RIGHTS_DOMAIN_ADMIN) continue;
|
|
$actorDomain = substr($actor,strrpos($actor,"@")+1);
|
|
Data::$user->auth($actor, "");
|
|
$allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN];
|
|
foreach(self::USERS as $affected => $affectedRights) {
|
|
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
|
|
// domain admins should be able to check any user on the same domain
|
|
if($actorDomain==$affectedDomain) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// they should be able to act for any user on the same domain who is not a global manager or admin
|
|
if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed)) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// they should be able to set rights for any user on their domain who is not a global manager or admin, up to domain admin level
|
|
foreach(self::LEVELS as $level) {
|
|
if($actorDomain==$affectedDomain && in_array($affectedRights, $allowed) && in_array($level, $allowed)) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
// they should also be able to list all users on their own domain
|
|
foreach(self::DOMAINS as $domain) {
|
|
if($domain=="@".$actorDomain) {
|
|
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function testGlobalManagerLogic() {
|
|
foreach(self::USERS as $actor => $actorRights) {
|
|
if($actorRights != User\Driver::RIGHTS_GLOBAL_MANAGER) continue;
|
|
$actorDomain = substr($actor,strrpos($actor,"@")+1);
|
|
Data::$user->auth($actor, "");
|
|
foreach(self::USERS as $affected => $affectedRights) {
|
|
$affectedDomain = substr($affected,strrpos($affected,"@")+1);
|
|
// global managers should be able to check any user
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
// they should only be able to act for regular users
|
|
if($actor==$affected || $affectedRights==User\Driver::RIGHTS_NONE) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// and they should only be able to set their own rights to regular user
|
|
foreach(self::LEVELS as $level) {
|
|
if($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, User\Driver::RIGHTS_GLOBAL_MANAGER])) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
// they should also be able to list all users
|
|
foreach(self::DOMAINS as $domain) {
|
|
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function testGlobalAdministratorLogic() {
|
|
foreach(self::USERS as $actor => $actorRights) {
|
|
if($actorRights != User\Driver::RIGHTS_GLOBAL_ADMIN) continue;
|
|
Data::$user->auth($actor, "");
|
|
// global admins can do anything
|
|
foreach(self::USERS as $affected => $affectedRights) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
foreach(self::LEVELS as $level) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
|
|
}
|
|
}
|
|
foreach(self::DOMAINS as $domain) {
|
|
$this->assertTrue(Data::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function testInvalidLevelLogic() {
|
|
foreach(self::USERS as $actor => $rights) {
|
|
if(in_array($rights, self::LEVELS)) continue;
|
|
Data::$user->auth($actor, "");
|
|
foreach(array_keys(self::USERS) as $affected) {
|
|
// users with unknown/invalid rights should be treated just like regular users and only be able to act for themselves
|
|
if($actor==$affected) {
|
|
$this->assertTrue(Data::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
|
|
$this->assertTrue(Data::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
|
|
} else {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
|
|
}
|
|
// they should never be able to set rights
|
|
foreach(self::LEVELS as $level) {
|
|
$this->assertFalse(Data::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
|
|
}
|
|
}
|
|
// they should not be able to list users
|
|
foreach(self::DOMAINS as $domain) {
|
|
$this->assertFalse(Data::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function testInternalExceptionLogic() {
|
|
$tests = [
|
|
// methods of User class to test, with parameters besides affected user
|
|
'exists' => [],
|
|
'remove' => [],
|
|
'add' => [''],
|
|
'passwordSet' => [''],
|
|
'propertiesGet' => [],
|
|
'propertiesSet' => [[]],
|
|
'rightsGet' => [],
|
|
'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN],
|
|
'list' => [],
|
|
];
|
|
// try first with a global admin (there should be no exception)
|
|
Data::$user->auth("gadm@example.com", "");
|
|
$this->assertCount(0, $this->checkExceptions("user@example.org", $tests));
|
|
// next try with a regular user acting on another user (everything should fail)
|
|
Data::$user->auth("user@example.com", "");
|
|
$this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests));
|
|
}
|
|
|
|
function testExternalExceptionLogic() {
|
|
// set up the test for an external driver
|
|
$this->setUp(Test\User\DriverExternalMock::class, Test\User\Database::class);
|
|
// run the previous test with the external driver set up
|
|
$this->testInternalExceptionLogic();
|
|
}
|
|
|
|
// meat of testInternalExceptionLogic and testExternalExceptionLogic
|
|
// calls each requested function with supplied arguments, catches authorization exceptions, and returns an array of caught failed calls
|
|
protected function checkExceptions(string $user, $tests): array {
|
|
$err = [];
|
|
foreach($tests as $func => $args) {
|
|
// list method does not take an affected user, so do not unshift for that one
|
|
if($func != "list") array_unshift($args, $user);
|
|
try {
|
|
call_user_func_array(array(Data::$user, $func), $args);
|
|
} catch(User\ExceptionAuthz $e) {
|
|
$err[] = $func;
|
|
}
|
|
}
|
|
return $err;
|
|
}
|
|
} |