Live Security: examples of advanced use
- Defining the Live Security function
- Live Security to prevent a measure from being displayed
- Applying Live Security to a specific chart
- Live Security using a different cube
- Checking whether a dimension exists in a data model
- Find out more...
The Live Security feature controls access to dashboard data based on the user’s profile. It filters the visible dimensions, measures and members in real time, according to rules defined by the administrator.
Configuration can be carried out:
- Via the graphical wizard: associates dimensions with user settings (LDAP or platform).
- Via a JavaScript script: enables advanced logic such as conditional filtering, hiding measures or retrieving permissions from another cube.
Here we will look at some examples of advanced use of Live Security.
Defining the Live Security function
💡 See the section Securing with Live Security for more details on how Live Security works and how to set it up.
The Live Security function, also known as the selection transformation function, can be configured whilst editing a data model, during the advanced configuration stage of the data model, in the Advanced tab. You can choose an existing selection transformation function, or create a new one manually for functions using JavaScript code, or via a wizard.
Using the wizard allows you to associate dimensions with user parameters. Consequently, any selection on a cube will force the configured dimensions to be filtered according to the values of the user parameters.
The manual approach, using a JavaScript script, allows for greater flexibility. The following example shows how to create an advanced selection transformation function, which can be modified to add secure dimensions (make changes between /* START CONFIG */ and /* END CONFIG */):
var securedDims = {};
securedDims["DIMENSION_NAME_1"] = getUserAttribute("USER_PARAM_1");
/* Ajouter ici les autres dimensions sur lequel du live security doit s’appliquer */
/*
securedDims["DIMENSION_NAME_2"] = getUserAttribute("USER_PARAM_2");
*/
/* END CONFIG */
/* MODIFY THE SCRIPT BELOW WITH CAUTION */
var sLogPrefix = "[LIVE_SECURITY] [live-sec-thread-" + Math.floor(Math.random()*16777215).toString(16) + "]"; /* 16777215 is FFFFFF in decimal */
for (var dimId in securedDims)
{
var dim = selection.dm.objName[dimId];
var persoVal = securedDims[dimId];
//Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + "Applying user filters on: " + dimId + ", persoVal:" + persoVal);
if (dim)
{
if (persoVal == null || persoVal.length == 0)
{
// user must no see any value
var persoValuesTab = ["-noval-"];
var filt = new FilterSelection(dim, -1, -1, [], persoValuesTab);
selection.setFilter(filt);
Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + " Filters (members) on " + dimId + ": [" + persoValuesTab + "]");
}
else if (persoVal && persoVal != ".*")
{
//user is limited to some value(s)
var persoValuesTab = persoVal.split("|");
Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + " Applying user filters on " + dimId + ": [" + persoValuesTab + "]");
var exFilter = selection.filterByDimName[dimId];
if (!exFilter)
{
//there is no exisitng filter on that dimension => create a new one
var filt = new FilterSelection(dim, -1, -1, [], persoValuesTab);
selection.setFilter(filt);
Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + " Resolved filters (members) on " + dimId + ": [" + persoValuesTab + "]");
}
else
{
//there is already a filter on that dimension => merge (intersect) into a new one
var filt = new FilterSelection(dim, -1, -1, [], persoValuesTab);
filt.recalcIds();
exFilter.recalcIds();
exFilter = mergeFilters(exFilter, filt);
origIdsTab = exFilter.origIds;
selection.setFilter(exFilter);
Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + " Resolved filters (origIds) on " + dimId + ": [" + exFilter.origIds + "]");
}
}
else // perso value is .*
{
// do nothing, user can see everything
Packages.com.digdash.utils.MessageStack.getInstance().addDebug(sLogPrefix + " Resolved filters on " + dimId + ": All (.*)");
}
}
else
{
Packages.com.digdash.utils.MessageStack.getInstance().addError(sLogPrefix + " Dimension " + dimId + "not found");
}
}
User parameter
You must create a user parameter associated with each dimension to be secured (for example,USER_PARAM_1)and assign it to the relevant users.
In the example below, the user will be authorised to view rows in theDIMENSION_NAME_1dimension containing the valuesA, B or C.
See the section Add a user parameter for further details.

