Query-based Sync permissions

Overview

The Query-based sync permission system is based on role-based access control lists and as such, permissions are assigned to roles, not users directly. This means that a user's current privileges are the sum of all roles they belong to.

The permission system recognizes three levels of permissions: Realm, Class, and Object-level. The hierarchy works like this: If a user does not have higher-level Read access, they cannot see anything on the lower levels. For example, if a user lacks Read access at the Realm-level, they cannot see any data in the Realm, even if they have Read at the class or object-level. However, just because they have higher-level Read access does not mean they can see everything on the lower level -- just that they can see anything at all. In this way, the app developer can decide for themselves what granularity they want for permissions in their data model.

Query-based Sync Permissions were formerly referred to as Fine-grained Permissions

Overview of query-based sync permissions

When first starting, any user who can connect to a Realm file can make any change to the data. This is designed to allow you to get up and running quickly, syncing data, and iterating on your data models. Once you are ready to start implementing access control, an admin can define access control lists inside the Realm file, either on individual objects or whole classes.

Realm is an offline-first database, but permission checks are performed by the server. When a user makes a change to their local database, it will eventually be uploaded to the server, but if the server determines that the user tried to make changes that they were not allowed to make, the server will refuse to integrate it and instruct the client to revert the change. This all happens transparently from the perspective of the app.

A reversal of an illegal change looks like any other change coming from the server.

To minimize the risk of a user unintentionally making illegal changes while offline, the permission metadata is replicated to each client for offline access, and can be queried in the app for UI purposes. No permission checks are done automatically by the client -- the app has to manually query the permission metadata.

Roles

The query-based sync permission model is based on roles. Roles are granted permissions, not users, but users, in turn, are assigned roles. This way, a users privileges are the sum of all roles they are part of.

A user can be a member of as many roles as needed. The Realm Object Server automatically adds all users to the special role called everyone when they first connect.

A unique role is also automatically created for every user in the system when they first connect: __User:<syncIdentity>. The user is also automatically a member of this role.

In a new Realm file, the everyone role has all permissions enabled.

When you as a developer are ready to integrate permissions in your app, you would usually define a new administrator role which has yourself as a member, and then reduce the privileges of the everyone role. Note that an administrator role is different than an admin user on the Realm Object Server.

When assigning a role to a User, the user is added as a member of the Role object instead of the role being attached to the user. Since the Role object is just a normal Realm object, it can be found, queried and manipulated the same way as other objects:

Swift
Objective-C
Java
Javascript
.Net
// List all roles
let roles = realm.objects(PermissionRole.self)
// You can query roles in order to find a specific one
let role = realm.objects(PermissionRole.self).filter("name = %@", "my-role").first
// Making changes to a Role requires a write transaction
let user = getUserId()
try! realm.write {
role.users.append(user)
}
// So does creating a new role
try! realm.write {
let newRole = realm.create(PermissionRole.self, value: ["my-new-role"])
}
// List all roles
RLMResults<RLMPermissionRole *> *roles = [RLMPermissionRole allObjectsInRealm:realm];
// You can query roles in order to find a specific one
RLMPermissionRole *role = [RLMPermissionRole objectsInRealm:realm where:@"name = %@", @"my-role"].firstObject;
// Making changes to a Role requires a write transaction
RLMPermissionUser *user = getUserId();
[realm transactionWithBlock:^{
[role.users addObject:user];
}];
// So does creating a new role
[realm transactionWithBlock:^{
[RLMPermissionRole createInRealm:realm withValue:@[@"my-new-role"]];
}];
// List all roles
RealmResults<Role> roles = realm.getRoles();
// You can query roles in order to find a specific one
Role role = realm.getRoles().where()
.equalTo("name", "my-role")
.findFirst();
// Making changes to a Role requires a write transaction
String user = getUserId();
realm.executeTransaction((Realm r) -> {
role.addMember(user);
});
// So does creating a new role
realm.executeTransaction((Realm r) -> {
r.insert(new Role("my-new-role"));
});
// List all roles
let roles = realm.objects(Realm.Permissions.Role);
// You can query roles in order to find a specific one
let role = realm
.objects(Realm.Permissions.Role)
.filtered(`name = 'my-role'`)[0];
// Making changes to a Role requires a write transaction
let user = getUser();
realm.write(() => {
role.members.push(user);
})
// So does creating a new role
realm.write(() => {
realm.create(Realm.Permissions.Role, { name: "my-new-role" });
});
// List all roles
var roles = realm.All<PermissionRole>();
// You can query roles in order to find a specific one
var role = realm.All<PermissionRole>().FirstOrDefault(r => r.Name == "my-role");
// Making changes to a Role requires a write transaction
realm.Write(() =>
{
var user = PermissionUser.Get(realm, "some-user-id");
role.Users.Add(user);
});
// So does creating a new role
realm.Write(() =>
{
var newRole = PermissionRole.Get(realm, "my-new-role");
});

