From 4d362adcedf8fd30fff12e97b76c9d6413a16c37 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 18 Sep 2018 20:47:42 +0200 Subject: [PATCH 1/2] Add findOrCreate helper methods --- CHANGELOG.md | 2 +- .../io/realm/ObjectLevelPermissionsTest.java | 112 ++++++++++++++++++ .../realm/internal/sync/PermissionHelper.java | 65 ++++++++++ .../sync/permissions/ClassPermissions.java | 24 ++++ .../sync/permissions/RealmPermissions.java | 23 ++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 410b70b7a4..1de82595ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ### Enhancements +* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` (#XXX). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). - ## 5.5.1 (YYYY-MM-DD) ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 79eb7fd407..13183c24a0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -520,6 +520,118 @@ public void allPrivileges() { assertNoAccess(nobody); } + @Test + public void findOrCreate_unmanagedObjectThrows() { + RealmPermissions realmPermissions = new RealmPermissions(); + try { + realmPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + + ClassPermissions classPermissions = new ClassPermissions(); + try { + classPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + } + + @Test + public void findOrCreate_notInTransactionThrows() { + RealmPermissions realmPermissions = realm.getPermissions(); + try { + realmPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + try { + classPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + } + + @Test + public void findOrCreate_nullThrows() { + realm.beginTransaction(); + RealmPermissions realmPermissions = realm.getPermissions(); + try { + //noinspection ConstantConditions + realmPermissions.findOrCreate(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + try { + //noinspection ConstantConditions + classPermissions.findOrCreate(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void findOrCreate_createRole() { + realm.beginTransaction(); + assertNull(realm.where(Role.class).equalTo("name", "role1").findFirst()); + assertNull(realm.where(Role.class).equalTo("name", "role2").findFirst()); + + // Realm permissions + RealmPermissions realmPermissions = realm.getPermissions(); + Permission p = realmPermissions.findOrCreate("role1"); + assertEquals("role1", p.getRole().getName()); + assertTrue(p.getRole().getMembers().isEmpty()); + + // Class permissions + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + p = classPermissions.findOrCreate("role2"); + assertEquals("role2", p.getRole().getName()); + assertTrue(p.getRole().getMembers().isEmpty()); + } + + @Test + public void findOrCreate_createPermission() { + realm.beginTransaction(); + realm.createObject(Role.class, "role1"); + + RealmPermissions realmPermissions = realm.getPermissions(); + assertEquals(1, realmPermissions.getPermissions().size()); + Permission p = realmPermissions.findOrCreate("role1"); + assertNoAccess(p); + assertEquals("role1", p.getRole().getName()); + assertEquals(2, realmPermissions.getPermissions().size()); + assertTrue(p.equals(realmPermissions.getPermissions().last())); + + // Class permissions + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + assertEquals(1, classPermissions.getPermissions().size()); + p = classPermissions.findOrCreate("role2"); + assertNoAccess(p); + assertEquals("role2", p.getRole().getName()); + assertEquals(2, classPermissions.getPermissions().size()); + assertTrue(p.equals(classPermissions.getPermissions().last())); + } + + @Test + public void findOrCreate_findExistingPermission() { + realm.beginTransaction(); + + RealmPermissions realmPermissions = realm.getPermissions(); + Permission p = realmPermissions.findOrCreate("everyone"); + assertFullAccess(p); + assertEquals("everyone", p.getRole().getName()); + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + p = classPermissions.findOrCreate("everyone"); + assertFullAccess(p); + assertEquals("everyone", p.getRole().getName()); + } + + private void assertFullAccess(RealmPrivileges privileges) { assertTrue(privileges.canRead()); assertTrue(privileges.canUpdate()); diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java new file mode 100644 index 0000000000..880d28439e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.internal.sync; + +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.internal.annotations.ObjectServer; +import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.Role; + +/** + * Helper class for working with fine-grained permissions + */ +@ObjectServer +public class PermissionHelper { + + /** + * Finds or creates the permission object for a given role. Creating objects if they cannot + * be found. + * + * @param container RealmObject containg the permission objects + * @param permissions the list of permissions + * @param roleName the role to search for + * @return + */ + public static Permission findOrCreatePermissionForRole(RealmObject container, RealmList permissions, String roleName) { + if (!container.isManaged()) { + throw new IllegalStateException("'findOrCreate()' can only be called on managed objects."); + } + Realm realm = container.getRealm(); + if (!realm.isInTransaction()) { + throw new IllegalStateException("'findOrCreate()' can only be called inside a write transaction."); + } + + // Find existing permission object or create new one + Permission permission = permissions.where().equalTo("role.name", roleName).findFirst(); + if (permission == null) { + + // Find existing role or create new one + Role role = realm.where(Role.class).equalTo("name", roleName).findFirst(); + if (role == null) { + role = realm.createObject(Role.class, roleName); + } + + permission = realm.copyToRealm(new Permission.Builder(role).noPrivileges().build()); + permissions.add(permission); + } + + return permission; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java index aadd98706f..d2a4d94854 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java @@ -23,6 +23,7 @@ import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.sync.PermissionHelper; /** * Class describing all permissions related to a given Realm model class. These permissions will @@ -61,6 +62,7 @@ public ClassPermissions() { * @param clazz class to create permissions. */ public ClassPermissions(Class clazz) { + //noinspection ConstantConditions if (clazz == null) { throw new IllegalArgumentException("Non-null 'clazz' required."); } @@ -89,4 +91,26 @@ public String getName() { public RealmList getPermissions() { return permissions; } + + /** + * Finds the permissions associated with a given {@link Role}. If either the role or the permission + * object doesn't exists, it will be created. + *

+ * If the {@link Permission} object is created because one didn't exist already, it will be + * created with all privileges disabled. + *

+ * If the the {@link Role} object is created because one didn't exists, it will be created + * with no members. + * + * @param roleName name of the role to find. + * @return permission object for the given role. + * @throws IllegalStateException if this object is not managed by Realm. + * @throws IllegalStateException if this method is not called inside a write transaction. + * @throws IllegalArgumentException if a {@code null} or empty + */ + public Permission findOrCreate(String roleName) { + // Error handling done in the helper class + return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); + } + } diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java index 41b0f05009..1b8915e245 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java @@ -15,11 +15,13 @@ */ package io.realm.sync.permissions; +import io.realm.Realm; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.sync.PermissionHelper; /** * Class describing all permissions related to a given Realm. Permissions attached to this class @@ -48,4 +50,25 @@ public RealmPermissions() { public RealmList getPermissions() { return permissions; } + + /** + * Finds the permissions associated with a given {@link Role}. If either the role or the permission + * object doesn't exists, it will be created. + *

+ * If the {@link Permission} object is created because one didn't exist already, it will be + * created with all privileges disabled. + *

+ * If the role {@link Role} object is created because one didn't exists, it will be created + * with no members. + * + * @param roleName name of the role to find. + * @return permission object for the given role. + * @throws IllegalStateException if this object is not managed by Realm. + * @throws IllegalStateException if this method is not called inside a write transaction. + * @throws IllegalArgumentException if a {@code null} or empty + */ + public Permission findOrCreate(String roleName) { + // Error handling done in the helper class + return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); + } } From 85146a097af723d9e6cc296dccca44e81ba6c11f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 18 Sep 2018 21:10:24 +0200 Subject: [PATCH 2/2] Fix changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de82595ed..8d752279e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Enhancements -* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` (#XXX). +* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` (#6168). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). ## 5.5.1 (YYYY-MM-DD)