Security Package Processes
The Security Package ships with 24 processes. Most are never run by hand - they are invoked by the card’s buttons or by one of the two orchestrating processes.
Every process is listed below, grouped by the role it plays. Each entry carries the process's source reproduced verbatim from the package - use Source under a process to expand it. This page mirrors the package rather than being written prose, so it is excluded from site search; search for a process by name from the Security Package page instead.
How they chain
Model.Security.Refresh runs the whole pipeline in dependency order:
- Synchronise model objects -
Model.Dim.Group,Model.Dim.User,Model.Dim.Dimension,Model.Dim.Application,Model.Dim.Screens - Dimension access -
Execute Dimension Access, thenModel.Dim.Element, thenDimension Access Saved - Element access -
Execute Element Access, thenElement Access Saved - User allocation -
User Group Allocation, thenUser Groups Saved - Screen access -
Screen Access Tags, thenScreen Access Saved
Why dimension access comes before the element build
Model.Dim.Element only populates elements for dimensions that are currently secured, so dimension access has to be applied first. Running the element build earlier would rebuild it against the previous set of enabled dimensions.
Index
Orchestration
Dimension builders
- Model.Dim.Dimension
- Model.Dim.Application
- Model.Dim.Screens
- Model.Dim.Group
- Model.Dim.User
- Model.Dim.Element
Execution
- Model.Security.Execute Dimension Access
- Model.Security.Execute Element Access
- Model.Security.Screen Access Tags
Save buttons
- Model.Cube.Security.Save Dimension Access Changes
- Model.Cube.Security.Save Element Access Changes
- Model.Cube.Security.Save Screen Access Changes
- Model.Cube.Security.Save User Group Changes
Saved state
- Model.Cube.Security.Dimension Access Saved
- Model.Cube.Security.Element Access Saved
- Model.Cube.Security.Screen Access Saved
- Model.Cube.Security.User Groups Saved
- Model.Cube.Security.User Group Allocation
User and group management
- Model.Dim.User.Add User
- Model.Dim.User.Delete User
- Model.Dim.Group.Add Group
- Model.Dim.Group.Delete Group
Orchestration
These are the only two processes you would normally run by hand.
Model.Security.Setup
Run once, immediately after importing the package. Aborts if the Security cube is missing, builds every supporting dimension from the model’s existing objects, grants all groups write access to the model, and gives Admin users an access tag for every application.
Source (446 lines)
js
function setupmodelwriteaccess() {
var modelid =
script.modelId();
var groups =
userSecurity.groups.list();
var updated = 0;
var failed = 0;
for (var groupid in groups) {
var grouprecord =
groups[groupid];
if (!grouprecord) {
continue;
}
var groupname = String(
typeof grouprecord.name === "function"
? grouprecord.name()
: grouprecord.name || ""
).trim();
if (
!groupname ||
groupname === "No Group"
) {
continue;
}
/*
* Admin already has full access and MODLR
* does not permit it to be edited.
*/
if (
groupname.toLowerCase() ===
"admin"
) {
continue;
}
try {
var group =
userSecurity.groups.get(
groupname
);
if (
group &&
group.setModelPermissions(
modelid,
"WRITE_ACCESS"
)
) {
updated++;
}
} catch (error) {
failed++;
script.log(
"Model Write Access failed: " +
groupname +
" / " +
String(error)
);
}
}
script.log(
"Model Write Access: " +
updated +
" groups updated, " +
failed +
" failed."
);
}
function pre() {
script.log(
"Security setup process prepared."
);
}
function begin() {
script.log(
"Security package setup started."
);
if (!cube.exists("Security")) {
script.abort(
"Security cube is missing."
);
return;
}
runprocess(
"Model.Dim.Security Measures"
);
runprocess(
"Model.Dim.Group"
);
setupmodelwriteaccess();
runprocess(
"Model.Dim.User"
);
runprocess(
"Model.Dim.Dimension"
);
runprocess(
"Model.Dim.Application"
);
runprocess(
"Model.Dim.Screens"
);
runprocess(
"Model.Dim.Element"
);
/*
* Ensure Admin users have full access to every
* application immediately after setup.
*/
setupadminaccess();
script.log(
"Security package setup completed."
);
}
function setupadminaccess() {
var applications =
security.applications();
var modelusers =
JSON.parse(
security.users()
);
for (
var a = 0;
a < applications.length;
a++
) {
var applicationid =
getvalue(
applications[a],
"id"
);
var applicationname =
getvalue(
applications[a],
"name"
);
if (!applicationid) {
continue;
}
var application =
security.application(
applicationid
);
if (!application) {
script.log(
"Unable to access application: " +
applicationname
);
continue;
}
var alltag = null;
/*
* Retrieve or create the All screen tag.
*/
if (
application.tags.exists(
"All"
)
) {
alltag =
application.tags.get(
"All"
);
} else {
alltag =
application.tags.createScreen(
"All"
);
script.log(
"All tag created: " +
applicationname
);
}
if (!alltag) {
script.log(
"Unable to prepare All tag: " +
applicationname
);
continue;
}
/*
* Make every application screen visible
* through the All tag.
*/
var screens =
application.screens.list();
for (
var s = 0;
s < screens.length;
s++
) {
var screenname =
getvalue(
screens[s],
"title"
);
if (screenname) {
alltag.set(
screenname,
true
);
}
}
var usersadded = 0;
var tagsadded = 0;
var usersfailed = 0;
/*
* Find every model user belonging to Admin.
*/
for (
var u = 0;
u < modelusers.length;
u++
) {
var securityuser =
getsecurityuser(
modelusers[u]
);
if (!securityuser) {
continue;
}
try {
if (
!securityuser.hasGroup(
"Admin"
)
) {
continue;
}
/*
* A user must be an application contributor
* before receiving an application tag.
*/
if (
!application.users.exists(
securityuser
)
) {
if (
application.users.add(
securityuser
)
) {
usersadded++;
}
}
if (
!alltag.hasUser(
securityuser
)
) {
if (
alltag.addUser(
securityuser
)
) {
tagsadded++;
}
}
} catch (error) {
usersfailed++;
script.log(
"Unable to grant Admin access: " +
getuseremail(
modelusers[u]
) +
" / " +
applicationname +
" / " +
String(error)
);
}
}
script.log(
"Admin access for " +
applicationname +
": " +
usersadded +
" contributors added, " +
tagsadded +
" All tags added, " +
usersfailed +
" failed."
);
}
}
function getsecurityuser(record) {
var userid = String(
record.id || ""
).trim();
var email =
getuseremail(record);
var securityuser = null;
/*
* Try the user ID first.
*/
if (userid) {
try {
securityuser =
userSecurity.users.get(
userid
);
} catch (error) {
securityuser = null;
}
}
/*
* Fall back to the user's email.
*/
if (
!securityuser &&
email
) {
try {
securityuser =
userSecurity.users.getFromEmail(
email
);
} catch (error) {
securityuser = null;
}
}
return securityuser;
}
function getuseremail(record) {
return String(
record.email ||
record.username ||
""
).trim();
}
function getvalue(
object,
property
) {
if (!object) {
return "";
}
if (
typeof object[property] ===
"function"
) {
return String(
object[property]() || ""
).trim();
}
return String(
object[property] || ""
).trim();
}
function runprocess(processname) {
if (!process.exists(processname)) {
script.abort(
"Required process is missing: " +
processname
);
return;
}
script.log(
"Running: " +
processname
);
process.execute(
processname
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Security setup process finished."
);
}Model.Security.Refresh
Runs the full pipeline - every dimension build, execution and save-state process, in dependency order. Use it to apply changes staged across several tables at once. Each step is guarded: a missing process aborts the run by name rather than being skipped silently.
Source (121 lines)
js
function pre() {
script.log(
"Security refresh process prepared."
);
}
function begin() {
script.log(
"Full security refresh started."
);
/*
* Synchronise model objects.
*/
runprocess(
"Model.Dim.Group"
);
runprocess(
"Model.Dim.User"
);
runprocess(
"Model.Dim.Dimension"
);
runprocess(
"Model.Dim.Application"
);
runprocess(
"Model.Dim.Screens"
);
/*
* Apply Dimension Access before rebuilding Element,
* because Element depends on the enabled dimensions.
*/
runprocess(
"Model.Security.Execute Dimension Access"
);
runprocess(
"Model.Dim.Element"
);
runprocess(
"Model.Cube.Security.Dimension Access Saved"
);
/*
* Apply element security.
*/
runprocess(
"Model.Security.Execute Element Access"
);
runprocess(
"Model.Cube.Security.Element Access Saved"
);
/*
* Apply user-to-group allocations.
*/
runprocess(
"Model.Cube.Security.User Group Allocation"
);
runprocess(
"Model.Cube.Security.User Groups Saved"
);
/*
* Apply application screen tags.
*/
runprocess(
"Model.Security.Screen Access Tags"
);
runprocess(
"Model.Cube.Security.Screen Access Saved"
);
script.log(
"Full security refresh completed."
);
}
function runprocess(processname) {
if (!process.exists(processname)) {
script.abort(
"Required process is missing: " +
processname
);
return;
}
script.log(
"Running: " +
processname
);
process.execute(
processname
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Security refresh process finished."
);
}Dimension builders
These rebuild the package’s supporting dimensions from the model’s actual objects. Each wipes its hierarchy and repopulates it, so they are safe to re-run at any time. They are what the card’s per-table Refresh buttons call.
Model.Dim.Dimension
Populates the Dimension dimension from every dimension in the model.
Source (42 lines)
js
var dim = "Dimension"
function pre() {
// This function is called once before the processes is executed.
// Use this to setup prompts.
script.log('process pre-execution parameters parsed.');
}
function begin() {
// This function is called once at the start of the process
script.log('process execution started.');
hierarchy.createOrWipe(dim, "No Dimension");
hierarchy.createOrWipe(dim, "Default");
const dims = JSON.parse(dimension.list());
let nameReference = "name"
for (let index = 0; index < dims.length; index++) {
hierarchy.group(
dim,
"Default",
"All Dimensions"
,
dims[index][nameReference]
);
}
hierarchy.group(dim, "No Dimension", "", "No Dimension");
}
function data(record) {
// This function is called once for each line of data on the second cycle
// Use this to build dimensions and push data into cubes
}
function end() {
// This function is called once at the end of the process
script.log('process execution finished.');
}Model.Dim.Application
Populates the Application dimension from every application in the model.
Source (211 lines)
js
var dim = "Application";
function pre() {
script.log(
"Application dimension process prepared."
);
}
function begin() {
script.log(
"Application dimension process started."
);
/*
* Retrieve every application available to the model.
*/
var applications =
security.applications();
if (
!applications ||
applications.length === 0
) {
script.log(
"No applications found. " +
"Application dimension was not rebuilt."
);
return;
}
/*
* Create the dimension when missing.
*/
if (!dimension.exists(dim)) {
dimension.create(
dim,
"Standard"
);
}
/*
* Rebuild the application hierarchies and aliases.
*/
hierarchy.createOrWipe(
dim,
"Default"
);
hierarchy.createOrWipe(
dim,
"No Application"
);
alias.createOrWipe(
dim,
"Name"
);
alias.createOrWipe(
dim,
"ID - Name"
);
alias.createOrWipe(
dim,
"Name - ID"
);
var applicationsadded = 0;
for (
var i = 0;
i < applications.length;
i++
) {
var application =
applications[i];
var applicationid =
getvalue(
application,
"id"
);
var applicationname =
getvalue(
application,
"name"
);
if (
!applicationid ||
!applicationname
) {
script.log(
"Skipped application with missing ID or name."
);
continue;
}
/*
* The application ID is the real element.
*/
hierarchy.group(
dim,
"Default",
"All Applications",
applicationid,
1
);
/*
* The aliases allow the application to be
* selected or displayed using its name.
*/
alias.set(
dim,
"Name",
applicationid,
applicationname
);
alias.set(
dim,
"ID - Name",
applicationid,
applicationid +
" - " +
applicationname
);
alias.set(
dim,
"Name - ID",
applicationid,
applicationname +
" - " +
applicationid
);
applicationsadded++;
script.log(
"Application added: " +
applicationname +
" / " +
applicationid
);
}
/*
* Create No Application in its own hierarchy.
*/
hierarchy.group(
dim,
"No Application",
"",
"No Application",
1
);
script.log(
"Application dimension completed: " +
applicationsadded +
" applications added."
);
}
function getvalue(
application,
property
) {
if (
application[property] !==
undefined &&
typeof application[property] !==
"function"
) {
return String(
application[property] || ""
).trim();
}
if (
typeof application[property] ===
"function"
) {
return String(
application[property]() || ""
).trim();
}
return "";
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Application dimension process finished."
);
}Model.Dim.Screens
Populates the Screen dimension from every screen in the model.
Source (257 lines)
js
var dim = "Screen";
function pre() {
script.log(
"Screen dimension process prepared."
);
}
function begin() {
script.log(
"Screen dimension process started."
);
var applications =
security.applications();
if (
!applications ||
applications.length === 0
) {
script.log(
"No applications found. " +
"Screen dimension was not rebuilt."
);
return;
}
var results = [];
var processedscreens = {};
var totalscreens = 0;
var duplicatesskipped = 0;
/*
* Read all applications and screens before
* rebuilding the hierarchies.
*/
for (
var a = 0;
a < applications.length;
a++
) {
var applicationid =
getvalue(
applications[a],
"id"
);
var applicationname =
getvalue(
applications[a],
"name"
);
if (
!applicationid ||
!applicationname
) {
continue;
}
var application =
security.application(
applicationid
);
if (!application) {
continue;
}
var screens =
application.screens.list();
var screennames = [];
for (
var s = 0;
s < screens.length;
s++
) {
var screenname =
getvalue(
screens[s],
"title"
);
if (!screenname) {
continue;
}
var screenkey =
screenname.toLowerCase();
/*
* Each screen title is included only once.
*/
if (processedscreens[screenkey]) {
duplicatesskipped++;
script.log(
"Duplicate screen skipped: " +
screenname +
" / " +
applicationname
);
continue;
}
processedscreens[screenkey] = true;
screennames.push(
screenname
);
totalscreens++;
}
if (screennames.length > 0) {
results.push({
name: applicationname,
screens: screennames
});
}
}
if (totalscreens === 0) {
script.log(
"No valid screens found. " +
"Screen dimension was not rebuilt."
);
return;
}
if (!dimension.exists(dim)) {
dimension.create(
dim,
"Standard"
);
}
hierarchy.createOrWipe(
dim,
"Default"
);
hierarchy.createOrWipe(
dim,
"No Screen"
);
/*
* Build each application as a consolidation by
* adding its screens first.
*/
for (
var r = 0;
r < results.length;
r++
) {
var result =
results[r];
for (
var x = 0;
x < result.screens.length;
x++
) {
hierarchy.group(
dim,
"Default",
result.name,
result.screens[x],
1
);
}
/*
* Both All Screens and the application are
* consolidation members, so use structure().
*/
hierarchy.structure(
dim,
"Default",
"All Screens",
result.name,
1
);
script.log(
"Application hierarchy created: " +
result.name +
" / " +
result.screens.length +
" screens"
);
}
/*
* Maintain No Screen in a separate hierarchy.
*/
hierarchy.group(
dim,
"No Screen",
"",
"No Screen",
1
);
script.log(
"Screen dimension completed: " +
results.length +
" applications, " +
totalscreens +
" unique screens and " +
duplicatesskipped +
" duplicates skipped."
);
}
function getvalue(
object,
property
) {
if (!object) {
return "";
}
if (
typeof object[property] ===
"function"
) {
return String(
object[property]() || ""
).trim();
}
return String(
object[property] || ""
).trim();
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Screen dimension process finished."
);
}Model.Dim.Group
Populates the Group dimension from the model’s security groups.
Source (222 lines)
js
var dim = "Group";
var hierarchyname = "Default";
var parentname = "All Groups";
function pre() {
script.log(
"Group dimension process prepared."
);
}
function begin() {
script.log(
"Group dimension process started."
);
if (!dimension.exists(dim)) {
dimension.create(
dim,
"Standard"
);
}
if (
!hierarchy.exists(
dim,
hierarchyname
)
) {
hierarchy.create(
dim,
hierarchyname
);
}
if (
!hierarchy.exists(
dim,
"No Group"
)
) {
hierarchy.create(
dim,
"No Group"
);
}
var groups =
userSecurity.groups.list();
var validgroups = {};
var groupsadded = 0;
/*
* Add all actual model security groups and record
* their names.
*/
for (var groupid in groups) {
var group =
groups[groupid];
if (!group) {
continue;
}
var groupname =
getgroupname(group);
if (
!groupname ||
groupname.toLowerCase() ===
"no group" ||
groupname.toLowerCase() ===
"all groups"
) {
continue;
}
validgroups[
groupname.toLowerCase()
] = true;
hierarchy.group(
dim,
hierarchyname,
parentname,
groupname,
1
);
groupsadded++;
}
/*
* Never remove the protected Admin member.
*/
validgroups["admin"] = true;
/*
* Collect obsolete dimension members before
* deleting anything.
*/
var obsoletegroups = [];
if (
hierarchy.hasMember(
dim,
hierarchyname,
parentname
)
) {
var childcount = Number(
hierarchy.childCount(
dim,
hierarchyname,
parentname
)
);
for (
var i = 0;
i < childcount;
i++
) {
var childname =
hierarchy.childByIndex(
dim,
hierarchyname,
parentname,
i
);
if (
childname &&
!validgroups[
childname.toLowerCase()
]
) {
obsoletegroups.push(
childname
);
}
}
}
/*
* Delete dimension elements that no longer
* correspond to a real model security group.
*/
for (
var d = 0;
d < obsoletegroups.length;
d++
) {
element.delete(
dim,
obsoletegroups[d]
);
script.log(
"Obsolete Group element removed: " +
obsoletegroups[d]
);
}
/*
* Create No Group in its separate hierarchy.
*/
hierarchy.group(
dim,
"No Group",
"",
"No Group",
1
);
script.log(
"Group dimension synchronised: " +
groupsadded +
" groups added or confirmed, " +
obsoletegroups.length +
" obsolete groups removed."
);
}
function getgroupname(group) {
if (
typeof group.getName ===
"function"
) {
return String(
group.getName() || ""
).trim();
}
if (
typeof group.name ===
"function"
) {
return String(
group.name() || ""
).trim();
}
return String(
group.name || ""
).trim();
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Group dimension process finished."
);
}Model.Dim.User
Populates the User dimension from the model’s users.
Source (239 lines)
js
var dimensionname = "User";
function pre() {
script.log(
"User dimension process prepared."
);
}
function begin() {
script.log(
"User dimension process started."
);
/*
* Create missing structures only.
* Existing users are not wiped.
*/
if (!dimension.exists(dimensionname)) {
dimension.create(
dimensionname,
"Standard"
);
}
if (
!hierarchy.exists(
dimensionname,
"Default"
)
) {
hierarchy.create(
dimensionname,
"Default"
);
}
if (
!hierarchy.exists(
dimensionname,
"No User"
)
) {
hierarchy.create(
dimensionname,
"No User"
);
}
/*
* Remove placeholders created automatically
* with a new Standard dimension.
*/
removeplaceholder(
"Sample Child One"
);
removeplaceholder(
"Sample Child Two"
);
removeplaceholder(
"Sample Parent"
);
/*
* Create missing aliases.
*/
createalias("Name");
createalias("ID - Name");
createalias("Name - ID");
createalias("Email");
var allusers =
security.users();
if (typeof allusers === "string") {
allusers =
JSON.parse(allusers);
}
var added = 0;
var updated = 0;
var skipped = 0;
for (
var i = 0;
i < allusers.length;
i++
) {
var currentuser =
allusers[i];
var id = String(
currentuser.id || ""
).trim();
var name = String(
currentuser.name ||
currentuser.username ||
id
).trim();
var email = String(
currentuser.email ||
currentuser.username ||
""
).trim();
if (!id) {
skipped++;
continue;
}
var alreadyexists =
element.exists(
dimensionname,
id
);
/*
* Add or reconnect the user beneath All Users.
*/
hierarchy.group(
dimensionname,
"Default",
"All Users",
id,
1
);
alias.set(
dimensionname,
"Name",
id,
name
);
alias.set(
dimensionname,
"ID - Name",
id,
id + " - " + name
);
alias.set(
dimensionname,
"Name - ID",
id,
name + " - " + id
);
alias.set(
dimensionname,
"Email",
id,
email
);
if (alreadyexists) {
updated++;
} else {
added++;
}
}
/*
* Maintain No User in its separate hierarchy.
*/
hierarchy.group(
dimensionname,
"No User",
"",
"No User",
1
);
script.log(
"User dimension summary: " +
added +
" added, " +
updated +
" updated, " +
skipped +
" skipped."
);
}
function removeplaceholder(
elementname
) {
if (
element.exists(
dimensionname,
elementname
)
) {
element.delete(
dimensionname,
elementname
);
script.log(
"Removed User placeholder: " +
elementname
);
}
}
function createalias(
aliasname
) {
if (
!alias.exists(
dimensionname,
aliasname
)
) {
alias.create(
dimensionname,
aliasname
);
}
}
function data(record) {
// Not required.
}
function end() {
script.log(
"User dimension process finished."
);
}Model.Dim.Element
Populates the Element dimension with the elements of the currently secured dimensions - which is why it has to run after dimension access has been applied.
Source (501 lines)
js
var TARGET_DIMENSION = "Element";
var TARGET_HIERARCHY = "Default";
/*
* These dimensions copy only the specified hierarchy.
* All other enabled dimensions copy every hierarchy.
*
* Names are case-sensitive.
*/
var RESTRICTED_HIERARCHIES = {
"Scenario": "Planning Scenarios",
"Asset": "Asset List",
"Account": "Account List",
"Employee": "Employee List"
};
function pre() {
script.log(
"Element dimension process prepared."
);
}
function begin() {
script.log(
"Element dimension process started."
);
/*
* Security cube order:
*
* row[0] = Element
* row[1] = User
* row[2] = Group
* row[3] = Dimension
* row[4] = Screen
* row[5] = Security Measures
* row[6] = Value
*/
var securitySlice = cube.slice(
"Security",
[
"No Element",
"No User",
"No Group",
"",
"No Screen",
"Dimension Access"
]
);
var enabledDimensions = [];
var processedDimensions = {};
/*
* Include dimensions where Dimension Access = Yes.
*/
while (!securitySlice.EOF()) {
var row = securitySlice.Next();
var sourceDimension = String(
row[3] || ""
).trim();
var accessValue = String(
row[6] || ""
)
.trim()
.toLowerCase();
if (
accessValue !== "yes" ||
!sourceDimension ||
processedDimensions[sourceDimension]
) {
continue;
}
/*
* Prevent Element from being included as a
* processable source dimension.
*/
if (
sourceDimension ===
TARGET_DIMENSION
) {
script.log(
"Skipped target dimension: " +
sourceDimension
);
continue;
}
/*
* Skip missing source dimensions before rebuilding
* the target hierarchy.
*/
if (
!dimension.exists(
sourceDimension
)
) {
script.log(
"Skipped missing dimension: " +
sourceDimension
);
continue;
}
processedDimensions[sourceDimension] = true;
enabledDimensions.push(
sourceDimension
);
if (
hasOwnProperty(
RESTRICTED_HIERARCHIES,
sourceDimension
)
) {
script.log(
"Enabled restricted dimension: " +
sourceDimension +
" / " +
RESTRICTED_HIERARCHIES[sourceDimension]
);
} else {
script.log(
"Enabled normal dimension: " +
sourceDimension +
" / All hierarchies"
);
}
}
script.log(
"Enabled source dimensions found: " +
enabledDimensions.length
);
/*
* Never wipe Element > Default if no valid source
* dimensions were found.
*/
if (enabledDimensions.length === 0) {
script.log(
"No enabled source dimensions found. " +
"Element hierarchy was not rebuilt."
);
return;
}
hierarchy.createOrWipe(
TARGET_DIMENSION,
TARGET_HIERARCHY
);
for (
var i = 0;
i < enabledDimensions.length;
i++
) {
var sourceDimension =
enabledDimensions[i];
buildDimensionTree(
sourceDimension
);
}
}
function data(record) {
// The Security cube slice is processed in begin().
}
function end() {
script.log(
"Element dimension process finished."
);
}
/*
* Builds the permitted hierarchy or hierarchies for a
* source dimension.
*
* Restricted dimensions:
* Copy only the configured hierarchy.
*
* Other dimensions:
* Copy all available hierarchies.
*/
function buildDimensionTree(
sourceDimension
) {
var dimensionParent =
sourceDimension + " Parent";
var availableHierarchies = parseJson(
dimension.hierarchies(
sourceDimension
)
);
var restrictedHierarchy = "";
if (
hasOwnProperty(
RESTRICTED_HIERARCHIES,
sourceDimension
)
) {
restrictedHierarchy =
RESTRICTED_HIERARCHIES[
sourceDimension
];
}
var hierarchyProcessed = false;
for (
var i = 0;
i < availableHierarchies.length;
i++
) {
var sourceHierarchy =
getName(
availableHierarchies[i]
);
if (!sourceHierarchy) {
script.log(
"Unable to read hierarchy name for " +
sourceDimension
);
continue;
}
/*
* For a restricted dimension, skip every hierarchy
* except the configured hierarchy.
*/
if (
restrictedHierarchy &&
sourceHierarchy !==
restrictedHierarchy
) {
script.log(
"Skipped additional hierarchy: " +
sourceDimension +
" / " +
sourceHierarchy
);
continue;
}
hierarchyProcessed = true;
copySourceHierarchy(
sourceDimension,
sourceHierarchy,
dimensionParent
);
}
/*
* This normally means that the configured hierarchy
* name does not exactly match the model.
*/
if (
restrictedHierarchy &&
!hierarchyProcessed
) {
script.log(
"Configured hierarchy not found: " +
sourceDimension +
" / " +
restrictedHierarchy
);
}
}
/*
* Copies one source hierarchy into Element > Default.
*/
function copySourceHierarchy(
sourceDimension,
sourceHierarchy,
dimensionParent
) {
var hierarchyParent =
sourceDimension +
" " +
sourceHierarchy;
hierarchy.structure(
TARGET_DIMENSION,
TARGET_HIERARCHY,
dimensionParent,
hierarchyParent,
1
);
script.log(
"Building: " +
sourceDimension +
" / " +
sourceHierarchy
);
/*
* A blank source member returns hierarchy roots.
* false means roots only.
*/
var roots =
hierarchy.iterateChildren(
sourceDimension,
sourceHierarchy,
"",
false
);
var rootCount = 0;
for (var root of roots) {
if (
!root ||
!root.name
) {
continue;
}
rootCount++;
copySourceMember(
sourceDimension,
sourceHierarchy,
root.name,
hierarchyParent
);
}
script.log(
"Roots processed for " +
sourceDimension +
" / " +
sourceHierarchy +
": " +
rootCount
);
}
/*
* Recursively copies all source parents and leaves.
*/
function copySourceMember(
sourceDimension,
sourceHierarchy,
sourceMember,
targetParent
) {
var childCount = Number(
hierarchy.childCount(
sourceDimension,
sourceHierarchy,
sourceMember
)
);
if (childCount > 0) {
/*
* Parent members retain the dimension and
* hierarchy prefixes.
*/
var targetConsolidation =
sourceDimension +
" " +
sourceHierarchy +
" " +
sourceMember;
hierarchy.structure(
TARGET_DIMENSION,
TARGET_HIERARCHY,
targetParent,
targetConsolidation,
1
);
for (
var i = 0;
i < childCount;
i++
) {
var sourceChild =
hierarchy.childByIndex(
sourceDimension,
sourceHierarchy,
sourceMember,
i
);
if (!sourceChild) {
continue;
}
copySourceMember(
sourceDimension,
sourceHierarchy,
sourceChild,
targetConsolidation
);
}
} else {
/*
* Leaves omit the hierarchy prefix.
*
* This allows the same leaf to be shared where
* normal dimensions contain it in several hierarchies.
*/
var targetLeaf =
sourceDimension +
" " +
sourceMember;
hierarchy.group(
TARGET_DIMENSION,
TARGET_HIERARCHY,
targetParent,
targetLeaf,
1
);
}
}
/*
* Safe object-property check.
*/
function hasOwnProperty(
objectValue,
propertyName
) {
return Object.prototype.hasOwnProperty.call(
objectValue,
propertyName
);
}
/*
* Parse functions that return JSON text.
*/
function parseJson(value) {
if (
value === null ||
value === undefined ||
value === ""
) {
return [];
}
if (typeof value === "string") {
return JSON.parse(value);
}
return value;
}
/*
* Read a name from either a string or an object.
*/
function getName(value) {
if (typeof value === "string") {
return value;
}
if (!value) {
return "";
}
return (
value.name ||
value.Name ||
value.identifier ||
value.id ||
""
);
}Execution
These take the values saved in the Security cube and apply them to the model’s real security settings. This is the layer that actually changes access.
Model.Security.Execute Dimension Access
Applies which dimensions are secured model-wide.
Source (429 lines)
js
var SECURITY_CUBE = "Security";
var GROUP_DIMENSION = "Group";
var GROUP_HIERARCHY = "Default";
var GROUP_PARENT = "All Groups";
var ADMIN_GROUP = "Admin";
var WRITE_ACCESS = "WRITE_ACCESS";
var NO_ACCESS = "NO_ACCESS";
var modelName;
var modelId;
function pre() {
script.log(
"Dimension Access process prepared."
);
}
function begin() {
script.log(
"Dimension Access process started."
);
modelName = script.modelName();
modelId = script.modelId();
var settingsUpdated =
userSecurity.updateServerSettings({
"DIMENSIONS": true,
"MODELS": true,
"APPLICATIONS": false,
"CUBES": false,
"MAPPINGS": false,
"PROCESSES": false,
"VARIABLES": false,
"WORKVIEWS": false,
"CARDS": false,
"TABLES": false,
"SCHEDULES": false
});
if (!settingsUpdated) {
script.abort(
"Unable to enable dimension security."
);
return;
}
/*
* Dimension Access values are stored at No Group.
*/
var dimensionSelections =
readDimensionSelections();
/*
* Safety check: do not deny every dimension if
* the slice unexpectedly returns no values.
*/
if (dimensionSelections.count === 0) {
script.abort(
"No Dimension Access values found at No Group. " +
"No permissions were changed."
);
return;
}
var existingGroups =
getExistingGroups();
var groupCount = Number(
hierarchy.childCount(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT
)
);
script.log(
"Groups found: " +
groupCount
);
for (
var i = 0;
i < groupCount;
i++
) {
var groupName =
hierarchy.childByIndex(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT,
i
);
if (
!groupName ||
groupName === "No Group"
) {
continue;
}
/*
* Admin is protected and must not be edited.
*/
if (groupName === ADMIN_GROUP) {
script.log(
"Skipped protected Admin group."
);
continue;
}
var securityGroup =
getOrCreateGroup(
groupName,
existingGroups
);
if (!securityGroup) {
script.log(
"Unable to process group: " +
groupName
);
continue;
}
applyDimensionAccess(
groupName,
securityGroup,
dimensionSelections.values
);
}
}
/*
* Read the global Dimension Access selections.
*
* No Element
* No User
* No Group
* Current Dimension
* No Screen
* Dimension Access
*/
function readDimensionSelections() {
var securitySlice = cube.slice(
SECURITY_CUBE,
[
"No Element",
"No User",
"No Group",
"",
"No Screen",
"Dimension Access"
]
);
var selections = {};
var count = 0;
while (!securitySlice.EOF()) {
var row =
securitySlice.Next();
var dimensionName = String(
row[3] || ""
).trim();
var value = String(
row[6] || ""
)
.trim()
.toLowerCase();
if (
!dimensionName ||
dimensionName === "No Dimension"
) {
continue;
}
/*
* Yes grants access.
* No explicitly denies access.
*/
if (
value !== "yes" &&
value !== "no"
) {
continue;
}
selections[dimensionName] =
value === "yes";
count++;
}
script.log(
"Dimension Access values found: " +
count
);
return {
values: selections,
count: count
};
}
function applyDimensionAccess(
groupName,
securityGroup,
dimensionSelections
) {
try {
securityGroup.setModelPermissions(
modelId,
WRITE_ACCESS
);
} catch (modelError) {
script.log(
"Unable to set model access: " +
groupName +
" / " +
String(modelError)
);
return;
}
var modelDimensions =
parseJson(
dimension.list()
);
var allowed = 0;
var denied = 0;
var unchanged = 0;
var failed = 0;
for (
var i = 0;
i < modelDimensions.length;
i++
) {
var dimensionDetails =
modelDimensions[i];
var dimensionName =
getName(
dimensionDetails
);
if (!dimensionName) {
continue;
}
/*
* Blank dimensions are not changed.
*
* Only explicit Yes and No values are applied.
*/
if (
dimensionSelections[dimensionName] ===
undefined
) {
unchanged++;
continue;
}
var dimensionId =
dimensionDetails.id ||
dimension.getId(
dimensionName
);
var access =
dimensionSelections[dimensionName]
? WRITE_ACCESS
: NO_ACCESS;
try {
securityGroup.setObjectPermissions(
modelName,
"DIMENSIONS",
dimensionId,
access
);
if (access === WRITE_ACCESS) {
allowed++;
} else {
denied++;
}
} catch (dimensionError) {
failed++;
script.log(
"Dimension access failed: " +
groupName +
" / " +
dimensionName +
" / " +
String(dimensionError)
);
}
}
script.log(
"Dimension Access summary for " +
groupName +
": " +
allowed +
" allowed, " +
denied +
" denied, " +
unchanged +
" blank/unchanged, " +
failed +
" failed"
);
}
function getOrCreateGroup(
groupName,
existingGroups
) {
if (groupName === ADMIN_GROUP) {
return null;
}
if (existingGroups[groupName]) {
return userSecurity.groups.get(
groupName
);
}
var securityGroup =
userSecurity.groups.create(
groupName
);
if (securityGroup) {
existingGroups[groupName] = true;
script.log(
"Security group created: " +
groupName
);
}
return securityGroup;
}
function getExistingGroups() {
var lookup = {};
var groups =
userSecurity.groups.list();
for (var groupId in groups) {
var group =
groups[groupId];
if (group && group.name) {
lookup[group.name] = true;
}
}
return lookup;
}
function parseJson(value) {
if (
value === null ||
value === undefined ||
value === ""
) {
return [];
}
if (typeof value === "string") {
return JSON.parse(value);
}
return value;
}
function getName(value) {
if (typeof value === "string") {
return value;
}
if (!value) {
return "";
}
return (
value.name ||
value.Name ||
value.identifier ||
""
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Dimension Access process finished."
);
}Model.Security.Execute Element Access
Applies which elements of a secured dimension each group can access.
Source (521 lines)
js
var SECURITY_CUBE = "Security";
var GROUP_DIMENSION = "Group";
var GROUP_HIERARCHY = "Default";
var GROUP_PARENT = "All Groups";
var ADMIN_GROUP = "Admin";
var READ_ACCESS = "READ_ACCESS";
var WRITE_ACCESS = "WRITE_ACCESS";
var NO_ACCESS = "NO_ACCESS";
var modelName;
function pre() {
script.log(
"Element Access process prepared."
);
}
function begin() {
script.log(
"Element Access process started."
);
modelName =
script.modelName();
var groupCount = Number(
hierarchy.childCount(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT
)
);
for (
var i = 0;
i < groupCount;
i++
) {
var groupName =
hierarchy.childByIndex(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT,
i
);
if (
!groupName ||
groupName === "No Group" ||
groupName === ADMIN_GROUP
) {
continue;
}
var securityGroup = null;
try {
securityGroup =
userSecurity.groups.get(
groupName
);
} catch (error) {
securityGroup = null;
}
if (!securityGroup) {
script.log(
"Security group not found: " +
groupName
);
continue;
}
applyElementAccess(
groupName,
securityGroup
);
}
}
function applyElementAccess(
groupName,
securityGroup
) {
var permissionMaps = {};
/*
* Previously saved permissions are set to
* No Access first. This removes permissions
* when a previously selected cell is cleared.
*/
loadElementAccess(
groupName,
"Element Access Saved",
permissionMaps,
true
);
/*
* Current selections override the saved baseline.
*/
loadElementAccess(
groupName,
"Element Access",
permissionMaps,
false
);
var applied = 0;
var failed = 0;
for (
var dimensionName in permissionMaps
) {
var hierarchyMaps =
permissionMaps[dimensionName];
for (
var hierarchyName in hierarchyMaps
) {
try {
securityGroup.setElementPermissions(
modelName,
dimensionName,
hierarchyName,
hierarchyMaps[
hierarchyName
]
);
applied++;
script.log(
"Element access applied: " +
groupName +
" / " +
dimensionName +
" / " +
hierarchyName
);
} catch (error) {
failed++;
script.log(
"Element access failed: " +
groupName +
" / " +
dimensionName +
" / " +
hierarchyName +
" / " +
String(error)
);
}
}
}
script.log(
"Element Access summary for " +
groupName +
": " +
applied +
" hierarchy maps applied, " +
failed +
" failed."
);
}
function loadElementAccess(
groupName,
measureName,
permissionMaps,
revokeOnly
) {
/*
* Security cube order:
*
* row[0] = Element
* row[1] = User
* row[2] = Group
* row[3] = Dimension
* row[4] = Screen
* row[5] = Security Measures
* row[6] = Value
*/
var securitySlice =
cube.slice(
SECURITY_CUBE,
[
"",
"No User",
groupName,
"",
"No Screen",
measureName
]
);
while (!securitySlice.EOF()) {
var row =
securitySlice.Next();
var securityElement =
String(
row[0] || ""
).trim();
var dimensionName =
String(
row[3] || ""
).trim();
if (
!securityElement ||
!dimensionName ||
!dimension.exists(
dimensionName
)
) {
continue;
}
var access =
revokeOnly
? NO_ACCESS
: getAccess(row[6]);
addElementPermission(
permissionMaps,
dimensionName,
securityElement,
access
);
}
}
/*
* Convert the Element dimension member back into
* its original dimension, hierarchy and element.
*/
function addElementPermission(
permissionMaps,
dimensionName,
securityElement,
access
) {
var dimensionPrefix =
dimensionName + " ";
/*
* Ignore elements belonging to another dimension.
*/
if (
securityElement.indexOf(
dimensionPrefix
) !== 0
) {
return;
}
/*
* Ignore the dimension wrapper.
*
* Example:
* Screen Parent
*/
if (
securityElement ===
dimensionName + " Parent"
) {
return;
}
var sourceHierarchies =
parseJson(
dimension.hierarchies(
dimensionName
)
);
for (
var i = 0;
i < sourceHierarchies.length;
i++
) {
var hierarchyName =
getName(
sourceHierarchies[i]
);
if (!hierarchyName) {
continue;
}
var hierarchyWrapper =
dimensionName +
" " +
hierarchyName;
/*
* Handle a hierarchy and member with the same
* name, such as:
*
* Screen > No Screen > No Screen
*/
if (
securityElement ===
hierarchyWrapper
) {
if (
hierarchy.hasMember(
dimensionName,
hierarchyName,
hierarchyName
)
) {
addToPermissionMap(
permissionMaps,
dimensionName,
hierarchyName,
hierarchyName,
access
);
}
return;
}
/*
* Handle parent/consolidation members.
*
* Example:
* Screen Default All Screens
*/
var parentPrefix =
hierarchyWrapper + " ";
if (
securityElement.indexOf(
parentPrefix
) === 0
) {
var originalParent =
securityElement.substring(
parentPrefix.length
);
addToPermissionMap(
permissionMaps,
dimensionName,
hierarchyName,
originalParent,
access
);
return;
}
}
/*
* Handle leaf members.
*
* Example:
* Screen lucki becomes lucki.
*/
var originalLeaf =
securityElement.substring(
dimensionPrefix.length
);
/*
* Apply a shared leaf to every hierarchy that
* contains it.
*/
for (
var h = 0;
h < sourceHierarchies.length;
h++
) {
var sourceHierarchy =
getName(
sourceHierarchies[h]
);
if (
sourceHierarchy &&
hierarchy.hasMember(
dimensionName,
sourceHierarchy,
originalLeaf
)
) {
addToPermissionMap(
permissionMaps,
dimensionName,
sourceHierarchy,
originalLeaf,
access
);
}
}
}
function addToPermissionMap(
permissionMaps,
dimensionName,
hierarchyName,
elementName,
access
) {
if (
!permissionMaps[
dimensionName
]
) {
permissionMaps[
dimensionName
] = {};
}
if (
!permissionMaps[
dimensionName
][hierarchyName]
) {
permissionMaps[
dimensionName
][hierarchyName] = {};
}
permissionMaps[
dimensionName
][hierarchyName][elementName] =
access;
}
/*
* Yes or Write grants Write Access.
* Read grants Read Access.
* No, blank or anything else grants No Access.
*/
function getAccess(cellValue) {
var value =
String(
cellValue || ""
)
.trim()
.toLowerCase();
if (
value === "yes" ||
value === "write"
) {
return WRITE_ACCESS;
}
if (value === "read") {
return READ_ACCESS;
}
return NO_ACCESS;
}
function parseJson(value) {
if (
value === null ||
value === undefined ||
value === ""
) {
return [];
}
if (typeof value === "string") {
return JSON.parse(value);
}
return value;
}
function getName(value) {
if (typeof value === "string") {
return value;
}
if (!value) {
return "";
}
return (
value.name ||
value.Name ||
value.identifier ||
value.id ||
""
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Element Access process finished."
);
}Model.Security.Screen Access Tags
Applies which screens each group can reach, written as access tags.
Source (429 lines)
js
var cubeName = "Security";
function pre() {
script.log(
"Screen access process prepared."
);
}
function begin() {
script.log(
"Screen access process started."
);
security.refresh();
var applications =
security.applications();
var groups =
userSecurity.groups.list();
var users =
getUsers();
for (
var a = 0;
a < applications.length;
a++
) {
var appId =
getValue(
applications[a],
"id"
);
var app =
security.application(appId);
if (!app) {
continue;
}
var screens =
app.screens.list();
var allTag =
getTag(
app,
"All"
);
/*
* All makes every screen visible.
*/
for (
var s = 0;
s < screens.length;
s++
) {
var screenName =
getValue(
screens[s],
"title"
);
if (screenName) {
allTag.set(
screenName,
true
);
}
}
for (var groupId in groups) {
var groupName =
getGroupName(
groups[groupId]
);
if (
!groupName ||
groupName === "No Group"
) {
continue;
}
/*
* Admin users receive All access.
*/
if (
groupName.toLowerCase() ===
"admin"
) {
assignAdmin(
app,
allTag,
users
);
continue;
}
configureGroup(
app,
allTag,
users,
groupName,
screens
);
}
}
script.log(
"Screen access process completed."
);
}
function configureGroup(
app,
allTag,
users,
groupName,
screens
) {
var groupTag =
getTag(
app,
groupName
);
groupTag.clear();
var validScreens = {};
for (
var i = 0;
i < screens.length;
i++
) {
var name =
getValue(
screens[i],
"title"
);
validScreens[
name.toLowerCase()
] = true;
}
var accessSlice =
cube.slice(
cubeName,
[
"No Element",
"No User",
groupName,
"No Dimension",
"",
"Screen Access"
]
);
var allowed = 0;
while (!accessSlice.EOF()) {
var row =
accessSlice.Next();
var screenName =
String(
row[4] || ""
).trim();
var value =
String(
row[6] || ""
)
.trim()
.toLowerCase();
if (
!validScreens[
screenName.toLowerCase()
] ||
(
value !== "yes" &&
value !== "no"
)
) {
continue;
}
var visible =
value === "yes";
groupTag.set(
screenName,
visible
);
if (visible) {
allowed++;
}
}
var limited =
allowed < screens.length;
for (
var u = 0;
u < users.length;
u++
) {
var user =
users[u];
try {
var belongs =
user.hasGroup(
groupName
);
if (belongs) {
/*
* Add the user as an application
* contributor first.
*/
if (
!app.users.exists(user)
) {
app.users.add(user);
}
/*
* Remove All from restricted users,
* except Admin users.
*/
if (
limited &&
!user.hasGroup("Admin") &&
allTag.hasUser(user)
) {
allTag.removeUser(user);
}
if (
!groupTag.hasUser(user)
) {
groupTag.addUser(user);
}
} else if (
groupTag.hasUser(user)
) {
groupTag.removeUser(user);
}
} catch (error) {
script.log(
"Unable to update user: " +
getEmail(user) +
" / " +
groupName
);
}
}
}
function assignAdmin(
app,
allTag,
users
) {
for (
var i = 0;
i < users.length;
i++
) {
var user =
users[i];
try {
if (!user.hasGroup("Admin")) {
continue;
}
if (!app.users.exists(user)) {
app.users.add(user);
}
if (!allTag.hasUser(user)) {
allTag.addUser(user);
}
} catch (error) {
script.log(
"Unable to update Admin user: " +
getEmail(user)
);
}
}
}
function getTag(
app,
tagName
) {
if (app.tags.exists(tagName)) {
return app.tags.get(tagName);
}
return app.tags.createScreen(
tagName
);
}
function getUsers() {
var records =
security.users();
if (typeof records === "string") {
records =
JSON.parse(records);
}
var users = [];
for (
var i = 0;
i < records.length;
i++
) {
var record =
records[i];
var user = null;
try {
user =
userSecurity.users.get(
record.id
);
} catch (error) {
try {
user =
userSecurity.users.getFromEmail(
record.email ||
record.username
);
} catch (emailError) {
user = null;
}
}
if (user) {
users.push(user);
}
}
return users;
}
function getGroupName(group) {
if (!group) {
return "";
}
if (
typeof group.getName ===
"function"
) {
return String(
group.getName() || ""
).trim();
}
return String(
group.name || ""
).trim();
}
function getValue(
object,
property
) {
if (!object) {
return "";
}
if (
typeof object[property] ===
"function"
) {
return String(
object[property]() || ""
).trim();
}
return String(
object[property] || ""
).trim();
}
function getEmail(user) {
try {
return user.getEmail();
} catch (error) {
return "";
}
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Screen access process finished."
);
}Save buttons
These are what the Save Changes button on each table calls. Each chains the execution and state processes for its own area, so one button press applies that table and records the result.
Model.Cube.Security.Save Dimension Access Changes
Chains Model.Dim.Element, then Execute Dimension Access, then Dimension Access Saved.
Source (26 lines)
js
function pre() {
// This function is called once before the processes is executed.
// Use this to setup prompts.
script.log('process pre-execution parameters parsed.');
}
function begin() {
// This function is called once at the start of the process
script.log('process execution started.');
process.execute("Model.Dim.Element")
process.execute("Model.Security.Execute Dimension Access")
process.execute("Model.Cube.Security.Dimension Access Saved")
}
function data(record) {
// This function is called once for each line of data on the second cycle
// Use this to build dimensions and push data into cubes
}
function end() {
// This function is called once at the end of the process
script.log('process execution finished.');
}Model.Cube.Security.Save Element Access Changes
Chains Execute Element Access, then Element Access Saved.
Source (24 lines)
js
function pre() {
// This function is called once before the processes is executed.
// Use this to setup prompts.
script.log('process pre-execution parameters parsed.');
}
function begin() {
// This function is called once at the start of the process
script.log('process execution started.');
process.execute("Model.Security.Execute Element Access")
process.execute("Model.Cube.Security.Element Access Saved")
}
function data(record) {
// This function is called once for each line of data on the second cycle
// Use this to build dimensions and push data into cubes
}
function end() {
// This function is called once at the end of the process
script.log('process execution finished.');
}Model.Cube.Security.Save Screen Access Changes
Chains Screen Access Tags, then Screen Access Saved.
Source (26 lines)
js
function pre() {}
function begin() {
script.log(
"Saving Screen Access changes."
);
process.execute(
"Model.Security.Screen Access Tags"
);
process.execute(
"Model.Cube.Security.Screen Access Saved"
);
}
function data(record) {}
function end() {
script.log(
"Screen Access changes saved."
);
}Model.Cube.Security.Save User Group Changes
Chains User Group Allocation, then User Groups Saved.
Source (39 lines)
js
function pre() {
script.log(
"Save User Group Changes prepared."
);
}
function begin() {
script.log(
"Saving User Group changes."
);
/*
* Real user IDs are allocated.
* Randomized IDs are skipped.
*/
process.execute(
"Model.Cube.Security.User Group Allocation"
);
/*
* All input values are then marked as saved.
*/
process.execute(
"Model.Cube.Security.User Groups Saved"
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Save User Group Changes finished."
);
}Saved state
These write back the committed state, which is how the card knows whether a table’s values have been applied - and therefore whether to show the "values in this table aren’t saved" prompt.
Model.Cube.Security.Dimension Access Saved
Records the committed dimension access.
Source (87 lines)
js
Model.Cube.Security.Dimension Access Saved
function pre() {
// This function is called once before the processes is executed.
// Use this to setup prompts.
script.log('process pre-execution parameters parsed.');
}
function begin() {
script.log(
"Process execution started."
);
/*
* Security cube order:
*
* row[0] = Element
* row[1] = User
* row[2] = Group
* row[3] = Dimension
* row[4] = Screen
* row[5] = Security Measures
* row[6] = Cell value
*/
var securitySlice = cube.slice(
"Security",
[
"No Element",
"No User",
"No Group",
"",
"No Screen",
"Dimension Access"
]
);
var valuesCopied = 0;
while (!securitySlice.EOF()) {
var row = securitySlice.Next();
var dimensionName = row[3];
var sourceValue = row[6];
if (!dimensionName) {
continue;
}
cube.set(
sourceValue,
"Security",
[
row[0],
row[1],
row[2],
dimensionName,
row[4],
"Dimension Access Saved"
]
);
valuesCopied++;
script.log(
"Copied Dimension Access: " +
dimensionName +
" / Value: " +
String(sourceValue)
);
}
script.log(
"Values copied: " +
valuesCopied
);
}
function data(record) {
// This function is called once for each line of data on the second cycle
// Use this to build dimensions and push data into cubes
}
function end() {
// This function is called once at the end of the process
script.log('process execution finished.');
}Model.Cube.Security.Element Access Saved
Records the committed element access.
Source (143 lines)
js
function pre() {
script.log(
"Element Access Saved process prepared."
);
}
function begin() {
script.log(
"Saving Element Access values."
);
/*
* Security cube order:
*
* row[0] = Element
* row[1] = User
* row[2] = Group
* row[3] = Dimension
* row[4] = Screen
* row[5] = Security Measures
* row[6] = Cell value
*/
/*
* Clear previous saved values.
*
* This ensures that a value removed from Element Access
* is also removed from Element Access Saved.
*/
var savedSlice = cube.slice(
"Security",
[
"",
"No User",
"",
"",
"No Screen",
"Element Access Saved"
]
);
var valuesCleared = 0;
while (!savedSlice.EOF()) {
var savedRow =
savedSlice.Next();
cube.set(
"",
"Security",
[
savedRow[0],
savedRow[1],
savedRow[2],
savedRow[3],
savedRow[4],
"Element Access Saved"
]
);
valuesCleared++;
}
/*
* Read the current Element Access values.
*/
var securitySlice = cube.slice(
"Security",
[
"",
"No User",
"",
"",
"No Screen",
"Element Access"
]
);
var valuesCopied = 0;
while (!securitySlice.EOF()) {
var row =
securitySlice.Next();
var elementName =
row[0];
var groupName =
row[2];
var dimensionName =
row[3];
var sourceValue =
row[6];
if (
!elementName ||
!groupName ||
!dimensionName
) {
continue;
}
cube.set(
sourceValue,
"Security",
[
elementName,
row[1],
groupName,
dimensionName,
row[4],
"Element Access Saved"
]
);
valuesCopied++;
}
script.log(
"Element Access Saved summary: " +
valuesCleared +
" old values cleared, " +
valuesCopied +
" values copied."
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Element Access Saved process finished."
);
}Model.Cube.Security.Screen Access Saved
Records the committed screen access.
Source (99 lines)
js
var CUBE = "Security";
var CURRENT = "Screen Access";
var SAVED = "Screen Access Saved";
function pre() {}
function begin() {
script.log(
"Saving Screen Access values."
);
/*
* Clear previously saved values so a cell that
* is now blank does not retain an old saved value.
*/
var oldValues = cube.slice(
CUBE,
[
"No Element",
"No User",
"",
"No Dimension",
"",
SAVED
]
);
while (!oldValues.EOF()) {
var oldRow =
oldValues.Next();
cube.set(
"",
CUBE,
[
oldRow[0],
oldRow[1],
oldRow[2],
oldRow[3],
oldRow[4],
SAVED
]
);
}
/*
* Copy current Screen Access values.
*/
var currentValues = cube.slice(
CUBE,
[
"No Element",
"No User",
"",
"No Dimension",
"",
CURRENT
]
);
var copied = 0;
while (!currentValues.EOF()) {
var row =
currentValues.Next();
cube.set(
row[6],
CUBE,
[
row[0],
row[1],
row[2],
row[3],
row[4],
SAVED
]
);
copied++;
}
script.log(
"Screen Access values saved: " +
copied
);
}
function data(record) {}
function end() {
script.log(
"Screen Access Saved finished."
);
}Model.Cube.Security.User Groups Saved
Records the committed user allocations.
Source (104 lines)
js
var SECURITY_CUBE =
"Security";
function pre() {
script.log(
"User Groups Saved prepared."
);
}
function begin() {
script.log(
"Updating User Groups Saved."
);
/*
* Clear the previous saved values.
*/
var savedSlice = cube.slice(
SECURITY_CUBE,
[
"No Element",
"",
"No Group",
"No Dimension",
"No Screen",
"User Groups Saved"
]
);
while (!savedSlice.EOF()) {
var savedRow =
savedSlice.Next();
cube.set(
"",
SECURITY_CUBE,
[
savedRow[0],
savedRow[1],
savedRow[2],
savedRow[3],
savedRow[4],
"User Groups Saved"
]
);
}
/*
* Copy every User Groups value, including rows
* belonging to randomized User IDs.
*/
var currentSlice = cube.slice(
SECURITY_CUBE,
[
"No Element",
"",
"No Group",
"No Dimension",
"No Screen",
"User Groups"
]
);
var valuesCopied = 0;
while (!currentSlice.EOF()) {
var row =
currentSlice.Next();
cube.set(
row[6],
SECURITY_CUBE,
[
row[0],
row[1],
row[2],
row[3],
row[4],
"User Groups Saved"
]
);
valuesCopied++;
}
script.log(
"User Groups values saved: " +
valuesCopied
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"User Groups Saved finished."
);
}Model.Cube.Security.User Group Allocation
Resolves users to their allocated groups.
Source (231 lines)
js
var SECURITY_CUBE =
"Security";
var CURRENT_MEASURE =
"User Groups";
var SAVED_MEASURE =
"User Groups Saved";
function pre() {
script.log(
"User Group Allocation prepared."
);
}
function begin() {
script.log(
"User Group Allocation started."
);
/*
* Build a lookup of real MODLR security user IDs.
*/
var securityUsers =
JSON.parse(
security.users()
);
var validUserIds = {};
for (
var i = 0;
i < securityUsers.length;
i++
) {
var securityUserId = String(
securityUsers[i].id || ""
).trim();
if (securityUserId) {
validUserIds[
securityUserId
] = true;
}
}
/*
* User Groups contains the selected group name.
*/
var allocationSlice = cube.slice(
SECURITY_CUBE,
[
"No Element",
"",
"No Group",
"No Dimension",
"No Screen",
CURRENT_MEASURE
]
);
var usersChanged = 0;
var usersUnchanged = 0;
var usersSkipped = 0;
var usersFailed = 0;
while (!allocationSlice.EOF()) {
var row =
allocationSlice.Next();
var userId = String(
row[1] || ""
).trim();
var newGroupName = String(
row[6] || ""
).trim();
if (
!userId ||
userId === "No User" ||
!newGroupName ||
newGroupName === "No Group"
) {
continue;
}
/*
* Skip randomized IDs which do not match a
* real MODLR security user.
*/
if (!validUserIds[userId]) {
usersSkipped++;
script.log(
"Skipped randomized User ID: " +
userId
);
continue;
}
var oldGroupName = String(
cube.get(
SECURITY_CUBE,
[
"No Element",
userId,
"No Group",
"No Dimension",
"No Screen",
SAVED_MEASURE
]
) || ""
).trim();
if (
newGroupName ===
oldGroupName
) {
usersUnchanged++;
continue;
}
var securityUser =
userSecurity.users.get(
userId
);
if (!securityUser) {
usersSkipped++;
continue;
}
var newGroup =
userSecurity.groups.get(
newGroupName
);
if (!newGroup) {
usersFailed++;
script.log(
"Security group not found: " +
newGroupName
);
continue;
}
try {
/*
* Remove only the previously saved group.
*/
if (
oldGroupName &&
oldGroupName !== "No Group" &&
oldGroupName !== newGroupName &&
securityUser.hasGroup(
oldGroupName
)
) {
securityUser.removeGroup(
oldGroupName
);
}
/*
* Add the newly selected group.
*/
if (
!securityUser.hasGroup(
newGroupName
)
) {
securityUser.addGroup(
newGroupName
);
}
usersChanged++;
script.log(
"User Group updated: " +
userId +
" / " +
(
oldGroupName ||
"No previous group"
) +
" -> " +
newGroupName
);
} catch (error) {
usersFailed++;
script.log(
"User Group update failed: " +
userId +
" / " +
String(error)
);
}
}
script.log(
"User Group summary: " +
usersChanged +
" changed, " +
usersUnchanged +
" unchanged, " +
usersSkipped +
" randomized IDs skipped, " +
usersFailed +
" failed."
);
}
function data(record) {
// Not required.
}
function end() {
script.log(
"User Group Allocation finished."
);
}User and group management
Called by the Manage Users and Manage Groups modals rather than by any table.
Model.Dim.User.Add User
Creates a user from the name and email entered in the modal.
Source (148 lines)
js
var usercreated = false;
function pre() {
script.prompt(
"Enter the new user's name",
"newusername",
""
);
script.prompt(
"Enter the new user's email",
"newuseremail",
""
);
}
function begin() {
script.log(
"Add User process started."
);
var username = String(
typeof newusername !== "undefined"
? newusername
: ""
).trim();
var useremail = String(
typeof newuseremail !== "undefined"
? newuseremail
: ""
)
.trim()
.toLowerCase();
/*
* Validate the user name.
*/
if (!username) {
script.abort(
"No user name was received."
);
return;
}
/*
* Validate the email address.
*/
if (
!useremail ||
useremail.indexOf("@") === -1
) {
script.abort(
"No valid user email was received."
);
return;
}
/*
* Refresh the security cache before checking
* whether the user already exists.
*/
security.refresh();
if (finduserbyemail(useremail)) {
script.abort(
"A user already exists with this email: " +
useremail
);
return;
}
/*
* Create the actual MODLR user.
*/
try {
security.createUser(
username,
useremail,
"",
true
);
usercreated = true;
} catch (createerror) {
script.abort(
"Unable to create MODLR user: " +
String(createerror)
);
return;
}
script.log(
"MODLR user created: " +
username +
" / " +
useremail
);
/*
* Refresh the security cache after creating
* the user.
*/
security.refresh();
}
function finduserbyemail(useremail) {
try {
return (
userSecurity.users.getFromEmail(
useremail
) || null
);
} catch (lookuperror) {
return null;
}
}
function data(record) {
// Not required.
}
function end() {
if (usercreated) {
security.refresh();
script.log(
"Refreshing User dimension."
);
process.execute(
"Model.Dim.User"
);
}
script.log(
"Add User process finished."
);
}Model.Dim.User.Delete User
Removes a user.
Source (241 lines)
js
var userdimension = "User";
var userhierarchy = "Default";
var userparent = "All Users";
var protectedemail =
"emilywhitie+1@modlr.co";
function pre() {
/*
* The row button supplies the underlying
* User dimension element.
*/
script.prompt(
"User to delete",
"usertodelete",
""
);
}
function begin() {
script.log(
"Delete User process started."
);
var userid = String(
typeof usertodelete !== "undefined"
? usertodelete
: ""
).trim();
/*
* Validate the value supplied by the button.
*/
if (
!userid ||
userid.toLowerCase() === "null"
) {
script.abort(
"No valid user was supplied."
);
return;
}
/*
* Never delete structural User members.
*/
var protectedid =
userid.toLowerCase();
if (
protectedid === "all users" ||
protectedid === "no user"
) {
script.abort(
"Protected User member cannot be deleted: " +
userid
);
return;
}
/*
* Confirm the selected element is a direct
* child of All Users.
*/
if (!isuserdimensionchild(userid)) {
script.abort(
"User is not beneath All Users: " +
userid
);
return;
}
/*
* Find the matching real MODLR user.
*/
var users = JSON.parse(
security.users()
);
var securityuser = null;
for (
var i = 0;
i < users.length;
i++
) {
var currentid = String(
users[i].id || ""
).trim();
if (currentid === userid) {
securityuser = users[i];
break;
}
}
/*
* Remove the real MODLR user from the model
* before deleting their dimension element.
*/
if (securityuser) {
var useremail = String(
securityuser.email ||
securityuser.username ||
""
)
.trim()
.toLowerCase();
var username = String(
securityuser.name || userid
).trim();
var securitydeleted;
try {
securitydeleted =
security.removeUser(
userid
);
} catch (securityerror) {
script.abort(
"Unable to remove MODLR user: " +
username +
" / " +
String(securityerror)
);
return;
}
/*
* Some MODLR versions return no value after
* successfully removing the user. Only an
* explicit false is treated as a failure.
*/
if (securitydeleted === false) {
script.abort(
"MODLR user could not be removed: " +
username
);
return;
}
script.log(
"MODLR user removed from model: " +
username +
" / " +
useremail
);
} else {
/*
* The element can still be removed when its
* corresponding MODLR user no longer exists.
*/
script.log(
"MODLR user not found. Removing stale " +
"User dimension member: " +
userid
);
}
/*
* Delete the User dimension element.
*/
try {
element.delete(
userdimension,
userid
);
script.log(
"User dimension member deleted: " +
userid
);
} catch (dimensionerror) {
script.abort(
"The MODLR user was removed, but the User " +
"dimension member could not be deleted: " +
userid +
" / " +
String(dimensionerror)
);
return;
}
script.log(
"User deleted successfully: " +
userid
);
}
function isuserdimensionchild(
userid
) {
var childcount = Number(
hierarchy.childCount(
userdimension,
userhierarchy,
userparent
)
);
for (
var i = 0;
i < childcount;
i++
) {
var childid =
hierarchy.childByIndex(
userdimension,
userhierarchy,
userparent,
i
);
if (
String(childid) ===
String(userid)
) {
return true;
}
}
return false;
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Delete User process finished."
);
}Model.Dim.Group.Add Group
Creates a group from the name entered in the modal.
Source (207 lines)
js
var groupdimension = "Group";
var grouphierarchy = "Default";
var groupparent = "All Groups";
function pre() {
script.prompt(
"New group name",
"newgroup",
""
);
}
function begin() {
script.log(
"Create User Group process started."
);
var groupname = String(
typeof newgroup !== "undefined"
? newgroup
: ""
).trim();
if (!groupname) {
script.abort(
"Please enter a group name."
);
return;
}
var lowergroupname =
groupname.toLowerCase();
/*
* Prevent protected and structural names.
*/
if (
lowergroupname === "admin" ||
lowergroupname === "no group" ||
lowergroupname === "all groups"
) {
script.abort(
"This group name cannot be used: " +
groupname
);
return;
}
/*
* Create missing model structures.
*/
if (
!dimension.exists(
groupdimension
)
) {
dimension.create(
groupdimension,
"Standard"
);
script.log(
"Group dimension created."
);
}
if (
!hierarchy.exists(
groupdimension,
grouphierarchy
)
) {
hierarchy.create(
groupdimension,
grouphierarchy
);
script.log(
"Group Default hierarchy created."
);
}
/*
* Create the real model security group.
*/
if (
!securitygroupexists(
groupname
)
) {
var securitygroup;
try {
securitygroup =
userSecurity.groups.create(
groupname
);
} catch (securityerror) {
script.abort(
"Unable to create model security group: " +
groupname +
" / " +
String(securityerror)
);
return;
}
if (!securitygroup) {
script.abort(
"Unable to create model security group: " +
groupname
);
return;
}
script.log(
"Model security group created: " +
groupname
);
} else {
script.log(
"Model security group already exists: " +
groupname
);
}
/*
* Add the group beneath All Groups.
*
* If All Groups does not exist, this first
* relationship creates it as the parent.
*
* Calling this for an existing relationship
* is safe and also reconnects an orphaned group.
*/
try {
hierarchy.group(
groupdimension,
grouphierarchy,
groupparent,
groupname,
1
);
} catch (hierarchyerror) {
script.abort(
"Security group was created, but the Group " +
"dimension could not be updated: " +
groupname +
" / " +
String(hierarchyerror)
);
return;
}
script.log(
"Group dimension member placed beneath " +
groupparent +
": " +
groupname
);
script.log(
"User group ready: " +
groupname
);
}
function securitygroupexists(
groupname
) {
var groups =
userSecurity.groups.list();
var targetname =
groupname.toLowerCase();
for (var groupid in groups) {
var group =
groups[groupid];
if (
group &&
String(group.name || "")
.trim()
.toLowerCase() === targetname
) {
return true;
}
}
return false;
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Create User Group process finished."
);
}Model.Dim.Group.Delete Group
Removes a group, unallocating its members.
Source (308 lines)
js
var GROUP_DIMENSION = "Group";
var GROUP_HIERARCHY = "Default";
var GROUP_PARENT = "All Groups";
var SECURITY_CUBE = "Security";
function pre() {
script.prompt(
"Group to delete",
"grouptodelete",
""
);
}
function begin() {
script.log(
"Delete Group process started."
);
if (
typeof grouptodelete === "undefined" ||
grouptodelete === null ||
grouptodelete === ""
) {
return;
}
var groupName = String(
grouptodelete
).trim();
if (
!groupName ||
groupName.toLowerCase() === "null"
) {
script.abort(
"No valid group was supplied."
);
return;
}
/*
* Protect structural and administrative groups.
*/
var protectedName =
groupName.toLowerCase();
if (
protectedName === "admin" ||
protectedName === "no group" ||
protectedName === "all groups"
) {
script.abort(
"Protected group cannot be deleted: " +
groupName
);
return;
}
if (!isGroupDimensionChild(groupName)) {
script.abort(
"Group is not beneath All Groups: " +
groupName
);
return;
}
/*
* Clear allocations before deleting the group.
*/
var currentCleared =
clearGroupValues(
groupName,
"User Groups"
);
var savedCleared =
clearGroupValues(
groupName,
"User Groups Saved"
);
script.log(
"Group allocations cleared: " +
currentCleared +
" current and " +
savedCleared +
" saved."
);
/*
* Delete the actual model security group.
*/
if (securityGroupExists(groupName)) {
var securityDeleted = false;
try {
securityDeleted =
userSecurity.groups.delete(
groupName
);
} catch (securityError) {
script.abort(
"Unable to delete model security group: " +
groupName +
" / " +
String(securityError)
);
return;
}
if (!securityDeleted) {
script.abort(
"Model security group could not be deleted: " +
groupName
);
return;
}
script.log(
"Model security group deleted: " +
groupName
);
} else {
script.log(
"Model security group did not exist: " +
groupName
);
}
/*
* Delete the Group dimension element.
*/
try {
element.delete(
GROUP_DIMENSION,
groupName
);
script.log(
"Group dimension member deleted: " +
groupName
);
} catch (dimensionError) {
script.abort(
"Security group was deleted, but the Group " +
"dimension member could not be deleted: " +
groupName +
" / " +
String(dimensionError)
);
return;
}
script.log(
"Group deleted successfully: " +
groupName
);
}
/*
* Clears cells containing the selected group name.
*
* Security cube order:
*
* row[0] = Element
* row[1] = User
* row[2] = Group
* row[3] = Dimension
* row[4] = Screen
* row[5] = Security Measures
* row[6] = Value
*/
function clearGroupValues(
groupName,
measureName
) {
var securitySlice =
cube.slice(
SECURITY_CUBE,
[
"No Element",
"",
"No Group",
"No Dimension",
"No Screen",
measureName
]
);
var valuesCleared = 0;
var targetName =
groupName.toLowerCase();
while (!securitySlice.EOF()) {
var row =
securitySlice.Next();
var cellValue = String(
row[6] || ""
).trim();
if (
cellValue.toLowerCase() !==
targetName
) {
continue;
}
cube.set(
"",
SECURITY_CUBE,
[
row[0],
row[1],
row[2],
row[3],
row[4],
measureName
]
);
valuesCleared++;
}
return valuesCleared;
}
function isGroupDimensionChild(
groupName
) {
var childCount = Number(
hierarchy.childCount(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT
)
);
for (
var i = 0;
i < childCount;
i++
) {
var childName =
hierarchy.childByIndex(
GROUP_DIMENSION,
GROUP_HIERARCHY,
GROUP_PARENT,
i
);
if (childName === groupName) {
return true;
}
}
return false;
}
function securityGroupExists(
groupName
) {
var groups =
userSecurity.groups.list();
var targetName =
groupName.toLowerCase();
for (var groupId in groups) {
var group =
groups[groupId];
if (
group &&
String(group.name || "")
.trim()
.toLowerCase() === targetName
) {
return true;
}
}
return false;
}
function data(record) {
// Not required.
}
function end() {
script.log(
"Delete Group process finished."
);
}Related
- Security Package - installing and using the package
- Process Functions - the function reference these processes are built on
- Creating Processes - how scripted processes work generally