Full-Sync permissions

1
Last updated 2 months ago

Overview

When using full-synchronization, data access can be controlled at the individual Realm level based off the specific path assigned to a Realm. As a result, you might architect your application to partition the Realms with end user groups in mind. For example, it is common that an application might have a global read-only Realm (i.e. /globalRealm) and also user-specific data in individual Realms (i.e. /~/myRealm) which are created for each end user. It is normal to have a full-sync application which contains numerous Realms.

Full-Sync Permissions were formerly referred to as Path-level Permissions

Overview of full-sync permissions

Paths

By default, a user can only create Realms within their own scoped path: /<userId>/. To simplify creating URLs, the tilde character, ~, can be used in place of the userId. For example, if a user with userId=1234 creates a Realm called myRealm, this would live at the path /1234/myRealm and is also addressable as /~/myRealm when called from the logged in user.

The only users that can create Realm files at other paths, such as the base path /, are admin users.

Access Levels

Access levels can be granted to users for specific synchronized Realms, using the client permissions APIs. There are three levels:

  • Read the ability to connect and sync the Realm

  • Write the ability to make changes to the data in the Realm

  • Manage - the ability to change permissions on the Realm

Note that each access level guarantees all allowed actions provided by less permissive access levels. Specifically, users with Write access to a Realm can always Read from that Realm.

Admin users can always read or write from any Realm.

Permissions for a Realm can be set on a per-user basis. When a user attempts to sync a Realm, first the server checks to see if there are per-user permissions set for that user on that Realm. If there are no per-user permissions set for that user, the default permissions for the Realm are used. For example, a Realm might have mayRead set true by default, with individual users being granted mayWrite permissions.

By default, a Realm is exclusive to its owner: the owner has all permissions on it, and no other user has _any _permissions for it. Other users must be explicitly granted access.

Reading Permissions

Swift
Objective-C
Java
Javascript
.Net

To get all the Realms a user has access to, along with the level of access for each Realm, use the SyncUser.retrievePermissions(...) method.

SyncUser.current?.retrievePermissions { permissions, error in
if let error = error {
// handle error
return
}
// success! access permissions
}

To get all the Realms a user has access to, along with the level of access for each Realm, use the -[RLMSyncUser retrievePermissionsWithCallback:] method.

[[RLMSyncUser currentUser] retrievePermissionsWithCallback:^(RLMResults<RLMSyncPermission *> *permissions, NSError *error) {
if (error) {
// handle error
return;
}
// success! access permissions
}];

To get a collection of all the Permissions a user has been granted, use the getPermissions(callback)method:

PermissionManager pm = user.getPermissionManager();
// Retrieve
pm.getPermissions(new PermissionManager.PermissionsCallback() {
@Override
public void onSuccess(RealmResults<Permission> permissions) {
// Permissions can be queried like normal
Permission p = permissions.where().equalTo("path", realmPath).findFirst();
// Changelisteners can be registered
// The PermissionManager keeps a reference to these as long as the Permission Manager
// is open, so they do not risk getting GC'ed
permissions.addChangeListener(new RealmChangeListener() {
@Override
public void onChange(RealmResults<Permission> permissions) {
// Permissions changed
}
});
}
@Override
public void onError(ObjectServerError error) {
// handle error
}
});
let user = Realm.Sync.User.current;
user.getGrantedPermissions().then(permissions => {
// permissions is a collection of permission objects
})
.catch(error => {
// an error occurred
});

To get a collection of all the Permissions a user has been granted, use the User.GetGrantedPermissionsAsync method:

var permissions = await user.GetGrantedPermissionsAsync(Recipient.CurrentUser, millisecondTimeout: 2000);
// Permissions is a regular query
var writePermissions = permissions.Where(p => p.MayWrite);
// Queries are live and emit notifications
writePermissions.SubscribeForNotifications((sender, changes, error) =>
{
// handle permission changes
});

To get permissions granted by a user, pass in Recipient.OtherUser. The millisecondTimeout argument controls the maximum time to wait for response from the server. When the timeout elapses, the last known state is returned, so even if the client is currently offline, some data may still be shown.

Modifying Permissions

Modifying the access control settings for a Realm file is performed through one of two means: applying/revoking permission values and offer/response objects.

Granting permissions

Swift
Objective-C
Java
Javascript
.Net
let permission = SyncPermission(realmPath: realmPath, // The remote Realm path on which to apply the changes
identity: anotherUserID, // The user ID for which these permission changes should be applied
accessLevel: .write) // The access level to be granted
user.apply(permission) { error in
if let error = error {
// handle error
return
}
// permission was successfully applied
}