Granting Roles Permissions

Once you have a Role, it can be granted permissions. This is done by creating a Permission object containing that role:

Swift
Objective-C
Java
Javascript
.Net
// Create Permission object that grants read and update privileges to a Role
try! realm.write {
let permission = realm.create(Permission.self, value: {
role: getRole(),
canRead: true,
canUpdate: true
})
}
// Create Permission object that grants read and update privileges to a Role
[realm transactionWithBlock:^{
[RLMPermission createInRealm:realm withValue:@{
@"role": getRole(),
@"canRead": @YES,
@"canUpdate": @YES
}];
}];
// Create Permission object that grants read and update privileges to a Role
Role role = getRole();
Permission permission = new Permission.Builder(role)
.canRead(true)
.canUpdate(true)
.build();
// Create Permission object that grants read and update privileges to a Role
let permission = realm.create(Realm.Permissions.Permission, {
role: getRole(),
canRead: true,
canUpdate: true,
});
// Create Permission object that grants read and update privileges to a Role
realm.Write(() =>
{
// The permission can be associated with a Realm, a class, or an object.
// Alternatively, you can pass in a collection of permissions to add the
// new one to.
var permission = Permission.Get("my-role", realm);
permission.CanRead = true;
permission.CanUpdate = true;
});

By itself, the Permission object does nothing and the privileges described in the object are not enforced until the object has been added to an appropriate permission list at either the Realm-level, the Class-level or the Object-level. See the respective subsections for details on how to do this.

A single Permission object can be used in multiple places at the same time. This can be useful if you easily want to update privileges across multiple objects at the same time.

The following privileges can be set when creating or modifying the Permission object:

  • canCreate

  • canRead

  • canUpdate

  • canDelete

  • canSetPermissions

  • canQuery

  • canModifySchema

These privileges have different semantics depending on the level they are applied. See the sections below for the meaning of each privilege at the given level.

Realm-level permissions

Realm-level permissions apply globally to the Realm file and are modified through a special singleton Realm object that is accessed the following way:

Swift
Objective-C
Java
Javascript
.Net
// List all Realm-level permissions
let realmPermissions = realm.permissions
// Find Realm level permissions for a given role
let rolePermissions = realmPermissions.filter("role.name = %@", "my-role").first
// List all Realm-level permissions
RLMRealmPermissions *permissions = [RLMRealmPermissions objectInRealm:realm];
// Find Realm level permissions for a given role
RLMPermission *rolePermissions = [permissions objectsWhere:@"role.name = %@", @"my-role"]
// Get the wrapper object for all Realm-level permissions
RealmPermissions realmPermissions = realm.getPermissions()
// List all Realm-level permissions
RealmList<Permission> allPermissions = realmPermissions.getPermissions()
// Find permissions for a given role
Permission rolePermissions = realmPermissions.getPermissions()
.equalTo("role.name", "my-role")
.findFirst()
// Get the global Realm-level permissions object
let realmPermissions = realm.permissions();
// Find permissions for a given role
let rolePermissions = realmPermissions.permissions
.filtered(`role.name = 'my-role'`)[0];
// List the Realm-level permissions for all roles
let allPermissions = realmPermissions.permissions;
// List all Realm-level permissions
var realmPermissions = RealmPermission.Get(realm).Permissions;
// Find Realm level permissions for a given role
var rolePermissions = realmPermissions.FirstOrDefault(p => p.Role.Name == "my-role");

Adding Realm-level permissions for a role is done by adding a new Permission object to the list of Realm-level permissions. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Adding new permissions must be done within a write transaction
try! realm.write {
// Grant read-only access at the Realm-level, which means
// that users with this role can read all objects in the Realm
// unless restricted by Class or Object level permissions.
let permissions = realm.permissions.findOrCreate(forRoleNamed: "my-role")
permissions.canRead = true
permissions.canQuery = true
}
// Permissions must be modified inside a write transaction
[realm transactionWithBlock:^{
// Grant read-only access at the Realm-level, which means
// that users with this role can read all objects in the Realm
// unless restricted by Class or Object level permissions.
RLMPermission *permissions = [RLMPermission permissionForRoleNamed:@"my-role" onRealm:realm];
permissions.canRead = true;
permissions.canQuery = true;
}];
// Adding new permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
RealmPermissions realmPermissions = realm.getPermissions()
// Grant read-only access at the Realm-level, which means
// that users with this role can read all objects in the Realm
// unless restricted by Class or Object level permissions.
Permission permissions = realmPermissions.findOrCreate("my-role")
permissions.setCanRead(true)
permissions.setCanQuery(true)
});
// Adding new permissions must be done within a write transaction
realm.write(() => {
let realmPermissions = realm.permissions().permissions;
// Grant read-only access at the Realm-level, which means
// that users with this role can read all objects in the Realm
// unless restricted by Class or Object level permissions.
let role = realm.objects(Realm.Permissions.Role)
.filtered(`name = 'my-role'`)[0];
let permission = realm.create(Realm.Permissions.Permission, {
role: role,
canRead: true,
canQuery: true,
});
// Add it to the list of permissions for it to take affect.
realmPermissions.push(permission);
});
// Adding new permissions must be done within a write transaction
realm.Write(() =>
{
// Grant read-only access at the Realm-level, which means
// that users with this role can read all objects in the Realm
// unless restricted by Class or Object level permissions.
var permission = Permission.Get("my-role", realm);
permission.CanRead = true;
permission.CanQuery = true;
});

