mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
fix: fix permission checks for roles/circles
This commit is contained in:
@@ -17,6 +17,7 @@ use OCA\Forum\Db\ReadMarkerMapper;
|
||||
use OCA\Forum\Db\Role;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Db\ThreadMapper;
|
||||
use OCA\Forum\Service\PermissionService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\IRequest;
|
||||
@@ -40,6 +41,8 @@ class CategoryControllerTest extends TestCase {
|
||||
private ReadMarkerMapper $readMarkerMapper;
|
||||
/** @var RoleMapper&MockObject */
|
||||
private RoleMapper $roleMapper;
|
||||
/** @var PermissionService&MockObject */
|
||||
private PermissionService $permissionService;
|
||||
/** @var IUserSession&MockObject */
|
||||
private IUserSession $userSession;
|
||||
/** @var LoggerInterface&MockObject */
|
||||
@@ -55,6 +58,13 @@ class CategoryControllerTest extends TestCase {
|
||||
$this->threadMapper = $this->createMock(ThreadMapper::class);
|
||||
$this->readMarkerMapper = $this->createMock(ReadMarkerMapper::class);
|
||||
$this->roleMapper = $this->createMock(RoleMapper::class);
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
// By default, grant access to all categories (tests that need filtering can override)
|
||||
$this->permissionService->method('getAccessibleCategories')
|
||||
->willReturnCallback(function () {
|
||||
// Return IDs 1-100 to cover all test categories
|
||||
return range(1, 100);
|
||||
});
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
@@ -67,6 +77,7 @@ class CategoryControllerTest extends TestCase {
|
||||
$this->threadMapper,
|
||||
$this->readMarkerMapper,
|
||||
$this->roleMapper,
|
||||
$this->permissionService,
|
||||
$this->userSession,
|
||||
$this->logger
|
||||
);
|
||||
@@ -592,31 +603,10 @@ class CategoryControllerTest extends TestCase {
|
||||
$user->method('getUID')->willReturn($userId);
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
// User has a role
|
||||
$role = new Role();
|
||||
$role->setId(1);
|
||||
$role->setName('User');
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
// Category permission allows viewing
|
||||
$categoryPerm = new CategoryPerm();
|
||||
$categoryPerm->setId(1);
|
||||
$categoryPerm->setCategoryId($categoryId);
|
||||
$categoryPerm->setTargetType('role');
|
||||
$categoryPerm->setTargetId('1');
|
||||
$categoryPerm->setCanView(true);
|
||||
$categoryPerm->setCanPost(false);
|
||||
$categoryPerm->setCanReply(false);
|
||||
$categoryPerm->setCanModerate(false);
|
||||
|
||||
$this->categoryPermMapper->expects($this->once())
|
||||
->method('findByCategoryAndRoles')
|
||||
->with($categoryId, [1])
|
||||
->willReturn([$categoryPerm]);
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasCategoryPermission')
|
||||
->with($userId, $categoryId, $permission)
|
||||
->willReturn(true);
|
||||
|
||||
$response = $this->controller->checkPermission($categoryId, $permission);
|
||||
|
||||
@@ -634,29 +624,10 @@ class CategoryControllerTest extends TestCase {
|
||||
$user->method('getUID')->willReturn($userId);
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$role = new Role();
|
||||
$role->setId(1);
|
||||
$role->setName('User');
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->willReturn([$role]);
|
||||
|
||||
// Category permission does not allow moderating
|
||||
$categoryPerm = new CategoryPerm();
|
||||
$categoryPerm->setId(1);
|
||||
$categoryPerm->setCategoryId($categoryId);
|
||||
$categoryPerm->setTargetType('role');
|
||||
$categoryPerm->setTargetId('1');
|
||||
$categoryPerm->setCanView(true);
|
||||
$categoryPerm->setCanPost(false);
|
||||
$categoryPerm->setCanReply(false);
|
||||
$categoryPerm->setCanModerate(false);
|
||||
|
||||
$this->categoryPermMapper->expects($this->once())
|
||||
->method('findByCategoryAndRoles')
|
||||
->with($categoryId, [1])
|
||||
->willReturn([$categoryPerm]);
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasCategoryPermission')
|
||||
->with($userId, $categoryId, $permission)
|
||||
->willReturn(false);
|
||||
|
||||
$response = $this->controller->checkPermission($categoryId, $permission);
|
||||
|
||||
@@ -674,16 +645,10 @@ class CategoryControllerTest extends TestCase {
|
||||
$user->method('getUID')->willReturn($userId);
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
// User has Admin role
|
||||
$adminRole = new Role();
|
||||
$adminRole->setId(1);
|
||||
$adminRole->setName('Admin');
|
||||
$adminRole->setRoleType(Role::ROLE_TYPE_ADMIN);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$adminRole]);
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasCategoryPermission')
|
||||
->with($userId, $categoryId, $permission)
|
||||
->willReturn(true);
|
||||
|
||||
$response = $this->controller->checkPermission($categoryId, $permission);
|
||||
|
||||
|
||||
@@ -4,404 +4,25 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Forum\Tests\Db;
|
||||
|
||||
use OCA\Forum\Db\Category;
|
||||
use OCA\Forum\Db\CategoryMapper;
|
||||
use OCA\Forum\Db\Role;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class CategoryMapperTest extends TestCase {
|
||||
private CategoryMapper $mapper;
|
||||
/** @var IDBConnection&MockObject */
|
||||
private IDBConnection $db;
|
||||
/** @var IUserSession&MockObject */
|
||||
private IUserSession $userSession;
|
||||
/** @var RoleMapper&MockObject */
|
||||
private RoleMapper $roleMapper;
|
||||
/** @var LoggerInterface&MockObject */
|
||||
private LoggerInterface $logger;
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->db = $this->createMock(IDBConnection::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->roleMapper = $this->createMock(RoleMapper::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->mapper = new CategoryMapper(
|
||||
$this->db,
|
||||
$this->userSession,
|
||||
$this->roleMapper,
|
||||
$this->logger
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminReturnsTrueForAdminRole(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('admin1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$adminRole = new Role();
|
||||
$adminRole->setId(1);
|
||||
$adminRole->setRoleType(Role::ROLE_TYPE_ADMIN);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('admin1')
|
||||
->willReturn([$adminRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminReturnsFalseForNonAdminRole(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$userRole = new Role();
|
||||
$userRole->setId(3);
|
||||
$userRole->setRoleType(Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([$userRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminReturnsFalseWhenNotAuthenticated(): void {
|
||||
$this->userSession->method('getUser')->willReturn(null);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testGetUserRoleIdsReturnsGuestRoleForUnauthenticatedUser(): void {
|
||||
$this->userSession->method('getUser')->willReturn(null);
|
||||
|
||||
$guestRole = new Role();
|
||||
$guestRole->setId(4);
|
||||
$guestRole->setRoleType(Role::ROLE_TYPE_GUEST);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByRoleType')
|
||||
->with('guest')
|
||||
->willReturn($guestRole);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('getUserRoleIds');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertEquals([4], $result);
|
||||
}
|
||||
|
||||
public function testGetUserRoleIdsReturnsEmptyArrayWhenGuestRoleNotFound(): void {
|
||||
$this->userSession->method('getUser')->willReturn(null);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByRoleType')
|
||||
->with('guest')
|
||||
->willThrowException(new DoesNotExistException('Guest role not found'));
|
||||
|
||||
// Expect logger to be called when guest role is not found
|
||||
$this->logger->expects($this->once())
|
||||
->method('error')
|
||||
->with($this->stringContains('Guest role not found'));
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('getUserRoleIds');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertEquals([], $result);
|
||||
}
|
||||
|
||||
public function testGetUserRoleIdsReturnsUserRolesForAuthenticatedUser(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$role1 = new Role();
|
||||
$role1->setId(3);
|
||||
$role1->setRoleType(Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$role2 = new Role();
|
||||
$role2->setId(5);
|
||||
$role2->setRoleType(Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([$role1, $role2]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('getUserRoleIds');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertEquals([3, 5], $result);
|
||||
}
|
||||
|
||||
public function testFilterByPermissionsSkipsFilteringForAdminUser(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('admin1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$adminRole = new Role();
|
||||
$adminRole->setId(1);
|
||||
$adminRole->setRoleType(Role::ROLE_TYPE_ADMIN);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('admin1')
|
||||
->willReturn([$adminRole]);
|
||||
|
||||
$category1 = new Category();
|
||||
$category1->setId(1);
|
||||
|
||||
$category2 = new Category();
|
||||
$category2->setId(2);
|
||||
|
||||
$categories = [$category1, $category2];
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('filterByPermissions');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper, $categories);
|
||||
|
||||
// Admin should see all categories
|
||||
$this->assertEquals($categories, $result);
|
||||
}
|
||||
|
||||
public function testFilterByPermissionsReturnsEmptyArrayWhenNoCategories(): void {
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('filterByPermissions');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper, []);
|
||||
|
||||
$this->assertEquals([], $result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminReturnsTrueForModeratorRole(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('mod1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$modRole = new Role();
|
||||
$modRole->setId(2);
|
||||
$modRole->setRoleType(Role::ROLE_TYPE_MODERATOR);
|
||||
|
||||
$adminRole = new Role();
|
||||
$adminRole->setId(1);
|
||||
$adminRole->setRoleType(Role::ROLE_TYPE_ADMIN);
|
||||
|
||||
// User has both moderator and admin roles
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('mod1')
|
||||
->willReturn([$modRole, $adminRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
// Should return true because one of the roles is admin
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testGetUserRoleIdsReturnsMultipleRoles(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$role1 = new Role();
|
||||
$role1->setId(3);
|
||||
$role1->setRoleType(Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$role2 = new Role();
|
||||
$role2->setId(5);
|
||||
$role2->setRoleType(Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$role3 = new Role();
|
||||
$role3->setId(7);
|
||||
$role3->setRoleType(Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([$role1, $role2, $role3]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('getUserRoleIds');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertEquals([3, 5, 7], $result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminReturnsFalseWhenUserHasNoRoles(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testGetUserRoleIdsReturnsEmptyArrayWhenUserHasNoRoles(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('getUserRoleIds');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertEquals([], $result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminWithModeratorRoleOnly(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('mod1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$modRole = new Role();
|
||||
$modRole->setId(2);
|
||||
$modRole->setRoleType(Role::ROLE_TYPE_MODERATOR);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('mod1')
|
||||
->willReturn([$modRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
// Moderator is not admin
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminWithDefaultRoleType(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$defaultRole = new Role();
|
||||
$defaultRole->setId(3);
|
||||
$defaultRole->setRoleType(Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([$defaultRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testIsCurrentUserAdminWithCustomRoleType(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
$customRole = new Role();
|
||||
$customRole->setId(10);
|
||||
$customRole->setRoleType(Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with('user1')
|
||||
->willReturn([$customRole]);
|
||||
|
||||
// Use reflection to call private method
|
||||
$reflection = new \ReflectionClass($this->mapper);
|
||||
$method = $reflection->getMethod('isCurrentUserAdmin');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->mapper);
|
||||
|
||||
$this->assertFalse($result);
|
||||
public function testConstructor(): void {
|
||||
$this->assertInstanceOf(CategoryMapper::class, $this->mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,8 +710,12 @@ class PermissionServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetAccessibleCategoriesForGuestUserWithNoGuestRole(): void {
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByRoleType')
|
||||
$category1 = $this->createCategory(1, 'Category 1', 'category-1');
|
||||
|
||||
$this->categoryMapper->method('findAll')
|
||||
->willReturn([$category1]);
|
||||
|
||||
$this->roleMapper->method('findByRoleType')
|
||||
->with(Role::ROLE_TYPE_GUEST)
|
||||
->willThrowException(new DoesNotExistException('Guest role not found'));
|
||||
|
||||
@@ -722,12 +726,18 @@ class PermissionServiceTest extends TestCase {
|
||||
|
||||
public function testGetAccessibleCategoriesReturnsEmptyWhenUserHasNoRoles(): void {
|
||||
$userId = 'user1';
|
||||
$category1 = $this->createCategory(1, 'Category 1', 'category-1');
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
$this->categoryMapper->method('findAll')
|
||||
->willReturn([$category1]);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
$result = $this->service->getAccessibleCategories($userId);
|
||||
|
||||
$this->assertCount(0, $result);
|
||||
@@ -735,12 +745,6 @@ class PermissionServiceTest extends TestCase {
|
||||
|
||||
public function testGetAccessibleCategoriesReturnsEmptyWhenNoCategoriesExist(): void {
|
||||
$userId = 'user1';
|
||||
$role = $this->createRole(1, 'User', false, false, false, false, Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
@@ -754,9 +758,8 @@ class PermissionServiceTest extends TestCase {
|
||||
public function testGetAccessibleCategoriesHandlesExceptions(): void {
|
||||
$userId = 'user1';
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
$this->categoryMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
->willThrowException(new \Exception('Database error'));
|
||||
|
||||
$result = $this->service->getAccessibleCategories($userId);
|
||||
@@ -786,6 +789,378 @@ class PermissionServiceTest extends TestCase {
|
||||
return $role;
|
||||
}
|
||||
|
||||
// ---- Team (circle) permission tests ----
|
||||
|
||||
/**
|
||||
* Create a PermissionService partial mock where getUserCircleIds is overridden
|
||||
* to return the given circle IDs, allowing team permission paths to be tested
|
||||
* without the Circles app installed.
|
||||
*
|
||||
* @param array<string>|null $circleIds Circle IDs to return, or null for "Circles unavailable"
|
||||
*/
|
||||
private function createServiceWithCircleIds(?array $circleIds): PermissionService {
|
||||
$service = $this->getMockBuilder(PermissionService::class)
|
||||
->setConstructorArgs([
|
||||
$this->userRoleMapper,
|
||||
$this->roleMapper,
|
||||
$this->categoryPermMapper,
|
||||
$this->categoryMapper,
|
||||
$this->threadMapper,
|
||||
$this->postMapper,
|
||||
$this->userManager,
|
||||
$this->logger,
|
||||
])
|
||||
->onlyMethods(['getUserCircleIds'])
|
||||
->getMock();
|
||||
|
||||
$service->method('getUserCircleIds')
|
||||
->willReturn($circleIds);
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionGrantedByTeamWhenRoleDenies(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
// User role denies view on this category
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
$rolePerm = $this->createCategoryPerm(1, $categoryId, 3, false, false, false, false);
|
||||
|
||||
// But team grants view
|
||||
$teamPerm = $this->createTeamCategoryPerm(10, $categoryId, 'circle-abc', true, false, false, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturn($rolePerm);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->with($categoryId, ['circle-abc'])
|
||||
->willReturn([$teamPerm]);
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionGrantedByTeamWhenNoRolePermEntryExists(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
// User role has no permission entry for this category at all
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$teamPerm = $this->createTeamCategoryPerm(10, $categoryId, 'circle-abc', true, true, true, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->with($categoryId, ['circle-abc'])
|
||||
->willReturn([$teamPerm]);
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canPost'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionDeniedByBothRoleAndTeam(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
$rolePerm = $this->createCategoryPerm(1, $categoryId, 3, false, false, false, false);
|
||||
|
||||
// Team also denies
|
||||
$teamPerm = $this->createTeamCategoryPerm(10, $categoryId, 'circle-abc', false, false, false, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturn($rolePerm);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->with($categoryId, ['circle-abc'])
|
||||
->willReturn([$teamPerm]);
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertFalse($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionTeamNotCheckedForGuestUser(): void {
|
||||
$categoryId = 1;
|
||||
|
||||
$guestRole = $this->createRole(4, 'Guest', false, false, false, true, Role::ROLE_TYPE_GUEST);
|
||||
$rolePerm = $this->createCategoryPerm(1, $categoryId, 4, false, false, false, false);
|
||||
|
||||
$this->roleMapper->method('findByRoleType')
|
||||
->with(Role::ROLE_TYPE_GUEST)
|
||||
->willReturn($guestRole);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturn($rolePerm);
|
||||
|
||||
// Team mapper should never be called for guest users
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndTeamIds');
|
||||
|
||||
$result = $this->service->hasCategoryPermission(null, $categoryId, 'canView');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionTeamNotCheckedWhenCirclesUnavailable(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
// Circles unavailable → null circle IDs → no team check
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndTeamIds');
|
||||
|
||||
$service = $this->createServiceWithCircleIds(null);
|
||||
|
||||
$this->assertFalse($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionTeamNotCheckedWhenUserHasNoCircles(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
// User is in no circles
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndTeamIds');
|
||||
|
||||
$service = $this->createServiceWithCircleIds([]);
|
||||
|
||||
$this->assertFalse($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionMultipleTeamsOneGrants(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
// User is in two teams; first denies, second grants
|
||||
$teamPerm1 = $this->createTeamCategoryPerm(10, $categoryId, 'circle-aaa', false, false, false, false);
|
||||
$teamPerm2 = $this->createTeamCategoryPerm(11, $categoryId, 'circle-bbb', true, true, false, false);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->with($categoryId, ['circle-aaa', 'circle-bbb'])
|
||||
->willReturn([$teamPerm1, $teamPerm2]);
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-aaa', 'circle-bbb']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionTeamGrantsSpecificPermissionOnly(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
// Team grants view but not post
|
||||
$teamPerm = $this->createTeamCategoryPerm(10, $categoryId, 'circle-abc', true, false, false, false);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->willReturn([$teamPerm]);
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
$this->assertFalse($service->hasCategoryPermission($userId, $categoryId, 'canPost'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionAdminBypassesTeamCheck(): void {
|
||||
$userId = 'admin1';
|
||||
$categoryId = 3;
|
||||
|
||||
$adminRole = $this->createRole(1, 'Admin', true, true, true, true, Role::ROLE_TYPE_ADMIN);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$adminRole]);
|
||||
|
||||
// Neither role perms nor team perms should be checked
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndRole');
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndTeamIds');
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionRoleGrantsBeforeTeamCheck(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 1;
|
||||
|
||||
// Role already grants the permission
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
$rolePerm = $this->createCategoryPerm(1, $categoryId, 3, true, true, true, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturn($rolePerm);
|
||||
|
||||
// Team mapper should not be called since role already granted
|
||||
$this->categoryPermMapper->expects($this->never())
|
||||
->method('findByCategoryAndTeamIds');
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$this->assertTrue($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
public function testGetAccessibleCategoriesIncludesTeamOnlyCategory(): void {
|
||||
$userId = 'user1';
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
$category1 = $this->createCategory(1, 'Role Access', 'role-access');
|
||||
$category2 = $this->createCategory(2, 'Team Only', 'team-only');
|
||||
$category3 = $this->createCategory(3, 'No Access', 'no-access');
|
||||
|
||||
$rolePerm1 = $this->createCategoryPerm(1, 1, 3, true, true, true, false);
|
||||
$teamPerm2 = $this->createTeamCategoryPerm(10, 2, 'circle-abc', true, false, false, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryMapper->method('findAll')
|
||||
->willReturn([$category1, $category2, $category3]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturnCallback(function ($catId, $roleId) use ($rolePerm1) {
|
||||
if ($catId === 1 && $roleId === 3) {
|
||||
return $rolePerm1;
|
||||
}
|
||||
throw new DoesNotExistException('Not found');
|
||||
});
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->willReturnCallback(function ($catId, $teamIds) use ($teamPerm2) {
|
||||
if ($catId === 2) {
|
||||
return [$teamPerm2];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
$result = $service->getAccessibleCategories($userId);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertContains(1, $result);
|
||||
$this->assertContains(2, $result);
|
||||
$this->assertNotContains(3, $result);
|
||||
}
|
||||
|
||||
public function testGetAccessibleCategoriesNoTeamAccessWhenCirclesUnavailable(): void {
|
||||
$userId = 'user1';
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
$category1 = $this->createCategory(1, 'Role Access', 'role-access');
|
||||
$category2 = $this->createCategory(2, 'Team Only', 'team-only');
|
||||
|
||||
$rolePerm1 = $this->createCategoryPerm(1, 1, 3, true, true, true, false);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryMapper->method('findAll')
|
||||
->willReturn([$category1, $category2]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willReturnCallback(function ($catId, $roleId) use ($rolePerm1) {
|
||||
if ($catId === 1 && $roleId === 3) {
|
||||
return $rolePerm1;
|
||||
}
|
||||
throw new DoesNotExistException('Not found');
|
||||
});
|
||||
|
||||
// Circles unavailable
|
||||
$service = $this->createServiceWithCircleIds(null);
|
||||
|
||||
$result = $service->getAccessibleCategories($userId);
|
||||
|
||||
// Only role-granted category, not the team-only one
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertContains(1, $result);
|
||||
}
|
||||
|
||||
public function testHasCategoryPermissionTeamMapperExceptionHandledGracefully(): void {
|
||||
$userId = 'user1';
|
||||
$categoryId = 3;
|
||||
|
||||
$role = $this->createRole(3, 'User', false, false, false, false, Role::ROLE_TYPE_DEFAULT);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndRole')
|
||||
->willThrowException(new DoesNotExistException('Not found'));
|
||||
|
||||
$this->categoryPermMapper->method('findByCategoryAndTeamIds')
|
||||
->willThrowException(new \Exception('Database error'));
|
||||
|
||||
$service = $this->createServiceWithCircleIds(['circle-abc']);
|
||||
|
||||
// Should return false, not throw
|
||||
$this->assertFalse($service->hasCategoryPermission($userId, $categoryId, 'canView'));
|
||||
}
|
||||
|
||||
// ---- Helper methods ----
|
||||
|
||||
private function createCategoryPerm(int $id, int $categoryId, int $roleId, bool $canView, bool $canPost, bool $canReply, bool $canModerate): CategoryPerm {
|
||||
$perm = new CategoryPerm();
|
||||
$perm->setId($id);
|
||||
@@ -799,6 +1174,19 @@ class PermissionServiceTest extends TestCase {
|
||||
return $perm;
|
||||
}
|
||||
|
||||
private function createTeamCategoryPerm(int $id, int $categoryId, string $teamId, bool $canView, bool $canPost, bool $canReply, bool $canModerate): CategoryPerm {
|
||||
$perm = new CategoryPerm();
|
||||
$perm->setId($id);
|
||||
$perm->setCategoryId($categoryId);
|
||||
$perm->setTargetType(CategoryPerm::TARGET_TYPE_TEAM);
|
||||
$perm->setTargetId($teamId);
|
||||
$perm->setCanView($canView);
|
||||
$perm->setCanPost($canPost);
|
||||
$perm->setCanReply($canReply);
|
||||
$perm->setCanModerate($canModerate);
|
||||
return $perm;
|
||||
}
|
||||
|
||||
private function createCategory(int $id, string $name, string $slug): Category {
|
||||
$category = new Category();
|
||||
$category->setId($id);
|
||||
|
||||
Reference in New Issue
Block a user