unleash-server
Version:
Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.
337 lines • 14 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessService = void 0;
const permissions = __importStar(require("../types/permissions"));
const model_1 = require("../types/model");
const name_exists_error_1 = __importDefault(require("../error/name-exists-error"));
const role_in_use_error_1 = __importDefault(require("../error/role-in-use-error"));
const role_schema_1 = require("../schema/role-schema");
const constants_1 = require("../util/constants");
const project_1 = require("../types/project");
const invalid_operation_error_1 = __importDefault(require("../error/invalid-operation-error"));
const bad_data_error_1 = __importDefault(require("../error/bad-data-error"));
const { ADMIN } = permissions;
const PROJECT_ADMIN = [
permissions.UPDATE_PROJECT,
permissions.DELETE_PROJECT,
permissions.CREATE_FEATURE,
permissions.UPDATE_FEATURE,
permissions.DELETE_FEATURE,
];
const isProjectPermission = (permission) => PROJECT_ADMIN.includes(permission);
class AccessService {
constructor({ accessStore, userStore, roleStore, environmentStore, }, { getLogger }, groupService) {
this.store = accessStore;
this.userStore = userStore;
this.roleStore = roleStore;
this.groupService = groupService;
this.environmentStore = environmentStore;
this.logger = getLogger('/services/access-service.ts');
}
/**
* Used to check if a user has access to the requested resource
*
* @param user
* @param permission
* @param projectId
*/
async hasPermission(user, permission, projectId, environment) {
this.logger.info(`Checking permission=${permission}, userId=${user.id}, projectId=${projectId}, environment=${environment}`);
try {
const userP = await this.getPermissionsForUser(user);
return userP
.filter((p) => !p.project ||
p.project === projectId ||
p.project === constants_1.ALL_PROJECTS)
.filter((p) => !p.environment ||
p.environment === environment ||
p.environment === constants_1.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) {
if (user.isAPI) {
return user.permissions?.map((p) => ({
permission: p,
}));
}
return this.store.getPermissionsForUser(user.id);
}
async getPermissions() {
const bindablePermissions = await this.store.getAvailablePermissions();
const environments = await this.environmentStore.getAll();
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 {
project: projectPermissions,
environments: allEnvironmentPermissions,
};
}
async addUserToRole(userId, roleId, projectId) {
return this.store.addUserToRole(userId, roleId, projectId);
}
async addGroupToRole(groupId, roleId, createdBy, projectId) {
return this.store.addGroupToRole(groupId, roleId, createdBy, projectId);
}
async addAccessToProject(users, groups, projectId, roleId, createdBy) {
return this.store.addAccessToProject(users, groups, projectId, roleId, createdBy);
}
async getRoleByName(roleName) {
return this.roleStore.getRoleByName(roleName);
}
async setUserRootRole(userId, role) {
const newRootRole = await this.resolveRootRole(role);
if (newRootRole) {
try {
await this.store.removeRolesOfTypeForUser(userId, model_1.RoleType.ROOT);
await this.store.addUserToRole(userId, newRootRole.id, project_1.DEFAULT_PROJECT);
}
catch (error) {
throw new Error(`Could not add role=${newRootRole.name} to userId=${userId}`);
}
}
else {
throw new bad_data_error_1.default(`Could not find rootRole=${role}`);
}
}
async getUserRootRoles(userId) {
const userRoles = await this.store.getRolesForUserId(userId);
return userRoles.filter((r) => r.type === model_1.RoleType.ROOT);
}
async removeUserFromRole(userId, roleId, projectId) {
return this.store.removeUserFromRole(userId, roleId, projectId);
}
async removeGroupFromRole(groupId, roleId, projectId) {
return this.store.removeGroupFromRole(groupId, roleId, projectId);
}
async updateUserProjectRole(userId, roleId, projectId) {
return this.store.updateUserProjectRole(userId, roleId, projectId);
}
async updateGroupProjectRole(userId, roleId, projectId) {
return this.store.updateGroupProjectRole(userId, roleId, projectId);
}
//This actually only exists for testing purposes
async addPermissionToRole(roleId, permission, environment) {
if (isProjectPermission(permission) && !environment) {
throw new Error(`ProjectId cannot be empty for permission=${permission}`);
}
return this.store.addPermissionsToRole(roleId, [permission], environment);
}
//This actually only exists for testing purposes
async removePermissionFromRole(roleId, permission, environment) {
if (isProjectPermission(permission) && !environment) {
throw new Error(`ProjectId cannot be empty for permission=${permission}`);
}
return this.store.removePermissionFromRole(roleId, permission, environment);
}
async getRoles() {
return this.roleStore.getRoles();
}
async getRole(id) {
const role = await this.store.get(id);
const rolePermissions = await this.store.getPermissionsForRole(role.id);
return {
...role,
permissions: rolePermissions,
};
}
async getRoleData(roleId) {
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() {
return this.roleStore.getProjectRoles();
}
async getRolesForProject(projectId) {
return this.roleStore.getRolesForProject(projectId);
}
async getRolesForUser(userId) {
return this.store.getRolesForUserId(userId);
}
async wipeUserPermissions(userId) {
return Promise.all([
this.store.unlinkUserRoles(userId),
this.store.unlinkUserGroups(userId),
this.store.clearUserPersonalAccessTokens(userId),
this.store.clearPublicSignupUserTokens(userId),
]);
}
async getUsersForRole(roleId) {
const userIdList = await this.store.getUserIdsForRole(roleId);
if (userIdList.length > 0) {
return this.userStore.getAllWithId(userIdList);
}
return [];
}
async getProjectUsersForRole(roleId, projectId) {
const userRoleList = await this.store.getProjectUsersForRole(roleId, projectId);
if (userRoleList.length > 0) {
const userIdList = userRoleList.map((u) => u.userId);
const users = await this.userStore.getAllWithId(userIdList);
return users.map((user) => {
const role = userRoleList.find((r) => r.userId == user.id);
return {
...user,
addedAt: role.addedAt,
};
});
}
return [];
}
async getProjectRoleAccess(projectId) {
const roles = await this.roleStore.getProjectRoles();
const users = await Promise.all(roles.map(async (role) => {
const projectUsers = await this.getProjectUsersForRole(role.id, projectId);
return projectUsers.map((u) => ({ ...u, roleId: role.id }));
}));
const groups = await this.groupService.getProjectGroups(projectId);
return [roles, users.flat(), groups];
}
async createDefaultProjectRoles(owner, projectId) {
if (!projectId) {
throw new Error('ProjectId cannot be empty');
}
const ownerRole = await this.roleStore.getRoleByName(model_1.RoleName.OWNER);
// 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);
}
}
async removeDefaultProjectRoles(owner, projectId) {
this.logger.info(`Removing project roles for ${projectId}`);
return this.roleStore.removeRolesForProject(projectId);
}
async getRootRoleForAllUsers() {
return this.roleStore.getRootRoleForAllUsers();
}
async getRootRoles() {
return this.roleStore.getRootRoles();
}
async resolveRootRole(rootRole) {
const rootRoles = await this.getRootRoles();
let role;
if (typeof rootRole === 'number') {
role = rootRoles.find((r) => r.id === rootRole);
}
else {
role = rootRoles.find((r) => r.name === rootRole);
}
return role;
}
async getRootRole(roleName) {
const roles = await this.roleStore.getRootRoles();
return roles.find((r) => r.name === roleName);
}
async getAllRoles() {
return this.roleStore.getAll();
}
async createRole(role) {
const baseRole = {
...(await this.validateRole(role)),
roleType: constants_1.CUSTOM_ROLE_TYPE,
};
const rolePermissions = role.permissions;
const newRole = await this.roleStore.create(baseRole);
if (rolePermissions) {
await this.store.addEnvironmentPermissionsToRole(newRole.id, rolePermissions);
}
return newRole;
}
async updateRole(role) {
await this.validateRole(role, role.id);
const baseRole = {
id: role.id,
name: role.name,
description: role.description,
roleType: constants_1.CUSTOM_ROLE_TYPE,
};
const rolePermissions = role.permissions;
const newRole = await this.roleStore.update(baseRole);
if (rolePermissions) {
await this.store.wipePermissionsFromRole(newRole.id);
await this.store.addEnvironmentPermissionsToRole(newRole.id, rolePermissions);
}
return newRole;
}
async deleteRole(id) {
await this.validateRoleIsNotBuiltIn(id);
const roleUsers = await this.getUsersForRole(id);
if (roleUsers.length > 0) {
throw new role_in_use_error_1.default('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, existingId) {
const exists = await this.roleStore.nameInUse(roleName, existingId);
if (exists) {
throw new name_exists_error_1.default(`There already exists a role with the name ${roleName}`);
}
return Promise.resolve();
}
async validateRoleIsNotBuiltIn(roleId) {
const role = await this.store.get(roleId);
if (role.type !== constants_1.CUSTOM_ROLE_TYPE) {
throw new invalid_operation_error_1.default('You cannot change built in roles.');
}
}
async validateRole(role, existingId) {
const cleanedRole = await role_schema_1.roleSchema.validateAsync(role);
if (existingId) {
await this.validateRoleIsNotBuiltIn(existingId);
}
await this.validateRoleIsUnique(role.name, existingId);
return cleanedRole;
}
async isChangeRequestsEnabled(project, environment) {
return this.store.isChangeRequestsEnabled(project, environment);
}
}
exports.AccessService = AccessService;
//# sourceMappingURL=access-service.js.map