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
1 change: 1 addition & 0 deletions .env-cmdrc-template
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"MAX_STRATEGY_OPERATION": 100,
"RELAY_BYPASS_HTTPS": true,
"RELAY_BYPASS_VERIFICATION": true,
"PERMISSION_CACHE_ACTIVATED": true,
"HISTORY_ACTIVATED": true,
"METRICS_ACTIVATED": true,
"METRICS_MAX_PAGE": 50,
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ jobs:
MAX_STRATEGY_OPERATION: 100
RELAY_BYPASS_HTTPS: true
RELAY_BYPASS_VERIFICATION: true
PERMISSION_CACHE_ACTIVATED: true
METRICS_ACTIVATED: true
METRICS_MAX_PAGE: 50
MAX_REQUEST_PER_MINUTE: 0
SWITCHER_API_ENABLE: false
SWITCHER_API_LOGGER: false

- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@v1.8
uses: sonarsource/sonarcloud-github-action@v2.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
MAX_STRATEGY_OPERATION: 100
RELAY_BYPASS_HTTPS: true
RELAY_BYPASS_VERIFICATION: true
PERMISSION_CACHE_ACTIVATED: true
METRICS_ACTIVATED: true
METRICS_MAX_PAGE: 50
MAX_REQUEST_PER_MINUTE: 0
Expand Down
1 change: 1 addition & 0 deletions config/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ JWT_ADMIN_TOKEN_RENEW_INTERVAL=10m
MAX_STRATEGY_OPERATION=100
RELAY_BYPASS_HTTPS=true
RELAY_BYPASS_VERIFICATION=true
PERMISSION_CACHE_ACTIVATED=true
REGEX_MAX_TIMEOUT=3000
REGEX_MAX_BLACLIST=50
MAX_REQUEST_PER_MINUTE=0
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ services:
- MAX_STRATEGY_OPERATION=${MAX_STRATEGY_OPERATION}
- RELAY_BYPASS_HTTPS=${RELAY_BYPASS_HTTPS}
- RELAY_BYPASS_VERIFICATION=${RELAY_BYPASS_VERIFICATION}
- PERMISSION_CACHE_ACTIVATED=${PERMISSION_CACHE_ACTIVATED}
- HISTORY_ACTIVATED=${HISTORY_ACTIVATED}
- METRICS_ACTIVATED=${METRICS_ACTIVATED}
- METRICS_MAX_PAGE=${METRICS_MAX_PAGE}
Expand Down
1 change: 1 addition & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ app.get('/check', defaultLimiter, (req, res) => {
switcherapi_logger: process.env.SWITCHER_API_LOGGER,
relay_bypass_https: process.env.RELAY_BYPASS_HTTPS,
relay_bypass_verification: process.env.RELAY_BYPASS_VERIFICATION,
permission_cache: process.env.PERMISSION_CACHE_ACTIVATED,
history: process.env.HISTORY_ACTIVATED,
metrics: process.env.METRICS_ACTIVATED,
max_metrics_pages: process.env.METRICS_MAX_PAGE,
Expand Down
9 changes: 8 additions & 1 deletion src/client/permission-resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { verifyOwnership } from '../helpers';
import { RouterTypes } from '../models/permission';
import { getConfigs } from '../services/config';
import { getGroupConfigs } from '../services/group-config';
import { permissionCache } from '../helpers/cache';

export async function resolvePermission(args, admin) {
let elements;
Expand All @@ -12,6 +13,11 @@ export async function resolvePermission(args, admin) {
} else {
return [];
}

const cacheKey = permissionCache.permissionKey(admin._id, args.domain, elements, args.actions, args.router);
if (permissionCache.has(cacheKey)) {
return permissionCache.get(cacheKey);
}

let result = [];
for (const element of elements) {
Expand All @@ -30,6 +36,7 @@ export async function resolvePermission(args, admin) {
}
}
}


permissionCache.set(cacheKey, result);
return result;
}
55 changes: 55 additions & 0 deletions src/helpers/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class Cache {
constructor() {
this.cache = new Map();
}

set(key, value) {
if (!this.isPermissionCacheActivated()) {
return;
}

this.cache.set(key, value);
}

get(key) {
return this.cache.get(key);
}

permissionKey(adminId, domainId, elements, action, router) {
return JSON.stringify({
adminId: String(adminId),
domainId: String(domainId),
elements: elements.map(element => String(element._id)),
actions: action,
router: router
});
}

permissionReset(domainId, action, router) {
if (!domainId || !action || !router) {
return;
}

const keys = this.cache.keys();
for (const key of keys) {
const parsedKey = JSON.parse(key);
if (parsedKey.domainId === String(domainId) &&
parsedKey.actions.includes(action) &&
parsedKey.router === router) {
this.cache.delete(key);
}
}
}

isPermissionCacheActivated() {
return process.env.PERMISSION_CACHE_ACTIVATED === 'true';
}

has(key) {
return this.cache.has(key);
}
}

const permissionCache = new Cache();

export { permissionCache };
24 changes: 16 additions & 8 deletions src/services/permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ActionTypes, Permission, RouterTypes } from '../models/permission';
import { verifyOwnership } from '../helpers';
import { response } from './common';
import { getTeam, getTeams, verifyRequestedTeam } from './team';
import { permissionCache } from '../helpers/cache';