Modifying the Realm-level permissions for an existing role is done by finding the permission object for that role and modifying it. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Modifying permissions must be done within a write transaction
try! realm.write {
// Find permissions for the specific role
let permission = realm.permissions.findOrCreate(forRoleNamed: "my-role")
// Prevent `my-role` users from modifying any objects in the Realm.
permission.canUpdate = false
permission.canDelete = false
}
// Permissions must be modified inside a write transaction
[realm transactionWithBlock:^{
// Find permissions for the specific role
RLMPermission *permissions = [RLMPermission permissionForRoleNamed:@"my-role" onRealm:realm];
// Prevent `my-role` users from modifying any objects in the Realm.
permissions.canRead = false;
permissions.canQuery = false;
}];
// Modifying permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
RealmPermissions realmPermissions = realm.getPermissions();
// Find permissions for the specific role
Permission permission = realmPermissions.findOrCreate("my-role");
// Prevent `my-role` users from modifying any objects in the Realm.
permission.setCanUpdate(false);
permission.setCanDelete(false);
});
// Modifying permissions must be done within a write transaction
realm.write(() => {
let realmPermissions = realm.permissions().permissions;
// Find permissions for the specfic role
let permission = realmPermissions.filtered('role.name', 'my-role')[0];
// Prevent `my-role` users from modifying any objects in the Realm.
permission.canUpdate = false;
permission.canDelete = false;
});
// Modifying permissions must be done within a write transaction
realm.Write(() =>
{
// Find permissions for the specific role
var permission = Permission.Get("my-role", realm);
permission.CanUpdate = false;
permission.CanDelete = false;
});

Normal users can only grant other people access up to the level they themselves have, and only if they have the SetPermissions privilege. Realm Object Server admin users can see and edit everything.

The following table describes the effect of enabling or disabling privileges at the Realm level. If a privilege is marked with N/A it means that the privilege has no meaning at this level beyond being required to enable setting it at Class or Object level.

Type

ENABLED

DISABLED

canCreate

N/A

N/A

canRead

Role can see the Realm file itself, i.e. being able to meaningfully connect to it. Being able to see individual classes are covered by Class-level permissions.

Role cannot see anything in the Realm file. An user that is offline will still create the file and schema locally on the device, but as soon as the device connects to the server, it will revert all changes (leaving an empty Realm). This will most likely crash the app.

canUpdate

Role can make changes to objects in the Realm file. This does not include schema changes or changes to permissions which are covered by canModifySchema andcanSetPermissions.

Role cannot change anything in the Realm file. It is read-only. Note that the user can write changes to the Realm file while offline, but any change will be reverted by the server once the device is online again.

canDelete

N/A

N/A

canQuery

N/A

N/A

canSetPermissions

Role can set Realm-level permissions. A user with theSetPermissions privilege can never give other users higher privileges than they themselves have.

Role cannot set or change Realm-level permissions.

canModifySchema

Role can add classes to the schema, but not properties which are covered by Class-level permissions.

Role cannot modify the schema in the entire Realm.

Class-level permissions

Class-level permissions are permissions related to all objects of a given type.

They can be used reduce the scope of a Realm-level privilege, but cannot be used to broaden a privilege granted at the Realm-level. E.g. if canRead is set at the Realm-level, it is possible to set canRead to false at the Class-level, which will prevent the Role from seeing just this single class. However, if canRead was not set set the Realm-level, the value of canRead at the Class-level will be ignored and the class will not be readable.

Just like Realm-level permissions, Class-level permissions are exposed as Realm objects that can be accessed the following way:

