Skip to main content
Version: 21 BETA

Roles and Privileges

Protecting data while allowing fast and easy access to authorized users is a major challenge for web applications. The ORDA security architecture is implemented at the heart of your datastore and allows you to define specific privileges to all web or REST user sessions for the various resources in your project (datastore, dataclasses, functions, etc.).

Overview

The ORDA security architecture is based upon the concepts of privileges, permission actions (read, create, etc.), and resources.

When web users or REST users get logged, their session is automatically loaded with associated privilege(s). Privileges are assigned to the session using the session.setPrivileges() function.

Every user request sent within the session is evaluated against privileges defined in the project's roles.json file.

If a user attempts to execute an action and does not have the appropriate access rights, a privilege error is generated or, in the case of missing Read permission on attributes, they are not sent.

schema

Resources

You can assign specific permission actions to the following resources in your project:

Each time a resource is accessed within a session (whatever the way it is accessed), 4D checks that the session has the appropriate permissions, and rejects the access if it is not authorized.

Permissions

A permission is the ability to do an action on a resource. For example, execute the ds.myTable.myFunction() represents a permission. Permissions are defined for the project in the roles.json file. Each permission can be given to one or more privileges.

When no specific permission has been defined for a resource, access to the resource may be automatically unrestricted or restricted depending on the default mode defined for the project.

Permission actions

Available actions are related to target resource.

Actionsdatastoredataclassattributedata model function or singleton function
createCreate entity in any dataclassCreate entity in this dataclassCreate an entity with a value different from default value allowed for this attribute (ignored for alias attributes).n/a
readRead attributes in any dataclassRead attributes in this dataclassRead this attribute contentn/a
updateUpdate attributes in any dataclass.Update attributes in this dataclass.Update this attribute content (ignored for alias attributes).n/a
dropDelete data in any dataclass.Delete data in this dataclass.Delete a not null value for this attribute (except for alias and computed attribute).n/a
executeExecute any function on the project (datastore, dataclass, entity selection, entity, singleton)Execute any function on the dataclass. Dataclass functions, entity functions, and entity selection functions are handled as dataclass functionsn/aExecute this function
promoten/an/an/aAssociates a given privilege during the execution of the function. The privilege is temporary added and removed at the end of the function execution. By security, only the process executing the function is added the privilege, not the whole session.
Notes
  • An alias can be read as soon as the session privileges allow the access to the alias itself, even if the session privileges do no allow the access to the attributes resolving the alias.
  • A computed attribute can be accessed even if there are no permissions on the attributes upon which it is built.
  • You can assign a permission action to a singleton class (singleton type), in which case it will be applied to all its exposed functions, or to a singleton function (singletonMethod type).
  • You can set/remove the promote action dynamically to a web process using the promote() and demote() functions.
  • Default values: in the current implementation, only Null is available as default value.
  • In REST force login mode, the authentify() function is always executable by guest users, whatever the permissions configuration.

Setting permissions requires to be consistent, in particular update and drop permissions also need read permission (but create does not need it).

Inherited permissions

A permission action defined at a given level is inherited by default at lower levels, but several permissions can be set:

  • A permission action defined at the datastore level is automatically assigned to all dataclasses. The execute permission action defined at the datastore level applies to all functions of the project, including all singleton functions.
  • A permission action defined at a dataclass level overrides the datastore setting (if any). By default, all attributes of the dataclass inherit from the dataclass permission(s).
  • Unlike dataclass permissions, a permission action defined at the attribute level does not override the parent dataclass permission(s), but is added to. For example, if you assigned the "general" privilege to a dataclass and the "detail" privilege to an attribute of the dataclass, both "general" and "detail" privileges must be set to the session to access the attribute.
info

Permissions control access to datastore objects or functions. If you want to filter read data according to some criteria, you might consider restricting entity selections which can be more appropriate in this case.

Assigning permissions to ORDA class functions

When configuring permissions, ORDA class functions are declared in the applyTo element using the following syntax:

<DataclassName>.<functionName>

For example, if you want to apply a permission to the following function:

// cs.CityEntity class
Class extends Entity
Function getPopulation() : Integer
...

... you have to write:

"applyTo":"City.getPopulation"

