Cloud Software Group, Inc. EBX®
Documentation > Developer Guide
Navigation mode Documentation > Developer Guide

# Record permission

## Introduction

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) .

## Lexical structure

### Introduction

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;

### Character set

The Unicode character set is supported.

### Character case sensitivity

The DSL is case-sensitive.

### Comments

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;

### Keywords

There are two types of keyword:

- Reserved keywords are: if , then , else , begin , end , return , null , and , or , not , true , false , _ebx .

- 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.

## Identifiers

### Unquoted identifier

An unquoted identifier is an unlimited-length sequence of letters, digits or underscore ( _ ). The first character must be a letter.

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.

### Quoted identifiers

A quoted identifier is an unlimited length of Unicode characters

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 except "_ebx" .

## Types

### Supported types

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

### Literals

#### String literal

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.

#### Decimal literal

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 literal

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 literal

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 literal

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 literal

Boolean literals are true and false .

## Field access

### Simple fields

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 .

### Foreign table fields

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;

### List (multi-valued) field access

Multi-valued fields are not supported.

### Associations

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.

## Operators

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.

## Null value management

### Arithmetic operators

An arithmetic operator ( * , / , + and - ) returns null if any operand is null .

### Comparison operators

A Comparison operator ( < , <= , > , => , = and <> ) returns null if any operand is null .

### Boolean operators

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

### Index expressions

An indexed expression with an index that evaluates null or is out of range returns null .

### Functions

Functions usually return null if a parameter is null . An exception is the function isNull(value) , which never returns null .

## If statement

### "If then" statement

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 .

### "If then else" 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.

### "Then" or "else" body statement

A body statement can be:

- A return statement ,

- 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

## Return statement

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.

## Context variables

### Introduction

Record permissions can depend on the current dataspace, dataset, or session.

### Dataspace

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;

### Dataset

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;

### Session

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.
trackingInfo | string | The current session’s tracking information.

To check current user’s roles, see Roles .

Example:

    if session.userId = 'jdoe' then
      return readWrite;

## Context functions

### Roles

The following table describes the 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;

### Session

The following table describes the session functions:

Syntax | Description
--- | ---
getSessionInputParameter(parameterKey, lookupInParentSessions) | Returns the session input parameter corresponding to the specified parameterKey . In the context of a workflow interaction, the function looks for the parameter in the input parameters of the workflow task. When lookupInParentSessions is true , the function looks for the parameter in the parent sessions. Otherwise, it looks for the parameter only in the current session.
isInWorkflowInteraction(lookupInParentSessions) | Returns true if the current session is in a workflow interaction context. When lookupInParentSessions is true , the function returns true if a parent session is in a workflow interaction context. Otherwise, the function evaluates only the context of the current session.

Example:

    // Give read write access if the scripted rule is called in a workflow interaction context and the 'instance' input parameter for the workflow task is 'Library'.
    if isInWorkflowInteraction(true) and getSessionInputParameter('instance', true) = 'Library' then
      return readWrite;

## Other functions

### String matching functions

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;

### Aggregate functions

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;

### Date and time functions

The following table lists the date and time functions.

Syntax | Description
--- | ---
datetimeNow() | Returns the current date and time
dateNow() | Returns the current date
timeNow() | Returns the current time

Examples:

    // Give read write access if the last possible update date hasn't expired yet.
    if record.LastPossibleUpdateDate >= dateNow() then
      return readWrite;
    else
      return readOnly;

### Miscellaneous functions

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;

Documentation > Developer Guide