2021-08-12 15:04:37 +02:00
|
|
|
import * as permissions from '../types/permissions';
|
2022-07-21 16:23:56 +02:00
|
|
|
import User, { IProjectUser, IUser } from '../types/user';
|
2021-04-22 10:07:10 +02:00
|
|
|
import {
|
2022-07-21 16:23:56 +02:00
|
|
|
IAccessInfo,
|
2021-08-12 15:04:37 +02:00
|
|
|
IAccessStore,
|
2021-04-22 10:07:10 +02:00
|
|
|
IRole,
|
2022-01-13 11:14:17 +01:00
|
|
|
IRoleWithPermissions,
|
2022-09-30 13:36:45 +02:00
|
|
|
IRoleWithProject,
|
2021-04-22 10:07:10 +02:00
|
|
|
IUserPermission,
|
|
|
|
IUserRole,
|
2021-08-12 15:04:37 +02:00
|
|
|
} from '../types/stores/access-store';
|
|
|
|
import { Logger } from '../logger';
|
2023-01-18 17:08:07 +01:00
|
|
|
import { IAccountStore, IUnleashStores } from '../types/stores';
|
2021-08-12 15:04:37 +02:00
|
|
|
import {
|
2022-01-13 11:14:17 +01:00
|
|
|
IAvailablePermissions,
|
|
|
|
ICustomRole,
|
2021-08-12 15:04:37 +02:00
|
|
|
IPermission,
|
|
|
|
IRoleData,
|
|
|
|
IUserWithRole,
|
|
|
|
RoleName,
|
|
|
|
} from '../types/model';
|
2022-01-13 11:14:17 +01:00
|
|
|
import { IRoleStore } from 'lib/types/stores/role-store';
|
|
|
|
import NameExistsError from '../error/name-exists-error';
|
|
|
|
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
|
|
|
|
import RoleInUseError from '../error/role-in-use-error';
|
|
|
|
import { roleSchema } from '../schema/role-schema';
|
2023-06-14 15:40:40 +02:00
|
|
|
import {
|
|
|
|
ALL_ENVS,
|
|
|
|
ALL_PROJECTS,
|
|
|
|
CUSTOM_ROOT_ROLE_TYPE,
|
|
|
|
CUSTOM_PROJECT_ROLE_TYPE,
|
2023-06-22 16:42:01 +02:00
|
|
|
ROOT_ROLE_TYPES,
|
2023-06-14 15:40:40 +02:00
|
|
|
} from '../util/constants';
|
2022-01-13 11:14:17 +01:00
|
|
|
import { DEFAULT_PROJECT } from '../types/project';
|
|
|
|
import InvalidOperationError from '../error/invalid-operation-error';
|
2022-06-22 14:55:43 +02:00
|
|
|
import BadDataError from '../error/bad-data-error';
|
2022-07-21 16:23:56 +02:00
|
|
|
import { IGroupModelWithProjectRole } from '../types/group';
|
|
|
|
import { GroupService } from './group-service';
|
2023-06-14 15:40:40 +02:00
|
|
|
import { IFlagResolver, IUnleashConfig } from 'lib/types';
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
const { ADMIN } = permissions;
|
2021-03-11 22:51:58 +01:00
|
|
|
|
|
|
|
const PROJECT_ADMIN = [
|
2021-04-22 10:07:10 +02:00
|
|
|
permissions.UPDATE_PROJECT,
|
|
|
|
permissions.DELETE_PROJECT,
|
|
|
|
permissions.CREATE_FEATURE,
|
|
|
|
permissions.UPDATE_FEATURE,
|
|
|
|
permissions.DELETE_FEATURE,
|
2021-03-11 22:51:58 +01:00
|
|
|
];
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
interface IRoleCreation {
|
|
|
|
name: string;
|
|
|
|
description: string;
|
2023-06-14 15:40:40 +02:00
|
|
|
type?: 'root-custom' | 'custom';
|
2022-01-13 11:14:17 +01:00
|
|
|
permissions?: IPermission[];
|
|
|
|
}
|
|
|
|
|
2022-08-19 10:28:53 +02:00
|
|
|
export interface IRoleValidation {
|
|
|
|
name: string;
|
|
|
|
description?: string;
|
|
|
|
permissions?: Pick<IPermission, 'id' | 'environment'>[];
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
interface IRoleUpdate {
|
|
|
|
id: number;
|
|
|
|
name: string;
|
|
|
|
description: string;
|
2023-06-14 15:40:40 +02:00
|
|
|
type?: 'root-custom' | 'custom';
|
2022-01-13 11:14:17 +01:00
|
|
|
permissions?: IPermission[];
|
|
|
|
}
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
const isProjectPermission = (permission) => PROJECT_ADMIN.includes(permission);
|
2021-04-09 13:46:53 +02:00
|
|
|
|
2021-03-11 22:51:58 +01:00
|
|
|
export class AccessService {
|
2021-08-12 15:04:37 +02:00
|
|
|
private store: IAccessStore;
|
2021-04-22 10:07:10 +02:00
|
|
|
|
2023-01-18 17:08:07 +01:00
|
|
|
private accountStore: IAccountStore;
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
private roleStore: IRoleStore;
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2022-07-21 16:23:56 +02:00
|
|
|
private groupService: GroupService;
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
private environmentStore: IEnvironmentStore;
|
|
|
|
|
|
|
|
private logger: Logger;
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2023-06-14 15:40:40 +02:00
|
|
|
private flagResolver: IFlagResolver;
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
constructor(
|
2021-08-12 15:04:37 +02:00
|
|
|
{
|
|
|
|
accessStore,
|
2023-01-18 17:08:07 +01:00
|
|
|
accountStore,
|
2022-01-13 11:14:17 +01:00
|
|
|
roleStore,
|
|
|
|
environmentStore,
|
|
|
|
}: Pick<
|
|
|
|
IUnleashStores,
|
2023-01-18 17:08:07 +01:00
|
|
|
'accessStore' | 'accountStore' | 'roleStore' | 'environmentStore'
|
2022-01-13 11:14:17 +01:00
|
|
|
>,
|
2023-06-14 15:40:40 +02:00
|
|
|
{
|
|
|
|
getLogger,
|
|
|
|
flagResolver,
|
|
|
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
2022-07-21 16:23:56 +02:00
|
|
|
groupService: GroupService,
|
2021-04-22 10:07:10 +02:00
|
|
|
) {
|
2021-03-11 22:51:58 +01:00
|
|
|
this.store = accessStore;
|
2023-01-18 17:08:07 +01:00
|
|
|
this.accountStore = accountStore;
|
2022-01-13 11:14:17 +01:00
|
|
|
this.roleStore = roleStore;
|
2022-07-21 16:23:56 +02:00
|
|
|
this.groupService = groupService;
|
2022-01-13 11:14:17 +01:00
|
|
|
this.environmentStore = environmentStore;
|
2021-03-11 22:51:58 +01:00
|
|
|
this.logger = getLogger('/services/access-service.ts');
|
2023-06-14 15:40:40 +02:00
|
|
|
this.flagResolver = flagResolver;
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to check if a user has access to the requested resource
|
2021-04-22 10:07:10 +02:00
|
|
|
*
|
|
|
|
* @param user
|
|
|
|
* @param permission
|
|
|
|
* @param projectId
|
2021-03-11 22:51:58 +01:00
|
|
|
*/
|
2021-04-22 10:07:10 +02:00
|
|
|
async hasPermission(
|
2023-02-28 14:48:27 +01:00
|
|
|
user: Pick<IUser, 'id' | 'permissions' | 'isAPI'>,
|
2023-06-22 09:35:54 +02:00
|
|
|
permission: string | string[],
|
2021-04-22 10:07:10 +02:00
|
|
|
projectId?: string,
|
2022-01-13 11:14:17 +01:00
|
|
|
environment?: string,
|
2021-04-22 10:07:10 +02:00
|
|
|
): Promise<boolean> {
|
2023-06-22 09:35:54 +02:00
|
|
|
const permissionsArray = Array.isArray(permission)
|
|
|
|
? permission
|
|
|
|
: [permission];
|
|
|
|
|
|
|
|
const permissionLogInfo =
|
|
|
|
permissionsArray.length === 1
|
|
|
|
? `permission=${permissionsArray[0]}`
|
|
|
|
: `permissions=[${permissionsArray.join(',')}]`;
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
this.logger.info(
|
2023-06-22 09:35:54 +02:00
|
|
|
`Checking ${permissionLogInfo}, userId=${user.id}, projectId=${projectId}, environment=${environment}`,
|
2021-04-22 10:07:10 +02:00
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2021-04-23 12:52:29 +02:00
|
|
|
try {
|
2021-10-19 11:34:56 +02:00
|
|
|
const userP = await this.getPermissionsForUser(user);
|
2021-04-23 12:52:29 +02:00
|
|
|
return userP
|
|
|
|
.filter(
|
2021-08-12 15:04:37 +02:00
|
|
|
(p) =>
|
2021-04-23 12:52:29 +02:00
|
|
|
!p.project ||
|
|
|
|
p.project === projectId ||
|
|
|
|
p.project === ALL_PROJECTS,
|
|
|
|
)
|
2022-01-13 11:14:17 +01:00
|
|
|
.filter(
|
|
|
|
(p) =>
|
|
|
|
!p.environment ||
|
|
|
|
p.environment === environment ||
|
|
|
|
p.environment === ALL_ENVS,
|
|
|
|
)
|
2021-04-23 15:31:12 +02:00
|
|
|
.some(
|
2021-08-12 15:04:37 +02:00
|
|
|
(p) =>
|
2023-06-22 09:35:54 +02:00
|
|
|
permissionsArray.includes(p.permission) ||
|
|
|
|
p.permission === ADMIN,
|
2021-04-23 15:31:12 +02:00
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
this.logger.error(
|
2023-06-22 09:35:54 +02:00
|
|
|
`Error checking ${permissionLogInfo}, userId=${user.id} projectId=${projectId}`,
|
2021-04-23 15:31:12 +02:00
|
|
|
e,
|
|
|
|
);
|
2021-04-23 12:52:29 +02:00
|
|
|
return Promise.resolve(false);
|
|
|
|
}
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2023-02-28 14:48:27 +01:00
|
|
|
async getPermissionsForUser(
|
|
|
|
user: Pick<IUser, 'id' | 'isAPI' | 'permissions'>,
|
|
|
|
): Promise<IUserPermission[]> {
|
2021-05-03 19:33:26 +02:00
|
|
|
if (user.isAPI) {
|
2021-12-01 22:10:09 +01:00
|
|
|
return user.permissions?.map((p) => ({
|
2021-10-19 11:34:56 +02:00
|
|
|
permission: p,
|
|
|
|
}));
|
2021-05-03 19:33:26 +02:00
|
|
|
}
|
2021-04-09 13:46:53 +02:00
|
|
|
return this.store.getPermissionsForUser(user.id);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async getPermissions(): Promise<IAvailablePermissions> {
|
|
|
|
const bindablePermissions = await this.store.getAvailablePermissions();
|
|
|
|
const environments = await this.environmentStore.getAll();
|
|
|
|
|
2023-06-14 15:40:40 +02:00
|
|
|
const rootPermissions = bindablePermissions.filter(
|
|
|
|
({ type }) => type === 'root',
|
|
|
|
);
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
const projectPermissions = bindablePermissions.filter((x) => {
|
|
|
|
return x.type === 'project';
|
|
|
|
});
|
|
|
|
|
|
|
|
const environmentPermissions = bindablePermissions.filter((perm) => {
|
|
|
|
return perm.type === 'environment';
|
|
|
|
});
|
|
|
|
|
|
|
|
const allEnvironmentPermissions = environments.map((env) => {
|
|
|
|
return {
|
|
|
|
name: env.name,
|
|
|
|
permissions: environmentPermissions.map((permission) => {
|
|
|
|
return { environment: env.name, ...permission };
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
2023-06-14 15:40:40 +02:00
|
|
|
root: rootPermissions,
|
2022-01-13 11:14:17 +01:00
|
|
|
project: projectPermissions,
|
|
|
|
environments: allEnvironmentPermissions,
|
|
|
|
};
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async addUserToRole(
|
|
|
|
userId: number,
|
|
|
|
roleId: number,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.addUserToRole(userId, roleId, projectId);
|
|
|
|
}
|
|
|
|
|
2022-07-21 16:23:56 +02:00
|
|
|
async addGroupToRole(
|
|
|
|
groupId: number,
|
|
|
|
roleId: number,
|
|
|
|
createdBy: string,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.addGroupToRole(groupId, roleId, createdBy, projectId);
|
|
|
|
}
|
|
|
|
|
|
|
|
async addAccessToProject(
|
|
|
|
users: IAccessInfo[],
|
|
|
|
groups: IAccessInfo[],
|
|
|
|
projectId: string,
|
|
|
|
roleId: number,
|
|
|
|
createdBy: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.addAccessToProject(
|
|
|
|
users,
|
|
|
|
groups,
|
|
|
|
projectId,
|
|
|
|
roleId,
|
|
|
|
createdBy,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async getRoleByName(roleName: string): Promise<IRole> {
|
|
|
|
return this.roleStore.getRoleByName(roleName);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2021-04-30 13:25:24 +02:00
|
|
|
async setUserRootRole(
|
|
|
|
userId: number,
|
|
|
|
role: number | RoleName,
|
|
|
|
): Promise<void> {
|
|
|
|
const newRootRole = await this.resolveRootRole(role);
|
2021-04-22 10:07:10 +02:00
|
|
|
if (newRootRole) {
|
2021-03-11 22:51:58 +01:00
|
|
|
try {
|
2023-06-22 16:42:01 +02:00
|
|
|
await this.store.removeRolesOfTypeForUser(
|
|
|
|
userId,
|
|
|
|
ROOT_ROLE_TYPES,
|
|
|
|
);
|
2022-01-13 11:14:17 +01:00
|
|
|
|
|
|
|
await this.store.addUserToRole(
|
|
|
|
userId,
|
|
|
|
newRootRole.id,
|
|
|
|
DEFAULT_PROJECT,
|
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
} catch (error) {
|
2021-04-22 10:07:10 +02:00
|
|
|
throw new Error(
|
|
|
|
`Could not add role=${newRootRole.name} to userId=${userId}`,
|
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
2021-04-09 13:46:53 +02:00
|
|
|
} else {
|
2022-06-22 14:55:43 +02:00
|
|
|
throw new BadDataError(`Could not find rootRole=${role}`);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 13:36:45 +02:00
|
|
|
async getUserRootRoles(userId: number): Promise<IRoleWithProject[]> {
|
2021-04-09 13:46:53 +02:00
|
|
|
const userRoles = await this.store.getRolesForUserId(userId);
|
2023-06-22 16:42:01 +02:00
|
|
|
return userRoles.filter(({ type }) => ROOT_ROLE_TYPES.includes(type));
|
2021-04-09 13:46:53 +02:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async removeUserFromRole(
|
|
|
|
userId: number,
|
|
|
|
roleId: number,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.removeUserFromRole(userId, roleId, projectId);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-07-21 16:23:56 +02:00
|
|
|
async removeGroupFromRole(
|
|
|
|
groupId: number,
|
|
|
|
roleId: number,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.removeGroupFromRole(groupId, roleId, projectId);
|
|
|
|
}
|
|
|
|
|
2022-02-21 14:39:59 +01:00
|
|
|
async updateUserProjectRole(
|
|
|
|
userId: number,
|
|
|
|
roleId: number,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.updateUserProjectRole(userId, roleId, projectId);
|
|
|
|
}
|
|
|
|
|
2022-07-21 16:23:56 +02:00
|
|
|
async updateGroupProjectRole(
|
|
|
|
userId: number,
|
|
|
|
roleId: number,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
return this.store.updateGroupProjectRole(userId, roleId, projectId);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
//This actually only exists for testing purposes
|
2021-04-22 10:07:10 +02:00
|
|
|
async addPermissionToRole(
|
|
|
|
roleId: number,
|
|
|
|
permission: string,
|
2022-01-13 11:14:17 +01:00
|
|
|
environment?: string,
|
2021-04-22 10:07:10 +02:00
|
|
|
): Promise<void> {
|
2022-01-13 11:14:17 +01:00
|
|
|
if (isProjectPermission(permission) && !environment) {
|
2021-04-22 10:07:10 +02:00
|
|
|
throw new Error(
|
|
|
|
`ProjectId cannot be empty for permission=${permission}`,
|
|
|
|
);
|
|
|
|
}
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.store.addPermissionsToRole(
|
|
|
|
roleId,
|
|
|
|
[permission],
|
|
|
|
environment,
|
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
//This actually only exists for testing purposes
|
2021-04-22 10:07:10 +02:00
|
|
|
async removePermissionFromRole(
|
|
|
|
roleId: number,
|
|
|
|
permission: string,
|
2022-01-13 11:14:17 +01:00
|
|
|
environment?: string,
|
2021-04-22 10:07:10 +02:00
|
|
|
): Promise<void> {
|
2022-01-13 11:14:17 +01:00
|
|
|
if (isProjectPermission(permission) && !environment) {
|
2021-04-22 10:07:10 +02:00
|
|
|
throw new Error(
|
|
|
|
`ProjectId cannot be empty for permission=${permission}`,
|
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
2021-04-22 10:07:10 +02:00
|
|
|
return this.store.removePermissionFromRole(
|
|
|
|
roleId,
|
|
|
|
permission,
|
2022-01-13 11:14:17 +01:00
|
|
|
environment,
|
2021-04-22 10:07:10 +02:00
|
|
|
);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async getRoles(): Promise<IRole[]> {
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.roleStore.getRoles();
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async getRole(id: number): Promise<IRoleWithPermissions> {
|
|
|
|
const role = await this.store.get(id);
|
|
|
|
const rolePermissions = await this.store.getPermissionsForRole(role.id);
|
|
|
|
return {
|
|
|
|
...role,
|
|
|
|
permissions: rolePermissions,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async getRoleData(roleId: number): Promise<IRoleData> {
|
2021-04-22 10:07:10 +02:00
|
|
|
const [role, rolePerms, users] = await Promise.all([
|
2021-08-12 15:04:37 +02:00
|
|
|
this.store.get(roleId),
|
2021-03-11 22:51:58 +01:00
|
|
|
this.store.getPermissionsForRole(roleId),
|
|
|
|
this.getUsersForRole(roleId),
|
|
|
|
]);
|
2021-04-22 10:07:10 +02:00
|
|
|
return { role, permissions: rolePerms, users };
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async getProjectRoles(): Promise<IRole[]> {
|
|
|
|
return this.roleStore.getProjectRoles();
|
|
|
|
}
|
|
|
|
|
2021-03-11 22:51:58 +01:00
|
|
|
async getRolesForProject(projectId: string): Promise<IRole[]> {
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.roleStore.getRolesForProject(projectId);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async getRolesForUser(userId: number): Promise<IRole[]> {
|
|
|
|
return this.store.getRolesForUserId(userId);
|
|
|
|
}
|
|
|
|
|
2022-11-23 08:30:54 +01:00
|
|
|
async wipeUserPermissions(userId: number): Promise<void[]> {
|
|
|
|
return Promise.all([
|
|
|
|
this.store.unlinkUserRoles(userId),
|
|
|
|
this.store.unlinkUserGroups(userId),
|
|
|
|
this.store.clearUserPersonalAccessTokens(userId),
|
|
|
|
this.store.clearPublicSignupUserTokens(userId),
|
|
|
|
]);
|
2022-01-13 11:14:17 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async getUsersForRole(roleId: number): Promise<IUser[]> {
|
2021-03-11 22:51:58 +01:00
|
|
|
const userIdList = await this.store.getUserIdsForRole(roleId);
|
2021-08-12 15:04:37 +02:00
|
|
|
if (userIdList.length > 0) {
|
2023-01-18 17:08:07 +01:00
|
|
|
return this.accountStore.getAllWithId(userIdList);
|
2021-08-12 15:04:37 +02:00
|
|
|
}
|
|
|
|
return [];
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
async getProjectUsersForRole(
|
|
|
|
roleId: number,
|
|
|
|
projectId?: string,
|
2022-07-21 16:23:56 +02:00
|
|
|
): Promise<IProjectUser[]> {
|
|
|
|
const userRoleList = await this.store.getProjectUsersForRole(
|
2022-01-13 11:14:17 +01:00
|
|
|
roleId,
|
|
|
|
projectId,
|
|
|
|
);
|
2022-07-21 16:23:56 +02:00
|
|
|
if (userRoleList.length > 0) {
|
|
|
|
const userIdList = userRoleList.map((u) => u.userId);
|
2023-01-18 17:08:07 +01:00
|
|
|
const users = await this.accountStore.getAllWithId(userIdList);
|
2022-07-21 16:23:56 +02:00
|
|
|
return users.map((user) => {
|
2023-03-14 16:27:57 +01:00
|
|
|
const role = userRoleList.find((r) => r.userId == user.id)!;
|
2022-07-21 16:23:56 +02:00
|
|
|
return {
|
|
|
|
...user,
|
2023-03-14 16:27:57 +01:00
|
|
|
addedAt: role.addedAt!,
|
2022-07-21 16:23:56 +02:00
|
|
|
};
|
|
|
|
});
|
2022-01-13 11:14:17 +01:00
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2022-07-21 16:23:56 +02:00
|
|
|
async getProjectRoleAccess(
|
2021-04-22 10:07:10 +02:00
|
|
|
projectId: string,
|
2022-07-21 16:23:56 +02:00
|
|
|
): Promise<[IRole[], IUserWithRole[], IGroupModelWithProjectRole[]]> {
|
2022-01-13 11:14:17 +01:00
|
|
|
const roles = await this.roleStore.getProjectRoles();
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
const users = await Promise.all(
|
2021-08-12 15:04:37 +02:00
|
|
|
roles.map(async (role) => {
|
2022-01-13 11:14:17 +01:00
|
|
|
const projectUsers = await this.getProjectUsersForRole(
|
|
|
|
role.id,
|
|
|
|
projectId,
|
|
|
|
);
|
|
|
|
return projectUsers.map((u) => ({ ...u, roleId: role.id }));
|
2021-04-22 10:07:10 +02:00
|
|
|
}),
|
|
|
|
);
|
2022-07-21 16:23:56 +02:00
|
|
|
const groups = await this.groupService.getProjectGroups(projectId);
|
|
|
|
return [roles, users.flat(), groups];
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
async createDefaultProjectRoles(
|
2021-09-13 10:23:57 +02:00
|
|
|
owner: IUser,
|
2021-04-22 10:07:10 +02:00
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
if (!projectId) {
|
|
|
|
throw new Error('ProjectId cannot be empty');
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
const ownerRole = await this.roleStore.getRoleByName(RoleName.OWNER);
|
2021-03-11 22:51:58 +01:00
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
// TODO: remove this when all users is guaranteed to have a unique id.
|
2021-03-11 22:51:58 +01:00
|
|
|
if (owner.id) {
|
2021-04-22 10:07:10 +02:00
|
|
|
this.logger.info(
|
|
|
|
`Making ${owner.id} admin of ${projectId} via roleId=${ownerRole.id}`,
|
|
|
|
);
|
2022-01-13 11:14:17 +01:00
|
|
|
await this.store.addUserToRole(owner.id, ownerRole.id, projectId);
|
2021-04-22 10:07:10 +02:00
|
|
|
}
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
async removeDefaultProjectRoles(
|
|
|
|
owner: User,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
2021-03-11 22:51:58 +01:00
|
|
|
this.logger.info(`Removing project roles for ${projectId}`);
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.roleStore.removeRolesForProject(projectId);
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|
2021-04-09 13:46:53 +02:00
|
|
|
|
|
|
|
async getRootRoleForAllUsers(): Promise<IUserRole[]> {
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.roleStore.getRootRoleForAllUsers();
|
2021-04-09 13:46:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async getRootRoles(): Promise<IRole[]> {
|
2022-01-13 11:14:17 +01:00
|
|
|
return this.roleStore.getRootRoles();
|
2021-04-09 13:46:53 +02:00
|
|
|
}
|
|
|
|
|
2023-03-14 16:27:57 +01:00
|
|
|
public async resolveRootRole(
|
|
|
|
rootRole: number | RoleName,
|
|
|
|
): Promise<IRole | undefined> {
|
2021-04-30 13:25:24 +02:00
|
|
|
const rootRoles = await this.getRootRoles();
|
2023-03-14 16:27:57 +01:00
|
|
|
let role: IRole | undefined;
|
2021-04-30 13:25:24 +02:00
|
|
|
if (typeof rootRole === 'number') {
|
2021-08-12 15:04:37 +02:00
|
|
|
role = rootRoles.find((r) => r.id === rootRole);
|
2021-04-30 13:25:24 +02:00
|
|
|
} else {
|
2021-08-12 15:04:37 +02:00
|
|
|
role = rootRoles.find((r) => r.name === rootRole);
|
2021-04-30 13:25:24 +02:00
|
|
|
}
|
|
|
|
return role;
|
|
|
|
}
|
|
|
|
|
2023-03-14 16:27:57 +01:00
|
|
|
async getRootRole(roleName: RoleName): Promise<IRole | undefined> {
|
2022-01-13 11:14:17 +01:00
|
|
|
const roles = await this.roleStore.getRootRoles();
|
2021-08-12 15:04:37 +02:00
|
|
|
return roles.find((r) => r.name === roleName);
|
2021-04-09 13:46:53 +02:00
|
|
|
}
|
2022-01-13 11:14:17 +01:00
|
|
|
|
|
|
|
async getAllRoles(): Promise<ICustomRole[]> {
|
|
|
|
return this.roleStore.getAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
async createRole(role: IRoleCreation): Promise<ICustomRole> {
|
2023-06-14 15:40:40 +02:00
|
|
|
// CUSTOM_PROJECT_ROLE_TYPE is assumed by default for backward compatibility
|
|
|
|
const roleType =
|
|
|
|
role.type === CUSTOM_ROOT_ROLE_TYPE
|
|
|
|
? CUSTOM_ROOT_ROLE_TYPE
|
|
|
|
: CUSTOM_PROJECT_ROLE_TYPE;
|
|
|
|
|
|
|
|
if (
|
|
|
|
roleType === CUSTOM_ROOT_ROLE_TYPE &&
|
|
|
|
!this.flagResolver.isEnabled('customRootRoles')
|
|
|
|
) {
|
|
|
|
throw new InvalidOperationError(
|
|
|
|
'Custom root roles are not enabled.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
const baseRole = {
|
|
|
|
...(await this.validateRole(role)),
|
2023-06-14 15:40:40 +02:00
|
|
|
roleType,
|
2022-01-13 11:14:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const rolePermissions = role.permissions;
|
|
|
|
const newRole = await this.roleStore.create(baseRole);
|
|
|
|
if (rolePermissions) {
|
2023-06-14 15:40:40 +02:00
|
|
|
if (roleType === CUSTOM_ROOT_ROLE_TYPE) {
|
|
|
|
await this.store.addPermissionsToRole(
|
|
|
|
newRole.id,
|
|
|
|
rolePermissions.map(({ name }) => name),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
await this.store.addEnvironmentPermissionsToRole(
|
|
|
|
newRole.id,
|
|
|
|
rolePermissions,
|
|
|
|
);
|
|
|
|
}
|
2022-01-13 11:14:17 +01:00
|
|
|
}
|
|
|
|
return newRole;
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateRole(role: IRoleUpdate): Promise<ICustomRole> {
|
2023-06-14 15:40:40 +02:00
|
|
|
const roleType =
|
|
|
|
role.type === CUSTOM_ROOT_ROLE_TYPE
|
|
|
|
? CUSTOM_ROOT_ROLE_TYPE
|
|
|
|
: CUSTOM_PROJECT_ROLE_TYPE;
|
|
|
|
|
|
|
|
if (
|
|
|
|
roleType === CUSTOM_ROOT_ROLE_TYPE &&
|
|
|
|
!this.flagResolver.isEnabled('customRootRoles')
|
|
|
|
) {
|
|
|
|
throw new InvalidOperationError(
|
|
|
|
'Custom root roles are not enabled.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
await this.validateRole(role, role.id);
|
|
|
|
const baseRole = {
|
|
|
|
id: role.id,
|
|
|
|
name: role.name,
|
|
|
|
description: role.description,
|
2023-06-14 15:40:40 +02:00
|
|
|
roleType,
|
2022-01-13 11:14:17 +01:00
|
|
|
};
|
|
|
|
const rolePermissions = role.permissions;
|
|
|
|
const newRole = await this.roleStore.update(baseRole);
|
|
|
|
if (rolePermissions) {
|
2022-04-01 11:10:21 +02:00
|
|
|
await this.store.wipePermissionsFromRole(newRole.id);
|
2023-06-14 15:40:40 +02:00
|
|
|
if (roleType === CUSTOM_ROOT_ROLE_TYPE) {
|
|
|
|
await this.store.addPermissionsToRole(
|
|
|
|
newRole.id,
|
|
|
|
rolePermissions.map(({ name }) => name),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
await this.store.addEnvironmentPermissionsToRole(
|
|
|
|
newRole.id,
|
|
|
|
rolePermissions,
|
|
|
|
);
|
|
|
|
}
|
2022-01-13 11:14:17 +01:00
|
|
|
}
|
|
|
|
return newRole;
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteRole(id: number): Promise<void> {
|
2022-01-14 09:30:34 +01:00
|
|
|
await this.validateRoleIsNotBuiltIn(id);
|
|
|
|
|
2022-01-13 11:14:17 +01:00
|
|
|
const roleUsers = await this.getUsersForRole(id);
|
|
|
|
|
|
|
|
if (roleUsers.length > 0) {
|
|
|
|
throw new RoleInUseError(
|
|
|
|
'Role is in use by more than one user. You cannot delete a role that is in use without first removing the role from the users.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.roleStore.delete(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
async validateRoleIsUnique(
|
|
|
|
roleName: string,
|
|
|
|
existingId?: number,
|
|
|
|
): Promise<void> {
|
|
|
|
const exists = await this.roleStore.nameInUse(roleName, existingId);
|
|
|
|
if (exists) {
|
|
|
|
throw new NameExistsError(
|
|
|
|
`There already exists a role with the name ${roleName}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
async validateRoleIsNotBuiltIn(roleId: number): Promise<void> {
|
|
|
|
const role = await this.store.get(roleId);
|
2023-06-14 15:40:40 +02:00
|
|
|
if (
|
|
|
|
role.type !== CUSTOM_PROJECT_ROLE_TYPE &&
|
|
|
|
role.type !== CUSTOM_ROOT_ROLE_TYPE
|
|
|
|
) {
|
2022-01-13 11:14:17 +01:00
|
|
|
throw new InvalidOperationError(
|
2022-01-14 09:30:34 +01:00
|
|
|
'You cannot change built in roles.',
|
2022-01-13 11:14:17 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async validateRole(
|
2022-08-19 10:28:53 +02:00
|
|
|
role: IRoleValidation,
|
2022-01-13 11:14:17 +01:00
|
|
|
existingId?: number,
|
|
|
|
): Promise<IRoleCreation> {
|
|
|
|
const cleanedRole = await roleSchema.validateAsync(role);
|
|
|
|
if (existingId) {
|
|
|
|
await this.validateRoleIsNotBuiltIn(existingId);
|
|
|
|
}
|
|
|
|
await this.validateRoleIsUnique(role.name, existingId);
|
|
|
|
return cleanedRole;
|
|
|
|
}
|
2021-03-11 22:51:58 +01:00
|
|
|
}
|