@intuitionrobotics/permissions
Version:
250 lines • 12.5 kB
JavaScript
;
/*
* ts-common is the basic building blocks of our typescript projects
*
* Copyright (C) 2020 Intuition Robotics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PermissionsAssert = exports.PermissionsAssert_Class = void 0;
const ts_common_1 = require("@intuitionrobotics/ts-common");
const backend_1 = require("@intuitionrobotics/thunderstorm/backend");
const managment_1 = require("./db-types/managment");
const assign_1 = require("./db-types/assign");
const thunderstorm_1 = require("@intuitionrobotics/thunderstorm");
const backend_2 = require("@intuitionrobotics/user-account/backend");
const PermissionsModule_1 = require("./PermissionsModule");
class PermissionsAssert_Class extends ts_common_1.Module {
constructor() {
super("PermissionsAssert");
this.Middleware = (keys) => (req, data, response, scopes) => __awaiter(this, void 0, void 0, function* () {
let account;
yield this.CustomMiddleware(keys, (projectId, customFields) => __awaiter(this, void 0, void 0, function* () {
account = yield backend_2.AccountModule.validateSession(req, scopes, response);
yield this.assertUserPermissions(projectId, data.url, account._id, customFields);
}))(req, data, response, scopes);
return { account };
});
this.CustomMiddleware = (keys, action) => (req, data, response) => __awaiter(this, void 0, void 0, function* () {
const customFields = {};
let object;
switch (data.method) {
case thunderstorm_1.HttpMethod.POST:
case thunderstorm_1.HttpMethod.PATCH:
case thunderstorm_1.HttpMethod.PUT:
object = data.body;
break;
case thunderstorm_1.HttpMethod.GET:
case thunderstorm_1.HttpMethod.DELETE:
object = data.query;
break;
default:
throw new ts_common_1.BadImplementationException(`Generic custom fields cannot be extracted on api with method: ${data.method}`);
}
(0, ts_common_1._keys)(object).filter(key => keys.includes(key)).forEach(key => {
const oElement = object[key];
if (oElement === undefined || oElement === null)
return;
if (typeof oElement !== "string")
return;
customFields[key] = oElement;
});
const projectId = PermissionsModule_1.PermissionsModule.getProjectIdentity()._id;
yield action(projectId, customFields);
});
}
assertUserPermissions(projectId, path, userId, requestCustomField) {
return __awaiter(this, void 0, void 0, function* () {
const [apiDetails, userDetails] = yield Promise.all([
this.getApiDetails(path, projectId),
this.getUserDetails(userId)
]);
this._assertUserPermissionsImpl(apiDetails, projectId, userDetails, requestCustomField);
});
}
_assertUserPermissionsImpl(apiDetails, projectId, userDetails, requestCustomField) {
if (!apiDetails.apiDb.accessLevelIds) {
if (!this.config.strictMode)
return;
throw new backend_1.ApiException(403, `No permissions configuration specified for api: ${projectId}--${apiDetails.apiDb.path}`);
}
this.assertUserPermissionsImpl(userDetails.userGroups, apiDetails.requestPermissions, [requestCustomField]);
}
assertUserSharingGroup(granterUserId, userGroup) {
return __awaiter(this, void 0, void 0, function* () {
const [granterUser, groupToShare] = yield Promise.all([this.getUserDetails(granterUserId), assign_1.GroupPermissionsDB.queryUnique({ _id: userGroup.groupId })]);
groupToShare.customFields = this.getCombineUserGroupCF(userGroup, groupToShare);
const requestPermissions = yield this.getAccessLevels(groupToShare.accessLevelIds || []);
const requestCustomFields = groupToShare.customFields;
this.assertUserPermissionsImpl(granterUser.userGroups, requestPermissions, requestCustomFields);
});
}
assertUserPermissionsImpl(userGroups, requestPermissions, requestCustomFields) {
if (!requestPermissions.length)
return;
const requestPairWithLevelsObj = { accessLevels: requestPermissions, customFields: requestCustomFields };
let groupMatch = false;
const groupsMatchArray = userGroups.map(group => {
const groupPairWithLevelsObj = { accessLevels: group.__accessLevels || [], customFields: group.customFields || [] };
return this.isMatchWithLevelsObj(groupPairWithLevelsObj, requestPairWithLevelsObj);
});
for (const match of groupsMatchArray) {
if (match)
groupMatch = true;
}
if (!groupMatch) {
throw new backend_1.ApiException(403, "Action Forbidden");
}
}
getUserDetails(uuid) {
return __awaiter(this, void 0, void 0, function* () {
const user = yield assign_1.UserPermissionsDB.queryUnique({ accountId: uuid });
const userGroups = (0, ts_common_1.filterDuplicates)(user.groups || []);
const groups = yield (0, ts_common_1.batchActionParallel)(userGroups.map(userGroup => userGroup.groupId), 10, subGroupIds => assign_1.GroupPermissionsDB.query({ where: { _id: { $in: subGroupIds } } }));
return {
user,
userGroups: this.getCombineUserGroups(userGroups, groups)
};
});
}
getCombineUserGroupCF(userGroup, group) {
const cfArray = [];
if (group.customFields) {
cfArray.push(...group.customFields);
}
if (userGroup.customField) {
cfArray.push(userGroup.customField);
}
return cfArray;
}
getCombineUserGroups(userGroups, groups) {
const combinedGroups = [];
groups.forEach(group => {
const existUserGroupItem = userGroups.find(groupItem => groupItem.groupId === group._id);
if (!existUserGroupItem)
throw new ts_common_1.BadImplementationException("You are missing group in your code implementation");
userGroups.forEach((userGroup) => {
if (userGroup.groupId === group._id) {
combinedGroups.push(Object.assign(Object.assign({}, group), { customFields: this.getCombineUserGroupCF(userGroup, group) }));
}
});
});
return combinedGroups;
}
getApiDetails(_path, projectId) {
return __awaiter(this, void 0, void 0, function* () {
const path = _path.substring(0, (_path + '?').indexOf('?'));
const apiDb = yield managment_1.ApiPermissionsDB.queryUnique({ path, projectId });
const requestPermissions = yield this.getAccessLevels(apiDb.accessLevelIds || []);
return {
apiDb,
requestPermissions
};
});
}
getApisDetails(urls, projectId) {
return __awaiter(this, void 0, void 0, function* () {
const paths = urls.map(_path => _path.substring(0, (_path + '?').indexOf('?')));
const apiDbs = yield (0, ts_common_1.batchActionParallel)(paths, 10, elements => managment_1.ApiPermissionsDB.query({ where: { projectId, path: { $in: elements } } }));
return Promise.all(paths.map((path) => __awaiter(this, void 0, void 0, function* () {
const apiDb = apiDbs.find(_apiDb => _apiDb.path === path);
if (!apiDb)
return;
try {
const requestPermissions = yield this.getAccessLevels(apiDb.accessLevelIds);
return ({
apiDb,
requestPermissions
});
}
catch (e) {
return;
}
})));
});
}
getAccessLevels(_accessLevelIds) {
return __awaiter(this, void 0, void 0, function* () {
const accessLevelIds = (0, ts_common_1.filterDuplicates)(_accessLevelIds || []);
const requestPermissions = yield (0, ts_common_1.batchActionParallel)(accessLevelIds, 10, elements => managment_1.AccessLevelPermissionsDB.query({ where: { _id: { $in: elements } } }));
const idNotFound = accessLevelIds.find(lId => !requestPermissions.find(r => r._id === lId));
if (idNotFound)
throw new backend_1.ApiException(404, `Could not find api level with _id: ${idNotFound}`);
return requestPermissions;
});
}
isMatchWithLevelsObj(groupPair, requestPair) {
let match = true;
requestPair.customFields.forEach(requestCF => {
if (!this.doesCustomFieldsSatisfies(groupPair.customFields, requestCF))
match = false;
});
if (!match)
return false;
const groupDomainLevelMap = this.getDomainLevelMap(groupPair.accessLevels);
requestPair.accessLevels.forEach((requiredLevel, index) => {
const userAccessLevel = groupDomainLevelMap[requiredLevel.domainId];
if (userAccessLevel === undefined || userAccessLevel < requiredLevel.value)
match = false;
});
return match;
}
getDomainLevelMap(accessLevels) {
return accessLevels.reduce((toRet, accessLevel) => {
const levelForDomain = toRet[accessLevel.domainId];
if (!levelForDomain || levelForDomain < accessLevel.value)
toRet[accessLevel.domainId] = accessLevel.value;
return toRet;
}, {});
}
doesCustomFieldsSatisfies(groupCustomFields = [], requestCustomField) {
if (!Object.keys(requestCustomField).length)
return true;
for (const customField of groupCustomFields) {
if (this.doesCustomFieldSatisfies(customField, requestCustomField))
return true;
}
return false;
}
doesCustomFieldSatisfies(groupCustomField, requestCustomField) {
return Object.keys(requestCustomField).reduce((doesSatisfies, requestCustomFieldKey) => {
const customFieldRegEx = this.getRegEx(groupCustomField[requestCustomFieldKey]);
return doesSatisfies && customFieldRegEx.test(requestCustomField[requestCustomFieldKey]);
}, true);
}
getRegEx(value) {
if (!value)
return new RegExp(`^${value}$`, "g");
let regExValue = value;
const startRegEx = '^';
const endRegEx = '$';
if (value[0] !== startRegEx)
regExValue = startRegEx + regExValue;
if (value[value.length - 1] !== endRegEx)
regExValue = regExValue + endRegEx;
return new RegExp(regExValue, "g");
}
}
exports.PermissionsAssert_Class = PermissionsAssert_Class;
exports.PermissionsAssert = new PermissionsAssert_Class();
//# sourceMappingURL=permissions-assert.js.map