-
Notifications
You must be signed in to change notification settings - Fork 3
/
role-manager.ts
179 lines (160 loc) · 6.85 KB
/
role-manager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**
* SudoSOS back-end API service.
* Copyright (C) 2024 Study association GEWIS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @license
*/
/**
* This is the module page of the rbac role manager.
*
* @module rbac
*/
import User, { UserType } from '../entity/user/user';
import AssignedRole from '../entity/rbac/assigned-role';
import Role from '../entity/rbac/role';
import log4js, { Logger } from 'log4js';
import Permission from '../entity/rbac/permission';
import { In } from 'typeorm';
import MemberAuthenticator from '../entity/authenticator/member-authenticator';
import { SELLER_ROLE } from './default-roles';
import { AllowedAttribute } from './role-definitions';
/**
* The role manager is responsible for the management of registered roles in the system,
* and performing access checks based on user roles and user access.
*/
export default class RoleManager {
private logger: Logger;
constructor() {
this.logger = log4js.getLogger('RoleManager');
}
public async initialize() {
// Placeholder for possible (future) asynchronous logic to initialize the role manager
return this;
}
/**
* Performs an access check for the given parameters.
* This method can be used to verify if a user with the given role(s)
* is permitted to perform the given action (eg. create, read, update, delete) on the given
* properties of the given data entity, to which the user has the given relations.
*
* @param roles - The role name or list of role names to perform the check for.
* If a single role is supplied as string, it is converted to a list.
* @param action - The action on the entity to check access for.
* Commonly used actions are 'create', 'read', 'update', and 'delete'.
* @param relations - The ownership relations towards the object.
* The ownership relations describes the status of the user related to the object:
* the user can be the owner, creator, editor, or not related at all.
* Commonly used ownership relations are 'own', 'created' and 'all'.
* @param entity - The entity type name of the object. Most often this is a
* database entity, but it could also be a computed entity such as 'balance'.
* @param attributes - The list of attributes to access. The wildcard '*' can be
* used to verify that the user is allowed to access all properties.
* @returns {boolean} - True if access is allowed, false otherwise.
*/
public async can(
roles: string | string[],
action: string,
relations: string | string[],
entity: string,
attributes: AllowedAttribute[],
): Promise<boolean> {
if (process.env.NODE_ENV === 'development') return true;
// Convert roles to array if a single role is given.
let rolesArray: string[];
if (typeof roles === 'string') {
rolesArray = [roles];
} else {
rolesArray = roles;
}
// Convert relations to array if a single relation is given.
let relationsArray: string[];
if (typeof relations === 'string') {
relationsArray = [relations];
} else {
relationsArray = relations;
}
// Add the relation "all" to the relations, because if you have permission to access "all",
// it does not matter what the given relation is.
if (relationsArray.indexOf('all') === -1) {
relationsArray.push('all');
}
// Given the entity, action and relation, try to find whether such a permission exists for the
// given roles.
const applicablePermissions = await Permission.find({ where: {
entity, action, relation: In(relationsArray), role: { name: In(rolesArray) },
} });
// For all found permission records, get a single list of all attributes the user is allowed to access
const allAttributes = applicablePermissions.map((perm) => perm.attributes).flat();
// If the user has a wildcard as attribute, they are allowed to access everything, so return true.
const hasStar = allAttributes.some((a) => a === '*');
if (hasStar) {
return true;
}
// Find all attributes that the user should have, but the current set of permissions does not provide
const disallowedAttributes = attributes.filter((a) => !allAttributes.includes(a));
// Return whether the user is allowed to access all attributes
return disallowedAttributes.length === 0;
}
/**
* Returns all the ORGANS the user has rights over
* @param user
*/
public async getUserOrgans(user: User) {
const organs = (await MemberAuthenticator.find({ where: { user: { id: user.id } }, relations: ['authenticateAs'] })).map((organ) => organ.authenticateAs);
return organs.filter((organ) => organ.type === UserType.ORGAN);
}
/**
* Get all role names for which the given user passes the assignment check.
* @param user - The user for which role checking is performed.
* @param getPermissions - Whether the permissions of each role should also be returned
* @returns a list of role names.
*/
public async getRoles(user: User, getPermissions = false): Promise<Role[]> {
const roles = await Role.find({ where: [{
assignments: { userId: user.id },
}, {
roleUserTypes: { userType: user.type },
}], relations: { permissions: getPermissions } });
const organs = await this.getUserOrgans(user);
// If a user is part of an organ he gains seller rights.
if (organs.length > 0) {
const sellerRole = await Role.findOne({ where: { name: SELLER_ROLE }, relations: { permissions: getPermissions } });
roles.push(sellerRole);
}
return roles;
}
/**
* Sets (overwrites) all the assigned users of a role.
* @param users - The users being set the role
* @param roleName - The role to set
*/
public async setRoleUsers(users: User[], roleName: string) {
const role = await Role.findOne({ where: { name: roleName } });
if (!role) return undefined;
// Typeorm doesnt like empty deletes.
const drop: AssignedRole[] = await AssignedRole.find({ where: { role: { id: role.id } } });
if (drop.length !== 0) {
// Drop all assigned users.
await AssignedRole.delete({ role: { id: role.id } });
}
// Assign users the role
const promises = users.map((user) => (Object.assign(new AssignedRole(), {
user,
role,
})).save());
return Promise.all(promises);
}
}