Skip to content

Commit

Permalink
chore: release beta branch (#956)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriguy committed Nov 8, 2022
2 parents 71203fc + 4f8b7ee commit 8e6bd6b
Show file tree
Hide file tree
Showing 23 changed files with 1,933 additions and 3,880 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ module.exports = {
'sonarjs',
'@typescript-eslint',
],
rules: {
'import/extensions': ['error', 'ignorePackages', {
js: 'never',
ts: 'never',
}],
},
},

],
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# [10.0.0-beta.4](https://github.com/ForestAdmin/forest-express/compare/v10.0.0-beta.3...v10.0.0-beta.4) (2022-11-08)


### Features

* **chart:** add support for context variables inside SQL query ([#953](https://github.com/ForestAdmin/forest-express/issues/953)) ([1d088ff](https://github.com/ForestAdmin/forest-express/commit/1d088ff981f8ebb2dfcecbf15f933918a9a6a07e))

# [10.0.0-beta.3](https://github.com/ForestAdmin/forest-express/compare/v10.0.0-beta.2...v10.0.0-beta.3) (2022-11-02)


### Features

* **chart:** add support for context variables used by Workspaces ([#952](https://github.com/ForestAdmin/forest-express/issues/952)) ([07c2f94](https://github.com/ForestAdmin/forest-express/commit/07c2f94070a522208d1873babad49455b6885ae6))

# [10.0.0-beta.2](https://github.com/ForestAdmin/forest-express/compare/v10.0.0-beta.1...v10.0.0-beta.2) (2022-10-27)


### Bug Fixes

* **chart:** improve security on chart and rename keys ([#949](https://github.com/ForestAdmin/forest-express/issues/949)) ([a45500f](https://github.com/ForestAdmin/forest-express/commit/a45500f4c3b83c85a121a342ed190b1811273d36))

# [10.0.0-beta.1](https://github.com/ForestAdmin/forest-express/compare/v9.5.6...v10.0.0-beta.1) (2022-10-14)


### Code Refactoring

* use new permissions mechanism v4 ([#943](https://github.com/ForestAdmin/forest-express/issues/943)) ([ffcd051](https://github.com/ForestAdmin/forest-express/commit/ffcd0510fcd2dc8af4e7de6a3b1de4b66abea0f7))


### BREAKING CHANGES

* drop support of projects that are not using roles

Co-authored-by: Guillaume Gautreau <guillaumeg@forestadmin.com>

## [9.5.6](https://github.com/ForestAdmin/forest-express/compare/v9.5.5...v9.5.6) (2022-08-31)


Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "forest-express",
"description": "Official package for all Forest Express Lianas",
"version": "9.5.6",
"version": "10.0.0-beta.4",
"author": "Sandro Munda <sandro@munda.me>",
"contributors": [
"Arnaud Besnier <arnaudibesnier@gmail.com>",
Expand All @@ -28,6 +28,7 @@
"dependencies": {
"@babel/runtime": "7.19.0",
"@forestadmin/context": "1.31.0",
"@forestadmin/forestadmin-client": "1.0.0",
"base32-encode": "1.1.1",
"bitwise-xor": "0.0.0",
"bluebird": "3.7.1",
Expand Down
1 change: 0 additions & 1 deletion src/context/build-external.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const fs = require('fs');
const moment = require('moment');
const VError = require('verror');

const superagentRequest = require('superagent');
const path = require('path');
const openIdClient = require('openid-client');
Expand Down
12 changes: 9 additions & 3 deletions src/context/build-services.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
/* eslint-disable global-require */
const createForestAdminClient = require('@forestadmin/forestadmin-client').default;

module.exports = (context) =>
context
.addInstance('logger', () => require('../services/logger'))
.addUsingFunction('forestAdminClient', ({ env, logger }) => createForestAdminClient({
forestServerUrl: env.FOREST_URL,
envSecret: env.FOREST_ENV_SECRET,
logger: (level, ...args) => (env.DEBUG ? logger[level.toLowerCase()](...args) : {}),
}))
.addInstance('chartHandler', ({ forestAdminClient }) => forestAdminClient.chartHandler)
.addUsingClass('authorizationService', () => require('../services/authorization').default)
.addInstance('pathService', () => require('../services/path'))
.addInstance('errorHandler', () => require('../services/exposed/error-handler'))
.addInstance('ipWhitelist', () => require('../services/ip-whitelist'))
.addInstance('forestServerRequester', () => require('../services/forest-server-requester'))
.addInstance('schemasGenerator', () => require('../generators/schemas'))
.addInstance('baseFilterParser', () => require('../services/base-filters-parser'))
.addInstance('permissionsFormatter', () => require('../services/permissions-formatter'))
.addUsingClass('projectDirectoryFinder', () => require('../services/project-directory-finder'))
.addUsingClass('configStore', () => require('../services/config-store'))
.addUsingClass('permissionsGetter', () => require('../services/permissions-getter'))
.addUsingClass('permissionsChecker', () => require('../services/permissions-checker'))
.addUsingClass('apimapFieldsFormater', () => require('../services/apimap-fields-formater'))
.addUsingClass('authorizationFinder', () => require('../services/authorization-finder'))
.addUsingClass('apimapSorter', () => require('../services/apimap-sorter'))
Expand Down
196 changes: 105 additions & 91 deletions src/middlewares/permissions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const { inject } = require('@forestadmin/context');
const httpError = require('http-errors');
const { parameterize } = require('../utils/string');
const Schemas = require('../generators/schemas');
const QueryDeserializer = require('../deserializers/query');
Expand All @@ -9,15 +8,16 @@ class PermissionMiddlewareCreator {
constructor(collectionName) {
this.collectionName = collectionName;
const {
configStore, logger, permissionsChecker, modelsManager,
authorizationService, modelsManager,
} = inject();
this.logger = logger;
this.permissionsChecker = permissionsChecker;
this.configStore = configStore;

this.modelsManager = modelsManager;

/** @private @readonly @type {import('../services/authorization').default} */
this.authorizationService = authorizationService;
}

_getSmartActionInfoFromRequest(request) {
_getSmartActionName(request) {
const smartActionEndpoint = `${request.baseUrl}${request.path}`;
const smartActionHTTPMethod = request.method;
const smartAction = Schemas.schemas[this.collectionName].actions.find((action) => {
Expand All @@ -30,77 +30,7 @@ class PermissionMiddlewareCreator {
throw new Error(`Impossible to retrieve the smart action at endpoint ${smartActionEndpoint} and method ${smartActionHTTPMethod}`);
}

return {
userId: request.user.id,
actionName: smartAction.name,
};
}

static _getCollectionListInfoFromRequest(request) {
return { userId: request.user.id, ...request.query };
}

static _getLiveQueriesInfoFromRequest(request) {
const { query } = request.body;
return query;
}

static _getStatWithParametersInfoFromRequest(request) {
const parameters = { ...request.body };
// NOTICE: Remove useless information
delete parameters.timezone;

// NOTICE: Remove the field information from group_by_field => collection:id
if (parameters.group_by_field) {
[parameters.group_by_field] = parameters.group_by_field.split(':');
}

return parameters;
}

_getPermissionsInfo(permissionName, request) {
switch (permissionName) {
case 'actions':
return this._getSmartActionInfoFromRequest(request);
case 'browseEnabled':
return PermissionMiddlewareCreator._getCollectionListInfoFromRequest(request);
case 'liveQueries':
return PermissionMiddlewareCreator._getLiveQueriesInfoFromRequest(request);
case 'statWithParameters':
return PermissionMiddlewareCreator._getStatWithParametersInfoFromRequest(request);

default:
return { userId: request.user.id };
}
}

_checkPermission(permissionName) {
return async (request, response, next) => {
const permissionInfos = this._getPermissionsInfo(permissionName, request);

const environmentId = this.configStore.lianaOptions.multiplePermissionsCache
? this.configStore.lianaOptions.multiplePermissionsCache.getEnvironmentId(request)
: null;
try {
await this.permissionsChecker.checkPermissions(
request.user,
this.collectionName,
permissionName,
permissionInfos,
environmentId,
);
next();
} catch (error) {
this.logger.error(error.message);
next(httpError(403));
}
};
}

static _getRequestAttributes(request) {
const hasBodyAttributes = request.body && request.body.data && request.body.data.attributes;
return hasBodyAttributes
&& new QueryDeserializer(request.body.data.attributes).perform();
return smartAction.name;
}

// generate a middleware that will check that ids provided by the request exist
Expand All @@ -116,7 +46,9 @@ class PermissionMiddlewareCreator {
}

// if performing a `selectAll` let the `getIdsFromRequest` handle the scopes
const attributes = PermissionMiddlewareCreator._getRequestAttributes(request);
const hasBodyAttributes = request.body && request.body.data && request.body.data.attributes;
const attributes = hasBodyAttributes
&& new QueryDeserializer(request.body.data.attributes).perform();
if (attributes.allRecords) {
return next();
}
Expand All @@ -134,6 +66,7 @@ class PermissionMiddlewareCreator {
})),
});

// The implementation of ResourcesGetter uses the scopes !
const counter = new RecordsCounter(
this.modelsManager.getModelByName(this.collectionName),
request.user,
Expand All @@ -152,39 +85,120 @@ class PermissionMiddlewareCreator {
}

list() {
return this._checkPermission('browseEnabled');
return async (request, response, next) => {
try {
const { query: { segmentQuery = null } = {} } = request;
await this.authorizationService
.assertCanBrowse(request.user, this.collectionName, segmentQuery);

next();
} catch (error) {
next(error);
}
};
}

export() {
return this._checkPermission('exportEnabled');
return async (request, response, next) => {
try {
await this.authorizationService.assertCanExport(request.user, this.collectionName);
next();
} catch (error) {
next(error);
}
};
}

details() {
return this._checkPermission('readEnabled');
return async (request, response, next) => {
try {
await this.authorizationService.assertCanRead(request.user, this.collectionName);
next();
} catch (error) {
next(error);
}
};
}

create() {
return this._checkPermission('addEnabled');
return async (request, response, next) => {
try {
await this.authorizationService.assertCanAdd(request.user, this.collectionName);
next();
} catch (error) {
next(error);
}
};
}

update() {
return this._checkPermission('editEnabled');
return async (request, response, next) => {
try {
await this.authorizationService.assertCanEdit(request.user, this.collectionName);
next();
} catch (error) {
next(error);
}
};
}

delete() {
return this._checkPermission('deleteEnabled');
return async (request, response, next) => {
try {
await this.authorizationService.assertCanDelete(request.user, this.collectionName);
next();
} catch (error) {
next(error);
}
};
}

smartAction() {
return [this._checkPermission('actions'), this._ensureRecordIdsInScope()];
}

liveQueries() {
return this._checkPermission('liveQueries');
return [
async (request, response, next) => {
try {
const actionName = this._getSmartActionName(request);
const requestBody = request.body;

if (requestBody?.data?.attributes?.signed_approval_request) {
const signedParameters = this.authorizationService.verifySignedActionParameters(
requestBody?.data?.attributes?.signed_approval_request,
);
await this.authorizationService.assertCanApproveCustomAction({
user: request.user,
customActionName: actionName,
collectionName: this.collectionName,
requesterId: signedParameters?.data?.attributes?.requester_id,
});
request.body = signedParameters;
} else {
await this.authorizationService.assertCanTriggerCustomAction({
user: request.user,
customActionName: actionName,
collectionName: this.collectionName,
});
}

next();
} catch (error) {
next(error);
}
}, this._ensureRecordIdsInScope()];
}

statWithParameters() {
return this._checkPermission('statWithParameters');
stats() {
return async (request, response, next) => {
try {
await this.authorizationService
.assertCanRetrieveChart({
user: request.user,
chartRequest: request.body,
});
next();
} catch (error) {
next(error);
}
};
}
}

Expand Down
Loading

0 comments on commit 8e6bd6b

Please sign in to comment.