async function verifyRequestedTeamByPermission(permissionId, admin, action) {
let team = await getTeam({ permissions: permissionId });
Expand Down Expand Up @@ -48,49 +49,55 @@ export async function createPermission(args, teamId, admin) {

const team = await verifyRequestedTeam(teamId, admin, ActionTypes.CREATE);
team.permissions.push(permission._id);
permissionCache.permissionReset(team.domain, permission.action, permission.router);

await team.save();
return permission;
}

export async function updatePermission(args, id, admin) {
const permission = await getPermissionById(id);
await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);
const team = await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);

const updates = Object.keys(args);
updates.forEach((update) => permission[update] = args[update]);

permissionCache.permissionReset(team.domain, permission.action, permission.router);
return permission.save();
}

export async function deletePermission(id, admin) {
const permission = await getPermissionById(id);
await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.DELETE);
const team = await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.DELETE);

const teams = await getTeams({ permissions: permission._id });
teams.forEach(team => {
const indexValue = team.permissions.indexOf(permission._id);
team.permissions.splice(indexValue, 1);
team.save();
teams.forEach(pTeam => {
const indexValue = pTeam.permissions.indexOf(permission._id);
pTeam.permissions.splice(indexValue, 1);
pTeam.save();
});

permissionCache.permissionReset(team.domain, permission.action, permission.router);
return permission.deleteOne();
}

export async function addValue(args, id, admin) {
const permission = await verifyPermissionValueInput(id, args.value);
await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);
const team = await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);

const value = args.value.trim();
if (permission.values.includes(value)) {
throw new BadRequestError(`Value '${value}' already exist`);
}

permission.values.push(value);
permissionCache.permissionReset(team.domain, permission.action, permission.router);
return permission.save();
}

export async function removeValue(args, id, admin) {
const permission = await verifyPermissionValueInput(id, args.value);
await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);
const team = await verifyRequestedTeamByPermission(permission._id, admin, ActionTypes.UPDATE);

const value = args.value.trim();
const indexValue = permission.values.indexOf(value);
Expand All @@ -100,5 +107,6 @@ export async function removeValue(args, id, admin) {
}

permission.values.splice(indexValue, 1);
permissionCache.permissionReset(team.domain, permission.action, permission.router);
return permission.save();
}
30 changes: 28 additions & 2 deletions tests/client-api.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import mongoose from 'mongoose';
import request from 'supertest';
import sinon from 'sinon';
import app from '../src/app';
import { ActionTypes, RouterTypes } from '../src/models/permission';
import { permissionCache } from '../src/helpers/cache';
import Domain from '../src/models/domain';
import GroupConfig from '../src/models/group-config';
import { Config } from '../src/models/config';
Expand All @@ -27,11 +30,10 @@ import {
adminAccountId,
slack
} from './fixtures/db_client';
import { RouterTypes } from '../src/models/permission';