To apply the permission changes for all Realms managed by the user, specify a realmPath value of *. To apply the permission changes for all users authorized with the Object Server, specify a userID value of *.

In addition to granting permissions based on the Realm Object Server identity of the user, it is also possible to grant permissions based on their Realm Object Server username:

let permission = SyncPermission(realmPath: realmPath,
username: "alice@realm.example.org",
accessLevel: .write)
user.apply(permission) { error in
// ...
}
RLMSyncPermission *permission = [[RLMSyncPermission alloc] initWithRealmPath:realmPath // The remote Realm path on which to apply the changes
identity:anotherUserID // The user ID for which these permission changes should be applied
accessLevel:RLMSyncAccessLevelWrite]; // The access level to be granted
[user applyPermission:permission callback:^(NSError *error) {
if (error) {
// handle error
return;
}
// permission was successfully applied
}];

To apply the permission changes for all Realms managed by the user, specify a realmPath value of *. To apply the permission changes for all users authorized with the Object Server, specify a userID value of *.

In addition to granting permissions based on the Realm Object Server identity of the user, it is also possible to grant permissions based on their Realm Object Server username:

RLMSyncPermission *permission = [[RLMSyncPermission alloc] initWithRealmPath:realmPath
username:@"alice@realm.example.org"
accessLevel:RLMSyncAccessLevelWrite];
[user applyPermission:permission callback:^(NSError *error) {
// ...
}];

Permission changes can be applied (i.e. granted or revoked) via the applyPermissions(permissionRequest, callback) method in order to directly increase or decrease other users’ access to a Realm.

PermissionManager pm = user.getPermissionManager();
// Create request
UserCondition condition = UserCondition.userId(user.getIdentity());
AccessLevel accessLevel = AccessLevel.WRITE;
PermissionRequest request = new PermissionRequest(condition, url, accessLevel);
pm.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() {
@Override
public void onSucesss() {
// Permissions where succesfully changed
}
@Override
public void onError(ObjectServerError error) {
// Something went wrong
}
});

UserCondition specifies which users are effected and has 3 factory methods: - userId() - use this to apply permissions based on a user’s Identity(the internal Id that Realm generates). - userName() - use this to change permissions by specifying a user’s username in the Username/Password provider. This is normally their email address. - nonExistingPermissions() - use this to apply the permissions to all users not already having access to the Realm. This can e.g. be useful to give everyone read access to a Realm without having to enumerate them.

The last argument controls the AccessLevel that the user will be granted. Higher access implies all lower tiers, e.g. WRITE implies READ, ADMIN implies READ and WRITE.

let user = Realm.Sync.User.current;
user.applyPermissions(userId, realmPath, 'write').then(permissionChange => {
// an object with the applied changes and its metadata
})
.catch(error => {
// ...
});

To apply the permission changes for all Realms managed by the user, specify a realmPath value of *.

Permission changes can be applied (i.e. granted or revoked) via the User.ApplyPermissionsAsync method in order to directly increase or decrease other users’ access to a Realm.

var condition = PermissionCondition.UserId("some-user-id");
var realmUrl = "realm://my-server.com/myRealm";
await user.ApplyPermissionsAsync(condition, realmUrl, AccessLevel.Read);

There are three factory methods for PermissionCondition:

  • UserId - use this to apply permissions based on a user’s Identity(the internal Id that Realm generates).

  • Email - use this to change permissions by specifying a user’s email (username) in the Username/Password provider.

  • Default - use this to apply default permissions that will be granted to all users unless an explicit permission is applied for them. The AccessLevel granted alongside this condition will also be used as default access level for future new users.

The last argument controls the AccessLevel that the user will be granted. Higher access implies all lower tiers, e.g. Write implies Read, Admin implies Read and Write. If AccessLevel.None is passed, this will revoke the user’s permissions for this Realm.

Note that realmUrl needs to be the full url and the tilde character (~) will not be expanded to the user's id.

Revoking Permissions

Swift
Objective-C
Java
Javascript
.Net

Revoking permissions can either be done by granting a permission value with an access level of .none or by passing a permission value with any level to SyncUser.revokePermission(...).

Revoking permissions can either be done by granting a permission value with an access level of RLMSyncAccessLevelNone or by passing a permission value with any level to -[RLMSyncUser revokePermission:callback:].