It means that you cannot use the same function names in the various ORDA classes (entity, entity selection, dataclass) if you want them to be assigned privileges. In this case, you need to use distinct function names. For example, if you have created a "drop" function in both cs.CityEntity and cs.CitySelection classes, you need to give them different names such as dropEntity() and dropSelection(). You can then write in the "roles.json" file:

	"permissions": {
"allowed": [
{
"applyTo": "City.dropEntity",
"type": "method",
"promote": [
"name"
]
},
{
"applyTo": "City.dropSelection",
"type": "method",
"promote": [
"name"
]
}
]

Privileges and Roles

A privilege is the technical ability to run actions on resources, while a role is a privilege pusblished to be used by an administrator. Basically, a role gathers several privileges to define a business user profile. For example, "manageInvoices" could be a privilege while "secretary" could be a role (which includes "manageInvoices" and other privileges).

A privilege or a role can be associated to several "action + resource" combinations. Several privileges can be associated to an action. A privilege can include other privileges.

  • You create privileges and/or roles in the roles.json file (see below). You configure their scope by assigning them to permission action(s) applied to resource(s).

  • You allow privileges and/or roles to every user session using the .setPrivileges() function of the Session class.

Example

To allow a role in a session:


exposed Function authenticate($identifier : Text; $password : Text)->$result : Text

var $user : cs.UsersEntity

Session.clearPrivileges()

$result:="Your are authenticated as Guest"

$user:=ds.Users.query("identifier = :1"; $identifier).first()

If ($user#Null)
If (Verify password hash($password; $user.password))
Session.setPrivileges(New object("roles"; $user.role))
$result:="Your are authenticated as "+$user.role
End if
End if


roles.json file

The roles.json file describes the whole web security settings for the project. The roles.json file syntax is the following:

Property nameTypeMandatoryDescription
privilegesCollection of privilege objectsXList of defined privileges
[].privilegeTextPrivilege name
[].includesCollection of stringsList of included privilege names
rolesCollection of role objectsList of defined roles
[].roleTextRole name
[].privilegesCollection of stringsList of included privilege names
permissionsObjectXList of allowed actions
allowedCollection of permission objectsList of allowed permissions
[].applyToTextXTargeted resource name
[].typeTextXResource type: "datastore", "dataclass", "attribute", "method", "singletonMethod", "singleton"
[].readCollection of stringsList of privileges
[].createCollection of stringsList of privileges
[].updateCollection of stringsList of privileges
[].dropCollection of stringsList of privileges
[].executeCollection of stringsList of privileges
[].promoteCollection of stringsList of privileges
restrictedByDefaultBooleanIf true, access to resources without explicit permissions is denied
forceLoginBooleanIf true, enables "forceLogin" mode
Reminder
  • The "WebAdmin" privilege name is reserved to the application. It is not recommended to use this name for custom privileges.
  • privileges and roles names are case-insensitive.

Default File Location and Content

When a new project is created, a default roles.json file is generated at:

<project folder>/Project/Sources/ 

See Architecture section.

Default content:

/Project/Sources/roles.json

{
"privileges": [
],
"roles": [
],
"permissions": {
"allowed": [
{
"applyTo": "ds",
"type": "datastore",
"read": [],
"create": [],
"update": [],
"drop": [],
"execute": [],
"promote": []
}
]
},
"restrictedByDefault": false,
"forceLogin": false
}
Compatibility

In previous releases, the roles.json file was not created by default. As of 4D 20 R6, when opening an existing project that does not contain a roles.json file or the "forceLogin": true settings, the Activate REST authentication through ds.authentify() function button is available in the Web Features page of the Settings dialog box. This button automatically upgrades your security settings (you may have to modify your code, see this blog post).

Qodly Studio

In Qodly Studio for 4D, the login mode can be set using the Force login option in the Roles and Privileges panel.

Restriction Modes

The restrictedByDefault property configures how every resource are accessed when no specific permission is defined for it:

  • Unrestricted mode (restrictedByDefault: false): Resources without defined permissions are accessible to all requests. This mode is suitable for development environments where access can be gradually restricted.
  • Restricted mode (restrictedByDefault: true): Resources without defined permissions are blocked by default. This mode is recommended for production environments where access must be explicitly granted.
Compatibility

Depending on your environment, the recommended settings are:

  • Production: Set both restrictedByDefault and forceLogin to true. This ensures maximum security by requiring user authentication and explicitly defined permissions for resource access.
  • Development: Set both restrictedByDefault and forceLogin to false. This allows easier access during development and debugging, with the possibility to gradually apply restrictions.

Roles_Errors.json file

The roles.json file is parsed by 4D at startup. You need to restart the application if you want modifications in this file to be taken into account.

In case of error(s) when parsing the roles.json file, 4D loads the project but disables the global access protection - this allows the developer to access the files and to fix the error. An error file named Roles_Errors.json is generated in the Logs folder of the project and describes the error line(s). This file is automatically deleted when the roles.json file no longer contains error(s).

It is recommended to check at startup if a Roles_Errors.json file exists in the Logs folder, which means that there was a parsing error and that accesses will not limited. You can write for example:

/Sources/DatabaseMethods/onStartup.4dm
If (Not(File("/LOGS/"+"Roles_Errors.json").exists))

Else // you can prevent the project to open
ALERT("The roles.json file is malformed or contains inconsistencies, the application will quit.")
QUIT 4D
End if

Example of privilege configuration

/Project/Sources/roles.json

{
"forceLogin": true,
"restrictedByDefault": true,
"permissions": {
"allowed": [
{
"applyTo": "People",
"type": "dataclass",
"read": [
"viewPeople"
]
}
]
},
"privileges": [
{
"privilege": "viewPeople",
"includes": []
}
],
"roles": []
}