realm-object-server icon indicating copy to clipboard operation
realm-object-server copied to clipboard

Determining users that have been provided permission to a Realm

Open Taco55 opened this issue 7 years ago • 22 comments

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.

Taco55 avatar Mar 29 '17 12:03 Taco55

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.

pigeon-archive avatar Mar 29 '17 18:03 pigeon-archive

@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.

bigfish24 avatar Mar 31 '17 18:03 bigfish24

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....

Taco55 avatar Apr 03 '17 07:04 Taco55

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?

bigfish24 avatar Apr 05 '17 02:04 bigfish24

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.

mrackwitz avatar Apr 10 '17 23:04 mrackwitz

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 avatar Jul 12 '17 15:07 RoLu88

@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).

Taco55 avatar Jul 31 '17 20:07 Taco55

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 avatar Jul 31 '17 23:07 bigfish24

@bigfish24 Clear. For now, my work-around works good enough, but I look forward to the more solid solution and RMP 2.0!

Taco55 avatar Aug 01 '17 08:08 Taco55

@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 avatar Aug 08 '17 10:08 RoLu88

@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 avatar Aug 08 '17 11:08 nirinchev

@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?

RoLu88 avatar Aug 08 '17 11:08 RoLu88

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 avatar Aug 08 '17 12:08 nirinchev

@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...

RoLu88 avatar Aug 08 '17 12:08 RoLu88

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 avatar Aug 08 '17 12:08 nirinchev

@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?

RoLu88 avatar Aug 08 '17 15:08 RoLu88

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 avatar Aug 08 '17 15:08 nirinchev

@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!

RoLu88 avatar Aug 09 '17 06:08 RoLu88

@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?

RoLu88 avatar Aug 09 '17 10:08 RoLu88

That’s odd, I’ll try and investigate locally and get back at you when I have more information.

nirinchev avatar Aug 09 '17 10:08 nirinchev

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

Taco55 avatar Aug 09 '17 11:08 Taco55

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.

Taco55 avatar Jan 19 '18 16:01 Taco55