Live Security to prevent a measure from being displayed
An advanced use of Live Security allows measures to be hidden based on the user’s profile. In the following example, users have a ‘show_measure’parameter that can have a value of ‘yes’ or ‘no’:
var measureId = 'CA'; // id of the measure to hide
if (show_measure == 'non')
{
var measIndex = selection.indexOfMeasure(measureId);
if (measIndex != -1)
selection.removeMeasure(measIndex);
}
Applying Live Security to a specific chart
Live Security can also be tailored to a specific chart:
{
//Ajouter le code ici pour transformer la sélection de manière spécifique au flux
}
else
{
//Ajouter le code ici pour tous les autres flux
}
Live Security using a different cube
The most advanced example involves retrieving filter values from another cube rather than from user settings stored in LDAP.
Explanation of the scenario
The ‘base’ data is a table of cash flow transactions.
There is a ‘SECURITY_CODE’ field which is used to determine whether a logged-in user has the right to view the transaction or not.
There is another table, known as the ‘confidentiality’ table, which contains the users’ LOGIN details, belonging to groups with GROUP_ID (n-n relationship). It also contains the ‘SECURITY_CODE’ values associated with several groups.
These SECURITY_CODE values cannot be stored in an LDAP user variable as there are many possible values, and this information must remain in the database as it changes frequently.
Live Security Solution
The solution is therefore not to perform the join, but simply to add a Live Security function to the transactions cube, which will ‘fetch’ the ‘SECURITY_CODE’ values in real time from the confidentiality cube based on the logged-in user and apply them as a filter.
This way, the size of the final cube does not increase; the flattening process is very fast, and only 8GB of RAM is required on the server, whereas Tableau needed at least 32GB for slow loads.
Here is the Live Security script code for this example (to be adapted to your specific situation, between /* START CONFIG */ and /* END CONFIG */):
var cubeId = "id_cube_authorisations";
var userAttr = getUserAttribute("USER_LOGIN_SSO");
var dimSecurityCode = "SECURITY_CODE";
var dimUser = "LOGIN";
/* END CONFIG */
var sLogPrefix = "[SECURITY] [live-sec-thread-" + Math.floor(Math.random()*16777215).toString(16) + "]";
Packages.com.digdash.utils.MessageStack.getInstance().addText(sLogPrefix + " start...");
DimBean = function(id)
{
this.id = id;
this.members = [];
}
DimBean.prototype.toJSON = function()
{
return this.id;
}
Packages.com.digdash.utils.MessageStack.getInstance().addText(sLogPrefix + " user is " + userAttr);
var sel = new DataModelSelection();
sel.dm = { "variables":{} };
sel.pivot = 0;
sel.addBrowse(1, new DimBean(dimSecurityCode), -1, -1, null);
sel.addFilter(new FilterSelection(new DimBean(dimUser), -1, -1, [], [userAttr]));
var resultJSON = Packages.com.digdash.utils.ResultCubeToJavascript.getResultCubeLiveJSON(sessionId, JSON.stringify(sel), cubeId, null);
var cubRes = null;
eval("cubRes = " + resultJSON);
var resAxis = cubRes.axis[1];
var mbr = [];
for (var i = 0; i < resAxis.length; ++i)
{
mbr.push(resAxis[i].i);
}
if (mbr.length > 0)
{
var dim = selection.dm.getDimensionById(dimSecurityCode);
var filt = new FilterSelection(dim, -1, -1, [], mbr);
selection.setFilter(filt);
Packages.com.digdash.utils.MessageStack.getInstance().addText(sLogPrefix + " Dimension " + dimSecurityCode + " filtered on " + mbr);
}
else
{
Packages.com.digdash.utils.MessageStack.getInstance().addText(sLogPrefix + " No " + dimSecurityCode + " found for user " + userAttr);
}
Caching the result of the filtered cube
To speed up queries, the result of an already filtered cube can be cached. This allows subsequent calls to be served almost instantly.
To do this, simply add the following comment at the start of the script:
[
{kind:"input", type:"cube", id:"Cube_id"},
{kind:"input", type:"user", id:"uid"},
{kind:"output", type:"filter", id:"Dimension_id"}
]
@*/
The format in the comment is a JSON array.
It can contain as many lines as necessary.
Each line is an object with the following properties:
- kind:"input" : this is one of the parameters used as a key for the cache.
There are two types:- type:"cube"
id corresponds to the cube identifier used for security
Complete example: {kind:"input", type:"cube", id:"510d965087fe98d2e764981843219533"} - type:"user"
id then corresponds to a user attribute
Full example: {kind:"input", type:"user", id:"cn"}
- type:"cube"
- kind:"output": this is the element to be stored in the cache
type:"filter"
id corresponds to the identifier of the dimension to be filtered
Full example: {kind:"output", type:"filter", id:"Family"}
When the same user queries the same cube again using the same parameters, the server retrieves the result directly from the cache without performing any additional calculations, provided the cube remains in memory.
This reduces response times and server load.
Checking whether a dimension exists in a data model
In some cases, it may be useful to check whether a dimension is present in the data model, for example if the Live Security script is to be reused in a dependent model (union, column transformer, etc.)
{
Packages.com.digdash.utils.MessageStack.getInstance().addError("LaDimensionObligatoire n'est pas dans le modèle de données");
//dans cet exemple on choisit de faire échouer le Live Security si la dimension est absente du modèle
throw new Error("LaDimensionObligatoire n'est pas dans le modèle de données");
}
//ajouter un filtre sur LaDimensionObligatoire...