If AccessLevel.NONE is passed, this will revoke the user’s permissions for this Realm.

Revoking permissions can either be done by granting a permission value with an access level of 'none'.

Revoking permissions can either be done by granting a permission value with an access level of AccessLevel.None.

Offering Permissions

Swift
Objective-C
Java
Javascript
.Net

Permission offers can be used to share Realms between users. You need not write any server code; permission offers are created and accepted entirely through the client APIs.

If you want to share access to a Realm that you own or manage, create a permission offer. To do so, call the SyncUser.createOfferForRealm(at:, accessLevel:, expiration:, callback:) method. This method will asynchronously make a permission offer; the callback will be called with a string token representing the permission offer once the operation completes successfully.

Permission offers specify the URL of the Realm to offer access to, what level of access to grant to the recipient, and optionally a date after which the permission offer expires and can no longer be redeemed. (Users who have already accepted the offer do not lose access to the Realm.) If this date is unspecified or set to nil, the offer will never expire.

let realmURL = URL(string: "realm://realm.example.org/~/recipes")!
// Offer read-write access to `offeringUser`'s personal instance of the `recipes` Realm.
offeringUser.createOfferForRealm(at: realmURL, accessLevel: .write, expiration: nil) { (token, error) in
guard let token = token else {
print("Not able to create a permission offer! Error was: \(error!)")
return
}
// (`token` can now be passed out of the closure and given to another user.)
}

This string token can then be passed to a different user via any appropriate channel (such as e-mail) and accepted using the SyncUser.acceptOffer(forToken:, callback:) method. This method also runs asynchronously; the callback will be called with the URL of the Realm which was represented by the offer. This URL can then be used to open Realms as the recipient.

let token = getOfferToken()
receivingUser.acceptOffer(forToken: token) { (realmURL, error) in
guard let realmURL = realmURL else {
print("Not able to accept a permission offer! Error was: \(error!)")
return
}
// We can now use `realmURL` to open the Realm.
// (Remember that if we only have .read permissions, we must use the `asyncOpen` API.)
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: receivingUser, realmURL: realmURL))
let realm = try! Realm(configuration: config)
// ...
}

Note that a user’s device must be able to communicate with a Realm Object Server in order to create and accept permission offers.

Permissions granted by permission offers are additive: if a user has write access to a certain Realm and is offered (and accepts) read access to that same Realm, they do not lose their existing write access.

Permission offers can be used to share Realms between users. You need not write any server code; permission offers are created and accepted entirely through the client APIs.

If you want to share access to a Realm that you own or manage, create a permission offer. To do so, call the -[RLMSyncUser createOfferForRealmAtURL:accessLevel:expiration:callback:] method. This method will asynchronously make a permission offer; the callback will be called with a string token representing the permission offer once the operation completes successfully.

Permission offers specify the URL of the Realm to offer access to, what level of access to grant to the recipient, and a date after which the permission offer expires and can no longer be redeemed. (Users who have already accepted the offer do not lose access to the Realm.) If this date is specified as nil, the offer will never expire.

NSURL *realmURL = [NSURL URLWithString:@"realm://realm.example.org/~/recipes"];
// Offer read-write access to `offeringUser`'s personal instance of the `recipes` Realm.
[offeringUser createOfferForRealmAtURL:realmURL
accessLevel:RLMSyncAccessLevelWrite
expiration:nil
callback:^(NSString *token, NSError *error) {
if (error) {
NSLog(@"Not able to create a permission offer! Error was: %@", error);
return;
}
// (`token` can now be passed out of the block and given to another user...)
}];

This string token can then be passed to a different user via any appropriate channel (such as e-mail) and accepted using the -[RLMSyncUser acceptOfferForToken:callback:] method. This method also runs asynchronously; the callback will be called with the URL of the Realm which was represented by the offer. This URL can then be used to open Realms as the recipient.

NSString *token;
// (get token...)
[receivingUser acceptOfferForToken:token callback:^(NSURL *realmURL, NSError *error) {
if (error) {
NSLog(@"Not able to accept a permission offer! Error was: %@", error);
return;
}
// We can now use `realmURL` to open the Realm.
// (Remember that if we only have read permissions, we must use the `asyncOpen` API.)
RLMSyncConfiguration *syncConfig = [[RLMSyncConfiguration alloc] initWithUser:receivingUser realmURL:realmURL];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = syncConfig;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
// ...
}];

