UNPKG

@batolye/bdk-core

Version:

Module to provide core utilities for BulusAtolyesi applications and services

232 lines (177 loc) 7.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defineResourceRules = defineResourceRules; exports.defineUserAbilities = defineUserAbilities; exports.defineAbilities = defineAbilities; exports.hasServiceAbilities = hasServiceAbilities; exports.hasResourceAbilities = hasResourceAbilities; exports.removeZone = removeZone; exports.getQueryForAbilities = getQueryForAbilities; exports.findSubjectsForResource = findSubjectsForResource; exports.countSubjectsForResource = countSubjectsForResource; exports.RESOURCE_TYPE_KEY = exports.RESOURCE_TYPE = exports.RoleNames = exports.Roles = void 0; var _lodash = _interopRequireDefault(require("lodash")); var _ability = require("@casl/ability"); var _mongoose = require("@casl/mongoose"); var _extra = require("@casl/ability/extra"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Define some alias to simplify ability definitions _ability.Ability.addAlias("update", "patch"); _ability.Ability.addAlias("read", ["get", "find"]); _ability.Ability.addAlias("remove", "delete"); _ability.Ability.addAlias("all", ["read", "create", "update", "remove"]); const Roles = { member: 0, manager: 1, owner: 2 }; exports.Roles = Roles; const RoleNames = ["member", "manager", "owner"]; // Hooks that can be added to customize abilities computation exports.RoleNames = RoleNames; let hooks = []; // Get the unique global symbol to store resource type / zone on a resource object const RESOURCE_TYPE = "type"; exports.RESOURCE_TYPE = RESOURCE_TYPE; const RESOURCE_TYPE_KEY = Symbol.for(RESOURCE_TYPE); exports.RESOURCE_TYPE_KEY = RESOURCE_TYPE_KEY; function defineResourceRules(subject, resource, resourceService, can) { const role = Roles[resource.permissions]; if (role >= Roles.member) { can("read", resourceService, { _id: resource._id }); } if (role >= Roles.manager) { can("update", resourceService, { _id: resource._id }); can(["create", "remove"], "authorisations", { resource: resource._id }); } if (role >= Roles.owner) { can("remove", resourceService, { _id: resource._id }); } } // Hook computing default abilities for a given user function defineUserAbilities(subject, can, cannot) { // Register can("service", "users"); can("create", "users"); if (subject && subject._id) { // Read user profiles for authorizing can("read", "users"); // Update user profile and destroy it can(["update", "remove"], "users", { _id: subject._id }); // Access authorisation service, then rights will be granted on a per-resource basis can("service", "authorisations"); // Access storage service, then rights will be granted on a per-resource basis can("service", "storage"); // This is for the user avatar // take care that the storage service uses 'id' as input but produces _id as output can("create", "storage", { id: "avatars/" + subject._id.toString() }); can("create", "storage", { id: "avatars/" + subject._id.toString() + ".thumbnail" }); can(["read", "remove"], "storage", { _id: "avatars/" + subject._id.toString() }); can(["read", "remove"], "storage", { _id: "avatars/" + subject._id.toString() + ".thumbnail" }); } } // Compute abilities for a given user // subject: hook.params.user function defineAbilities(subject) { const { rules, can, cannot } = _ability.AbilityBuilder.extract(); // Run registered hooks hooks.forEach(hook => hook(subject, can, cannot)); // CASL cannot infer the object type from the object itself so we need // to tell it how he can find the object type, i.e. service name. return new _ability.Ability(rules, { subjectName: resource => { if (!resource || typeof resource === "string") { return resource; } return resource[RESOURCE_TYPE_KEY]; } }); } defineAbilities.registerHook = function (hook) { if (!hooks.includes(hook)) { hooks.push(hook); } }; defineAbilities.unregisterHook = function (hook) { hooks = hooks.filter(registeredHook => registeredHook !== hook); }; function hasServiceAbilities(abilities, service) { if (!abilities) return false; // The unique identifier of a service is its path not its name. // Indeed we have for instance a 'groups' service in each organisation // Take care that in client we have the service path while on server we have the actual object const path = typeof service === "string" ? service : service.core_getPath(); debugger; return abilities.can("service", path); } function hasResourceAbilities(abilities, operation, resourceType, zone, resource) { if (!abilities) return false; // Create a shallow copy adding zone and type let object = Object.assign({}, resource); object[RESOURCE_TYPE_KEY] = resourceType; // Add a virtual zone to take it into account for object having no link to it if (zone) object.zone = typeof zone === "object" ? zone._id.toString() : zone; const result = abilities.can(operation, object); debugger; return result; } // Utility function used to remove the virtual zone from query function removeZone(query) { _lodash.default.forOwn(query, (value, key) => { // Process current attributes or recurse // Take care to nested fields like 'field._id' if (key === "zone") { delete query.zone; } else if (Array.isArray(value)) { value.forEach(item => removeZone(item)); // Remove empty objects from array _lodash.default.remove(value, item => _lodash.default.isEmpty(item)); // Remove empty arrays from query if (_lodash.default.isEmpty(value)) delete query[key]; } else if (typeof value === "object") { removeZone(value); // Remove empty objects from query if (_lodash.default.isEmpty(value)) delete query[key]; } }); return query; } // Get the query used to filter the objects according to given abilities // A null query indicates that access should not be granted function getQueryForAbilities(abilities, operation, resourceType) { if (!abilities) return null; let query = (0, _mongoose.toMongoQuery)(abilities, resourceType, operation); // Remove any zone to avoid taking it into account because it is not really stored on objects return query ? removeZone(query) : null; } function buildSubjectsQueryForResource(resourceScope, resourceId, role) { let query = { [resourceScope + "._id"]: resourceId }; if (role) { Object.assign(query, { [resourceScope + ".permissions"]: RoleNames[role] }); } return query; } function findSubjectsForResource(subjectService, resourceScope, resourceId, role) { // Build the query let query = buildSubjectsQueryForResource(resourceScope, resourceId, role); // Execute the query return subjectService.find({ query }); } function countSubjectsForResource(subjectService, resourceScope, resourceId, role) { // Build the query let query = buildSubjectsQueryForResource(resourceScope, resourceId, role); // Indicate we'd only like to count query.$limit = 0; // Execute the query return subjectService.find({ query }); }