mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-12 13:48:35 +02:00
362 lines
11 KiB
TypeScript
362 lines
11 KiB
TypeScript
import * as permissions from '../types/permissions';
|
|
import User, { IUser } from '../types/user';
|
|
import {
|
|
IAccessStore,
|
|
IRole,
|
|
IUserPermission,
|
|
IUserRole,
|
|
} from '../types/stores/access-store';
|
|
import { IUserStore } from '../types/stores/user-store';
|
|
import { Logger } from '../logger';
|
|
import { IUnleashStores } from '../types/stores';
|
|
import {
|
|
IAvailablePermissions,
|
|
IPermission,
|
|
IRoleData,
|
|
IUserWithRole,
|
|
RoleName,
|
|
RoleType,
|
|
} from '../types/model';
|
|
|
|
export const ALL_PROJECTS = '*';
|
|
export const ALL_ENVS = '*';
|
|
|
|
const { ADMIN } = permissions;
|
|
|
|
const PROJECT_ADMIN = [
|
|
permissions.UPDATE_PROJECT,
|
|
permissions.DELETE_PROJECT,
|
|
permissions.CREATE_FEATURE,
|
|
permissions.UPDATE_FEATURE,
|
|
permissions.DELETE_FEATURE,
|
|
];
|
|
|
|
const PROJECT_REGULAR = [
|
|
permissions.CREATE_FEATURE,
|
|
permissions.UPDATE_FEATURE,
|
|
permissions.DELETE_FEATURE,
|
|
];
|
|
|
|
const isProjectPermission = (permission) => PROJECT_ADMIN.includes(permission);
|
|
|
|
export class AccessService {
|
|
private store: IAccessStore;
|
|
|
|
private userStore: IUserStore;
|
|
|
|
private logger: Logger;
|
|
|
|
private permissions: IPermission[];
|
|
|
|
constructor(
|
|
{
|
|
accessStore,
|
|
userStore,
|
|
}: Pick<IUnleashStores, 'accessStore' | 'userStore'>,
|
|
{ getLogger }: { getLogger: Function },
|
|
) {
|
|
this.store = accessStore;
|
|
this.userStore = userStore;
|
|
this.logger = getLogger('/services/access-service.ts');
|
|
// this.permissions = Object.values(permissions).map((p) => ({
|
|
// name: p,
|
|
// type: isProjectPermission(p)
|
|
// ? PermissionType.project
|
|
// : PermissionType.root,
|
|
// }));
|
|
}
|
|
|
|
/**
|
|
* Used to check if a user has access to the requested resource
|
|
*
|
|
* @param user
|
|
* @param permission
|
|
* @param projectId
|
|
*/
|
|
async hasPermission(
|
|
user: User,
|
|
permission: string,
|
|
projectId?: string,
|
|
environment?: string,
|
|
): Promise<boolean> {
|
|
this.logger.info(
|
|
`Checking permission=${permission}, userId=${user.id}, projectId=${projectId}, environment=${environment}`,
|
|
);
|
|
|
|
try {
|
|
const userP = await this.getPermissionsForUser(user);
|
|
console.log('My checks are', permission, projectId, environment);
|
|
console.log('My permissions for user are', userP);
|
|
|
|
return userP
|
|
.filter(
|
|
(p) =>
|
|
!p.project ||
|
|
p.project === projectId ||
|
|
p.project === ALL_PROJECTS,
|
|
)
|
|
.filter(
|
|
(p) =>
|
|
!p.environment ||
|
|
p.environment === environment ||
|
|
p.environment === ALL_ENVS,
|
|
)
|
|
.some(
|
|
(p) =>
|
|
p.permission === permission || p.permission === ADMIN,
|
|
);
|
|
} catch (e) {
|
|
this.logger.error(
|
|
`Error checking permission=${permission}, userId=${user.id} projectId=${projectId}`,
|
|
e,
|
|
);
|
|
return Promise.resolve(false);
|
|
}
|
|
}
|
|
|
|
async getPermissionsForUser(user: IUser): Promise<IUserPermission[]> {
|
|
if (user.isAPI) {
|
|
return user.permissions?.map((p) => ({
|
|
permission: p,
|
|
}));
|
|
}
|
|
console.log('Checking perms for user id', user.id);
|
|
return this.store.getPermissionsForUser(user.id);
|
|
}
|
|
|
|
async getPermissions(): Promise<IAvailablePermissions> {
|
|
return this.store.getAvailablePermissions();
|
|
}
|
|
|
|
async addUserToRole(
|
|
userId: number,
|
|
roleId: number,
|
|
projectId: string,
|
|
): Promise<void> {
|
|
return this.store.addUserToRole(userId, roleId, projectId);
|
|
}
|
|
|
|
async setUserRootRole(
|
|
userId: number,
|
|
role: number | RoleName,
|
|
): Promise<void> {
|
|
const newRootRole = await this.resolveRootRole(role);
|
|
if (newRootRole) {
|
|
try {
|
|
await this.store.removeRolesOfTypeForUser(
|
|
userId,
|
|
RoleType.ROOT,
|
|
);
|
|
|
|
const editorRole = await this.store.getRoleByName(
|
|
RoleName.EDITOR,
|
|
);
|
|
if (newRootRole.id === editorRole.id) {
|
|
const viewerRole = await this.store.getRoleByName(
|
|
RoleName.VIEWER,
|
|
);
|
|
await this.store.addUserToRole(
|
|
userId,
|
|
editorRole.id,
|
|
'default',
|
|
);
|
|
await this.store.addUserToRole(
|
|
userId,
|
|
viewerRole.id,
|
|
ALL_PROJECTS,
|
|
);
|
|
} else {
|
|
await this.store.addUserToRole(
|
|
userId,
|
|
newRootRole.id,
|
|
ALL_PROJECTS,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Could not add role=${newRootRole.name} to userId=${userId}`,
|
|
);
|
|
}
|
|
} else {
|
|
throw new Error(`Could not find rootRole=${role}`);
|
|
}
|
|
}
|
|
|
|
async getUserRootRoles(userId: number): Promise<IRole[]> {
|
|
const userRoles = await this.store.getRolesForUserId(userId);
|
|
return userRoles.filter((r) => r.type === RoleType.ROOT);
|
|
}
|
|
|
|
async removeUserFromRole(
|
|
userId: number,
|
|
roleId: number,
|
|
projectId: string,
|
|
): Promise<void> {
|
|
return this.store.removeUserFromRole(userId, roleId, projectId);
|
|
}
|
|
|
|
async addPermissionToRole(
|
|
roleId: number,
|
|
permission: string,
|
|
projectId?: string,
|
|
): Promise<void> {
|
|
if (isProjectPermission(permission) && !projectId) {
|
|
throw new Error(
|
|
`ProjectId cannot be empty for permission=${permission}`,
|
|
);
|
|
}
|
|
return this.store.addPermissionsToRole(roleId, [permission], projectId);
|
|
}
|
|
|
|
async removePermissionFromRole(
|
|
roleId: number,
|
|
permission: string,
|
|
projectId?: string,
|
|
): Promise<void> {
|
|
if (isProjectPermission(permission) && !projectId) {
|
|
throw new Error(
|
|
`ProjectId cannot be empty for permission=${permission}`,
|
|
);
|
|
}
|
|
return this.store.removePermissionFromRole(
|
|
roleId,
|
|
permission,
|
|
projectId,
|
|
);
|
|
}
|
|
|
|
async getRoles(): Promise<IRole[]> {
|
|
return this.store.getRoles();
|
|
}
|
|
|
|
async getRole(roleId: number): Promise<IRoleData> {
|
|
const [role, rolePerms, users] = await Promise.all([
|
|
this.store.get(roleId),
|
|
this.store.getPermissionsForRole(roleId),
|
|
this.getUsersForRole(roleId),
|
|
]);
|
|
return { role, permissions: rolePerms, users };
|
|
}
|
|
|
|
async getProjectRoles(): Promise<IRole[]> {
|
|
return this.store.getProjectRoles();
|
|
}
|
|
|
|
async getRolesForProject(projectId: string): Promise<IRole[]> {
|
|
return this.store.getRolesForProject(projectId);
|
|
}
|
|
|
|
async getRolesForUser(userId: number): Promise<IRole[]> {
|
|
return this.store.getRolesForUserId(userId);
|
|
}
|
|
|
|
async unlinkUserRoles(userId: number): Promise<void> {
|
|
return this.store.unlinkUserRoles(userId);
|
|
}
|
|
|
|
async getUsersForRole(roleId: number): Promise<IUser[]> {
|
|
const userIdList = await this.store.getUserIdsForRole(roleId);
|
|
if (userIdList.length > 0) {
|
|
return this.userStore.getAllWithId(userIdList);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async getProjectUsersForRole(
|
|
roleId: number,
|
|
projectId?: string,
|
|
): Promise<IUser[]> {
|
|
const userIdList = await this.store.getProjectUserIdsForRole(
|
|
roleId,
|
|
projectId,
|
|
);
|
|
if (userIdList.length > 0) {
|
|
return this.userStore.getAllWithId(userIdList);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
// Move to project-service?
|
|
async getProjectRoleUsers(
|
|
projectId: string,
|
|
): Promise<[IRole[], IUserWithRole[]]> {
|
|
const roles = await this.store.getProjectRoles();
|
|
|
|
const users = await Promise.all(
|
|
roles.map(async (role) => {
|
|
const usrs = await this.getProjectUsersForRole(
|
|
role.id,
|
|
projectId,
|
|
);
|
|
return usrs.map((u) => ({ ...u, roleId: role.id }));
|
|
}),
|
|
);
|
|
return [roles, users.flat()];
|
|
}
|
|
|
|
async createDefaultProjectRoles(
|
|
owner: IUser,
|
|
projectId: string,
|
|
): Promise<void> {
|
|
if (!projectId) {
|
|
throw new Error('ProjectId cannot be empty');
|
|
}
|
|
|
|
const ownerRole = await this.store.getRoleByName(RoleName.OWNER);
|
|
|
|
await this.store.addPermissionsToRole(
|
|
ownerRole.id,
|
|
PROJECT_ADMIN,
|
|
projectId,
|
|
);
|
|
|
|
// TODO: remove this when all users is guaranteed to have a unique id.
|
|
if (owner.id) {
|
|
this.logger.info(
|
|
`Making ${owner.id} admin of ${projectId} via roleId=${ownerRole.id}`,
|
|
);
|
|
await this.store.addUserToRole(owner.id, ownerRole.id, projectId);
|
|
}
|
|
|
|
const memberRole = await this.store.getRoleByName(RoleName.MEMBER);
|
|
|
|
await this.store.addPermissionsToRole(
|
|
memberRole.id,
|
|
PROJECT_REGULAR,
|
|
projectId,
|
|
);
|
|
}
|
|
|
|
async removeDefaultProjectRoles(
|
|
owner: User,
|
|
projectId: string,
|
|
): Promise<void> {
|
|
this.logger.info(`Removing project roles for ${projectId}`);
|
|
return this.store.removeRolesForProject(projectId);
|
|
}
|
|
|
|
async getRootRoleForAllUsers(): Promise<IUserRole[]> {
|
|
return this.store.getRootRoleForAllUsers();
|
|
}
|
|
|
|
async getRootRoles(): Promise<IRole[]> {
|
|
return this.store.getRootRoles();
|
|
}
|
|
|
|
public async resolveRootRole(rootRole: number | RoleName): Promise<IRole> {
|
|
const rootRoles = await this.getRootRoles();
|
|
let role: IRole;
|
|
if (typeof rootRole === 'number') {
|
|
role = rootRoles.find((r) => r.id === rootRole);
|
|
} else {
|
|
role = rootRoles.find((r) => r.name === rootRole);
|
|
}
|
|
return role;
|
|
}
|
|
|
|
async getRootRole(roleName: RoleName): Promise<IRole> {
|
|
const roles = await this.store.getRootRoles();
|
|
return roles.find((r) => r.name === roleName);
|
|
}
|
|
}
|