The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/apps/meteor/app/authorization/lib/createAuthorizationFunction...

101 lines
4.3 KiB

import type { IPermission, IRole, IUser } from '@rocket.chat/core-typings';
import { AuthorizationUtils } from './AuthorizationUtils';
export type AuthorizationDeps = {
/** The currently logged-in user id, or undefined. */
getCurrentUserId: () => IUser['_id'] | undefined;
/** The role ids assigned to a given user (Users scope). */
getUserRoles: (userId: IUser['_id']) => IRole['_id'][] | undefined;
/** Lookup a permission by id. */
getPermission: (permissionId: IPermission['_id']) => IPermission | undefined;
/** The scope of a role; defaults to 'Users' when the role is unknown. */
getRoleScope: (roleId: IRole['_id']) => IRole['scope'] | undefined;
/** Whether a subscription scoped to `rid` grants `roleId`. */
hasSubscriptionRole: (rid: string, roleId: IRole['_id']) => boolean;
/** Whether the permissions cache is hydrated; otherwise checks short-circuit to false. */
isReady: () => boolean;
};
export type AuthorizationFunctions = {
hasRole: (userId: IUser['_id'], roleId: IRole['_id'], scope?: string) => boolean;
hasAllPermission: (permissions: IPermission['_id'] | IPermission['_id'][], scope?: string, scopedRoles?: IRole['_id'][]) => boolean;
hasAtLeastOnePermission: (permissions: IPermission['_id'] | IPermission['_id'][], scope?: string) => boolean;
/** Alias of hasAllPermission, kept for parity with the previous API. */
hasPermission: (permissions: IPermission['_id'] | IPermission['_id'][], scope?: string, scopedRoles?: IRole['_id'][]) => boolean;
userHasAllPermission: (
permissions: IPermission['_id'] | IPermission['_id'][],
scope: string | undefined,
userId: IUser['_id'],
) => boolean;
};
/**
* Pure factory for the client-side authorization helpers. All store access is
* threaded through the {@link AuthorizationDeps} accessors, so the returned
* functions are testable in isolation and reusable across any state backend.
*/
export const createAuthorizationFunctions = (deps: AuthorizationDeps): AuthorizationFunctions => {
const hasRole = (userId: IUser['_id'], roleId: IRole['_id'], scope?: string): boolean => {
const roleScope = deps.getRoleScope(roleId) ?? 'Users';
switch (roleScope) {
case 'Subscriptions':
if (!scope) return false;
return deps.hasSubscriptionRole(scope, roleId);
case 'Users':
return deps.getUserRoles(userId)?.includes(roleId) ?? false;
default:
return false;
}
};
const checkPermissions = (
permissionIds: IPermission['_id'][],
scope: string | undefined,
userId: IUser['_id'],
scopedRoles: IRole['_id'][] | undefined,
quantifier: (this: IPermission['_id'][], predicate: (id: IPermission['_id']) => boolean) => boolean,
): boolean => {
const userRoles = deps.getUserRoles(userId);
return quantifier.call(permissionIds, (permissionId) => {
if (userRoles && AuthorizationUtils.isPermissionRestrictedForRoleList(permissionId, userRoles)) {
return false;
}
const roles = deps.getPermission(permissionId)?.roles ?? [];
return roles.some((roleId) => {
if (scopedRoles?.includes(roleId)) return true;
return hasRole(userId, roleId, scope);
});
});
};
const validatePermissions = (
permissions: IPermission['_id'] | IPermission['_id'][],
scope: string | undefined,
quantifier: (this: IPermission['_id'][], predicate: (id: IPermission['_id']) => boolean) => boolean,
userId: IUser['_id'] | undefined,
scopedRoles?: IRole['_id'][],
): boolean => {
if (!userId) return false;
if (!deps.isReady()) return false;
const ids = ([] as IPermission['_id'][]).concat(permissions);
return checkPermissions(ids, scope, userId, scopedRoles, quantifier);
};
const hasAllPermission: AuthorizationFunctions['hasAllPermission'] = (permissions, scope, scopedRoles) =>
validatePermissions(permissions, scope, Array.prototype.every, deps.getCurrentUserId(), scopedRoles);
const hasAtLeastOnePermission: AuthorizationFunctions['hasAtLeastOnePermission'] = (permissions, scope) =>
validatePermissions(permissions, scope, Array.prototype.some, deps.getCurrentUserId());
const userHasAllPermission: AuthorizationFunctions['userHasAllPermission'] = (permissions, scope, userId) =>
validatePermissions(permissions, scope, Array.prototype.every, userId);
return {
hasRole,
hasAllPermission,
hasAtLeastOnePermission,
hasPermission: hasAllPermission,
userHasAllPermission,
};
};