Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requests/Switcher API.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -5483,7 +5483,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "{\r\n # domain(name: \"Switcher API\") {\r\n # domain(name: \"My Domain\", environment: \"default\", component: \"InventoryWS\") {\r\n domain(_id: \"5e0ece606f4f994eac9007ae\", environment: \"default\") {\r\n _id\r\n name\r\n version\r\n description\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n activated\r\n group {\r\n _id\r\n name\r\n description\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n config {\r\n _id\r\n key\r\n description\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n strategies {\r\n _id\r\n strategy\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n operation\r\n values\r\n }\r\n components\r\n }\r\n }\r\n }\r\n}",
"query": "{\r\n # domain(name: \"Switcher API\") {\r\n # domain(name: \"My Domain\", environment: \"default\", component: \"InventoryWS\") {\r\n domain(_id: \"5e44eb76916dd10048d72542\", environment: \"default\", _component: \"switcherapi\") {\r\n _id\r\n name\r\n version\r\n description\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n activated\r\n group {\r\n _id\r\n name\r\n description\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n config {\r\n _id\r\n key\r\n description\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n strategies {\r\n _id\r\n strategy\r\n activated\r\n statusByEnv {\r\n env\r\n value\r\n }\r\n operation\r\n values\r\n }\r\n components\r\n }\r\n }\r\n }\r\n}",
"variables": ""
}
},
Expand Down
12 changes: 12 additions & 0 deletions src/api-docs/schemas/permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const permission = {
type: 'string',
enum: Object.values(RouterTypes)
},
enviroments: {
type: 'array',
items: {
type: 'string'
}
},
identifiedBy: {
type: 'string',
enum: Object.values(KeyTypes)
Expand Down Expand Up @@ -43,6 +49,12 @@ export default {
type: 'string',
enum: Object.values(RouterTypes)
},
enviroments: {
type: 'array',
items: {
type: 'string'
}
},
identifiedBy: {
type: 'string',
enum: Object.values(KeyTypes)
Expand Down
28 changes: 25 additions & 3 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,46 @@ export function validatePagingArgs(args) {
return true;
}

export async function verifyOwnership(admin, element, domainId, action, routerType, cascade = false, environment = undefined) {
/**
* This function verifies if the user has permission to perform the action(s) on the element(s).
* It initially checks if the user is the owner of the domain. If not, it checks if the user is a member of any team that has permission to perform the action(s) on the element(s).
* If the user is the owner of the domain, it returns the element(s) without any further checks.
* If the user is not the owner of the domain, it checks if the user is a member of any team that has permission to perform the action(s) on the element(s). If not, it throws an error.
* If the user is a member of any team that has permission to perform the action(s) on the element(s), it returns the element(s).
*
* @param {*} admin user object with the following properties: _id, teams
* @param {*} element object or array of objects to be verified
* @param {*} domainId domain id
* @param {*} actions single or array of actions to be verified (e.g. 'READ', ['READ', 'UPDATE'])
* @param {*} routerType router type (e.g. 'GROUP', 'DOMAIN')
* @param {*} cascade if true, it will check if the user has permission to perform the action(s) on the element(s) and its children
* @param {*} environment environment name (e.g. 'default')
*
* @returns element(s) if the user has permission to perform the action(s) on the element(s)
*/
export async function verifyOwnership(admin, element, domainId, actions, routerType, cascade = false, environment = undefined) {
// Return element if user is the owner of the domain
const domain = await getDomainById(domainId);
if (admin._id.equals(domain.owner)) {
return element;
}

// Throw error if user has no teams
const teams = await getTeams({ _id: { $in: admin.teams }, domain: domain._id, active: true });
if (!teams.length || !admin.teams.length) {
throw new PermissionError('It was not possible to find any team that allows you to proceed with this operation');
}

const actionsArray = Array.isArray(actions) ? actions : [actions];
let hasPermission = [];
let allowedElement;

// Verify each team permission
for (const team of teams) {
if (cascade) {
allowedElement = await verifyPermissionsCascade(team, element, action, routerType, environment);
allowedElement = await verifyPermissionsCascade(team, element, actionsArray, routerType, environment);
} else {
allowedElement = await verifyPermissions(team, element, action, routerType, environment);
allowedElement = await verifyPermissions(team, element, actionsArray, routerType, environment);
}

if (allowedElement) {
Expand Down
11 changes: 7 additions & 4 deletions src/helpers/permission.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ActionTypes, RouterTypes } from '../models/permission';
import { getPermission, getPermissions } from '../services/permission';

export async function verifyPermissions(team, element, action, routerType, environment) {
export async function verifyPermissions(team, element, actions, routerType, environment) {
actions.push(ActionTypes.ALL);

const permission = await getPermission({
_id: { $in: team.permissions },
action: { $in: [action, ActionTypes.ALL] },
action: { $in: actions },
active: true,
router: { $in: [routerType, RouterTypes.ALL] }
});
Expand All @@ -16,7 +18,7 @@ export async function verifyPermissions(team, element, action, routerType, envir
return verifyIdentifiers(permission, element);
}

export async function verifyPermissionsCascade(team, element, action, routerType, environment) {
export async function verifyPermissionsCascade(team, element, actions, routerType, environment) {
let orStatement = [];
if (routerType === RouterTypes.DOMAIN) {
orStatement = [
Expand All @@ -41,9 +43,10 @@ export async function verifyPermissionsCascade(team, element, action, routerType
];
}

actions.push(ActionTypes.ALL);
const foundPermission = await getPermissions({
_id: { $in: team.permissions },
action: { $in: [action, ActionTypes.ALL] },
action: { $in: actions },
active: true,
$or: orStatement
});
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function checkConfigComponent(req, res, next) {
next();
}

export async function checkEnvironmentStatusChange_v2(args, domain, field) {
export async function checkEnvironmentStatusChange(args, domain, field) {
const environment = await getEnvironments({ domain }, ['_id', 'name']);
const updates = Object.keys(field || args);
const isValidOperation = updates.every((update) => {
Expand Down
5 changes: 4 additions & 1 deletion src/models/permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export const ActionTypes = Object.freeze({
CREATE: 'CREATE',
READ: 'READ',
UPDATE: 'UPDATE',
DELETE: 'DELETE'
UPDATE_ENV_STATUS: 'UPDATE_ENV_STATUS',
UPDATE_RELAY: 'UPDATE_RELAY',
DELETE: 'DELETE',
DELETE_RELAY: 'DELETE_RELAY'
});

export const RouterTypes = Object.freeze({
Expand Down
2 changes: 1 addition & 1 deletion src/routers/group-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ router.patch('/groupconfig/removeStatus/:id', auth, [
check('id').isMongoId()
], validate, async (req, res) => {
try {
const groupconfig = await Services.removeGroupStatusEnv(req.params.id, req.body, req.admin);
const groupconfig = await Services.removeGroupStatusEnv(req.params.id, req.body.env, req.admin);
res.send(groupconfig);
} catch (e) {
responseException(res, e, 500);
Expand Down
25 changes: 16 additions & 9 deletions src/services/config-strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { permissionCache } from '../helpers/cache';
import { updateDomainVersion } from './domain';
import { getConfigById } from './config';
import { BadRequestError } from '../exceptions';
import { checkEnvironmentStatusChange_v2 } from '../middleware/validators';
import { checkEnvironmentStatusChange } from '../middleware/validators';
import { getEnvironment } from './environment';

async function verifyStrategyValueInput(strategyId, value) {
Expand Down Expand Up @@ -66,7 +66,8 @@ export async function createStrategy(args, admin) {
owner: admin._id
});

configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.CREATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.CREATE,
RouterTypes.STRATEGY, false, environment.name);

await configStrategy.save();
updateDomainVersion(configStrategy.domain);
Expand All @@ -76,7 +77,8 @@ export async function createStrategy(args, admin) {

export async function deleteStrategy(id, admin) {
let configStrategy = await getStrategyById(id);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.DELETE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.DELETE,
RouterTypes.STRATEGY, false, configStrategy.activated.keys().next().value);

await configStrategy.deleteOne();
updateDomainVersion(configStrategy.domain);
Expand All @@ -89,7 +91,8 @@ export async function deleteStrategy(id, admin) {

export async function updateStrategy(id, args, admin) {
let configStrategy = await getStrategyById(id);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE,
RouterTypes.STRATEGY, configStrategy.activated.keys().next().value);
configStrategy.updatedBy = admin.email;

const updates = Object.keys(args);
Expand All @@ -111,7 +114,8 @@ export async function addVal(id, args, admin) {
throw new BadRequestError(`Value '${value}' already exist`);
}

configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE,
RouterTypes.STRATEGY, configStrategy.activated.keys().next().value);
configStrategy.updatedBy = admin.email;

configStrategy.values.push(value);
Expand Down Expand Up @@ -143,7 +147,8 @@ export async function updateVal(id, args, admin) {
throw new BadRequestError(`Value '${newvalue}' already exist`);
}

configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE,
RouterTypes.STRATEGY, configStrategy.activated.keys().next().value);
configStrategy.updatedBy = admin.email;

configStrategy.values.splice(indexOldValue, 1);
Expand All @@ -164,7 +169,8 @@ export async function removeVal(id, args, admin) {
throw new BadRequestError(`Value '${value}' does not exist`);
}

configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE,
RouterTypes.STRATEGY, configStrategy.activated.keys().next().value);
configStrategy.updatedBy = admin.email;

configStrategy.values.splice(indexValue, 1);
Expand All @@ -182,12 +188,13 @@ export async function updateStatusEnv(id, args, admin) {
}

let configStrategy = await getStrategyById(id);
updates = await checkEnvironmentStatusChange_v2(args, configStrategy.domain);
updates = await checkEnvironmentStatusChange(args, configStrategy.domain);
if (configStrategy.activated.get(updates[0]) === undefined) {
throw new BadRequestError('Strategy does not exist on this environment');
}

configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, ActionTypes.UPDATE, RouterTypes.STRATEGY);
configStrategy = await verifyOwnership(admin, configStrategy, configStrategy.domain, [ActionTypes.UPDATE, ActionTypes.UPDATE_ENV_STATUS],
RouterTypes.STRATEGY, false, Object.keys(args)[0]);
configStrategy.updatedBy = admin.email;

configStrategy.activated.set(updates[0], args[updates[0]]);
Expand Down
30 changes: 21 additions & 9 deletions src/services/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getDomainById, updateDomainVersion } from './domain';
import { getGroupConfigById } from './group-config';
import { checkSwitcher } from '../external/switcher-api-facade';
import { BadRequestError, NotFoundError } from '../exceptions';
import { checkEnvironmentStatusChange_v2 } from '../middleware/validators';
import { checkEnvironmentStatusChange } from '../middleware/validators';
import { getComponentById, getComponents } from './component';
import { resolveVerification } from '../client/relay';
import { permissionCache } from '../helpers/cache';
Expand Down Expand Up @@ -132,7 +132,7 @@ export async function updateConfig(id, args, admin) {

// validates existing environment
if (args.disable_metrics) {
await checkEnvironmentStatusChange_v2(args, config.domain, args.disable_metrics);
await checkEnvironmentStatusChange(args, config.domain, args.disable_metrics);
}

// check permissions
Expand All @@ -152,13 +152,21 @@ export async function updateConfig(id, args, admin) {
export async function updateConfigRelay(id, args, admin) {
let config = await getConfigById(id);
isRelayValid(args);

const actions = [ActionTypes.UPDATE, ActionTypes.UPDATE_RELAY];

config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG);
// Verifies if the user is updating the status of the environment
if (Object.keys(args).length == 1 && args.activated) {
actions.push(ActionTypes.UPDATE_ENV_STATUS);
}

config = await verifyOwnership(admin, config, config.domain, actions,
RouterTypes.CONFIG, false, Object.keys(args.activated)[0]);
config.updatedBy = admin.email;

for (const update of Object.keys(args)) {
if (config.relay[update] && 'activated endpoint auth_token'.indexOf(update) >= 0) {
await checkEnvironmentStatusChange_v2(args, config.domain, args[update]);
await checkEnvironmentStatusChange(args, config.domain, args[update]);
Object.keys(args[update]).forEach((map) =>
config.relay[update].set(map, args[update][map]));
} else {
Expand All @@ -174,10 +182,11 @@ export async function updateConfigRelay(id, args, admin) {

export async function updateConfigStatus(id, args, admin) {
let config = await getConfigById(id);
config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG);
config = await verifyOwnership(admin, config, config.domain, [ActionTypes.UPDATE, ActionTypes.UPDATE_ENV_STATUS],
RouterTypes.CONFIG, false, Object.keys(args)[0]);
config.updatedBy = admin.email;

const updates = await checkEnvironmentStatusChange_v2(args, config.domain);
const updates = await checkEnvironmentStatusChange(args, config.domain);

updates.forEach((update) => config.activated.set(update, args[update]));
await config.save();
Expand All @@ -188,7 +197,8 @@ export async function updateConfigStatus(id, args, admin) {

export async function removeConfigStatusEnv(id, env, admin) {
let config = await getConfigById(id);
config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG);
config = await verifyOwnership(admin, config, config.domain, [ActionTypes.UPDATE, ActionTypes.UPDATE_ENV_STATUS],
RouterTypes.CONFIG, false, env);
config.updatedBy = admin.email;

updateDomainVersion(config.domain);
Expand Down Expand Up @@ -265,7 +275,8 @@ export async function updateComponent(id, args, admin) {

export async function removeRelay(id, env, admin) {
let config = await getConfigById(id);
config = await verifyOwnership(admin, config, config.domain, ActionTypes.DELETE, RouterTypes.CONFIG);
config = await verifyOwnership(admin, config, config.domain, [ActionTypes.DELETE, ActionTypes.DELETE_RELAY],
RouterTypes.CONFIG, false, env);
config.updatedBy = admin.email;

if (config.relay.activated?.get(env) != undefined) {
Expand All @@ -288,7 +299,8 @@ export async function removeRelay(id, env, admin) {
export async function verifyRelay(id, env, admin) {
let config = await getConfigById(id);
let domain = await getDomainById(config.domain);
config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG);
config = await verifyOwnership(admin, config, config.domain, [ActionTypes.UPDATE, ActionTypes.UPDATE_RELAY],
RouterTypes.CONFIG, false, env);

const code = await resolveVerification(config.relay, env);
if (!config.relay.verified?.get(env) && Object.is(domain.integrations.relay.verification_code, code)) {
Expand Down
10 changes: 6 additions & 4 deletions src/services/domain.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto';
import { checkEnvironmentStatusChange_v2 } from '../middleware/validators';
import { checkEnvironmentStatusChange } from '../middleware/validators';
import Component from '../models/component';
import { Config } from '../models/config';
import { ConfigStrategy } from '../models/config-strategy';
Expand Down Expand Up @@ -119,11 +119,12 @@ export async function updateDomain(id, args, admin) {
export async function updateDomainStatus(id, args, admin) {
let domain = await getDomainById(id);

domain = await verifyOwnership(admin, domain, domain._id, ActionTypes.UPDATE, RouterTypes.DOMAIN);
domain = await verifyOwnership(admin, domain, domain._id, [ActionTypes.UPDATE, ActionTypes.UPDATE_ENV_STATUS],
RouterTypes.DOMAIN, false, Object.keys(args)[0]);
domain.updatedBy = admin.email;
domain.lastUpdate = Date.now();

const updates = await checkEnvironmentStatusChange_v2(args, id);
const updates = await checkEnvironmentStatusChange(args, id);

updates.forEach((update) => domain.activated.set(update, args[update]));
return domain.save();
Expand All @@ -132,7 +133,8 @@ export async function updateDomainStatus(id, args, admin) {
export async function removeDomainStatusEnv(id, env, admin) {
let domain = await getDomainById(id);

domain = await verifyOwnership(admin, domain, domain._id, ActionTypes.UPDATE, RouterTypes.DOMAIN);
domain = await verifyOwnership(admin, domain, domain._id, [ActionTypes.UPDATE, ActionTypes.UPDATE_ENV_STATUS],
RouterTypes.DOMAIN, false, env);
domain.updatedBy = admin.email;
domain.lastUpdate = Date.now();

Expand Down
Loading