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, { 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 { 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 { 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 { return this.store.getAvailablePermissions(); } async addUserToRole( userId: number, roleId: number, projectId: string, ): Promise { return this.store.addUserToRole(userId, roleId, projectId); } async setUserRootRole( userId: number, role: number | RoleName, ): Promise { 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 { const userRoles = await this.store.getRolesForUserId(userId); return userRoles.filter((r) => r.type === RoleType.ROOT); } async removeUserFromRole( userId: number, roleId: number, projectId: string, ): Promise { return this.store.removeUserFromRole(userId, roleId, projectId); } async addPermissionToRole( roleId: number, permission: string, projectId?: string, ): Promise { 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 { if (isProjectPermission(permission) && !projectId) { throw new Error( `ProjectId cannot be empty for permission=${permission}`, ); } return this.store.removePermissionFromRole( roleId, permission, projectId, ); } async getRoles(): Promise { return this.store.getRoles(); } async getRole(roleId: number): Promise { 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 { return this.store.getProjectRoles(); } async getRolesForProject(projectId: string): Promise { return this.store.getRolesForProject(projectId); } async getRolesForUser(userId: number): Promise { return this.store.getRolesForUserId(userId); } async unlinkUserRoles(userId: number): Promise { return this.store.unlinkUserRoles(userId); } async getUsersForRole(roleId: number): Promise { const userIdList = await this.store.getUserIdsForRole(roleId); if (userIdList.length > 0) { return this.userStore.getAllWithId(userIdList); } return []; } async getProjectUsersForRole( roleId: number, projectId?: string, ): Promise { 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 { 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 { this.logger.info(`Removing project roles for ${projectId}`); return this.store.removeRolesForProject(projectId); } async getRootRoleForAllUsers(): Promise { return this.store.getRootRoleForAllUsers(); } async getRootRoles(): Promise { return this.store.getRootRoles(); } public async resolveRootRole(rootRole: number | RoleName): Promise { 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 { const roles = await this.store.getRootRoles(); return roles.find((r) => r.name === roleName); } }