Swift
Objective-C
Java
Javascript
.Net
// List the Class-level permissions for the given model class
let classPermissions = realm.permissions(forType: Person.self)
// Find Class-level permissions for a given role
let rolePermissions = classPermissions.filter("role.name = %@", "my-role").first
// List the Class-level permissions for the given model class
RLMClassPermission *classPermissions =
[RLMClassPermission objectInRealm:realm forClass:Person.class];
// Find Class-level permissions for a given role
RLMPermission *rolePermissions = [[classPermissions.permissions
objectsWhere:@"role.name = %@", @"my-role"]
firstObject];
// Get the Class-level permissions object for the Person class
ClassPermissions classPermissions = realm.getPermissions(Person.class);
// List the Class-level permissions for all roles
RealmList<Permission> permissions = classPermissions.getPermissions();
// Find Class-level permissions for a given role
Permission rolePermissions = classPermissions.getPermissions().where()
.equalTo("role.name", "my-role")
.findFirst();
// Get the Class-level permissions object for the Person class
let classPermissions = realm.permissions('Person');
// List the Class-level permissions for all roles
let permissions = classPermissions.permissions;
// Find Class-level permissions for a given role
let rolePermissions = classPermissions.permissions
.filtered(`role.name = 'my-role'`)[0];
// List the Class-level permissions for the given model class
var classPermissions = ClassPermission.Get<Person>(realm).Permissions;
// Find Class-level permissions for a given role
var rolePermission = classPermissions.FirstOrDefault(p => p.Role.Name == "my-role");

Adding Class-level permissions for a role is done by adding a new Permission object to the list of permissions. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Adding new permissions must be done within a write transaction
try! realm.write {
// Remove read-access for the Person class for users with the `my-role` role.
let permission = realm
.permissions(forType: Person.self)
.findOrCreate(forRoleNamed: "my-role")
permission.canRead = false
});
// Adding new permissions must be done within a write transaction
[realm transactionWithBlock:^{
// Remove read-access for the Person class for users with the `my-role` role.
RLMPermission *permission = [RLMPermission permissionForRoleNamed:@"my-role"
onClass:Person.class
realm:realm];
permission.canRead = false;
});
// Adding new permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
ClassPermissions realmPermissions = realm.getPermissions(Person.class);
// Remove read-access for the Person class for users with the `my-role` role.
Role role = realm.where(Role.class).equalTo("name", "my-role").findFirst();
Permission p = new Permission.Builder(role)
.canRead(false)
.build();
// Add it to the list of permissions for it to take affect.
classPermissions.getPermissions().add(permission);
});
// Adding new permissions must be done within a write transaction
realm.write(() => {
let realmPermissions = realm.permissions('Person').permissions;
// Remove read-access for the Person class for users with `my-role` role.
let role = realm.objects(Realm.Permissions.Role)
.filtered(`name = 'my-role'`)[0];
let permission = realm.create(Realm.Permissions.Permission, {
role: role,
canRead: false,
});
// Add it to the list of permissions for it to take affect.
classPermissions.push(permission);
});
// Adding new permissions must be done within a write transaction
realm.Write(() =>
{
var permission = Permission.Get<Person>("my-role", realm);
// Alternatively, there's a string-based API if, for some reason,
// you can't use the generic one.
// var permission = Permission.Get("my-role", "Person", realm);
permission.CanRead = false;
});

Modifying the Class-level permissions for an existing role is done by finding the permission object for that role and modify it. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Modifying permissions must be done within a write transaction
try! realm.write {
// Find permissions for the role and change it
let permission = realm
.permissions(forType: Person.self)
.findOrCreate(forRoleNamed: "my-role")
// Prevent `my-role` users from modifying any Person objects.
permission.canUpdate = false
permission.canDelete = false
});
// Modifying permissions must be done within a write transaction
[realm transactionWithBlock:^{
RLMPermission *permission = [RLMPermission permissionForRoleNamed:@"my-role"
onClass:Person.class
realm:realm];
// Prevent `my-role` users from modifying any Person objects.
permission.canUpdate = false;
permission.canDelete = false;
});
// Modifying permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
ClassPermissions classPermissions = realm.getPermissions(Person.class);
// Find permissions for the role and change it
Permission permission = classPermissions.getPermissions().where()
.equalTo("role.name", "my-role")
.findFirst();
// Prevent `my-role` users from modifying any Person objects.
permission.setCanUpdate(false);
permission.setCanDelete(false);
});
// Modifying permissions must be done within a write transaction
realm.write(() => {
let classPermissions = realm.permissions('Person').permissions;
// Find permissions for a specfic role
let permission = classPermissions.filtered(`role.name = 'my-role'`)[0];
// Prevent `my-role` users from modifying any objects in the Realm.
permission.canUpdate = false;
permission.canDelete = false;
});
// Modifying permissions must be done within a write transaction
realm.Write(() =>
{
var permission = Permission.Get<Person>("my-role", realm);
// Prevent `my-role` users from modifying any Person objects.
permission.CanUpdate = false;
permission.CanDelete = false;
});

Normal users can only grant other people access up to the level they themselves have, and only if they have the SetPermissions privilege. Admin users can see and edit everything.