Note that a user’s device must be able to communicate with a Realm Object Server in order to create and accept permission offers.

Permissions granted by permission offers are additive: if a user has write access to a certain Realm and is offered (and accepts) read access to that same Realm, they do not lose their existing write access.

In some situations you want to grant permissions to users, but either you don’t know who they are or perhaps you want to extend the offer through a different channel like email. Examples are scanning a QR code or sending access over an email.

In those cases, you can create a permission offer token that represents the intent of getting access, but access is not given until the receiver accepts the token.

A user can create this opaque token returned by makeOffer(offer, callback):

PermissionManager pm = user.getPermissionManager();
// Create Offer
String realmUrl = "realm://my-server.com/~/myRealm";
Date expiresAt = new Date(2017, 08, 25);
AccessLevel accessLevel = AccessLevel.READ;
PermissionOffer offer = new PermissionOffer(realmUrl, accessLevel, expiresAt);
// Accept token
pm.makeOffer(offer, new PermissionManager.MakeOfferCallback() {
@Override
public void onSucces(String token) {
// Send token to other users
}
@Override
public void onError(ObjectServerError error) {
// An error happened
}
});

The optional expiresAt argument controls when the offer expires - i.e. using the token after that date will no longer grant permissions to that Realm. You can also revoke the token manually using revokeOffer(token, callback).

Users who have already consumed the token to obtain permissions will not lose their access if the token is revoked or expires.

Once a user has received a token they can consume it to obtain the permissions offered:

PermissionManager pm = user.getPermissionManager();
String token = getToken();
var realmUrl = pm.acceptOffer(token, new PermissionManager.AcceptOfferCallback() {
@Override
public void onSucces(String realmUrl, Permission permission) {
// User can now access the Realm
SyncConfiguration config = new SyncConfiguration.Builder(realmUrl, user).build();
Realm realm = Realm.getInstance(config);
}
@Override
public void onError(ObjectServerError error) {
// An error happened
}
});

In some cases you might want to revoke existing offers after an app has been restarted. In that case, it is possible to get a list of all existing offers using getCreatedOffers(callback).

PermissionManager pm = user.getPermissionManager();
user.getCreatedOffers(new PermissionManager.OffersCallback() {
@Override
public void onSuccess(RealmResults<PermissionOffer> offers) {
for (PermissionOffer offer : offers) {
// Handle individual offer
}
}
@Override
public void onError(ObjectServerError error) {
// handle error
}
});

A user can offer permissions to their Realm by sharing the opaque token returned by offerPermissionsAsync:

const realmUrl = 'realm://my-server.com/~/myRealm';
const oneDay = 1000 * 60 * 60 * 24;
const expiration = new Date(Date.now() + 7 * oneDay);
userA.offerPermissions(realmUrl, 'write', expiration)
.then(token => { /* ... */ });

The optional expiresAt argument controls when the offer expires - i.e. using the token after that date will no longer grant permissions to that Realm. Users who have already consumed the token to obtain permissions will not lose their access after that date. If you want to revoke permissions, use applyPermissionsAsync.

Once a user has received a token, e.g. by sharing it via messaging app, or scanning a QR code, they can consume it to obtain the permissions offered:

const token = "...";
userB.acceptPermissionOffer(token)
.then(realmUrl => Realm.open({ schema: [/* ... */], sync: { user: userB, url: realmUrl }}))
.then(realm => {
// ..use the realm
});

A user can offer permissions to their Realm by sharing the opaque token returned by OfferPermissionsAsync:

var realmUrl = "realm://my-server.com/myRealm";
var expiration = DateTimeOffset.UtcNow.AddDays(7);
var token = await userA.OfferPermissionsAsync(realmUrl, AccessLevel.Write, expiresAt: expiration);

The optional expiresAt argument controls when the offer expires - i.e. using the token after that date will no longer grant permissions to that Realm. Users who have already consumed the token to obtain permissions will not lose their access after that date. If you want to revoke it, use ApplyPermissionsAsync.

Note that realmUrl needs to be the full url and the tilde character (~) will not be expanded to the user's id.

Once a user has received a token, e.g. by sharing it via messaging app, or scanning a QR code, they can consume it to obtain the permissions offered:

var token = "...";
var realmUrl = await userB.AcceptPermissionOfferAsync(token);
// Use the realmUrl to open the Realm
var config = new SyncConfiguration(userB, new Uri(realmUrl));
var realm = await Realm.GetInstanceAsync(config);

Not what you were looking for? Leave Feedback