realm-object-server
realm-object-server copied to clipboard
Help: creating a global Realm file to allow all users to write/read from it
Goals
My app is a collaborative app where users should be able to write/read into a global Realm file to share data among them. I would also like to include some initial data (realmObjects) in the Realm file which will be common to all users.
Expected Results
User will sign up to, then, be able to create data and share the data with all the users. The initial data, created only once, should be available to all users.
Actual Results
I am not able to assign user permissions (write/read) to each new user once they sign-up/login in the app. The common data is created multiple time since the query (realm.where(realmObject.class).findall() returns no results, even though it has been created.
Code Sample
public static final String AUTH_URL = "http://" + BuildConfig.OBJECT_SERVER_IP + ":9080/auth";
public static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/default";
UserManager Class
public class UserManager {
// Supported authentication mode
public enum AUTH_MODE {
PASSWORD,
FACEBOOK,
GOOGLE
}
private static AUTH_MODE mode = AUTH_MODE.PASSWORD; // default
public static void setAuthMode(AUTH_MODE m) {
mode = m;
}
public static void logoutActiveUser() {
switch (mode) {
case PASSWORD: {
break;
}
case FACEBOOK: {
LoginManager.getInstance().logOut();
break;
}
case GOOGLE: {
break;
}
}
SyncUser.currentUser().logout();
}
// Configure Realm for the current active user
public static void setActiveUser(SyncUser user) {
Realm.removeDefaultConfiguration();
SyncConfiguration defaultConfig = new SyncConfiguration.Builder(user, REALM_URL).name("Realm").schemaVersion(0).build();
Realm.setDefaultConfiguration(defaultConfig);
setDefaultPermissionsRealm(user);
}
private static void setDefaultPermissionsRealm(SyncUser user){
if (user.isAdmin()){
Realm realm = user.getManagementRealm();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Boolean mayRead = true; // Grant read access
Boolean mayWrite = true; // Keep current permission
Boolean mayManage = false; // Revoke management access
PermissionChange change = new PermissionChange(REALM_URL,
"*",
mayRead,
mayWrite,
mayManage);
realm.insert(change);
}
});
}
}
}
SplashActivity
SyncUser.loginAsync(SyncCredentials.usernamePassword(eMail, password, true), AUTH_URL, new SyncUser.Callback() {
@Override
public void onSuccess(SyncUser syncUser) {
loginRegistrationSuccess = true;
registrationComplete(syncUser);
}
@Override
public void onError(ObjectServerError error) {
loginRegistrationSuccess = false;
}
});
facebookAuthRegistration = new FacebookAuth((LoginButton) findViewById(R.id.sign_up_facebook), this) {
@Override
public void onRegistrationComplete(final LoginResult loginResult, User userInfo) {
UserManager.setAuthMode(UserManager.AUTH_MODE.FACEBOOK);
SyncCredentials credentials = SyncCredentials.facebook(loginResult.getAccessToken().getToken());
SyncUser.loginAsync(credentials, AUTH_URL, SplashScreenActivity.this);
}
};
googleAuthRegistration = new GoogleAuth((Button) findViewById(R.id.sign_up_google), this, googleAuthLogin.getmGoogleApiClient()) {
@Override
public void onRegistrationComplete(GoogleSignInResult result) {
UserManager.setAuthMode(UserManager.AUTH_MODE.GOOGLE);
GoogleSignInAccount acct = result.getSignInAccount();
SyncCredentials credentials = SyncCredentials.google(acct.getIdToken());
SyncUser.loginAsync(credentials, AUTH_URL, SplashScreenActivity.this);
}
};
@Override
public void onSuccess(SyncUser syncUser) {
loginRegistrationSuccess = true;
showProgress(false);
registrationComplete(syncUser);
}
private void registrationComplete(SyncUser syncUser) {
UserManager.setActiveUser(syncUser);
Intent mainIntent = new Intent(SplashScreenActivity.this, MainActivity.class);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
SplashScreenActivity.this.startActivity(mainIntent);
SplashScreenActivity.this.finish();
}
Version of Realm and Tooling
- Realm Object Server Version: 3.3.0
- Flavor:
- [x] Developer
- [ ] Professional
- [ ] Enterprise
- Server OS & Version: Ubuntu 16.04
- Client SDK Version: Android Studio 2.3.2 (compileSdkVersion 25)
- Client OS & Version:
Could you please help me out with this? Thanks
I am quite interested in this as well. Hope somebody can help with this.
Is there any example code for how to change the permissions on a global realm in JavaScript?
Thanks for filing this. First, we definitely want to make this easier, by adding the ability to create Realms manually via the Realm browser. Part of the effort to include the browser in the server dashboard is to rewrite the Mac version in JS and add new functionality, so this is an ongoing process.
To manually create a global Realm, I would recommend just doing this in Javascript during development:
Realm.Sync.User.login('http://my.realm-auth-server.com:9080', 'adminUsername', 'p@s$w0rd',
(error, adminUser) => {
if (!error) {
var realm = new Realm({
sync: {
user: adminUser,
url: 'realm://object-server-url:9080/globalRealm',
},
schema: [/* ... */]
});
// Apply permissions to everyone
const managementRealm = user.openManagementRealm();
var permObj;
managementRealm.write(() => {
permObj = managementRealm.create('PermissionChange', {
id: generateUniqueId(), // implement something that creates a unique id.
createdAt: new Date(),
updatedAt: new Date(),
userId: '*', // To apply the permission changes for all Users authorized with the Object Server, specify a userId value of *.
realmUrl: 'realm://object-server-url:9080/globalRealm',
});
});
// Listen for `PermissionChange` object to be processed
managementRealm.objects('PermissionChange').filtered('id = $0', permObj.id).addListener((objects, changes) => {
console.log("Permission Status: " + permObj.statusMessage);
});
})
As for handling the data in the global Realm, you can accomplish this via two mechanisms. To make your code work where you want to query the global Realm and check for existing data, you will instead need to open the Realm via asyncOpen API with admin user. Note the asyncOpen will return the Realm in a callback, ensuring the all the data is downloaded first. Whereas the regular open API will synchronously create a Realm on the client and start downloading the data asynchronously. Thus any initial query before the download completes will show incorrect data than what is represented on the server.
This method will work in most scenarios, but still has a race condition where the check for data even after waiting for download still doesn't ensure another client doesn't create the same value at the same time leading to duplicates.
Instead, the proper way to handle global unique data with Realm's sync is to use primary keys on the objects in the global Realm. Our merge algorithm is setup to "merge" objects with the same primary key together. This merge uses the same concepts - deletes always win, last update wins, and inserts in lists are ordered by time - but also uses another factor: default values lose to existing operations.
The last point is likely confusing, so let me try to explain. In Javascript, we support default values as specified:
const CarSchema = {
name: 'Car',
primaryKey: 'id',
properties: {
id: {type: 'int', default 0},
make: {type: 'string'},
model: {type: 'string'},
drive: {type: 'string', default: 'fwd'},
miles: {type: 'int', default: 0}
}
};
realm.write(() => {
// Since `miles` is left out it defaults to `0`, and since
// `drive` is specified, it overrides the default value
//
// Using `true` to create and update given the primary key
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'}, true);
});
In the above example, we are using default values for id, drive, and miles. If we perform this write on many clients, each individual copy of the object will merge with the others so that the Realm always has a single Car object with id=0. Now later on say we perform another write to update miles on client A, but client B also performs the first write just shortly after:
// Client A
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'Accord', miles: 500}, true);
});
// Client B
// Meanwhile another write happens by client B just briefly after client A's write
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'}, true);
});
Once client A and B sync between each other, client B's copy of the Car object will also have miles=500!
The reason for this is that client A specifically set miles=500, whereas client B just supplied a default value for miles. The merge algorithm will treat the specific set as the winner even though it happened before the creation of the object on client B.
This attribute makes it very powerful to use Realm sync to create global objects in a safe distributed manner. You can use default values to synchronously create objects on the client, so that even before downloading the server data, the object always exists, while using default values to make sure you preserve any existing operations made by other existing clients.
This is very very helpful. Thank you, Adam!
Spencer Schoeben
It still doesn't seem to be working, unfortunately. Permission changes aren't taking effect. I get back a blank string.
Opening Realm file: /Users/spencer/Dropbox (Integral Studio)/My Stuff/wp_realm_sync/realm-object-server/f31b150ef0de1638337c49c431879f49/realm%3A%2F%2Frealm.integral.sh%3A9080%2Fyachty%2Fproduction
Opening Realm file: /Users/spencer/Dropbox (Integral Studio)/My Stuff/wp_realm_sync/realm-object-server/f31b150ef0de1638337c49c431879f49/realm%3A%2F%2Frealm.integral.sh%3A9080%2F%7E%2F__management
Permission Status: null
Connection[1]: Session[1]: Starting session for '/Users/spencer/Dropbox (Integral Studio)/My Stuff/wp_realm_sync/realm-object-server/f31b150ef0de1638337c49c431879f49/realm%3A%2F%2Frealm.integral.sh%3A9080%2Fyachty%2Fproduction'
Connection[1]: Resolving 'realm.integral.sh:9080'
Connection[1]: Connecting to endpoint '138.197.233.20:9080' (1/1)
Connection[2]: Session[1]: Starting session for '/Users/spencer/Dropbox (Integral Studio)/My Stuff/wp_realm_sync/realm-object-server/f31b150ef0de1638337c49c431879f49/realm%3A%2F%2Frealm.integral.sh%3A9080%2F%7E%2F__management'
Connection[2]: Resolving 'realm.integral.sh:9080'
Connection[2]: Connecting to endpoint '138.197.233.20:9080' (1/1)
Connection[2]: Connected to endpoint '138.197.233.20:9080' (from '192.168.7.134:49502')
Connection[1]: Connected to endpoint '138.197.233.20:9080' (from '192.168.7.134:49501')
Connection[2]: Session[1]: Sending: BIND(server_path='/f31b150ef0de1638337c49c431879f49/__management', signed_user_token_size=633, need_file_ident_pair=0)
Connection[1]: Session[1]: Sending: BIND(server_path='/yachty/production', signed_user_token_size=597, need_file_ident_pair=1)
Connection[2]: Session[1]: Sending: IDENT(server_file_ident=5259679409261093284, client_file_ident=2, client_file_ident_secret=1494669451506513031, scan_server_version=57, scan_client_version=46, latest_server_version=57, latest_server_session_ident=1726426117877430376)
Connection[1]: Session[1]: Received: ALLOC(server_file_ident=1, client_file_ident=2, client_file_ident_secret=2428648948360514317)
Connection[1]: Session[1]: Sending: IDENT(server_file_ident=1, client_file_ident=2, client_file_ident_secret=2428648948360514317, scan_server_version=0, scan_client_version=0, latest_server_version=0, latest_server_session_ident=0)
Permission Status:
@bigfish24 Thanks for posting this example. Its very helpful. The realm is getting created, as is the row in the PermissionChange table in the management realm. However, the callback listener never fires / doesnt print console.log("Permission Status...) and it does not appear the realm gets set to mayRead as I want.
Thanks for the info @bigfish24, I appreciate that you create some examples in JavaScript, and even though my first post was in Java, you posted a very comprehensive description of the case, and I understand JavaScript. So, it is enough for me. I will wait you for solving the other problems related to the creation of Realm files by the admin to try if the permissionChanges take effect.
Thanks again. Truly appreciate it.
Glad this helped and we should have a fix for the permission bug in the next day or two. Sorry about that!
There was a regression within the permission management system when using admin users to operate with unowned Realm files (in the global scope, e.g. /global). We've a fix and bumped up test coverage around this case. This was shipped with version 1.7.5.
Further investigations have also shown that there is currently a bug in the Java client SDK which could be reproduced by a few users in related usage scenarios. We're still working on that. Please refer to https://github.com/realm/realm-java/issues/4750 for more info.
Is 1.7.5. out yet? I dont see it on the releases page.
The Github releases page was not updated, but 1.7.5 has been released. You should be able to just upgrade: https://realm.io/docs/realm-object-server/#upgrading. We will fix the Github releases page tomorrow.
The macOS bundle available on realm.io is still 1.7.4.
Ignore my previous (now deleted) post. I can confirm it works fine in 1.7.5 and 1.7.6.