const changeStrategy = async (strategyId, newOperation, status, environment) => {
const strategy = await ConfigStrategy.findById(strategyId).exec();
strategy.operation = newOperation ? newOperation : strategy.operation;
strategy.operation = newOperation || strategy.operation;
strategy.activated.set(environment, status !== undefined ? status : strategy.activated.get(environment));
strategy.updatedBy = adminMasterAccountId;
await strategy.save();
Expand Down Expand Up @@ -969,6 +971,30 @@ describe('Testing domain [Adm-GraphQL] ', () => {
expect(JSON.parse(req.text).data.permission[0].permissions).toMatchObject(JSON.parse(exptected));
});

test('CLIENT_SUITE - Should return list of Groups permissions - from cache', async () => {
const cacheSpy = sinon.spy(permissionCache, 'get');
permissionCache.permissionReset(domainId, ActionTypes.UPDATE, RouterTypes.GROUP);

await request(app)
.post('/adm-graphql')
.set('Authorization', `Bearer ${adminMasterAccountToken}`)
.send(graphqlUtils.permissionsQuery(domainId, undefined, `"UPDATE","DELETE"`, RouterTypes.GROUP));

expect(cacheSpy.callCount).toBe(0);

const req = await request(app)
.post('/adm-graphql')
.set('Authorization', `Bearer ${adminMasterAccountToken}`)
.send(graphqlUtils.permissionsQuery(domainId, undefined, `"UPDATE","DELETE"`, RouterTypes.GROUP));

const exptected = '[{"action":"UPDATE","result":"ok"},{"action":"DELETE","result":"ok"}]';
expect(req.statusCode).toBe(200);
expect(cacheSpy.callCount).toBe(1);
expect(JSON.parse(req.text)).not.toBe(null);
expect(JSON.parse(req.text).data.permission[0].name).toBe("Group Test");
expect(JSON.parse(req.text).data.permission[0].permissions).toMatchObject(JSON.parse(exptected));
});

test('CLIENT_SUITE - Should return list of Groups permissions - Unauthorized access', async () => {
const req = await request(app)
.post('/adm-graphql')
Expand Down
2 changes: 1 addition & 1 deletion tests/relay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { StrategiesType, ConfigStrategy } from '../src/models/config-strategy';

const changeStrategy = async (strategyId, newOperation, status, environment) => {
const strategy = await ConfigStrategy.findById(strategyId).exec();
strategy.operation = newOperation ? newOperation : strategy.operation;
strategy.operation = newOperation || strategy.operation;
strategy.activated.set(environment, status !== undefined ? status : strategy.activated.get(environment));
strategy.updatedBy = adminMasterAccountId;
await strategy.save();
Expand Down
66 changes: 66 additions & 0 deletions tests/unit-test/cache.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { permissionCache } from "../../src/helpers/cache";
import { ActionTypes, RouterTypes } from "../../src/models/permission";

describe("Test permissionCache", () => {

it("UNIT_CACHE - Should set and get cache", () => {
const cacheKey = permissionCache.permissionKey(
'adminId',
'domainId',
['element1Id', 'element2Id'],
[ActionTypes.UPDATE, ActionTypes.READ],
RouterTypes.GROUP
);

permissionCache.set(cacheKey, 'value');
expect(permissionCache.has(cacheKey)).toBe(true);
const result = permissionCache.get(cacheKey);
expect(result).toEqual('value');
});

it("UNIT_CACHE - Should reload cache", () => {
const cacheKey = permissionCache.permissionKey(
'adminId',
'domainId',
['element1Id', 'element2Id'],
[ActionTypes.UPDATE, ActionTypes.READ],
RouterTypes.GROUP
);

permissionCache.set(cacheKey, 'value');
permissionCache.permissionReset('domainId', ActionTypes.UPDATE, RouterTypes.GROUP);
const result = permissionCache.get(cacheKey);
expect(result).toBeUndefined();
});

it("UNIT_CACHE - Should not reload cache", () => {
const cacheKey = permissionCache.permissionKey(
'adminId',
'domainId',
['element1Id', 'element2Id'],
[ActionTypes.UPDATE, ActionTypes.READ],
RouterTypes.GROUP
);

permissionCache.set(cacheKey, 'value');
permissionCache.permissionReset('domainId', ActionTypes.UPDATE, RouterTypes.CONFIG);
const result = permissionCache.get(cacheKey);
expect(result).toEqual('value');
});

it("UNIT_CACHE - Should not reload cache - empty router/action", () => {
const cacheKey = permissionCache.permissionKey(
'adminId',
'domainId',
['element1Id', 'element2Id'],
[ActionTypes.UPDATE, ActionTypes.READ],
RouterTypes.GROUP
);

permissionCache.set(cacheKey, 'value');
permissionCache.permissionReset();
const result = permissionCache.get(cacheKey);
expect(result).toEqual('value');
});

});
2 changes: 1 addition & 1 deletion tests/unit-test/helpers.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { formatInput, containsValue } = require('../../src/helpers');
import { formatInput, containsValue } from '../../src/helpers';

describe('Test formatInput', () => {

Expand Down