realm-object-server
realm-object-server copied to clipboard
Determining users that have been provided permission to a Realm
I would like to determine the users that have been provided permission to the current SyncUser
's owned Realm (in my case using SyncPermissionChangeOffer
).
I would think to perform this like:
let permissionRealm = try! SyncUser.current!.permissionRealm()
let otherUserIDs = permissionRealm.objects(SyncPermission.self).filter("userId != %@", user.identity!).map { $0.userId}
However, the obtained result is empty. What do I wrong here? And what would be the preferred way to determine those users?
Furthermore, the next step would be to determine the user details based on the userID. This functionality was already requested (among myself at #142). But since this would be basic functionality that I would like to offer in my app before launch, I was wondering in what timeframe it could be expected (a few weeks, months or later?) .
Many thanks for the help.
Hi @Taco55. Thanks for reaching out about this. I wanted to let you know that we've received your message and that someone will review what you've shared and follow-up with you soon.
@Taco55 this functionality isn't currently offered as you have discovered. The permission Realm only contains the user's permissions. Thus if I grant access to my Realm to user X. User X's permission Realm includes the Permission
object that say he/she has access, whereas my permission Realm does not include a reference I have granted this.
I will note it in our roadmap, assuming we could get to this in a 2-4 week timeline as a rough estimate.
Great to here to this will be implemented in a future version.
I think that this would be import functionality. Especially when sensitive information is shared it might be important to know which users are granted permission, but also to have the ability to change this.
I would like to add to this, that in case the permission is granted using PermissionChangeOffer
, it is important to have the option to invalidate the token after use. Otherwise, a user would be able to get permission again, even after the permission was revoked or changed by the Realm owner.
whereas my permission Realm does not include a reference I have granted this.
When I look to my Permission
Realm with the Realm Browser after granting permission to user X, I can see a new entry with the userID
of user X. Isn't this just the reference that is needed to fetch the user info? Apparently, the userID
cannot be obtained with the commands I used in my first post and I will just wait until this becomes (officially) available....
I would like to add to this, that in case the permission is granted using PermissionChangeOffer, it is important to have the option to invalidate the token after use. Otherwise, a user would be able to get permission again, even after the permission was revoked or changed by the Realm owner.
I believe that after the token is used it cannot be reused again. @mrackwitz can you confirm?
I would like to add to this, that in case the permission is granted using PermissionChangeOffer, it is important to have the option to invalidate the token after use. Otherwise, a user would be able to get permission again, even after the permission was revoked or changed by the Realm owner.
I believe that after the token is used it cannot be reused again. @mrackwitz can you confirm?
This isn't actually the case. Tokens can be used by design multiple times by different users. The intention behind that was that this allows to craft URIs to share resources, e.g. realmTasks:///lists/groceries?token=$3cr3t…
. The default is that these are consistently working multiple times, so that this functionality is also suitable for sharing 1-to-n to be the complement to the PermissionChange
-based workflow which allows sharing 1-to-1 and 1-to-everyone.
Additional logic for revoking tokens after permissions were granted can be implemented either using the Event Handling framework or in the client of the user creating the PermissionOffer
by observing the permission-Realm for granted permissions. The latter is only guaranteed to take effect as soon as the offering user is online the next time.
This can be additionally mitigated depending on the scenario by setting an expiration date, so that the token expires after a given time.
I think that is a feature which really is needed. How else can I find out which user has access to a specific realm?
@RoLu88 Today I discovered that the new retrievePermissions
method of the SyncUser
class could work fairly well for this use case.
For example:
public class func retrieveSharedUserIds(realmURL: String, completionHandler: @escaping ((_ userIds: [String]?, _ error: Error?) -> ()) ) {
// Get realmPath from realmURL
let components = realmURL.components(separatedBy: "/")
let realmPath = "/" + components[3] + "/" + components[4]
let user = SyncUser.current!
user.retrievePermissions { permissions, error in
if let error = error { completionHandler(nil, error); return }
guard let permissions = permissions else { print("no permissions"); return }
let predicate = NSPredicate(format: "userId != %@ AND userId != '*' AND path = %@", user.identity!, realmPath)
let otherUserIDs = permissions.objects(with: predicate).map { $0.userId! }
completionHandler(otherUserIDs, nil)
}
}
With the retrieved ID's in userIds
, it is now possible to retrieve user details, for example using the new retrieveInfoForUser
method.
Unfortunately, this latter method does not work for me since it requires the identityProvider
of the user which I do not know in advance, and an admin user to retrieve this information . But maybe, it suffices for you.
@mrackwitz , @bigfish24
Could you please elaborate on how the retrieveInfoForUser
is intended to be used? For example in the above use case where only an e-mailadres of the current (non-admin) SyncUser
is required?
Although I don't know whether it is possible, it would be better to have the e-mailadres as a property of a SyncUser
so that no admin privilege is required and the identity provider is already available.
For now, I solved this by creating a "user details" Realm for each SyncUser
which contain all user details, and which can now be synced after a shared user id is retrieved using retrievePermissions
(unfortunately, this requires these Realms to have global read access).
Right now SyncUser.retrieveInfo(for:, identityProvider:, completion:)
is more of an administrative API. It came out of a need for use on the server-side (Node) to lookup the external identity from a Realm user ID when using custom auth. We added to the other client SDKs since it could have other uses.
We currently don't offer a straight forward way to know which users have been granted access to a given Realm. Instead, each user can view his/her permissions. We need to improve this and we are working on a revamp of the permissions alongside a RMP 2.0 launch in September.
@bigfish24 Clear. For now, my work-around works good enough, but I look forward to the more solid solution and RMP 2.0!
@Taco55 , @bigfish24 , @mrackwitz Do you know if the new features are already available for Realm using Xamarin? I can't find a class named "SyncUser" or "SyncPermissionValue". Is there any other documentation for Xamarin?
Thanks for your help!
@RoLu88 which new API are you referring to? On Xamarin, thanks to namespacing, SyncUser
is simply User
. For example, to get permissions granted to the current user, you can use User.Current. GetGrantedPermissionsAsync(Recipient.CurrentUser)
.
@nirinchev tahnks for the explanation, but I think I should have read all the details bigfish24 wrote.
'We currently don't offer a straight forward way to know which users have been granted access to a given Realm. Instead, each user can view his/her permissions.'
I exactly need the described feature to find out which users have been granted access to a given Realm and looking forward to RMP 2.0 launch in September.
@bigfish24 Is there any workaround for this scenario without asking for each users permissions? How do I get to know the user ids which might have access?
Sorry to hear that's not what you hoped for. Do you think you can share more information about the use case you'd like to support? Probably knowing the business requirements will help us figure out a temporary workaround.
@nirinchev We're developing an app which should be used by several users sharing the same data / Realm. Let's say there is an admin user in this scenario who is allowed to grant users the permission to get access to the realm with some kind of an QR Code which the new user can scan to get all the granted permissions.
On the other side the admin user should all the time be able to revoke or modify the permissions without getting in touch with the user. We need a simple way to request all the users granted access and their permissions to a given Realm.
I hope this clarifies our needs...
If there's just one admin user granting/revoking permissions, you could use User. GetGrantedPermissionsAsync(Recipient.OtherUser)
from the admin device. This will return an IQueryable
of all permissions granted by the admin which can later be filtered. Here's a minimal example:
private const string serverUrl = "realm://my-server.com";
// Checks if the user can administer the realm
public async Task<bool> CanAdministerRealm(User user, string realmUrl)
{
var permissions = await user.GetGrantedPermissionsAsync(Recipient.CurrentUser);
var relativePath = realmUrl.Replace(serverUrl, string.Empty);
return permissions.Any(p => p.Path == relativePath && p.MayManage);
}
// Return all permissions granted by grantor
public async Task<IQueryable<Permission>> GetGrantedPermissions(User user, string realmUrl)
{
var permissions = await user.GetGrantedPermissionsAsync(Recipient.OtherUser);
var relativePath = realmUrl.Replace(serverUrl, string.Empty);
return permissions.Where(p => p.Path == relativePath);
}
// Revoke a permission
public Task RevokePermissionsAsync(User grantor, Permission permission)
{
var condition = PermissionCondition.UserId(permission.UserId);
var realmUrl = "http://my-server.com" + permission.Path;
return user.ApplyPermissionsAsync(condition, realmUrl, AccessLevel.None);
}
Keep in mind that this will only work if you only have a single user managing the realm. If you have more than one, they'll only see permissions granted by them, but not by other managers.
@nirinchev Thanks for your example. Does this solution also show me users which got granted the permission via a token generated by the admin user?
By "token", do you mean the token returned by user.OfferPermissionsAsync
? If that is the case, then yes - users who consumed such a token should be included in the result of user.GetGrantedPermissionsAsync
.
@nirinchev Yes, that's what I meant although I'm currently using the old way (new PermissionOffer()) I will give it a try and give you a feedback. Thanks!
@nirinchev I tried your proposal and I can't get it to work. I never get any values returned from user.GetGrantedPermissionsAsync(Recipient.OtherUser)
I tried to do it as follows:
var token = await user.OfferPermissionsAsync(realmUrl, AccessLevel.Write, expiresAt: expiration);
var realmUrlB = await userB.AcceptPermissionOfferAsync(token);
var permissions = await user.GetGrantedPermissionsAsync(Recipient.OtherUser);
"permissions" never contains any values...
var pList = permissions.Where(p => p.Path == $"http://{s.ServerIP}")
I checked via the Object Server and all permissions are granted as they should be.
Do I miss anything?
That’s odd, I’ll try and investigate locally and get back at you when I have more information.
That's also what I sometimes encountered using the retrievePermissions
method in Swift using the code I mentioned above. Sometimes the permissions object does contain data and sometimes is does not, but I can hardly reproduce it. Furthermore, a revoked permission seems never be updated correctly.
It seems a bug, which I already reported here #244
It is already a while since this issue was reported.
The retrievePermissions
or GetGrantedPermissionsAsync
has been improved in ROS 2.x and in my case the problem of returning empty values (as also mentioned by @nirinchev) did not happen anymore.
Although that's great, revoked or modified are still not processed correctly. Since #244 is already closed, I opened a new issue related to this problem at #334.