Files
Toju/toju-app/src/app/domains/access-control/domain/rules/room.rules.ts

210 lines
7.2 KiB
TypeScript

import {
PermissionState,
Room,
RoomMember,
RoomPermissionKey,
RoomPermissions,
RoomRole,
RoomRoleAssignment,
UserRole
} from '../../../../shared-kernel';
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
import {
getRolePermissionState,
permissionStateToBoolean,
resolveLegacyAllowState
} from '../util/access-control.util';
import type { MemberIdentity } from '../models/access-control.model';
import {
getAssignedRoleIds,
normalizeRoomRoleAssignments,
removeRoleFromAssignments
} from './role-assignment.rules';
import {
getRoomRoleById,
normalizeRoomRoles,
withUpdatedRole
} from './role.rules';
import { normalizeChannelPermissionOverrides, resolveRoomPermission } from './permission.rules';
function applyRolePermissionChanges(
roles: readonly RoomRole[],
role: RoomRole | null | undefined,
changes: Partial<Record<RoomPermissionKey, PermissionState | undefined>>
): RoomRole[] {
if (!role) {
return [...roles];
}
return withUpdatedRole(roles, role.id, {
permissions: {
...role.permissions,
...changes
}
});
}
function getEveryoneLegacyPermissionChanges(
role: RoomRole,
permissions: Partial<RoomPermissions>
): Partial<Record<RoomPermissionKey, PermissionState | undefined>> {
return {
joinVoice: resolveLegacyAllowState(permissions.allowVoice, role.permissions?.joinVoice, 'deny'),
shareScreen: resolveLegacyAllowState(permissions.allowScreenShare, role.permissions?.shareScreen, 'deny'),
uploadFiles: resolveLegacyAllowState(permissions.allowFileUploads, role.permissions?.uploadFiles, 'deny')
};
}
function getModeratorLegacyPermissionChanges(
role: RoomRole,
permissions: Partial<RoomPermissions>
): Partial<Record<RoomPermissionKey, PermissionState | undefined>> {
return {
manageChannels: resolveLegacyAllowState(permissions.moderatorsManageRooms, role.permissions?.manageChannels, 'inherit'),
manageIcon: resolveLegacyAllowState(permissions.moderatorsManageIcon, role.permissions?.manageIcon, 'inherit')
};
}
function getAdminLegacyPermissionChanges(
role: RoomRole,
permissions: Partial<RoomPermissions>
): Partial<Record<RoomPermissionKey, PermissionState | undefined>> {
return {
manageChannels: resolveLegacyAllowState(permissions.adminsManageRooms, role.permissions?.manageChannels, 'inherit'),
manageIcon: resolveLegacyAllowState(permissions.adminsManageIcon, role.permissions?.manageIcon, 'inherit')
};
}
export function resolveLegacyRole(room: Room, identity: MemberIdentity | null | undefined): UserRole {
if (!identity) {
return 'member';
}
if (room.hostId === identity.id || room.hostId === identity.oderId) {
return 'host';
}
const assignedRoleIds = getAssignedRoleIds(room.roleAssignments, identity);
if (assignedRoleIds.includes(SYSTEM_ROLE_IDS.admin)) {
return 'admin';
}
if (assignedRoleIds.includes(SYSTEM_ROLE_IDS.moderator)) {
return 'moderator';
}
if (
resolveRoomPermission(room, identity, 'manageRoles') ||
resolveRoomPermission(room, identity, 'banMembers') ||
resolveRoomPermission(room, identity, 'manageBans') ||
resolveRoomPermission(room, identity, 'manageServer')
) {
return 'admin';
}
if (
resolveRoomPermission(room, identity, 'kickMembers') ||
resolveRoomPermission(room, identity, 'deleteMessages') ||
resolveRoomPermission(room, identity, 'manageChannels') ||
resolveRoomPermission(room, identity, 'manageIcon')
) {
return 'moderator';
}
return 'member';
}
export function deriveLegacyRoomPermissions(room: Pick<Room, 'roles' | 'permissions' | 'slowModeInterval'>): RoomPermissions {
const roles = normalizeRoomRoles(room.roles, room.permissions);
const everyoneRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.everyone);
const moderatorRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.moderator);
const adminRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.admin);
return {
allowVoice: permissionStateToBoolean(getRolePermissionState(everyoneRole, 'joinVoice'), true),
allowScreenShare: permissionStateToBoolean(getRolePermissionState(everyoneRole, 'shareScreen'), true),
allowFileUploads: permissionStateToBoolean(getRolePermissionState(everyoneRole, 'uploadFiles'), true),
adminsManageRooms: getRolePermissionState(adminRole, 'manageChannels') === 'allow',
moderatorsManageRooms: getRolePermissionState(moderatorRole, 'manageChannels') === 'allow',
adminsManageIcon: getRolePermissionState(adminRole, 'manageIcon') === 'allow',
moderatorsManageIcon: getRolePermissionState(moderatorRole, 'manageIcon') === 'allow',
slowModeInterval: room.slowModeInterval ?? room.permissions?.slowModeInterval ?? 0
};
}
export function withLegacyRoomPermissions(room: Room, permissions: Partial<RoomPermissions>): Room {
const roles = normalizeRoomRoles(room.roles, room.permissions);
const everyoneRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.everyone);
const moderatorRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.moderator);
const adminRole = getRoomRoleById(roles, SYSTEM_ROLE_IDS.admin);
let nextRoles = applyRolePermissionChanges(roles, everyoneRole, everyoneRole ? getEveryoneLegacyPermissionChanges(everyoneRole, permissions) : {});
nextRoles = applyRolePermissionChanges(
nextRoles,
moderatorRole,
moderatorRole ? getModeratorLegacyPermissionChanges(moderatorRole, permissions) : {}
);
nextRoles = applyRolePermissionChanges(nextRoles, adminRole, adminRole ? getAdminLegacyPermissionChanges(adminRole, permissions) : {});
return normalizeRoomAccessControl({
...room,
roles: nextRoles,
slowModeInterval: permissions.slowModeInterval ?? room.slowModeInterval ?? room.permissions?.slowModeInterval ?? 0
});
}
export function hydrateRoomMembers(room: Room): RoomMember[] {
const roles = normalizeRoomRoles(room.roles, room.permissions);
const roleAssignments = normalizeRoomRoleAssignments(room.roleAssignments, room.members, roles);
return (room.members ?? []).map((member) => {
const roleIds = getAssignedRoleIds(roleAssignments, member);
const hydratedRoom: Room = {
...room,
roles,
roleAssignments
};
return {
...member,
roleIds,
role: resolveLegacyRole(hydratedRoom, member)
};
});
}
export function normalizeRoomAccessControl(room: Room): Room {
const roles = normalizeRoomRoles(room.roles, room.permissions);
const roleAssignments = normalizeRoomRoleAssignments(room.roleAssignments, room.members, roles);
const channelPermissions = normalizeChannelPermissionOverrides(room.channelPermissions, roles);
const slowModeInterval = room.slowModeInterval ?? room.permissions?.slowModeInterval ?? 0;
const nextRoom: Room = {
...room,
roles,
roleAssignments,
channelPermissions,
slowModeInterval
};
nextRoom.permissions = deriveLegacyRoomPermissions(nextRoom);
nextRoom.members = hydrateRoomMembers(nextRoom);
return nextRoom;
}
export function removeRole(
roles: readonly RoomRole[],
assignments: readonly RoomRoleAssignment[] | undefined,
roleId: string
): { roles: RoomRole[]; roleAssignments: RoomRoleAssignment[] } {
const nextRoles = roles.filter((role) => role.id !== roleId || role.isSystem);
return {
roles: normalizeRoomRoles(nextRoles),
roleAssignments: removeRoleFromAssignments(assignments, roleId)
};
}