UNPKG

@qikdev/sdk

Version:

Promise based Javascript SDK

836 lines (707 loc) 26.7 kB
/** * Creates a new Access Module instance. * This module provides a number of helper functions for managing and understanding permissions of the current * application or user * @alias access * @constructor * @hideconstructor * @param {QikCore} qik A reference to the parent instance of the QikCore module. This module is usually created by a QikCore instance that passes itself in as the first argument. */ var QikAccess = function (QikCore) { if (!QikCore.auth) { throw new Error( `Can't Instantiate QikAccess before QikAccess has been initialized`, ); } if (!QikCore.utils) { throw new Error( `Can't Instantiate QikAccess before QikCore.utils has been initialized`, ); } ////////////////////////////////// var service = {}; /////////////////////////////////////////////// /** * @alias access.hasFeature * @description Checks if a user has a specific feature enabled for their organisation * @param {Object} user The user to check * @param {String} feature The feature to check is enabled/disabled * @example * const user = sdk.auth.getCurrentUser(); * const formsFeatureIsEnabled = sdk.access.hasFeature(user, 'forms'); */ service.hasFeature = function (user, feature) { const features = user?.organisation?.features || {}; if (features.all) { return true; } return Boolean(features[feature]); }; /////////////////////////////////////////////// /** * @alias access.isAdministrator * @description Check if a user object is an administrator * @param {Object} user The user session to check * @example * const userIsAnAdministrator = sdk.access.isAdministrator({firstName:'Jeffrey', lastName:'Winger', ...}); */ service.isAdministrator = function (user) { return user && user?.userType == "administrator"; }; /////////////////////////////////////////////// /** * @alias access.getAllDescendants * @description Get all children of a specified scope * @param {Object} scope A scope object with child scopes * @example * const childScopes = sdk.access.getAllDescendants({title:'Global', children:[{title:'Australia', ...}, {title:'New Zealand', ...}]}); */ service.getAllDescendants = function (scope) { //Start by including the scope id var results = []; addScope(scope); function addScope(entry) { //Add this scope to the results var id = QikCore.utils.id(entry); results.push(id); (entry.children || []).forEach(function (child) { addScope(child); }); } return results; }; /////////////////////////////////////////////// /** * @alias access.hashPermissions * @description Get a lookup of all permissions a user has been granted * @param {Object} user A user session object * @example * const permissions = sdk.access.hashPermissions({firstName:'Mighty', lastName:'Mouse', permissionSets:[...], ...}); */ service.hashPermissions = function (user) { var results = {}; for (var key in user?.permissions || {}) { //Get the permission set var permissionSet = user?.permissions[key]; //Get all Scope ids from the permission set var allIDs = service.getAllDescendants(permissionSet); //For each permission defined in the set (permissionSet.permissions || []).forEach(function (permissionString) { //Include the top scope if (!results[permissionString]) { results[permissionString] = []; } results[permissionString] = [ ...new Set(results[permissionString].concat(allIDs)), ]; }); } /////////////////////////////////////////////// return results; }; /////////////////////////////////////////////// /** * @alias access.allUserScopes * @description Return an array of ids of all scopes that a given user has been granted a permission in * @param {Object} user A user session object * @example * const permissions = sdk.access.allUserScopes({firstName:'Mighty', lastName:'Mouse', permissionSets:[...], ...}); * * // Would result in: * ['61eca4746971e75c1fc670cf', '61eca4746971e75c1fc670ca', '77eca4746971e75c1fc670cf'], */ service.allUserScopes = function (user) { var collected = {}; var permissions = user?.permissions || {}; //////////////////////// function pluckScope(scopeObject) { if (!scopeObject) { return; } var scopeID = QikCore.utils.id(scopeObject); if (!scopeID) { return; } //Add to the collection collected[scopeID] = 1; //If there are children, then recursively pluck the ids if (scopeObject.children && scopeObject.children.length) { scopeObject.children.forEach(function (scope) { pluckScope(scope); }); } } //////////////////////// for (var scopeID in permissions) { pluckScope(permissions[scopeID]); } //////////////////////// return Object.keys(collected); }; /////////////////////////////////////////////// /** * @alias access.actionableScopes * @description Return an array of all scopes that a given user can perform a specified action in * @param {Object} user A user session object * @param {String} action The action * @param {String} definition The defined type of item the action will be performed on * @param {String} type The basic type of item the action will be performed on * @param {Object} options Additional options and parameters * @example * const user = sdk.auth.getCurrentUser(); * const canCreateInScopes = sdk.access.actionableScopes(user, 'create', 'car', 'article'); * const canDeleteInScopes = sdk.access.actionableScopes(user, 'delete', 'profile'); * * // Would result in something like: * ['61eca4746971e75c1fc670cf', '61eca4746971e75c1fc670ca', '77eca4746971e75c1fc670cf'], */ service.actionableScopes = function ( user, action, definition, type, options, ) { options = options || {}; ///////////////////////// var validScopes = []; if (user?.userType == "administrator") { validScopes = service.allUserScopes(user, options); } else { var permissionsLookup = service.hashPermissions(user); var checkTypeName = definition || type; if (!checkTypeName) { return validScopes; } let definitionScopes = !definition ? [] : permissionsLookup[`${definition}.${action}`] || []; let typeScopes = !type ? [] : permissionsLookup[`${type}.${action}`] || []; validScopes = [...definitionScopes, ...typeScopes]; } ///////////////////////// return validScopes || []; }; /////////////////////////////////////////////// /** * @alias access.isOwner * @description Return whether or not a user is considered an Owner of a specified item * @param {Object} user The user to check * @param {Object} item The item * @example * const user = sdk.auth.getCurrentUser(); * const item = {title:'A piece of content', meta:{personaAuthor:'61eca4746971e75c1fc670ca'}, ...} * * // Returns true if the user owns the item * const isTheOwner = sdk.access.isOwner(user, item); */ service.isOwner = function (user, item) { var userID = QikCore.utils.id(user); var personaID = QikCore.utils.id(user?.persona); var metaObject = item.meta; if (!metaObject) { return false; } ///////////////////////////////////////////// var userOwners = metaObject.userOwners || []; var users = QikCore.utils.ids([...userOwners, metaObject.userAuthor]); var userHash = QikCore.utils.hash(users); if (userHash[userID]) { return true; } ///////////////////////////////////////////// var personaOwners = metaObject.personaOwners || []; var personas = QikCore.utils.ids([ ...personaOwners, metaObject.personaAuthor, ]); var personaHash = QikCore.utils.hash(personas); if (personaHash[personaID]) { return true; } const isAssignee = service.isAssignee(user, item); return isAssignee; }; /** * @alias access.isAssignee * @description Return whether or not a user is an assignee of a specified item * @param {Object} user The user to check * @param {Object} item The item * @example * const user = sdk.auth.getCurrentUser(); * const item = {title:'A workflow card', assignees:['61eca4746971e75c1fc670ca'], ...} * * // Returns true if the user owns the item * const isAssignee = sdk.access.isAssignee(user, item); */ service.isAssignee = function (user, contentItem) { // Check the user profile ids const profileIDs = QikCore.utils.ids(user?.profiles); if (!profileIDs.length) { return false; } // Check if the content item has assignees set const assigneeIDs = QikCore.utils.ids(contentItem.assignees); if (!assigneeIDs.length) { return false; } // Return the intersection of assignees and profile ids const set = new Set(assigneeIDs); const matches = profileIDs.filter((item) => set.has(item)); const isAssignee = !!matches.length; return isAssignee; }; /////////////////////////////////////////////// /** * @alias access.allPermissions * @description Returns an array of all permissions a given user has access to * @param {Object} user The user to check * @example * const user = sdk.auth.getCurrentUser(); * const permissions = sdk.access.allPermissions(user); * * // Returns * ['image.viewany', 'testimonial.viewany', 'testimonial.viewfield.title', ...] */ service.allPermissions = function (user) { var allPermissions = Object.values(user?.permissions || {}) .map(function (permissionScope) { return permissionScope.permissions; }) .flat(); return allPermissions; }; /////////////////////////////////////////////// /** * @alias access.allPermissionTypes * @description Returns an array of content types a given user has permission to interact with * @param {Object} user The user to check * @param {Boolean} asHash Whether to retrieve the result as a keyed object hash, by default * the response will be an array * @example * const user = sdk.auth.getCurrentUser(); * * // Returns ['image', 'testimonial'] * const types = sdk.access.allPermissionTypes(user); * * // Returns {image:true, testimonial:true} * const types = sdk.access.allPermissionTypes(user, true); */ service.allPermissionTypes = function (user, asHash) { if (asHash) { return service.allPermissions(user).reduce(function (set, string) { var type = string.split(".")[0]; set[type] = true; return set; }, {}); } else { return service.allPermissions(user).map(function (string) { return string.split(".")[0]; }); } }; /////////////////////////////////////////////// /** * @alias access.canKnowOf * @description Checks if a user has any permissions relating to a specified type of content * @param {Object} user The user to check * @param {String} typeKey The type or definition to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * const canAccessImageSection = sdk.access.canKnowOf(user, 'image'); * const canAccessCustomerUploadSection = sdk.access.canKnowOf(user, 'customerUpload'); */ service.canKnowOf = function (user, typeKey, options) { options = options || {}; options.cache = options.cache || {}; //Let us know if there are any permissions for this user return service.allPermissionTypes(user, true)[typeKey]; }; /////////////////////////////////////////////// /** * @alias access.checkActionAccess * @description Helpful function for checking whether a given user has access to do a given action on a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {String} ANY_ACTION The 'any' action to check * @param {String} OWN_ACTION The 'owned' action to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * const canEditItem = sdk.access.checkActionAccess(user, {_id:'61eca4746971e75c1fc670ca', meta:{...}}, 'editany', 'editown'); * const canViewItem = sdk.access.checkActionAccess(user, {_id:'61eca4746971e75c1fc670ca', meta:{...}}, 'viewany', 'viewown'); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.checkActionAccess(user, item, 'editany', 'editown', {cache}); * }) */ service.checkActionAccess = function ( user, item, ANY_ACTION, OWN_ACTION, options, ) { options = options || {}; options.cache = options.cache || {}; //Allow everything if we're a super user if (service.isAdministrator(user)) { return true; } ////////////////////////////////// var type = item.meta?.type; var definition = item.meta?.definition || type; if (!definition) { return false; } ////////////////////////////////// //The user should be able to list any scopes that they have //permission to do things in. if (ANY_ACTION == "listany" || OWN_ACTION == "listown") { if (type === "scope") { var itemID = QikCore.utils.id(item); return !!user.permissions[itemID]; } } ////////////////////////////////// var itemScopes = QikCore.utils.ids(item.meta.scopes || []); //Scopes don't have meta.scopes, instead use the scope's //path (ancestors) plus the scope's own ID if (type === "scope") { itemScopes = [ ...QikCore.utils.ids(item.path || []), QikCore.utils.id(item), ]; } ////////////////////////////////// var actionAnyScopesHash; var anyCacheKey = `access-${user?.cacheKey}-${ANY_ACTION}-${definition}-${type}`; if (options.cache[anyCacheKey]) { actionAnyScopesHash = options.cache[anyCacheKey]; } else { //Check if we can action this type of thing in any scopes var actionAnyScopes = QikCore.utils.ids( service.actionableScopes(user, ANY_ACTION, definition, type, options), ); //Get a fash hash of the scopes actionAnyScopesHash = QikCore.utils.hash(actionAnyScopes); options.cache[anyCacheKey] = actionAnyScopesHash; } ////////////////////////////////// //If we have data about what they can do if (actionAnyScopesHash) { //Check if the item is in any of the scopes the user can access var isInAnyScopes = itemScopes.some(function (scopeID) { return actionAnyScopesHash[scopeID]; }); if (isInAnyScopes) { return true; } } ////////////////////////////////// //Check if the user owns this content var isOwner = service.isOwner(user, item); //If the user is the owner if (isOwner) { var actionOwnScopesHash; var ownCacheKey = `access-${user?.cacheKey}-${OWN_ACTION}-${definition}-${type}`; if (options.cache[ownCacheKey]) { actionOwnScopesHash = options.cache[ownCacheKey]; } else { //Check if we can action this type of thing in any scopes var actionOwnScopes = QikCore.utils.ids( service.actionableScopes(user, OWN_ACTION, definition, type, options), ); //Get a fash hash of the scopes actionOwnScopesHash = QikCore.utils.hash(actionOwnScopes); options.cache[ownCacheKey] = actionOwnScopesHash; } //If we have data about what they can do if (actionOwnScopesHash) { //Check if the item is in any of the scopes the user can access var isInOwnScopes = itemScopes.some(function (scopeID) { return actionOwnScopesHash[scopeID]; }); if (isInOwnScopes) { return true; } } } ////////////////////////////////// //No access by default return false; }; /////////////////////////////////////////////// /** * @alias access.has * @description Helpful function for checking whether a given user has a specified permission * @param {Object} user The user to check * @param {String} permission The permission the user may have been granted * @example * const user = sdk.auth.getCurrentUser(); * const isAllowedToCreateItems = sdk.access.has(user, 'video.create'); */ service.has = function (user, permission) { //Allow everything if we're a super user if (service.isAdministrator(user)) { return true; } var permissionsLookup = service.hashPermissions(user); return permissionsLookup[permission]; }; /////////////////////////////////////////////// /** * @alias access.canCreate * @description Helpful function for checking whether a given user can create a specified type of item * @param {Object} user The user to check * @param {String} definition The defined type key * @param {String} type The basic type key * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * const userCanCreateShortFilms = sdk.access.canCreate(user, 'shortFilm', 'video'); */ service.canCreate = function (user, definition, type, options) { options = options || {}; options.cache = options.cache || {}; //Allow everything if we're a super user if (service.isAdministrator(user)) { return true; } var createableScopes = service.actionableScopes( user, "create", definition, type, ); return createableScopes.length; }; /////////////////////////////////////////////// /** * @alias access.canListItem * @description Helpful function for checking whether a given user can list a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canListItem(user, item, {cache}); * }) */ service.canListItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; //Check if we can list the item var canList = service.checkActionAccess( user, item, "listany", "listown", options, ); if (canList) { return true; } //If we can view the item then we should be able to list it also var canView = service.canViewItem(user, item, options); if (canView) { return true; } return false; }; /////////////////////////////////////////////// /** * @alias access.canViewItem * @description Helpful function for checking whether a given user can view a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canViewItem(user, item, {cache}); * }) */ service.canViewItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; //Check if we can view the item var canView = service.checkActionAccess( user, item, "viewany", "viewOwn", options, ); if (canView) { return true; } //If we can edit the item then we should be able to view it also var canEdit = service.canEditItem(user, item, options); if (canEdit) { return true; } return false; }; /////////////////////////////////////////////// /** * @alias access.canEditItem * @description Helpful function for checking whether a given user can edit a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canEditItem(user, item, {cache}); * }) */ service.canEditItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; var allowed = service.checkActionAccess( user, item, "editany", "editown", options, ); return allowed; }; /////////////////////////////////////////////// /** * @alias access.canDeleteItem * @description Helpful function for checking whether a given user can delete a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canDeleteItem(user, item, {cache}); * }) */ service.canDeleteItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; return service.checkActionAccess( user, item, "deleteany", "deleteown", options, ); }; /////////////////////////////////////////////// /** * @alias access.canRestoreItem * @description Helpful function for checking whether a given user can restore a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canRestoreItem(user, item, {cache}); * }) */ service.canRestoreItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; return service.checkActionAccess( user, item, "restoreany", "restoreown", options, ); }; /////////////////////////////////////////////// /** * @alias access.canEraseItem * @description Helpful function for checking whether a given user can erase a specified item * @param {Object} user The user to check * @param {Object} item The item to check * @param {Object} options Additional options and parameters * @param {Object} options.cache Provide an existing object to use as an in memory cache, can be useful to increase performance if running this function in a large for/while loop * @example * const user = sdk.auth.getCurrentUser(); * * // Example of looping through a large array of items * const largeArrayOfItems = [{_id:'...', title...}, {_id:'...', title...}...] * * // Create an in memory cache object outside of the loop * const cache = {} * * const filtered = largeArrayOfItems.filter(function(item) { * return sdk.access.canEraseItem(user, item, {cache}); * }) */ service.canEraseItem = function (user, item, options) { options = options || {}; options.cache = options.cache || {}; return service.checkActionAccess( user, item, "eraseany", "eraseown", options, ); }; return service; }; /////////////////////////////////////////////////////////////////////////////// export default QikAccess;