Use the record permission DSL (Domain Specific Language) to specify access rules on records of a given table.
The main goals of the record permission DSL are to not require Java coding, and to be easy to use by people without deep programming knowledge.
You can specify permission on any table using a script. The script consists of a sequence of if then else and return statements that indicate the permission for a record.
You can edit the script using the Data Model Assistant (DMA).
The script has following structure:
begin <statement 1> <statement 2> ... <last statement> end
All statements except the last one must be an "if then" or an "if then else" statement.
The last statement can be an "if then", "if then else" or "return" statement.
Example:
if isMember(administrator) then // Administrator has no restrictions. return readWrite; if isMember('french-team') and record.Country='F' then //Members of 'french-team' can view and modify data for France. return readWrite; if isMember('us-team') and record.Country='US' then //Members of 'us-team' can view and modify data for US. return readWrite; // This statement is not actually needed as 'hidden' is the default permission. return hidden;
The Unicode character set is supported.
The DSL is case-sensitive.
A single line comment extends from // to the end of the current line:
// This is a comment if record.LastName = 'Doe' then // This is another comment. return readOnly;
A multi-line comment extends from /* and ends with */:
/* This is a sample of a multi-line comment */ if record.isActive then return readWrite;
There are two types of keyword:
Reserved keywords are: if, then, else, begin, end, return, null, and, or, not, true, false.
Unreserved keywords are all other keywords defined by the DSL.
The main difference between the two types of keywords is that unreserved ones, but not reserved ones, can be used as plain (unquoted) identifiers.
An unquoted identifier is an unlimited-length sequence of letters, digits or underscore (_). The first character must be a letter or an underscore.
Valid letters are a to z and A to Z. Valid digits are 0 to 9.
An unquoted identifier cannot be equal to a reserved keyword.
A quoted identifier is an unlimited length of any Unicode character except a double quote (").
Quoted identifiers must be used surrounded by double quotes.
An unquoted identifier can be used surrounded by double quotes.This means that identifier "a_name" is equal to a_name.
Quoted identifiers can be reserved keywords.
The following types are supported:
Type | EBX® corresponding types |
---|---|
Boolean | xs:boolean |
Decimal | xs:decimal xs:int xs:integer |
String | xs:string xs:anyURI xs:Name osd:text osd:html osd:email osd:password osd:color osd:resource osd:locale osd:dataspaceKey osd:datasetName |
Timestamp | xs:dateTime |
Date | xs:date |
Time | xs:time |
String literals can be any sequence of Unicode characters surrounded by single quotes. The following table displays characters that need to be replaced by an escape sequence:
Character | Escape sequence |
---|---|
Tab | \t |
Backspace | \b |
New line | \n |
Carriage return | \r |
Form feed | \f |
Single quote | \' |
Backlash | \\ |
A character can be specified by a Unicode escape sequence that has the format \uXXXX with XXXX the hexadecimal code of the Unicode character.
Examples
Value | Syntax |
---|---|
O’Harra | 'O\'Harra' |
Noël | 'No\u00EBl' |
été | '\u00e9\u00E9' |
Note
An invalid escape or Unicode sequence generates an error at compile time.
The following decimal formats are supported:
Format | Examples |
---|---|
Integer | 546 -67 |
Floating point | 54.987 -433.876 0.00054 -0.0032 |
Exponent notation | 34.654e-5 -45E+65 1.543e23 |
Timestamp literals have the format dt(yyyy-MM-dd hh:mm:ss.sss).
Seconds are optional. When seconds are not specified, 0 is assumed. Seconds can have fractions up to millisecond precision.
The dates that are not valid in the Gregorian calendar generate an error at compile time.
Examples:
dt(2010-01-02 00:00:00.000) dt(2019-2-3 12:56:7) dt(2019-2-3 12:56:7.5) dt(2019-5-7 1:6)
Date literals have format d(yyyy-MM-dd).
The dates that are not valid in the Gregorian calendar generate an error at compile time.
Examples:
d(2010-01-02) d(2019-2-3) dt(2019-5-7)
Time literals have the format t(hh:mm:ss.sss).
Seconds are optional. When seconds arenot specified, 0 is assumed. Seconds can have fractions up to millisecond precision.
Invalid times generate an error at compile time.
Examples:
t(00:00:00) t(12:56:7) t(12:56:7.5) t(1:6)
Boolean literals are true and false.
Only access to optimized/indexed table field is supported. Dot notation is used to access tables fields. For example, a condition of current table fields whose path is ./OfficeAddress/City would be:
if record.OfficeAddress.City = 'Paris' then return readWrite;
The alias record always refers to the current record. Depending on context, other aliases might be available.
Each step (parts separated by a dot) is an identifier. This means that the following quoted notation can be used for any step:
if record."OfficeAddress".City = 'Paris' then return readWrite;
This is useful for steps equal to a reserved keyword or using characters, such as the minus character (-) or dot (.), which are not compatible with unquoted identifiers.
At runtime, any step can evaluate to null. In this case, the full field expression evaluates to null.
You can access foreign tables by "following" foreign keys using dot notation.
In the following example, the field Supervisor is a foreign key:
if record.Supervisor.Name = 'John Doe' then return readWrite;
There can be multiple levels of foreign keys, such as in the following example:
if record.Supervisor.Supervisor.Supervisor.Name = 'John Doe' then return readOnly;
Multi-valued fields are not supported.
Aggregate functions can take as input expressions based on an association. In the following example, the field ManagedUsers is an association:
// All users that manage at least 2 persons have read write access. if count(record.ManagedUsers[]) >= 2 then return readWrite;
You can apply a filter on an association. You must use an alias to access fields from the association. An association alias is declared using :. In the following example, the alias is u1:
// All users that manage at least one person whose office is in Briton has read only access. if exists(record.ManagedUsers:u1[u1.OfficeAddress.City='Briton']) then return readOnly;
A filter on an association can reference a field of the current record:
// All users that manage at least one person whose office is in the same city as user has read only access. if exists(record.ManagedUsers:t1[t1.OfficeAddress.City=record.OfficeAddress.City]) then return readOnly;
Note
Currently, it is not possible to:
Use an aggregate function in between [].
Select fields of an association to aggregate values.
By default, operation evaluation order is based on precedence and associativity. The order of evaluation can be indicated explicitly by using parentheses.
The following table shows all operators from highest to lowest precedence and their associativity:
Precedence Level | Operator | Operand type | Result type | Associativity |
---|---|---|---|---|
8 | [] (access to an element of a list) . (access to fields) () (parenthesis) | List index must be a decimal. | Can be any type. | Left to right. |
7 | not | Boolean. | Boolean. | |
6 | * / | Decimal. | Decimal. | Left to right. |
5 | + - | Decimal. | Decimal. | Left to right. |
4 | < <= > >= | String, Decimal, timestamp, date, time (3). | Boolean. | Not associative. |
3 | = <> | String, decimal, timestamp, date, time, boolean (3). | Boolean. | |
2 | and | Boolean. | Boolean. | Left to right. |
1 | or | Boolean. | Boolean. | Left to right. |
An arithmetic operator (*, /, + and -) returns null if any operand is null.
A Comparison operator (<, <=, >, =>, = and <>) returns null if any operand is null.
Boolean operators use thread-value logic.
Truth table for and is:
And | true | false | null |
true | true | false | null |
false | false | false | false |
null | null | false | null |
Truth table for or is:
Or | true | false | null |
true | true | true | true |
false | true | false | null |
null | true | null | null |
An indexed expression with an index that evaluates null or is out of range returns null.
Functions usually return null if a parameter is null. An exception is the function isNull(value), which never returns null.
An "if then" statement has the following syntax:
if condition-expression then then-body-statement
The condition expression must evaluate to a boolean type.
If the expression evaluates to true, the "then" statement is executed. If the expression evaluates to false or null, the "then" statement is ignored.
The 'then' statement is a body statement.
An "if then else" statement has the following syntax:
if condition-expression then then-body-statement else else-body-statement
The condition expression must be of boolean type.
If the expression evaluates to true, then the "then" statement is executed. If the expression evaluates to false or null, then the "else" statement is executed.
A "then" or "else" statement is a body statement.
Note
The expression:
if condition-expression then statement-a; else statement-b;
might not be equivalent to:
if not condition-expression then statement-b; else statement-a;
Indeed, if the expression is null, then the "else" statement is executed in both cases.
A body statement can be:
An "if then" or "if then else" statement,
A statement block.
A statement block is used to group multiple statements. It starts with the keyword begin and ends with the keyword end.
begin <statement 1> <statement 2> ... <last statement> end
All statements except last one must be an "if then" or an "if then else" statement.
The last statement can be a "if then", "if then else" or a "return" statement.
if isMember('sales-team')then begin if record.Country='F' then return readWrite; if record.Country='UK' then return readOnly; end else begin if record.Country='D' then return readOnly; if record.Country='B' then return readWrite; return hidden; end
A return statement specifies access to records that meet given conditions.
The following table shows valid return statements.
Return statement | Description |
---|---|
return hidden; | The record is hidden (the user has no access). |
return readOnly; | The record is read only for the current user. |
return readWrite; | The record can be read and modified by the current user. |
If no return statement applies to a given record, the value hidden is assumed.
Record permissions can depend on the current dataspace, dataset, or session.
The predefined alias dataspace provides access to information on the current dataspace.
This alias gives access to the following fields:
Name | Type | Description |
---|---|---|
name | string | The name of the dataspace. Because the dataspace namespace and the snapshot namespace are independent, the returned string is only an identifier in the context of one of the namespaces. For a global dataspace or snapshot identifier, use the field id. |
id | string | The persistent identifier of a dataspace or snapshot. Compared to name, this identifier additionally specifies whether this id is for a dataspace or a snapshot. |
isSnapshot | boolean | Is true if dataspace is a snapshot and false if dataspace is a branch. |
Example:
if dataspace.name = 'branch-R' then return readOnly;
The predefined alias dataset provides access to information on the current dataset.
This alias has the following fields:
Name | Type | Description |
---|---|---|
name | string | The name of the dataset. |
Example:
if dataset.name = 'TEST' then return readWrite;
The predefined alias session provides access to information on the current user session.
This alias has the following fields:
Name | Type | Description |
---|---|---|
userId | string | The current user’s id. |
userEmail | string | The current user’s email. |
To check current user’s roles, see Roles.
Example:
if session.userId = 'jdoe' then return readWrite;
The following table describes isMember() function:
Syntax | Description |
---|---|
isMember('rolea', 'roleb'…) | Returns true if the current user has at least one of the specified roles. The following built-in roles must be specified without quotes: administrator, readOnly, everyone. Custom roles are specified as string literals. Specifying a role that does not exist is not an error. (It is considered as a role with no members.) Note The roles administrator and 'administrator' can be two distinct roles. This means that isMember(administrator) might not return the same result as is_member('administrator'). The same rule applies for other built in roles. |
Example:
if isMember('SALES', 'SUPPORT') then return readWrite; // Role administrator and not surrounded by quotes: if isMember(administrator, 'SUPPORT') then return readOnly;
A string matching function takes three parameters:
A stringExpression that evaluates to a string.
A pattern that must be a string literal.
An optional boolean literal isCaseSensitive that indicates if matching is case-sensitive. If omitted, it is assumed that matching is case-insensitive.
Syntax | Description |
---|---|
matches(stringExpression, pattern, isCaseSensitive) | Returns true if stringExpression matches a java regular expression pattern. |
startsWith(stringExpression, pattern, isCaseSensitive) | Returns true if stringExpression starts with string pattern. |
endsWith(stringExpression, pattern, isCaseSensitive) | Returns true if stringExpression ends with string pattern. |
contains(stringExpression, pattern, isCaseSensitive) | Returns true if stringExpression contains a string pattern. |
containsWholeWord(stringExpression, pattern, isCaseSensitive) | Returns true if stringExpression contains a whole word pattern. |
Examples:
// Give read write access if first name starts with 'a', 'b' or 'c' (case-sensitive). if matches(record.FirstName, '[a-c].*', true) then return readWrite; // Give read only access if first name starts with 'Lé' (case-insensitive). if startsWith(record.FirstName, 'Lé') then return readOnly; // Give read only access if first name ends with 'my' (case-insensitive). if endsWith(record.FirstName, 'my') then return readOnly; // Give read only access if email contains with 'BeauMont@' (case-sensitive). if contains(record.Email, 'BeauMont@', true) then return readOnly; // Give read write access if last name contains the whole word 'Michel' (case-insensitive). if containsWholeWord(record.LastName, 'Michel', false) then return readWrite;
The following aggregate function can be used with associations:
Syntax | Description |
---|---|
count(expression) | Returns the number of rows of an expression. |
exists(expression) | Returns true if the expression evaluates to at least one row. |
Examples:
// Give read write access if managed users count is at least 2. if count(record.ManagedUsers[]) >= 2 then return readWrite; // Give read only access if count of managed users that are not in Paris is less than 4. if count(record.ManagedUsers:m1[m1.OfficeAddress.City<>'Paris']) < 4 then return readOnly; // Give read write access if managed users count is at least 1. if exists(record.ManagedUsers[]) then return readWrite; // Give read write access if count of managed users that are not in Briton is at least 1. if exists(record.ManagedUsers:u1[u1.OfficeAddress.City<>'Briton']) then return readWrite;
The following table describes built-in functions that return a boolean.
Syntax | Description |
---|---|
isNull(value) | Returns true if value is null. Value can be an expression be of any type. |
Examples:
// Give read write access if no supervisor. if isNull(record.supervisor) then return readWrite;