import _ from 'lodash';
import { EventDispatcher } from './fluro.utils';
/**
* TODO need to add function to check canCreateAnyKindOf('tag') including any sub definitions
*/
///////////////////////////////////////////////////////////////////////////////
/**
* Creates a new FluroAccess service
* This module provides helpful functions and tools for managing and understanding a user's permissions and access control
*
* @alias access
* @constructor
* @hideconstructor
* @param {FluroCore} fluro A reference to the parent instance of the FluroCore module. This module is usually created by a FluroCore instance that passes itself in as the first argument.
*/
var FluroAccess = function(FluroCore) {
if (!FluroCore.auth) {
throw new Error(`Can't Instantiate FluroAccess before FluroAccess has been initialized`);
}
//////////////////////////////////
var store = {};
var service = {};
//Create a new dispatcher
var dispatcher = new EventDispatcher();
dispatcher.bootstrap(service);
///////////////////////////////////////////////////
/**
*
* Sets the default application so that if the current user is running
* in the context of an application and not an authenticated user this
* service can still understand and respond according to the permission sets of the
* application itself
* @alias access.setDefaultApplication
* @param {Object} application The application session data, usually available before this service is initialized
* @example
* fluro.access.setDefaultApplication(window.applicationData._application)
*/
service.setDefaultApplication = function(application) {
store.application = application;
//Dispatch an event that the application data changed
dispatcher.dispatch('application', application);
}
//////////////////////////////////
service.isFluroAdmin = function(webMode) {
var user = service.retrieveCurrentSession(webMode);
//If we are not authenticated as a user
if (!user) {
return;
}
//If we are not an administrator
if (user.accountType != 'administrator') {
return;
}
//If we are pretending to be someone else
//or impersonating a persona
if (user.pretender) {
return;
}
//We are a fluro admin
return true;
}
//////////////////////////////////
/**
* Returns either the currently logged in user, or the acting application
* @alias access.retrieveCurrentSession
* @return {Object} The user or application session that is currently active
*/
service.retrieveCurrentSession = function(webMode) {
var user;
if (FluroCore.GLOBAL_AUTH) {
user = FluroCore.auth.getCurrentUser();
} else {
if (webMode || FluroCore.userContextByDefault) {
user = FluroCore.app ? FluroCore.app.user : null;
} else {
user = FluroCore.auth.getCurrentUser();
}
}
//////////////////////////////////
var application = store.application;
//////////////////////////////////
if (user) {
return user;
}
if (application) {
return application.session || application;
}
return
}
//////////////////////////////////
/**
* Checks whether a user has permission to perform a specified action for a specified type of content
* If no user is set but an application is then it will return according to the permissions of the application
* This function is synchronous and returns a basic true or false boolean
* @param {String} action The action to check permissions for eg. 'create', 'view any', 'edit own', 'delete any' etc
* @param {String} type The type or definition name eg. 'photo', 'article', 'team'
* @param {String} parentType The basic type, for instance if the type you are checking is 'photo' the parent type would be 'image' so that
* you can get an accurate return value if the user has permission to perform the action on all definitions of an 'image' type content item
* @return {Boolean} true or false depending on whether the user has the required permissions
* @alias access.can
* @example
*
* fluro.access.can('create' 'photo', 'image');
* fluro.access.can('edit any' 'service', 'event');
*
*/
service.can = function(action, type, parentType, webMode) {
//Get the current session
var session = service.retrieveCurrentSession(webMode);
//If we are not logged in and are not
//running as an application then we can't
//do anything
if (!session) {
console.log('No session');
return false;
}
//If we are an administrator
//then we have access to do everything
//so there is no point continuing with checking all the other criteria
if (service.isFluroAdmin() && !webMode) {
return true;
}
/////////////////////////////////////////////////////
//If using shorthand
switch (action) {
case 'view':
return service.can('view any', type, parentType, webMode) || service.can('view own', type, parentType, webMode);
break;
case 'edit':
return service.can('edit any', type, parentType, webMode) || service.can('edit own', type, parentType, webMode);
break;
}
/////////////////////////////////////////////////////
//Get the permission string we actually want to check against
var permissionString = (`${action} ${type}`).trim();
/////////////////////////////////////////////////////
//Track the realms we are allowed to do this in
var realms = [];
/////////////////////////////////////////////////////
//Check if we can do this permission in any realms
switch (action) {
case 'view any':
var canViewAnyRealms = service.retrieveActionableRealms('view any ' + type, webMode);
var canEditAnyRealms = service.retrieveActionableRealms('edit any ' + type, webMode);
//Combine the realms
realms = realms.concat(canViewAnyRealms);
realms = realms.concat(canEditAnyRealms);
break;
case 'view own':
var canViewOwnRealms = service.retrieveActionableRealms('view own ' + type, webMode);
var canEditOwnRealms = service.retrieveActionableRealms('edit own ' + type, webMode);
//Combine the realms
realms = realms.concat(canViewOwnRealms);
realms = realms.concat(canEditOwnRealms);
break;
default:
realms = service.retrieveActionableRealms(permissionString, webMode);
break;
}
/////////////////////////////////////////////////////
//If there are realms that we can do this in
//then we can return true here
if (realms.length) {
return true;
}
/////////////////////////////////////////////////////
//Check if the user has any permissions on the parent type that would allow them to perform the action
if (parentType && parentType.length) {
//Check if we have flowdown from the parent type
var includeDefined = service.retrieveActionableRealms('include defined ' + parentType, webMode);
//If not there is no point continuing with the check
if (!includeDefined.length) {
return false;
}
//If so we now need to check if we can perform
//the action on the parent in any realms
switch (action) {
case 'view any':
var canViewAnyParentRealms = service.retrieveActionableRealms('view any ' + parentType, webMode);
var canEditAnyParentRealms = service.retrieveActionableRealms('edit any ' + parentType, webMode);
//Combine the realms
realms = realms.concat(canViewAnyRealms);
realms = realms.concat(canEditAnyRealms);
break;
case 'view own':
var canViewOwnParentRealms = service.retrieveActionableRealms('view own ' + parentType, webMode);
var canEditOwnParentRealms = service.retrieveActionableRealms('edit own ' + parentType, webMode);
//Combine the realms
realms = realms.concat(canViewOwnParentRealms);
realms = realms.concat(canEditOwnParentRealms);
break;
default:
realms = service.retrieveActionableRealms(action + ' ' + parentType, webMode);
break;
}
if (realms.length) {
////////console.log('Return true because of parent permissions')
return true;
}
}
//Nope we cant
return false;
}
//////////////////////////////////
/**
* Checks whether a user has permission any permissions for a specified type of content
* If no user is set but an application is then it will return according to the permissions of the application
* This function is synchronous and returns a basic true or false boolean
* @param {String} type The type or definition name eg. 'photo', 'article', 'team'
* @param {String} parentType The basic type, for instance if the type you are checking is 'photo' the parent type would be 'image' so that
* you can get an accurate return value if the user has permission to perform the action on all definitions of an 'image' type content item
* @return {Boolean} true or false depending on whether the user has the required permissions
* @alias access.canKnowOf
* @example
*
* fluro.access.canKnowOf('photo', 'image');
* fluro.access.canKnowOf('event');
*
*/
service.canKnowOf = function(type, parentType, webMode) {
//Get the current session
var session = service.retrieveCurrentSession(webMode);
//If we are not logged in and are not
//running as an application then we can't
//do anything
if (!session) {
return false;
}
//If we are an administrator
//then we have access to do everything
//so there is no point continuing with checking all the other criteria
if (service.isFluroAdmin() && !webMode) {
return true;
}
/////////////////////////////////////////////////////
//Get the permission string we actually want to check against
var actionsToCheck = [
'view any',
'view own',
'view any',
'edit own',
'create',
]
/////////////////////////////////////////////////////
var canAccess = _.some(actionsToCheck, function(action) {
return service.can(action, type, parentType, webMode);
});
if (canAccess) {
return true;
}
/////////////////////////////////////////////////////
if (FluroCore.types && FluroCore.types.glossary) {
var subTypes = _.some(service.glossary, function(term, key) {
if (term.parentType == type) {
return _.some(actionsToCheck, function(action) {
return service.can(action, key, null, webMode);
});
}
});
return subTypes;
}
}
/////////////////////////////////////////////////////
//Flatten all children for a specified permission set
//so you get a flat array of included realm ids
//this function is recursive and will include all sub realms
function retrieveKeys(set, additional) {
if (set.children && set.children.length) {
set.children.forEach(function(child) {
retrieveKeys(child, additional);
})
}
additional.push(String(set._id));
}
//////////////////////////////////
/**
* Retrieves all realms the acting user or application can perform an action in
* @param {String} permission The permission string to retrieve realms for
* @return {Array} An array of realms that the user can perform the action in
* @alias access.retrieveActionableRealms
* @example
*
* //Returns an array of all realms the user is allowed to do the specified action
* var realms = fluro.access.retrieveActionableRealms('create photo');
*/
service.retrieveActionableRealms = function(action, webMode) {
//Get the current acting user session
var session = service.retrieveCurrentSession(webMode);
//No session so can't perform any actions
//in any realms
if (!session) {
return [];
}
/////////////////////////////////
//Get the permission sets
var permissionSets = session.permissionSets;
//Find all realms that the current acting session
//can perform the specified action in
var realms = _.chain(permissionSets)
.map(function(realmSet, key) {
//Does the set include this permission
var hasPermission = _.includes(realmSet.permissions, action);
if (hasPermission) {
var keys = [];
retrieveKeys(realmSet, keys);
return keys;
}
})
.flatten()
.compact()
.value();
/////////////////////////////////
return realms;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Check whether a user has a specific permission, useful for checking custom permissions
* or simply whether or not a user has a permission in any realm
* @param {String} permission The permission to check
* @return {Boolean}
* @alias access.has
* @example
*
* //Returns true or false if the user has the permission
* var hasPermission = fluro.access.has('create photo');
*/
service.has = function(permission, webMode) {
//Get the current acting user session
var user = service.retrieveCurrentSession(webMode);
if (!user) {
return false;
}
if (service.isFluroAdmin() && !webMode) {
return true;
}
///////////////////////////////////////////////////////////////////////////////
var permissionSets = user.permissionSets;
//Get all of the possible permissions
var permissions = _.chain(permissionSets)
.reduce(function(results, set, key) {
results.push(set.permissions);
return results;
}, [])
// .map(retrieveSubRealms)
.flattenDeep()
.compact()
.uniq()
.value();
//Check if any of the users permissions include the one
//we are looking for
return _.includes(permissions, permission);
}
/////////////////////////////////////////////////////
/**
* Checks whether the currently authenticated user is the author or owner of a specified
* content item
* @param {Object} item The item to check if the user is an author of
* @return {Boolean}
* @alias access.isAuthor
* @example
*
* //Returns true or false
* var isAuthor = fluro.access.isAuthor({title:'My article', _id:'55bbf345de...'});
*/
service.isAuthor = function(item, webMode) {
//Get the current acting user session
var user = service.retrieveCurrentSession(webMode);
if (!user) {
return false;
}
if (!item) {
return false;
}
////////////////////////////////////////
var userID = FluroCore.utils.getStringID(user);
var authorID = FluroCore.utils.getStringID(item.author);
//The user is the author if the user's id matches
//the content author's id
var author = userID == authorID;
////////////////////////////////////////
//If we are the author at this point
//return early
if (author) {
return true;
}
//Check if the persona matches the managed author
var personaID = FluroCore.utils.getStringID(user.persona);
var managedAuthorID = FluroCore.utils.getStringID(item.managedAuthor);
//If the user's persona is the managed author of the content
if (personaID == managedAuthorID) {
author = true;
}
////////////////////////////////////////
//If we are the author at this point
//return early
if (author) {
return true;
}
////////////////////////////////////////
//Check if the item has any owners listed on it
var ownerIDs = FluroCore.utils.arrayIDs(item.owners);
//If owners are listed
if (ownerIDs && ownerIDs.length) {
//Check if the user is listed as an owner
author = _.includes(ownerIDs, userID);
}
////////////////////////////////////////
//If we are the author at this point
//return early
if (author) {
return true;
}
////////////////////////////////////////
//Check if the item has any managed owners listed on it
var managedOwnerIDs = FluroCore.utils.arrayIDs(item.managedOwners);
//If managed owners are listed
if (managedOwnerIDs && managedOwnerIDs.length) {
//Check if the user is listed as an owner
author = _.includes(managedOwnerIDs, personaID);
}
////////////////////////////////////////
var itemID = FluroCore.utils.getStringID(item);
//If the user is trying to edit their own user
if (userID == itemID) {
author = true;
}
//If the user is trying to edit their own persona
if (personaID == itemID) {
author = true;
}
return author;
}
/////////////////////////////////////////////////////
/**
* Check whether the current acting user can edit a specified content item
* @param {Object} item The item to check if the user can edit
* @return {Boolean}
* @alias access.canEditItem
* @example
*
* //Returns true
* var canEdit = fluro.access.canEditItem({title:'My article', _id:'55bbf345de...'});
*/
service.canEditItem = function(item, isUser, webMode) {
if (!item) {
return false;
}
//Get the current acting user or application
var user = service.retrieveCurrentSession(webMode);
if (!user) {
return false;
}
//Store the itemID in case we need to reference it below
var itemID = FluroCore.utils.getStringID(item);
/////////////////////////////////////
//Check the account of the user
//and the account of the content
var userAccountID = FluroCore.utils.getStringID(user.account);
var contentAccountID = FluroCore.utils.getStringID(item.account);
//If there is an account listed on the content and it does not
//match the account of the user then we can't edit it
if (contentAccountID && (contentAccountID != userAccountID)) {
return false;
}
/////////////////////////////////////
//If we are a Fluro Admin we can do anything!
if (service.isFluroAdmin()) {
return true;
}
////////////////////////////////////////////////////
if (item._type && item._type != 'realm') {
if (item.realms && !item.realms.length) {
return true;
}
}
/////////////////////////////////////
//Get the definition name of the item
//we are trying to edit
var definitionName = item._type;
var parentType;
//If the item is a defined type
//store the definition and the parent type
if (item.definition) {
definitionName = item.definition;
parentType = item._type;
}
////////////////////////////////////////
if (item._type == 'process') {
if (item.assignedTo && item.assignedTo.length) {
var intersect = _.intersection(FluroCore.utils.arrayIDs(item.assignedTo), user.contacts);
if (intersect && intersect.length) {
return true;
}
}
if (item.assignedToTeam && item.assignedToTeam.length) {
//Check if the user is in any of the teams
var userTeams = _.map(user.visibleRealms, '_team');
var intersect = _.intersection(config.arrayIDs(item.assignedToTeam), userTeams);
if (intersect && intersect.length) {
if (!callback) {
return true;
}
return callback(true);
}
}
}
////////////////////////////////////////
//Check if the user is the author of this content
var author = service.isAuthor(item);
//If the content we are checking is a Fluro User
//We used to allow the user to edit their own user
//but we don't allow this anymore
//user profile
// if (isUser) {
// definitionName = 'user';
// if (author) {
// return true;
// }
// }
////////////////////////////////////////
//Find the realms we are allowed to edit this kind of content in
var editAnyRealms = service.retrieveActionableRealms('edit any ' + definitionName, webMode);
var editOwnRealms = service.retrieveActionableRealms('edit own ' + definitionName, webMode);
////////////////////////////////////////
//Keep track of the realms of the content
var contentRealmIDs;
//If we are checking a realm then we need to check the trail
//instead of the 'item.realms' array
if (definitionName == 'realm' || parentType == 'realm') {
//Check the realm.trail
contentRealmIDs = FluroCore.utils.arrayIDs(item.trail);
//Include the realm itself
contentRealmIDs.push(itemID);
} else {
//Retrieve all the realms the content is currently in
contentRealmIDs = FluroCore.utils.arrayIDs(item.realms);
}
////////////////////////////////////////
//Check if the user has any permissions on the parent type that will allow them to access this content
if (parentType && parentType.length) {
var includeDefined = service.retrieveActionableRealms('include defined ' + parentType, webMode);
//If we can adjust the parent and it's defined child types in any realms
if (includeDefined.length) {
var canEditAnyParentRealms = service.retrieveActionableRealms('edit any ' + parentType, webMode);
editAnyRealms = editAnyRealms.concat(canEditAnyParentRealms);
var canEditOwnParentRealms = service.retrieveActionableRealms('edit own ' + parentType, webMode);
editOwnRealms = editOwnRealms.concat(canEditOwnParentRealms);
}
}
////////////////////////////////////////
//Find realms the content is in that we are allowed to edit within
var matchedAnyRealms = _.intersection(editAnyRealms, contentRealmIDs);
//We are allowed to edit anything in these realms
//So return true
if (matchedAnyRealms.length) {
return true;
}
////////////////////////////////////////
//If we are the author of the content
if (author) {
//Find own matches between this content
var matchedOwnRealms = _.intersection(editOwnRealms, contentRealmIDs);
//We are allowed to edit anything in these realms
//So return true
if (matchedOwnRealms.length) {
return true;
}
}
return false;
}
/////////////////////////////////////////////////////
/**
* Check whether the current acting user can view a specified content item
* @param {Object} item The item to check if the user can view
* @return {Boolean}
* @alias access.canViewItem
* @example
*
* //Returns true
* var canView = fluro.access.canViewItem({title:'My article', _id:'55bbf345de...'});
*/
service.canViewItem = function(item, isUser, webMode) {
if (!item) {
return false;
}
//Get the current acting user or application
var user = service.retrieveCurrentSession(webMode);
if (!user) {
return false;
}
/////////////////////////////////////
//If we are a Fluro Admin we can do anything!
if (service.isFluroAdmin()) {
return true;
}
////////////////////////////////////////////////////
if (item._type && item._type != 'realm') {
if (item.realms && !item.realms.length) {
return true;
}
}
/////////////////////////////////////
//Store the itemID in case we need to reference it below
var itemID = FluroCore.utils.getStringID(item);
/////////////////////////////////////
var definitionName = item._type;
var parentType
if (item.definition) {
definitionName = item.definition;
parentType = item._type;
}
////////////////////////////////////////
//Check if the user is the author of this content
var author = service.isAuthor(item);
// if (isUser) {
// definitionName = 'user';
// if (author) {
// return true;
// }
// }
////////////////////////////////////////
//Get the realms we are allowed to work in
var viewAnyRealms = service.retrieveActionableRealms('view any ' + definitionName, webMode);
var viewOwnRealms = service.retrieveActionableRealms('view own ' + definitionName, webMode);
var editAnyRealms = service.retrieveActionableRealms('edit any ' + definitionName, webMode);
var editOwnRealms = service.retrieveActionableRealms('edit own ' + definitionName, webMode);
//Combine any
var combinedAnyRealms = [];
combinedAnyRealms = combinedAnyRealms.concat(viewAnyRealms);
combinedAnyRealms = combinedAnyRealms.concat(editAnyRealms);
//Combine own
var combinedOwnRealms = [];
combinedOwnRealms = combinedOwnRealms.concat(viewOwnRealms);
combinedOwnRealms = combinedOwnRealms.concat(editOwnRealms);
////////////////////////////////////////
//Keep track of the realms of the content
var contentRealmIDs;
//If we are checking a realm then we need to check the trail
//instead of the 'item.realms' array
if (definitionName == 'realm' || parentType == 'realm') {
//Check the realm.trail
contentRealmIDs = FluroCore.utils.arrayIDs(item.trail);
//Include the realm itself
console.log('PUSH?', contentRealmIDs)
contentRealmIDs.push(itemID);
} else {
//Retrieve all the realms the content is currently in
contentRealmIDs = FluroCore.utils.arrayIDs(item.realms);
}
////////////////////////////////////////
//Check if the user has any permissions on the parent type that will allow them to access this content
if (parentType && parentType.length) {
var includeDefined = service.retrieveActionableRealms('include defined ' + parentType, webMode);
if (includeDefined.length) {
var canEditAnyParentRealms = service.retrieveActionableRealms('edit any ' + parentType, webMode);
var canViewAnyParentRealms = service.retrieveActionableRealms('view any ' + parentType, webMode);
combinedAnyRealms = combinedAnyRealms.concat(canEditAnyParentRealms, canViewAnyParentRealms);
var canEditOwnParentRealms = service.retrieveActionableRealms('edit own ' + parentType, webMode);
var canViewOwnParentRealms = service.retrieveActionableRealms('view own ' + parentType, webMode);
combinedOwnRealms = combinedOwnRealms.concat(canEditOwnParentRealms, canViewOwnParentRealms);
}
}
////////////////////////////////////////
//Find any matches between this content
var matchedAnyRealms = _.intersection(combinedAnyRealms, contentRealmIDs);
//We are allowed to view anything in these realms
//So return true
if (matchedAnyRealms.length) {
return true;
}
////////////////////////////////////////
//If we are the author
if (author) {
//Find own matches between this content
var matchedOwnRealms = _.intersection(combinedOwnRealms, contentRealmIDs);
//We are allowed to view anything in these realms
//So return true
if (matchedOwnRealms.length) {
return true;
}
}
return false;
}
/////////////////////////////////////////////////////
/**
* Check whether the current acting user can delete a specified content item
* @param {Object} item The item to check if the user can delete
* @return {Boolean}
* @alias access.canDeleteItem
* @example
*
* //Returns true
* var canDelete = fluro.access.canDeleteItem({title:'My article', _id:'55bbf345de...'});
*/
service.canDeleteItem = function(item, isUser, webMode) {
if (!item) {
return false;
}
//Get the current acting user or application
var user = service.retrieveCurrentSession(webMode);
if (!user) {
return false;
}
//Store the itemID in case we need to reference it below
var itemID = FluroCore.utils.getStringID(item);
/////////////////////////////////////
//Check the account of the user
//and the account of the content
var userAccountID = FluroCore.utils.getStringID(user.account);
var contentAccountID = FluroCore.utils.getStringID(item.account);
//If there is an account listed on the content and it does not
//match the account of the user then we can't delete it
if (contentAccountID && (contentAccountID != userAccountID)) {
return false;
}
/////////////////////////////////////
//If we are a Fluro Admin we can do anything!
if (service.isFluroAdmin()) {
return true;
}
////////////////////////////////////////////////////
if (item._type && item._type != 'realm') {
if (item.realms && !item.realms.length) {
return true;
}
}
/////////////////////////////////////
//Get the definition name of the item
//we are trying to delete
var definitionName = item._type;
var parentType;
//If the item is a defined type
//store the definition and the parent type
if (item.definition) {
definitionName = item.definition;
parentType = item._type;
}
////////////////////////////////////////
//Check if the user is the author of this content
var author = service.isAuthor(item);
//If the content we are checking is a Fluro User
//We used to allow the user to delete their own user
//but we don't allow this anymore
//user profile
// if (isUser) {
// definitionName = 'user';
// if (author) {
// return true;
// }
// }
////////////////////////////////////////
//Find the realms we are allowed to delete this kind of content in
var deleteAnyRealms = service.retrieveActionableRealms('delete any ' + definitionName, webMode);
var deleteOwnRealms = service.retrieveActionableRealms('delete own ' + definitionName, webMode);
////////////////////////////////////////
//Keep track of the realms of the content
var contentRealmIDs;
//If we are checking a realm then we need to check the trail
//instead of the 'item.realms' array
if (definitionName == 'realm' || parentType == 'realm') {
//Check the realm.trail
contentRealmIDs = FluroCore.utils.arrayIDs(item.trail);
//Include the realm itself
contentRealmIDs.push(itemID);
} else {
//Retrieve all the realms the content is currently in
contentRealmIDs = FluroCore.utils.arrayIDs(item.realms);
}
////////////////////////////////////////
//Check if the user has any permissions on the parent type that will allow them to access this content
if (parentType && parentType.length) {
var includeDefined = service.retrieveActionableRealms('include defined ' + parentType, webMode);
//If we can adjust the parent and it's defined child types in any realms
if (includeDefined.length) {
var canEditAnyParentRealms = service.retrieveActionableRealms('delete any ' + parentType, webMode);
deleteAnyRealms = deleteAnyRealms.concat(canEditAnyParentRealms);
var canEditOwnParentRealms = service.retrieveActionableRealms('delete own ' + parentType, webMode);
deleteOwnRealms = deleteOwnRealms.concat(canEditOwnParentRealms);
}
}
////////////////////////////////////////
//Find realms the content is in that we are allowed to delete within
var matchedAnyRealms = _.intersection(deleteAnyRealms, contentRealmIDs);
//We are allowed to delete anything in these realms
//So return true
if (matchedAnyRealms.length) {
return true;
}
////////////////////////////////////////
//If we are the author of the content
if (author) {
//Find own matches between this content
var matchedOwnRealms = _.intersection(deleteOwnRealms, contentRealmIDs);
//We are allowed to delete anything in these realms
//So return true
if (matchedOwnRealms.length) {
return true;
}
}
return false;
}
/////////////////////////////////////////////////////
service.retrieveSelectableRealms = function(action, definition, type, options) {
if (!options) {
options = {}
}
///////////////////////////////////
var params = {
definition: definition,
parentType: type,
type,
}
///////////////////////////////////
if (options.flat) {
params.flat = true;
}
///////////////////////////////////
return new Promise(function(resolve, reject) {
//Retrieve all the realms the user is allowed to know about
FluroCore.api.get('/realm/selectable', {
params,
}).then(function(res) {
return resolve(res.data)
}, reject);
return;
});
}
/**
service.retrieveSelectableRealms = function(action, type, parentType, options) {
if (!options) {
options = {};
}
return new Promise(function(resolve, reject) {
//Get the current acting user or application
var user = service.retrieveCurrentSession();
if (!user) {
resolve([]);
return;
}
//If we are a super user
if (service.isFluroAdmin()) {
//This returns the full list of all realms in a proper tree structure
FluroCore.api.get('/realm/tree').then(function(res) {
return resolve(res.data)
}, reject);
return;
}
////////////////////////////////////////////////////
//Get the permission sets of the user
//and then map the structure
var permissionSets = user.permissionSets;
//Permission String to search for
var searchString = `${action} ${type}`;
/////////////////////////////////////////////////////
//Flatten all children for a specified permission set
//so you a flat array of realm ids that are included
function retrieveSubRealms(set) {
var results = [set];
if (set.children && set.children.length) {
_.each(set.children, function(child) {
var additional = retrieveSubRealms(child);
results = results.concat(additional);
})
}
return results;
}
////////////////////////////////////////////////////
//Find all realms on the top level that we have the requested permission
//in and then get all child realms and flatten the list, this will give us
//all the realms that we can do the action in.
var selectableRealms = _.chain(permissionSets)
.filter(function(realmSet, key) {
//Find all permission sets where the user has the requested permission
var includesType = _.includes(realmSet.permissions, searchString);
var includedFromParent;
//If the parent type was provided also then check any sub definitions
//of the basic type
if (parentType && parentType.length) {
//Check if we can action the parent type
var includesParent = _.includes(realmSet.permissions, action + ' ' + parentType);
//Check if we can action variants of the parent type
var includesVariations = _.includes(realmSet.permissions, 'include defined ' + parentType);
//Include this realm if both of the above return true
includedFromParent = (includesParent && includesVariations);
}
var shouldInclude = (includesType || includedFromParent)
return shouldInclude;
})
//Recursively get all the child realms
.map(retrieveSubRealms)
.flattenDeep()
.map(function(realm) {
return _.pick(realm, [
'title',
'definition',
'_discriminator',
'_discriminatorType',
'trail',
'color',
'bgColor',
'_id',
'fullDefinition',
'depth',
])
// console.log(realm.depth, realm.title);
// return realm;
})
.uniq(function(realm) {
return realm._id
})
.orderBy(function(realm) {
if (realm.trail) {
return realm.trail.length;
} else {
return 0;
}
})
.reduce(function(set, realm) {
if (true) {
set[realm._id] = realm;
return set;
}
var lastRealmParent = _.last(realm.trail);
if (set[lastRealmParent]) {
if (!set[lastRealmParent].children) {
set[lastRealmParent].children = [];
}
set[lastRealmParent].children.push(realm);
realm.nested = true;
}
set[realm._id] = realm;
return set;
}, {})
.values()
.filter(function(realm) {
return !realm.nested;
})
.orderBy(function(realm) {
return realm.title;
})
.value();
/////////////////////////////////////
//Create a copy of the realm so we aren't mucking around with original user object
// var realmTree = angular.copy(selectableRealms);
//Resolve with our tree
return resolve(selectableRealms);
})
}
/**/
/**
* @name access.addEventListener
* @description Adds a callback that will be triggered whenever the specified event occurs
* @function
* @param {String} event The event to listen for
* @param {Function} callback The function to fire when this event is triggered
* @example
* //Listen for when the user session changes
* fluro.access.addEventListener('change', function(userSession) {})
*/
/**
* @name access.removeEventListener
* @description Removes all a callback from the listener list
* @function
* @param {String} event The event to stop listening for
* @param {Function} callback The function to remove from the listener list
* @example
* //Stop listening for the change event
* fluro.access.removeEventListener('change', myFunction)
*/
/**
* @name access.removeAllListeners
* @description Removes all listening callbacks for all events
* @function
* @example
* fluro.access.removeAllListeners()
*/
//////////////////////////////////
service.retrievePermissions = function(options) {
return new Promise(function(resolve, reject) {
//Load the glossary
console.log('Reload terminology for permissions');
FluroCore.types.reloadTerminology(options)
.then(function(terms) {
var derivatives = _.reduce(terms, function(set, type) {
var basicType = type.parentType;
if (!basicType) {
return set;
}
var existing = set[basicType];
if (!existing) {
existing = set[basicType] = {
names: [],
types: [],
};
}
existing.names.push(type.plural);
existing.types.push(type)
return set;
}, {});
//////////////////////////////////////////////////////
//Loop through and structure the available permissions
var permissions = _.chain(terms)
// .orderBy('title')
.reduce(function(set, type) {
//Create a copy so we dont pollute the types entry
type = JSON.parse(JSON.stringify(type));
//Get the basic type, or otherwise it is a basic type
var basicType = type.parentType || type.definitionName;
var definitionName = type.definitionName;
var title = terms[basicType] ? terms[basicType].title : basicType;
// //Check if an entry exists for this basic type
// var existing = set[basicType];
// if (!existing) {
// existing = set[basicType] = {
// title,
// definitionName: basicType,
// definitions: [],
// }
// }
var isDefineable = (definitionName == basicType);
///////////////////////////////////////////////////
//Create an array for all the possible permissions
type.permissions = [];
///////////////////////////////////////////////////
//Push it into the group
// existing.definitions.push(type);
set.push(type);
switch (definitionName) {
case 'account':
isDefineable = false;
type.permissions.push({
title: `Administrate Account Information`,
value: `administrate account`,
description: `Update billing, view invoices, add credit and modify Account Information`,
});
return set;
break;
}
///////////////////////////////////////////////////
switch (basicType) {
case 'simpleemail':
case 'smscorrespondence':
isDefineable = false;
type.permissions.push({
title: `Create new ${type.plural}`,
value: `create ${definitionName}`,
description: `Can create new ${type.plural}`,
})
type.permissions.push({
title: `View any ${type.plural}`,
value: `view any ${definitionName}`,
description: `Can view ${type.plural} regardless of who the sender is`,
})
type.permissions.push({
title: `View owned ${type.plural}`,
value: `view own ${definitionName}`,
description: `Can view ${type.plural} that were originally sent by the user`,
})
break;
default:
type.permissions.push({
title: `Create new ${type.plural}`,
value: `create ${definitionName}`,
description: `Can create new ${type.plural}`,
})
type.permissions.push({
title: `View any ${type.plural}`,
value: `view any ${definitionName}`,
description: `Can view ${type.plural} regardless of who the creator is`,
})
type.permissions.push({
title: `View owned ${type.plural}`,
value: `view own ${definitionName}`,
description: `Can view ${type.plural} that were originally created by the user, or the user is listed as an 'owner'`,
})
type.permissions.push({
title: `Edit any ${type.plural}`,
value: `edit any ${definitionName}`,
description: `Can edit ${type.title} regardless of who the creator is`,
})
type.permissions.push({
title: `Edit owned ${type.plural}`,
value: `edit own ${definitionName}`,
description: `Can edit ${type.plural} that were originally created by the user, or the user is listed as an 'owner'`,
})
type.permissions.push({
title: `Delete any ${type.plural}`,
value: `delete any ${definitionName}`,
description: `Can delete ${type.plural} regardless of who the creator is`,
})
type.permissions.push({
title: `Delete owned ${type.plural}`,
value: `delete own ${definitionName}`,
description: `Can delete ${type.plural} that were originally created by the user, or the user is listed as an 'owner'`,
})
/////////////////////////////////////////////////////
type.permissions.push({
title: `Destroy any ${type.plural}`,
value: `destroy any ${definitionName}`,
description: `Can destroy ${type.plural} permanently from the trash regardless of who the creator is`,
})
type.permissions.push({
title: `Destroy owned ${type.plural}`,
value: `destroy own ${definitionName}`,
description: `Can destroy ${type.plural} permanently from the trash that were originally created by the user, or the user is listed as an 'owner'`,
})
/////////////////////////////////////////////////////
type.permissions.push({
title: `Restore any ${type.plural}`,
value: `restory any ${definitionName}`,
description: `Can restore ${type.plural} from the trash. regardless of who the creator is`,
})
type.permissions.push({
title: `Restore owned ${type.plural}`,
value: `restory own ${definitionName}`,
description: `Can restore ${type.plural} from the trash. that were originally created by the user, or the user is listed as an 'owner'`,
})
/////////////////////////////////////////////////////
break;
}
/////////////////////////////////////////////////////
switch (definitionName) {
case 'interaction':
case 'post':
type.permissions.push({
title: `Submit new ${type.plural}`,
value: `submit ${definitionName}`,
description: `Can submit new ${type.plural} through the use of a form.`,
})
break;
case 'transaction':
type.permissions.push({
title: `Refund ${type.plural}`,
value: `refund ${definitionName}`,
description: `Can process ${type.plural} refunds`,
})
break;
case 'contact':
type.permissions.push({
title: `Send SMS Text Message`,
value: `sms`,
description: `Can send SMS Messages to ${type.plural} that the user is allowed to view`,
})
type.permissions.push({
title: `Send Basic Emails`,
value: `email`,
description: `Can send email messages via fluro to contacts that the user is allowed to view`,
})
break;
case 'checkin':
type.permissions.push({
title: `Leader Override Checkout ${type.plural}`,
value: `leader checkout ${definitionName}`,
description: `Can manually override and checkout a contact without providing the PIN Number`,
})
break;
case 'ticket':
type.permissions.push({
title: `Scan / Collect ${type.plural}`,
value: `collect ${definitionName}`,
description: `Can scan a ticket and mark it as 'collected'`,
})
break;
case 'policy':
type.permissions.push({
title: `Grant ${type.plural}`,
value: `grant ${definitionName}`,
description: `Can allocate any ${type.plural} to other users`,
})
type.permissions.push({
title: `Grant held ${type.plural}`,
value: `grant held ${definitionName}`,
description: `Can allocate ${type.plural} that are held by the current user to other users`,
})
type.permissions.push({
title: `Revoke ${type.plural}`,
value: `revoke ${definitionName}`,
description: `Can revoke ${type.plural} from other users`,
})
break;
case 'role':
type.permissions.push({
title: `Assign individual ${type.plural}`,
value: `assign role`,
description: `Can assign individual permission sets to other users`,
})
break;
case 'persona':
type.permissions.push({
title: `Assign individual roles`,
value: `assign role`,
description: `Can assign individual permission sets to other users`,
})
type.permissions.push({
title: `Impersonate ${type.plural}`,
value: `impersonate`,
description: `Can impersonate other user personas`,
})
break;
case 'team':
type.permissions.push({
title: `Join ${type.plural}`,
value: `join ${definitionName}`,
description: `Can join or add members to ${type.plural} if those ${type.plural} allow provisional membership`,
})
type.permissions.push({
title: `Leave ${type.plural}`,
value: `leave ${definitionName}`,
description: `Can leave or remove members from ${type.plural} if those ${type.plural} allow provisional membership`,
})
break;
}
///////////////////////////////////////////
if (isDefineable) {
var matchedSet = derivatives[basicType];
var description = `Apply all the selected permissions to all ${type.title} definitions`;
if (matchedSet) {
description = `Apply all the selected permissions to all ${type.title} definitions, Eg. (${matchedSet.names.join(', ')})`;
}
// if (isDefineable) {
type.permissions.push({
title: `Include all defined ${type.title} types`,
value: `include defined ${definitionName}`,
description,
})
// }
}
// switch(key) {
// case '':
// break;
// }
//Return the set
return set;
}, [])
// .values()
.orderBy('title')
.value();
resolve(permissions);
})
.catch(reject);
})
}
//////////////////////////////////
return service;
}
///////////////////////////////////////////////////////////////////////////////
export default FluroAccess;