The following table describes the effect of enabling or disabling a given privilege at the Class level. If a privilege is marked with N/A it means that the privilege has no meaning at this level beyond being required to enable setting it at the Object level.

Type

ENABLED

DISABLED

canCreate

Role can create objects of this type.

If a user has the canCreate privilege, but not canUpdate,they are still able to modify newly-created objects inside the same transaction that object was created in.

Role cannot create objects of this type.

canRead

Role can see objects of this type.

Role cannot see any objects of this type. It is still allowed to query them, but the query result will be empty

canUpdate

Role can change properties in objects of this type.

Role cannot make any change to properties in objects of this type.

canDelete

N/A

N/A

canQuery

Role is allowed to create a server side Subscription for this type.

Role is not allowed to create a server side Subscription for this type. Note: Local queries will always work.

canSetPermissions

Role can set Class-level permissions for the class.

Role cannot set class-level permissions for the class.

canModifySchema

Role can add properties to this class. Deleting and renaming fields is currently not supported by the Realm Object Server.

Role cannot modify the schema for this class.

Object-level permissions

Object-level permissions are permissions related to a single Realm object.

They can be used to reduce the scope of Class-level privileges, but cannot be used to broaden a privilege granted at the Class-level. E.g. if canRead is enabled at the Class-level, it is possible to disable canRead at the Object-level which will prevent the Role from seeing just this single object. However, if canRead was disabled at the Class-level, no matter the value of canRead at the Object-level the object will not be readable.

In order to add permissions to your objects, you must add a special Access Control List (ACL) property. If your objects do not have the ACL property, the objects will be fully accessible by anyone (provided that they are discoverable -- see the canQuery/canRead permission at the Class level).

The ACL property is added the following way:

Swift
Objective-C
Java
Javascript
.Net
// The ACL property is a `List<Permission>` field with a user-defined name
class Person: Object {
@objc dynamic var name = ""
let permissions = List<Permission>()
}
// The ACL property is a `RLMArray<RLMPermission *>` field
// with a user-defined name
@interface Person : RLMObject
@property NSString *name;
@property RLMArray<RLMPermission *><RLMPermission> *permissions;
@end
// The ACL property is a `RealmList<Permission>` field
// with a user-defined name
public class Person extends RealmObject {
public String name;
public RealmList<Permission> permissions = new RealmList<>();
}
// The ACL property is a list-of-permission property
// with a user-defined name
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
permissions: '__Permission[]'
}
};
// The ACL property is an `IList<Permission>` property with a user-defined name
public class Person : RealmObject
{
// Other properties ...
public IList<Permission> Permissions { get; }
}

If there is an ACL property, but no permissions have been added for any role, then nobody except Realm Object Server admin users will have access to the object. This includes the user creating the object.

Adding Object-level permissions for a role is done adding a new Permission object to the list of permissions. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Adding new permissions must be done within a write transaction
try! realm.write {
// Grant users with the `shared-objects` role access to read and modify
// this object
let person = getPerson()
let permissions = person.permissions.findOrCreate(forRoleNamed: "shared-objects")
permissions.canRead = true
permissions.canUpdate = true
permissions.canDelete = true
}
// Adding new permissions must be done within a write transaction
[realm transactionWithBlock:^{
// Grant users with the `shared-objects` role access to read and modify
// this object
Person *person = getPerson();
RLMPermission *permissions = [RLMPermission
permissionForRoleNamed:@"shared-objects"
inArray:person.permissions];
permissions.canRead = true;
permissions.canUpdate = true;
permissions.canDelete = true;
}];
// Adding new permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
Person p = realm.where(Person.class).equalto("id", getId()).findFirst()
// Grant users with the `shared-objects` role access to read and modify
// this object
Role role = realm.where(Role.class)
.equalTo("name", "shared-objects")
.findFirst();
Permission p = new Permission.Builder(role)
.canRead(true)
.canUpdate(true)
.canDelete(true)
.build();
// Add it to the list of permissions associated with the object
// for it to take affect.
p.permissions.add(permission);
});
// Adding new permissions must be done within a write transaction
realm.write(() => {
let person = realm.objects('Person').filtered(`id = "my-id"`)[0];
// Grant users with the `shared-objects` role access to read and modify
// this object
let role = realm.objects(Realm.Permissions.Role)
.filtered(`id = "shared-objects"`)[0];
let permission = realm.create(Realm.Permissions.Permission, {
role: role,
canRead: true,
canUpdate: true,
canDelete: true,
});
// Add it to the list of permissions for it to take affect.
person.permissions.push(permission);
});
var person = GetSomePerson();
// Adding new permissions must be done within a write transaction
realm.Write(() =>
{
// Grant users with the `shared-objects` role access to read and modify
// this object. Using the Permission.Get API that accepts a RealmObject
// will use reflection to find the `IList<Permission>` property.
var permission = Permission.Get("shared-objects", person);
// Alternatively, you can pass in the permission collection directly:
// var permission = Permission.Get("shared-objects", person.Permissions);
permission.CanRead = true;
permission.CanUpdate = true;
permission.CanDelete = true;
});

