UNPKG

@intuitionrobotics/permissions

Version:
250 lines • 12.5 kB
"use strict"; /* * 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