admin area
This commit is contained in:
747
core/admin/domain/services/AuthorizationService.test.ts
Normal file
747
core/admin/domain/services/AuthorizationService.test.ts
Normal file
@@ -0,0 +1,747 @@
|
||||
import { AuthorizationService } from './AuthorizationService';
|
||||
import { AdminUser } from '../entities/AdminUser';
|
||||
|
||||
describe('AuthorizationService', () => {
|
||||
describe('TDD - Test First', () => {
|
||||
describe('canListUsers', () => {
|
||||
it('should allow owner to list users', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canList = AuthorizationService.canListUsers(owner);
|
||||
|
||||
// Assert
|
||||
expect(canList).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow admin to list users', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canList = AuthorizationService.canListUsers(admin);
|
||||
|
||||
// Assert
|
||||
expect(canList).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny regular user from listing users', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canList = AuthorizationService.canListUsers(user);
|
||||
|
||||
// Assert
|
||||
expect(canList).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny suspended admin from listing users', () => {
|
||||
// Arrange
|
||||
const suspendedAdmin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canList = AuthorizationService.canListUsers(suspendedAdmin);
|
||||
|
||||
// Assert
|
||||
expect(canList).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow owner with multiple roles to list users', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canList = AuthorizationService.canListUsers(owner);
|
||||
|
||||
// Assert
|
||||
expect(canList).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canPerformAction with manage', () => {
|
||||
it('should allow owner to manage any user', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canManage = AuthorizationService.canPerformAction(owner, 'manage', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canManage).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow admin to manage non-admin users', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canManage = AuthorizationService.canPerformAction(admin, 'manage', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canManage).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny admin from managing other admins', () => {
|
||||
// Arrange
|
||||
const admin1 = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin1@example.com',
|
||||
displayName: 'Admin 1',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const admin2 = AdminUser.create({
|
||||
id: 'admin-2',
|
||||
email: 'admin2@example.com',
|
||||
displayName: 'Admin 2',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canManage = AuthorizationService.canPerformAction(admin1, 'manage', admin2);
|
||||
|
||||
// Assert
|
||||
expect(canManage).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny regular user from managing anyone', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canManage = AuthorizationService.canPerformAction(user, 'manage', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canManage).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow admin to manage suspended users', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const suspendedUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canManage = AuthorizationService.canPerformAction(admin, 'manage', suspendedUser);
|
||||
|
||||
// Assert
|
||||
expect(canManage).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canPerformAction with modify_roles', () => {
|
||||
it('should allow owner to modify roles', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canModify = AuthorizationService.canPerformAction(owner, 'modify_roles', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canModify).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny admin from modifying roles', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canModify = AuthorizationService.canPerformAction(admin, 'modify_roles', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canModify).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny regular user from modifying roles', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canModify = AuthorizationService.canPerformAction(user, 'modify_roles', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canModify).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canPerformAction with change_status', () => {
|
||||
it('should allow owner to change any status', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canChange = AuthorizationService.canPerformAction(owner, 'change_status', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canChange).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow admin to change non-admin status', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canChange = AuthorizationService.canPerformAction(admin, 'change_status', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canChange).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny admin from changing admin status', () => {
|
||||
// Arrange
|
||||
const admin1 = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin1@example.com',
|
||||
displayName: 'Admin 1',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const admin2 = AdminUser.create({
|
||||
id: 'admin-2',
|
||||
email: 'admin2@example.com',
|
||||
displayName: 'Admin 2',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canChange = AuthorizationService.canPerformAction(admin1, 'change_status', admin2);
|
||||
|
||||
// Assert
|
||||
expect(canChange).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny regular user from changing status', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canChange = AuthorizationService.canPerformAction(user, 'change_status', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canChange).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canPerformAction with delete', () => {
|
||||
it('should allow owner to delete any user', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canDelete = AuthorizationService.canPerformAction(owner, 'delete', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canDelete).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow admin to delete non-admin users', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canDelete = AuthorizationService.canPerformAction(admin, 'delete', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canDelete).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny admin from deleting other admins', () => {
|
||||
// Arrange
|
||||
const admin1 = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin1@example.com',
|
||||
displayName: 'Admin 1',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const admin2 = AdminUser.create({
|
||||
id: 'admin-2',
|
||||
email: 'admin2@example.com',
|
||||
displayName: 'Admin 2',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canDelete = AuthorizationService.canPerformAction(admin1, 'delete', admin2);
|
||||
|
||||
// Assert
|
||||
expect(canDelete).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny regular user from deleting anyone', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canDelete = AuthorizationService.canPerformAction(user, 'delete', targetUser);
|
||||
|
||||
// Assert
|
||||
expect(canDelete).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow owner to delete suspended users', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const suspendedUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
// Act
|
||||
const canDelete = AuthorizationService.canPerformAction(owner, 'delete', suspendedUser);
|
||||
|
||||
// Assert
|
||||
expect(canDelete).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPermissions', () => {
|
||||
it('should return correct permissions for owner', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const permissions = AuthorizationService.getPermissions(owner);
|
||||
|
||||
// Assert
|
||||
expect(permissions).toContain('users.view');
|
||||
expect(permissions).toContain('users.list');
|
||||
expect(permissions).toContain('users.manage');
|
||||
expect(permissions).toContain('users.roles.modify');
|
||||
expect(permissions).toContain('users.status.change');
|
||||
expect(permissions).toContain('users.create');
|
||||
expect(permissions).toContain('users.delete');
|
||||
expect(permissions).toContain('users.export');
|
||||
});
|
||||
|
||||
it('should return correct permissions for admin', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const permissions = AuthorizationService.getPermissions(admin);
|
||||
|
||||
// Assert
|
||||
expect(permissions).toContain('users.view');
|
||||
expect(permissions).toContain('users.list');
|
||||
expect(permissions).toContain('users.manage');
|
||||
expect(permissions).toContain('users.status.change');
|
||||
expect(permissions).toContain('users.create');
|
||||
expect(permissions).toContain('users.delete');
|
||||
expect(permissions).not.toContain('users.roles.modify');
|
||||
expect(permissions).not.toContain('users.export');
|
||||
});
|
||||
|
||||
it('should return empty permissions for regular user', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const permissions = AuthorizationService.getPermissions(user);
|
||||
|
||||
// Assert
|
||||
expect(permissions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty permissions for suspended admin', () => {
|
||||
// Arrange
|
||||
const suspendedAdmin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
// Act
|
||||
const permissions = AuthorizationService.getPermissions(suspendedAdmin);
|
||||
|
||||
// Assert
|
||||
expect(permissions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPermission', () => {
|
||||
it('should return true for owner with users.view permission', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const hasPermission = AuthorizationService.hasPermission(owner, 'users.view');
|
||||
|
||||
// Assert
|
||||
expect(hasPermission).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for admin with users.roles.modify permission', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const hasPermission = AuthorizationService.hasPermission(admin, 'users.roles.modify');
|
||||
|
||||
// Assert
|
||||
expect(hasPermission).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for regular user with any permission', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const hasPermission = AuthorizationService.hasPermission(user, 'users.view');
|
||||
|
||||
// Assert
|
||||
expect(hasPermission).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enforce', () => {
|
||||
it('should not throw for authorized action', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act & Assert - Should not throw
|
||||
expect(() => {
|
||||
AuthorizationService.enforce(owner, 'manage', targetUser);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw for unauthorized action', () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const targetUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act & Assert - Should throw
|
||||
expect(() => {
|
||||
AuthorizationService.enforce(user, 'manage', targetUser);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('enforcePermission', () => {
|
||||
it('should not throw for authorized permission', () => {
|
||||
// Arrange
|
||||
const owner = AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act & Assert - Should not throw
|
||||
expect(() => {
|
||||
AuthorizationService.enforcePermission(owner, 'users.view');
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw for unauthorized permission', () => {
|
||||
// Arrange
|
||||
const admin = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act & Assert - Should throw
|
||||
expect(() => {
|
||||
AuthorizationService.enforcePermission(admin, 'users.roles.modify');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
283
core/admin/domain/services/AuthorizationService.ts
Normal file
283
core/admin/domain/services/AuthorizationService.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { AdminUser } from '../entities/AdminUser';
|
||||
import { AuthorizationError } from '../errors/AdminDomainError';
|
||||
|
||||
/**
|
||||
* Domain service for authorization checks
|
||||
* Stateless service that enforces access control rules
|
||||
*/
|
||||
export class AuthorizationService {
|
||||
/**
|
||||
* Check if an actor can perform an action on a target user
|
||||
*/
|
||||
static canPerformAction(
|
||||
actor: AdminUser,
|
||||
action: 'view' | 'manage' | 'modify_roles' | 'change_status' | 'delete',
|
||||
target?: AdminUser
|
||||
): boolean {
|
||||
// Actors must be system admins and active
|
||||
if (!actor.isSystemAdmin() || !actor.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'view':
|
||||
return this.canView(actor, target);
|
||||
case 'manage':
|
||||
return this.canManage(actor, target);
|
||||
case 'modify_roles':
|
||||
return this.canModifyRoles(actor, target);
|
||||
case 'change_status':
|
||||
return this.canChangeStatus(actor, target);
|
||||
case 'delete':
|
||||
return this.canDelete(actor, target);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can view target user
|
||||
*/
|
||||
private static canView(actor: AdminUser, target?: AdminUser): boolean {
|
||||
if (!target) {
|
||||
// Viewing list - only admins can view
|
||||
return actor.isSystemAdmin();
|
||||
}
|
||||
|
||||
// Can always view self
|
||||
if (actor.id.equals(target.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Owner can view everyone
|
||||
if (actor.hasRole('owner')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin can view non-admin users
|
||||
if (actor.hasRole('admin')) {
|
||||
return !target.isSystemAdmin();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can manage target user
|
||||
*/
|
||||
private static canManage(actor: AdminUser, target?: AdminUser): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can always manage self
|
||||
if (actor.id.equals(target.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Owner can manage everyone
|
||||
if (actor.hasRole('owner')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin can manage non-admin users
|
||||
if (actor.hasRole('admin')) {
|
||||
return !target.isSystemAdmin();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can modify roles of target user
|
||||
*/
|
||||
private static canModifyRoles(actor: AdminUser, target?: AdminUser): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only owner can modify roles
|
||||
if (!actor.hasRole('owner')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot modify own roles (prevents accidental lockout)
|
||||
if (actor.id.equals(target.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can change status of target user
|
||||
*/
|
||||
private static canChangeStatus(actor: AdminUser, target?: AdminUser): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot change own status
|
||||
if (actor.id.equals(target.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Owner can change anyone's status
|
||||
if (actor.hasRole('owner')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin can change user status but not other admins/owners
|
||||
if (actor.hasRole('admin')) {
|
||||
return !target.isSystemAdmin();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can delete target user
|
||||
*/
|
||||
private static canDelete(actor: AdminUser, target?: AdminUser): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot delete self
|
||||
if (actor.id.equals(target.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Owner can delete anyone
|
||||
if (actor.hasRole('owner')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin can delete users but not admins/owners
|
||||
if (actor.hasRole('admin')) {
|
||||
return !target.isSystemAdmin();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce authorization - throws if not authorized
|
||||
*/
|
||||
static enforce(
|
||||
actor: AdminUser,
|
||||
action: 'view' | 'manage' | 'modify_roles' | 'change_status' | 'delete',
|
||||
target?: AdminUser
|
||||
): void {
|
||||
if (!this.canPerformAction(actor, action, target)) {
|
||||
const actionLabel = action.replace('_', ' ');
|
||||
const targetLabel = target ? `user ${target.email.value}` : 'user list';
|
||||
throw new AuthorizationError(
|
||||
`User ${actor.email.value} is not authorized to ${actionLabel} ${targetLabel}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can list users
|
||||
*/
|
||||
static canListUsers(actor: AdminUser): boolean {
|
||||
return actor.isSystemAdmin() && actor.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can create users
|
||||
*/
|
||||
static canCreateUsers(actor: AdminUser): boolean {
|
||||
// Only owner can create users with admin roles
|
||||
// Admin can create regular users
|
||||
return actor.isSystemAdmin() && actor.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor can assign a specific role
|
||||
*/
|
||||
static canAssignRole(actor: AdminUser, roleToAssign: string, target?: AdminUser): boolean {
|
||||
// Only owner can assign owner or admin roles
|
||||
if (roleToAssign === 'owner' || roleToAssign === 'admin') {
|
||||
return actor.hasRole('owner');
|
||||
}
|
||||
|
||||
// Admin can assign user role
|
||||
if (roleToAssign === 'user') {
|
||||
// Admin can assign user role to non-admin users
|
||||
// Owner can assign user role to anyone
|
||||
if (actor.hasRole('owner')) {
|
||||
return true;
|
||||
}
|
||||
if (actor.hasRole('admin')) {
|
||||
if (!target) {
|
||||
return true;
|
||||
}
|
||||
return !target.isSystemAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permissions for actor
|
||||
*/
|
||||
static getPermissions(actor: AdminUser): string[] {
|
||||
const permissions: string[] = [];
|
||||
|
||||
if (!actor.isSystemAdmin() || !actor.isActive()) {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
// Base permissions for all admins
|
||||
permissions.push(
|
||||
'users.view',
|
||||
'users.list'
|
||||
);
|
||||
|
||||
// Admin permissions
|
||||
if (actor.hasRole('admin')) {
|
||||
permissions.push(
|
||||
'users.manage',
|
||||
'users.status.change',
|
||||
'users.create',
|
||||
'users.delete'
|
||||
);
|
||||
}
|
||||
|
||||
// Owner permissions
|
||||
if (actor.hasRole('owner')) {
|
||||
permissions.push(
|
||||
'users.manage',
|
||||
'users.roles.modify',
|
||||
'users.status.change',
|
||||
'users.create',
|
||||
'users.delete',
|
||||
'users.export'
|
||||
);
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor has specific permission
|
||||
*/
|
||||
static hasPermission(actor: AdminUser, permission: string): boolean {
|
||||
const permissions = this.getPermissions(actor);
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce permission - throws if not authorized
|
||||
*/
|
||||
static enforcePermission(actor: AdminUser, permission: string): void {
|
||||
if (!this.hasPermission(actor, permission)) {
|
||||
throw new AuthorizationError(
|
||||
`User ${actor.email.value} does not have permission: ${permission}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user