Modifying the Object-level permissions for an existing role is done by finding the permission object for that role and modifying it. This requires a write transaction:

Swift
Objective-C
Java
Javascript
.Net
// Modifying permissions must be done within a write transaction
try! realm.write {
let person = getPerson()
// Prevent `shared-objects` users from modifying this object.
let permissions = person.permissions.findOrCreate(forRoleNamed: "shared-objects")
permissions.canUpdate = false
permissions.canDelete = false
}
// Modifying permissions must be done within a write transaction
[realm transactionWithBlock:^{
Person *person = getPerson();
// Prevent `shared-objects` users from modifying this object.
RLMPermission *permissions = [RLMPermission
permissionForRoleNamed:@"shared-objects"
inArray:person.permissions];
permissions.canUpdate = false
permissions.canDelete = false
}];
// Modifying permissions must be done within a write transaction
realm.executeTransaction((Realm r) -> {
Person p = realm.where(Person.class).equalTo("id", getId()).findFirst();
// Find permissions for a specific role and change them
Permission permission = p.permissions.where()
.equalTo("role.name", "shared-objects")
.findFirst();
// Prevent `shared-objects` users from modifying this object.
permission.setCanUpdate(false);
permission.setCanDelete(false);
});
// Modifying permissions must be done within a write transaction
realm.write(() => {
let person = realm.objects('Person')
.filtered(`id = "${getId()}"`)[0];
// Find permissions for a specific role and change them
let permission = person.permissions('role.name', 'shared-objects')[0];
// Prevent `shared-objects` users from modifying this object.
permission.canUpdate = false;
permission.canDelete = false;
});
var person = GetSomePerson();
// Modifying permissions must be done within a write transaction
realm.Write(() =>
{
// Prevent `shared-objects` users from modifying this object.
var permission = Permission.Get("shared-objects", person);
permission.CanUpdate = false;
permission.CanDelete = false;
});

Any object that is reachable through links or collections from an object to which a user has Read access will also be readable by that user, even if they explicitly do not have Read access to the reachable object. This is due to a current limitation in the way object graphs are represented in Realm.

The following table describes the effect of enabling or disabling a given privilege at the Object level:

Type

ENABLED

DISABLED

canCreate

N/A

N/A

canRead

Role can see and read this object. This include any referenced objects.

Role cannot read or see this object.

canUpdate

Role can update properties on this object. The ACL property is a special case handled by canSetPermissions.

Role is not allowed to change the value of any properties on the object.

canDelete

Role can delete the object.

Role is not allowed to delete the object.

canQuery

N/A

N/A

canSetPermissions

Role can modify Permission objects referenced by the custom ACL property on the object.

Role is not allowed to modify the custom ACL property on the object.

canModifySchema

N/A

N/A

User Privileges

Because a User can be part of multiple roles with many different permissions, it is often useful to be able to determine exactly what the current user can do with an object (or class, or the Realm file).

This can be achieved in the following way:

Swift
Objective-C
Java
Javascript
.Net
// Realm privileges
let privileges = realm.getPrivileges()
// Class privileges for `Person`
let privileges = realm.getPrivileges(Person.self)
// Object privileges
let person = getPerson()
let privileges = realm.getPrivileges(person)
// Realm privileges
struct RLMRealmPrivileges privileges = [realm privilegesForRealm];
// Class privileges for `Person`
struct RLMClassPrivileges privileges = [realm privilegesForClass:Person.class];
// Object privileges
Person *person = getPerson();
struct RLMObjectPrivileges privileges = [realm privilegesForObject:person];
// Realm privileges
RealmPrivileges privileges = realm.getPrivileges();
// Class privileges for `Person`
ClassPrivileges privileges = realm.getPrivileges(Person.class);
// Object privileges
Person person = getObject();
ObjectPrivileges privileges = realm.getPrivileges(person);
// Realm privileges
let privileges = realm.privileges();
// Class privileges for `Person`
let classPrivileges = realm.privileges('Person');
// Object privileges
let person = getPerson();
let objectPrivileges = realm.getPrivileges(person);
// Realm privileges
var privileges = realm.GetPrivileges();
// Class privileges for Person
var privileges = realm.GetPrivileges<Person>();
// Class privileges for Person using the string API
var privileges = realm.GetPrivileges("Person");
// Object privileges
var person = realm.Find<Person>(someId);
var privileges = realm.GetPrivileges(person);

