import { ResourceNotFoundException } from '@nestjs.pro/common/dist/exceptions/ResourceNotFoundException'; import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { Organization } from '@tco.ai/models/dist/RBAC/Organization'; import { Role } from '@tco.ai/models/dist/RBAC/Role'; import { RoleCreate } from '@tco.ai/models/dist/RBAC/RoleCreate'; import { RoleRepository } from '@tco.ai/models/dist/RBAC/RoleRepository'; import { User } from '@tco.ai/models/dist/RBAC/User'; import { PermissionsService } from '../Permissions/PermissionsService'; import { UsersService } from '../Users/UsersService'; import { Connection } from 'typeorm'; import { RBAC_DEFAULT_ROLES } from '../Startup/Defaults'; @Injectable() export class RolesService { private readonly rolesRepository: RoleRepository; private readonly permissionsService: PermissionsService; private readonly usersService: UsersService; public constructor(private readonly connection: Connection, permissionsService: PermissionsService, @Inject(forwardRef(() => UsersService)) usersService: UsersService) { this.rolesRepository = connection.getCustomRepository(RoleRepository); this.permissionsService = permissionsService; this.usersService = usersService; } /** * Search for roles. * * @param organization * * @return {Promise>} */ public search(organization: Organization): Promise> { console.log('search roles'); return this.rolesRepository.find({ where: { organization: organization } }); } /** * Retrieve a role by it's id. * * @param id * * @return {Promise} */ public async getById(id: string): Promise { return new Promise(async (resolve, reject) => { const role = await this.rolesRepository.findOne({ where: { id } }); if (role) { resolve(role); } else { reject(new ResourceNotFoundException('could not locate role')); } }); } /** * Retrieve role by it's name. * * @param {string} name * * @return {Promise} */ public async getByName(name: string): Promise { return new Promise(async (resolve, reject) => { const role = await this.rolesRepository.findOne({ where: { name } }); if (role) { resolve(role); } else { reject(new ResourceNotFoundException('could not locate role')); } }); } /** * Retrive a role by it's id and owning organization. * * @param organization * @param id * * @return {Promise} */ public async getByIdAndOrganization(id: string, organization: Organization): Promise { return new Promise(async (resolve, reject) => { const role = await this.rolesRepository.findOne({ where: { id, organization } }); if (role) { resolve(role); } else { reject(new ResourceNotFoundException('could not locate role')); } }); } /** * Retrieve a role by it's organization and name. * * @param organization * @param name * * @return {Promise} */ public async getByOrganizationAndName(organization: Organization, name: string): Promise { return new Promise(async (resolve, reject) => { const role = await this.rolesRepository.findOne({ where: { organization, name } }); if (role) { resolve(role); } else { reject(new ResourceNotFoundException('could not locate role')); } }); } /** * Get users belonging to a role id. * * @return {Promise} * @param id */ public async getUsersById(id: string): Promise> { const role = await this.rolesRepository.findOne({ where: { id }, relations: [ 'users' ] }); return role.users; } /** * Delete an existing role by it's id and owning organization. * * @param {Organization} organization * @param {string} id * * @return {Promise} Returns true if successful. */ public async deleteByIdAndOrganization(id: string, organization: Organization): Promise { return (await this.rolesRepository.delete({ id })).affected > 0; } /** * Delete an existing role by it's name and owning organization. * * @param {Organization} organization * @param name * * @return {Promise} Returns true if successful. */ public async deleteByOrganizationAndName(organization: Organization, name: string): Promise { return (await this.rolesRepository.delete({ organization, name })).affected > 0; } /** * Create a new role. * * @param principal * @param roleCreate * * @return {Promise} */ public create(principal: User, roleCreate: RoleCreate): Promise { return this.rolesRepository.save({ organization: principal.organization, name: roleCreate.name, description: roleCreate.description, type: roleCreate.type }); } /** * Add a permission to a role. * * @param principal * @param roleId * @param permissionId * * @return {Promise} */ public async addPermission(principal: User, roleId: string, permissionId: string): Promise { const role = await this.getByIdAndOrganization(roleId, principal.organization); const permission = await this.permissionsService.getByOrganizationAndId(principal.organization, permissionId); role.permissions.push(permission); return this.rolesRepository.save(role); } /** * Remove a permission assignment from an existing role by owning organization. * * @param organization * @param {string} roleId * @param {string} permissionId * * @return {Promise} */ public async permissionRemove(organization: Organization, roleId: string, permissionId: string): Promise { await this.rolesRepository .createQueryBuilder() .delete() .from(Role) .relation(Role, 'permissions') .of(await this.getByIdAndOrganization(roleId, organization)) .remove(await this.permissionsService.getByOrganizationAndId(organization, permissionId)); return this.getByIdAndOrganization(roleId, organization); } /** * Add a user to a role by owning organization. * * @param principal * @param roleId * @param userId * * @return {Promise} */ public async assignToUser(principal: User, roleId: string, userId: string): Promise { await this.rolesRepository .createQueryBuilder() .relation(Role, 'users') .of(await this.getByIdAndOrganization(roleId, principal.organization)) .add(await this.usersService.getByIdAndPrincipalOrganization(principal.organization, userId)); return true; } /** * Assign a role to a user by the role name and users email address. * * @param {string} email * @param {string} roleName * * @return {Promise} */ public async assignToEmailByName(email: string, roleName: string): Promise { await this.rolesRepository .createQueryBuilder() .relation(Role, 'users') .of(await this.getByName(roleName)) .add(await this.usersService.getByEmail(email)); return true; } /** * Remove a user from a role by owning organization. * * @param principal * @param roleId * @param userId * * @return {Promise} */ public async unassignFromUser(principal: User, roleId: string, userId: string): Promise { await this.rolesRepository .createQueryBuilder() .delete() .from(Role) .relation(Role, 'users') .of(await this.getById(roleId)) .remove(await this.usersService.getByIdAndPrincipalOrganization(principal.organization, userId)); return true; } public async generate(principal: User): Promise { for (let i = 0; i < RBAC_DEFAULT_ROLES.length; i++) { await this.deleteByOrganizationAndName(principal.organization, RBAC_DEFAULT_ROLES[ i ].name); console.log(`${ principal.email }: Creating ${ RBAC_DEFAULT_ROLES[ i ].name }...`); const role = await this.create(principal, RBAC_DEFAULT_ROLES[ i ]); for (let permissionCreate of RBAC_DEFAULT_ROLES[ i ].permissions) { permissionCreate.roles = [ role ]; await this.permissionsService.create(principal.organization, permissionCreate).then(() => { console.log(`${ principal.email }: Created permission "${ permissionCreate.name }..`); }).catch(() => { console.log(`${ principal.email }: Permission already exists "${ permissionCreate.name }..`); }); await this.addPermission(principal, role.id, (await this.permissionsService.getByOrganizationAndName(principal.organization, permissionCreate.name)).id); } console.log(`${ principal.email }: Assigning role "${ RBAC_DEFAULT_ROLES[ i ].name }" to user "${ principal.email }`); await this.unassignFromUser(principal, (await this.getByOrganizationAndName(principal.organization, RBAC_DEFAULT_ROLES[ i ].name)).id, principal.id); await this.assignToUser(principal, (await this.getByOrganizationAndName(principal.organization, RBAC_DEFAULT_ROLES[ i ].name)).id, principal.id); } return true; } }