import type { IAuthorization, RoomAccessValidator } from '@rocket.chat/core-services'; import { License, ServiceClass } from '@rocket.chat/core-services'; import type { IUser, IRole, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms, Users, Roles, Permissions } from '@rocket.chat/models'; import mem from 'mem'; import { canAccessRoom } from './canAccessRoom'; import { canReadRoom } from './canReadRoom'; import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; import './canAccessRoomLivechat'; // Register as class export class Authorization extends ServiceClass implements IAuthorization { protected name = 'authorization'; private getRolesCached = mem(this.getRoles.bind(this), { maxAge: 1000, cacheKey: JSON.stringify, }); private rolesHasPermissionCached = mem(this.rolesHasPermission.bind(this), { cacheKey: JSON.stringify, ...(process.env.TEST_MODE === 'true' && { maxAge: 1 }), }); constructor() { super(); const clearCache = (): void => { mem.clear(this.getRolesCached); mem.clear(this.rolesHasPermissionCached); }; this.onEvent('watch.roles', clearCache); this.onEvent('permission.changed', clearCache); this.onEvent('authorization.guestPermissions', (permissions: string[]) => { AuthorizationUtils.addRolePermissionWhiteList('guest', permissions); }); } override async started(): Promise { try { if (!(await License.hasValidLicense())) { return; } const permissions = await License.getGuestPermissions(); if (!permissions) { return; } AuthorizationUtils.addRolePermissionWhiteList('guest', permissions); } catch (error) { console.error('Authorization Service did not start correctly', error); } } async hasAllPermission(userId: string, permissions: string[], scope?: string): Promise { if (!userId) { return false; } return this.all(userId, permissions, scope); } async hasPermission(userId: string, permissionId: string, scope?: string): Promise { if (!userId) { return false; } return this.all(userId, [permissionId], scope); } async hasAtLeastOnePermission(userId: string, permissions: string[], scope?: string): Promise { if (!userId) { return false; } return this.atLeastOne(userId, permissions, scope); } async canAccessRoom(...args: Parameters): Promise { return canAccessRoom(...args); } async canReadRoom(...args: Parameters): Promise { return canReadRoom(...args); } async canAccessRoomId(rid: IRoom['_id'], uid: IUser['_id']): Promise { const room = await Rooms.findOneById>(rid, { projection: { _id: 1, t: 1, teamId: 1, prid: 1, abacAttributes: 1, }, }); if (!room) { return false; } return this.canAccessRoom(room, { _id: uid }); } async addRoleRestrictions(role: IRole['_id'], permissions: string[]): Promise { AuthorizationUtils.addRolePermissionWhiteList(role, permissions); } async getUsersFromPublicRoles(): Promise< { _id: string; username: string; roles: string[]; }[] > { const roleIds = await this.getPublicRoles(); return this.getUserFromRoles(roleIds); } private getPublicRoles = mem( async (): Promise => { const roles = Roles.find>({ scope: 'Users', description: { $exists: true, $ne: '' } }, { projection: { _id: 1 } }); return roles.map(({ _id }) => _id).toArray(); }, { maxAge: 10000 }, ); private getUserFromRoles = mem( async (roleIds: string[]) => { const users = Users.findUsersInRoles, '_id' | 'username' | 'roles'>>(roleIds, null, { sort: { username: 1, }, projection: { _id: 1, username: 1, roles: 1, }, }); return users .map((user) => ({ ...user, roles: user.roles.filter((roleId: string) => roleIds.includes(roleId)), })) .toArray(); }, { maxAge: 10000 }, ); private async rolesHasPermission(permission: string, roles: IRole['_id'][]): Promise { if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { return false; } const result = await Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } }); return !!result; } private async getRoles(uid: string, scope?: IRoom['_id']): Promise { const { roles: userRoles = [] } = (await Users.findOneById(uid, { projection: { roles: 1 } })) || {}; const { roles: subscriptionsRoles = [] } = (scope && (await Subscriptions.findOne>({ 'rid': scope, 'u._id': uid }, { projection: { roles: 1 } }))) || {}; return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); } private async atLeastOne(uid: string, permissions: string[] = [], scope?: string): Promise { const sortedRoles = await this.getRolesCached(uid, scope); for await (const permission of permissions) { if (await this.rolesHasPermissionCached(permission, sortedRoles)) { return true; } } return false; } private async all(uid: string, permissions: string[] = [], scope?: string): Promise { const sortedRoles = await this.getRolesCached(uid, scope); for await (const permission of permissions) { if (!(await this.rolesHasPermissionCached(permission, sortedRoles))) { return false; } } return true; } async hasAnyRole(userId: IUser['_id'], roleIds: IRole['_id'][], scope?: IRoom['_id']): Promise { if (!Array.isArray(roleIds)) { throw new Error('error-invalid-arguments'); } if (!userId) { return false; } return Roles.isUserInRoles(userId, roleIds, scope); } }