This can e.g. be used to toggle an Edit button if a user only have read access to an object:

Swift
Objective-C
Java
Javascript
.Net
let person = getPerson()
let privileges = realm.getPrivileges(person)
if privileges.contains(.update) {
showEditButton()
} else {
hideEditButton()
}
Person *person = getPerson();
struct RLMObjectPrivileges privileges = [realm privilegesForObject:person];
if (privileges.update) {
showEditButton()
}
else {
hideEditButton()
}
Person person = getObject();
ObjectPrivileges privileges = realm.getPrivileges(person);
if (privileges.canUpdate()) {
showEditButton();
} else {
hideEditButton();
}
let person = getPerson();
let objectPrivileges = realm.getPrivileges(person);
if (privileges.canUpdate) {
showEditButton();
} else {
hideEditButton();
}
var person = realm.Find<Person>(someId)
var privileges = realm.GetPrivileges(person);
if (privileges.HasFlag(ObjectPrivileges.Update))
{
ShowEditButton();
}
else
{
HideEditButton();
}

If a user does not have canRead access to the classes used by the permission system, querying about privileges will always return false , even if the user might have access. See the next section for more details.

Restricting access to Permission metadata

Query-based sync permissions are implemented using Realm classes and objects. This means that the permission system itself is subject to the same permission rules as normal model classes.

As a consequence it is possible to disallow users access to the information in the permission system by removing the canRead privilege for those classes. This is generally not recommended as it prevent users from knowing the full extend of their privileges.

The classes used for implementing the permissions system are:

Swift
Objective-C
Java
Javascript
.Net
RealmSwift.RealmPermission
RealmSwift.ClassPermission
RealmSwift.PermissionUser
RealmSwift.PermissionRole
RealmSwift.Permission
RLMPermission
RLMRealmPermission
RLMClassPermission
RLMPermissionUser
RLMPermissionRole
io.realm.sync.permissions.PermissionUser
io.realm.sync.permissions.Permission
io.realm.sync.permissions.RealmPermissions
io.realm.sync.permissions.ClassPermissions
io.realm.sync.permissions.Role
See the API docs here:
​https://realm.io/docs/java/latest/api/io/realm/sync/permissions/package-summary.html
// Coming soon
Realms.Sync.Permission
Realms.Sync.PermissionUser
Realms.Sync.RealmPermission
Realms.Sync.ClassPermission
Realms.Sync.PermissionRole
See the API docs here:
https://realm.io/docs/dotnet/latest/api/reference/Realms.Sync.Permission.html

Specifically it means that:

  • A user cannot modify any Realm-level permissions unless they have the setPermissions privilege on the Class-level permission object for the __Realm class.

  • A user cannot modify any Class-level permissions unless they have the setPermissions privilege on the Class-level permission object for the __Class class.

This can be verified the following way:

Swift
Objective-C
Java
Javascript
.Net
// Check access to Realm-level permissions
let realmPrivs = realm.getPrivileges()
realmPrivs.contains(.read) // Can see Realm-level permissions
realmPrivs.contains(.setPermissions) // Can modify Realm-level permissions
// Check access to Class-level permissions
let classPrivs = realm.getPrivileges(ClassPermissions.self)
classPrivs.contains(.read) // Can see Class-level permissions
classPrivs.contains(.setPermissions) // Can modify Class-level permissions
// Check access to Realm-level permissions
struct RLMRealmPrivilegs realmPrivs = realm.privilegesForRealm;
realmPrivs.read; // Can see Realm-level permissions
realmPrivs.setPermissions; // Can modify Realm-level permissions
// Check access to Class-level permissions
struct RLMClassPrivileges classPrivs = [realm privilegesForClass:ClassPermissions.class];
classPrivs.read; // Can see Class-level permissions
classPrivs.setPermissions; // Can modify Class-level permissions
// Check access to Realm-level permissions
RealmPrivileges realmPrivs = realm.getPrivileges(RealmPermissions.class);
realmPrivs.canRead(); // Can see Realm-level permissions
realmPrivs.canSetPermissions(); // Can modify Realm-level permissions
// Check access to Class-level permissions
ClassPrivileges classPrivs = realm.getPrivileges(ClassPermissions.class);
classPrivs.canRead(); // Can see Class-level permissions
classPrivs.canSetPermissions(); // Can modify Class-level permissions
// Check access to Realm-level permissions
let realmPrivileges = realm.privileges(Realm.Permissions.Realm);
realmPrivileges.canRead; // Can see Realm-level permissions
realmPrivileges.canSetPermissions; // Can modify Realm-level permissions
// Check access to Class-level permissions
let classPrivileges = realm.privileges(Realm.Permissions.Class);
classPrivileges.canRead; // Can see Class-level permissions
classPrivileges.canSetPermissions; // Can modify Class-level permissions
// Check access to Realm-level permissions
var realmPrivileges = realm.GetPrivileges();
// Can see Realm-level permissions
var canRead = realmPrivileges.HasFlag(RealmPrivileges.Read);
// Can modify Realm-level permissions
var canSetPermissions = realmPrivileges.HasFlag(RealmPrivileges.SetPermissions);
// Check access to Class-level permissions
var classPrivileges = realm.GetPrivileges<ClassPermission>();
// Can see Class-level permissions
var canReadClass = classPrivileges.HasFlag(ClassPrivileges.Read);
//Can modify Class-level permissions
var canSetPermissionsClass = classPrivileges.HasFlag(ClassPrivileges.SetPermissions);

