Add Access Controls to a Role using Data Permissions
You can configure data permissions in a role to define access controls at the user level instead of the object level.
In a data permission string, you define conditions under which users can access specific actions or actionGroups for a Type. See Define Permissions to learn about the data permission string format.
This topic provides an example of how use data permissions to define access controls for a given Type in a role .json file.
Configure access control with a data permission string
Consider the following example Foo Type:
entity type Foo {
id: !string
name: !string
description: string
}Create a role that defines data permissions. The following .json file represents the role Foo.Role with a data permission string for the Foo Type:
{
"id" : "Foo.Role",
"permissions": [
"allow:Foo::*"
],
"dataPermissions" : [
"Foo:write::(id == _context.userName)"
]
}This role defines the following:
id: The name of the rolepermissions: Permission strings that defineactionsoractionGroupsaccess for a given Type. In this example, this role allows full access to the fields in the Foo Type.dataPermissions: Data permission strings that define access control for specific objects. In this example, this role grants users access an instance of the Foo Type with anidthat matches the user's username.
After users in a group with the Foo.Role role create and upsert an instance of the Foo Type, they can only update their own instance of Foo as defined by "Foo:write::(id == _context.userName)".
To learn how to assign users to groups, see Assign Users.
Expressions in a data permission string
The permission string "Foo:write::(id == _context.userName)" contains the expression (id == _context.userName), which allows write access to an instance of the Foo Type with an id that matches the user's username.
You can also use fuctions from ExpressionEngineFunction to define the access control logic. See Use Expression Functions.
Demonstrate access controls with data permissions
- Run the following code in C3 AI Console to create and instance of Foo and and upsert it:
Foo.make({
id: '<yourUserId>', // Replace with your username
name: '<name>',
description: '<description>'
}).upsert()This creates a Foo object for your user.
- Run the following code in C3 AI Console to create a test user for userB:
TestIdp.createTestUser("userB", "Password1!", ["Foo.Role"])- Run the following code in C3 AI Console to create an instance of Foo with an
idthat does not match userB's username, and upsert it as userB:
TestIdp.executeAsUser("userB", () => Foo.make({
id: '<yourUserId>', // Replace with your username
name: '<name>',
description: '<description>'
}).upsert())This code throws an authorization error because according to the permission string "Foo:write::(id == _context.userName)", userB does not have access to update a Foo object with an id that does not match their own username. In this example, userB cannot update your user's Foo object.
- Run the following code in C3 AI Console to create an instance of Foo with an
idthat matches userB's username, and upsert it as userB:
TestIdp.executeAsUser("userB", () => Foo.make({
id: 'userB',
name: 'foo2',
description: 'foo2'
}).upsert())The code runs successfully because the id matches userB's username, as defined in the permission string "Foo:write::(id == _context.userName)".
Alternative to using data permissions
Alternatively, you can enable access control authorization on a given Type, so that the object maps a path from itself to a user.
To learn how enable access controls on a Type, see Add Access Controls to a Type using AclEnabled.
The system handles AclEnabled Types and data permissions with OR logic, so a user needs at least one but not both access paths defined to access an object.
Data permissions with nested roles
Users are added to groups, and each group has exactly one role. When a role contains nested roles, data permissions from the role and all its nested roles are flattened and combined.
How data permissions combine
- Within a role (including nested roles): Data permissions from a role and all its nested roles are flattened and combined using AND logic. This means all data permission conditions must be satisfied for access to be granted.
- Across groups (when a user is in multiple groups): If a user belongs to multiple groups, data permissions from the different groups' roles are combined using OR logic. This means a user needs to satisfy data permissions from at least one group to access an object.
Example: Data permissions with nested roles
Consider a role hierarchy where ParentRole contains ChildRole as a nested role:
{
"id": "ChildRole",
"dataPermissions": [
"Foo:write::(id == _context.userName)"
]
}{
"id": "ParentRole",
"nestedRoles": ["ChildRole"],
"dataPermissions": [
"Foo:write::(department == _context.userDepartment)"
]
}When a user is added to a group that has ParentRole, both data permissions from ParentRole and its nested ChildRole are flattened and evaluated together using AND logic. The user must satisfy both conditions:
- The
idmust match the user's username (fromChildRole) - The
departmentmust match the user's department (fromParentRole)
If the same user were also in another group with a different role that has data permissions for Foo, those would be OR-ed with the conditions above.