Viewing Permissions Data in Realm Studio

There are two way to view permissions in Studio. First you can see the Realm, Class, and Object permissions through the selection options on the right. When setting permissions through a script or client testing, this will update.

The second method is two view the permissions through the __Permission class. This will show the relationships between the roles, user, and permission objects. In order to see it, you need to enable "Show system classes" Under the "View" menu item.

Realm Studio showing permission classes

They are stored in the tables named __Realm , __Class, __Role, __User and __Permission.

Realm-level permissions are defined in a special object with id = 0 in the __Realm table.

Class-level permissions are stored in the __Class table. Each Realm class has an entry in that table.

Examples

How do I prevent users from adding themselves to Roles?

Since it is Roles that are granted privileges, not users, it can be considered a security hole if users can add themselves to other roles, effectively elevating their own privileges.

This can be prevented by making the Role class readonly for anyone except administrators. This is done by modifying the the Class-level permissions for the Role class the following way:

Javascript
realm.write(() => {
// Get the Class-level Permissions for the Role class.
val rolePermissions = realm.permissions(Realm.Permissions.Role);
// Reduce privileges to read only for the default "everyone" role
// that all users are part of. Users can, now, only modify roles if they
// are part of another role that grant that privilege.
val permissions = rolePermissions.findOrCreate("everyone");
permissions.canCreate = false;
permissions.canModify = false;
permissions.canDelete = false;
})

Modifying the Role this way should either be done by a server side script or alternatively by using Realm Studio:

Edit the Class-level permissions for the __Role class

How do I share an object with another user?

If a model class does not have an ACL property defined then all objects of that type is available to everyone if the Class-level permission for the class allow it.

If you want to allow some users to see your object and not others, you need to enable the Object-level permissions by adding the ACL property to the schema for that class:

Javascript
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
permissions: '__Permission[]' // ACL property
}
};

Once that is done, permissions can now be added to the object. Realm have created pre-defined roles for each user that only have the user as a member. You can use these roles if you only want to share the object with one other person.

Javascript
realm.write(() => {
// Grant full privileges to the creator of the object
let ownIdentity = user.identity;
let role = realm.objects(Realm.Permissions.Role)
.filtered(`id = "__User:{$ownIdentity}"`)[0];
let permission = realm.create(Realm.Permissions.Permission, {
role: role,
canRead: true,
canUpdate: true,
canDelete: true,
canSetPermissions: true,
});
// Grant read privileges to another user
let otherUserIdentity = getOtherUserIdentitySync();
let role = realm.objects(Realm.Permissions.Role)
.filtered(`id = "__User:{$otherUserIdentity}"`)[0];
let permission = realm.create(Realm.Permissions.Permission, {
role: role,
canRead: true,
});
});

Creating an object does not automatically grant admin privileges over that object. You also need to add your own privileges.

How do I find another users identity?

Users on the Realm Object Server are defined by an identity . The identity is used when finding a single users Role or otherwise assigning people to roles.

You can find your own identity this way:

Javascript
let credentials = Realm.Sync.Credentials.usernamePassword(username, 'password');
Realm.Sync.User.login(url, credentials).then((user) => {
user.identity // Get the users identity
});

Admin users can also lookup other users identity using their email:

Javascript
let provider = 'password';
let userId = 'user@email.com';
adminUser.retrieveAccount(provider, userId).then((account) => {
account.user_id; // Users identity
});

Non-admin users do not, currently , have any way of automatically looking up another users identity.

Best Practices

Schema Modifications

Our recommendation is to always remove the canModifySchema permission so that users cannot change the schema design as this could affect other users of the app. While it may be useful during the initial app development and the POC phase to allow developers to quickly iterate on the schema, once multiple developers are using the same realm data for development testing, a fat-fingered schema can bring the rest of the developers to a halt. However, this is something only a server-side app with a Realm admin account should have the ability to change in production.

The Everyone Role

Once ready to move into a production environment, it is best to disable the everyone role which can give blanket access to objects. This should be deprecated in favor of more specific and customized roles.

Not what you were looking for? Leave Feedback