diff --git a/Parse/src/main/java/com/parse/ParseACL.java b/Parse/src/main/java/com/parse/ParseACL.java index 44b7fa921..f8041619a 100644 --- a/Parse/src/main/java/com/parse/ParseACL.java +++ b/Parse/src/main/java/com/parse/ParseACL.java @@ -12,6 +12,8 @@ import org.json.JSONObject; import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; /** * A {@code ParseACL} is used to control which users can access or modify a particular object. Each @@ -23,9 +25,56 @@ public class ParseACL { private static final String PUBLIC_KEY = "*"; private final static String UNRESOLVED_KEY = "*unresolved"; - private static final String READ_PERMISSION = "read"; - private static final String WRITE_PERMISSION = "write"; private static final String KEY_ROLE_PREFIX = "role:"; + private static final String UNRESOLVED_USER_JSON_KEY = "unresolvedUser"; + + private static class Permissions { + private static final String READ_PERMISSION = "read"; + private static final String WRITE_PERMISSION = "write"; + + private final boolean readPermission; + private final boolean writePermission; + + /* package */ Permissions(boolean readPermission, boolean write) { + this.readPermission = readPermission; + this.writePermission = write; + } + + /* package */ Permissions(Permissions permissions) { + this.readPermission = permissions.readPermission; + this.writePermission = permissions.writePermission; + } + + /* package */ JSONObject toJSONObject() { + JSONObject json = new JSONObject(); + + try { + if (readPermission) { + json.put(READ_PERMISSION, true); + } + if (writePermission) { + json.put(WRITE_PERMISSION, true); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + return json; + } + + /* package */ boolean getReadPermission() { + return readPermission; + } + + /* package */ boolean getWritePermission() { + return writePermission; + } + + /* package */ static Permissions createPermissionsFromJSONObject(JSONObject object) { + boolean read = object.optBoolean(READ_PERMISSION, false); + boolean write = object.optBoolean(WRITE_PERMISSION, false); + return new Permissions(read, write); + } + } private static ParseDefaultACLController getDefaultACLController() { return ParseCorePlugins.getInstance().getDefaultACLController(); @@ -60,21 +109,20 @@ public static void setDefaultACL(ParseACL acl, boolean withAccessForCurrentUser) */ //TODO (grantland): This should be a list for multiple lazy users with read/write permissions. private ParseUser unresolvedUser; - private JSONObject permissionsById; + + private Map permissionsById; /** * Creates an ACL with no permissions granted. */ public ParseACL() { - permissionsById = new JSONObject(); + permissionsById = new HashMap<>(); } /* package */ ParseACL copy() { ParseACL copy = new ParseACL(); - try { - copy.permissionsById = new JSONObject(permissionsById.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); + for (String id : permissionsById.keySet()) { + copy.permissionsById.put(id, new Permissions(permissionsById.get(id))); } copy.unresolvedUser = unresolvedUser; if (unresolvedUser != null) { @@ -93,12 +141,14 @@ void setShared(boolean shared) { // Internally we expose the json object this wraps /* package */ JSONObject toJSONObject(ParseEncoder objectEncoder) { - JSONObject json; + JSONObject json = new JSONObject(); try { - json = new JSONObject(permissionsById.toString()); + for (String id: permissionsById.keySet()) { + json.put(id, permissionsById.get(id).toJSONObject()); + } if (unresolvedUser != null) { Object encoded = objectEncoder.encode(unresolvedUser); - json.put("unresolvedUser", encoded); + json.put(UNRESOLVED_USER_JSON_KEY, encoded); } } catch (JSONException e) { throw new RuntimeException(e); @@ -113,7 +163,7 @@ void setShared(boolean shared) { ParseACL acl = new ParseACL(); for (String key : ParseJSONUtils.keys(object)) { - if (key.equals("unresolvedUser")) { + if (key.equals(UNRESOLVED_USER_JSON_KEY)) { JSONObject unresolvedUser; try { unresolvedUser = object.getJSONObject(key); @@ -122,16 +172,12 @@ void setShared(boolean shared) { } acl.unresolvedUser = (ParseUser) decoder.decode(unresolvedUser); } else { - String userId = key; - JSONObject permissions; try { - permissions = object.getJSONObject(userId); + Permissions permissions = Permissions.createPermissionsFromJSONObject(object.getJSONObject(key)); + acl.permissionsById.put(key, permissions); } catch (JSONException e) { throw new RuntimeException("could not decode ACL: " + e.getMessage()); } - for (String accessType : ParseJSONUtils.keys(permissions)) { - acl.setAccess(accessType, userId, true); - } } } return acl; @@ -153,15 +199,11 @@ public ParseACL(ParseUser owner) { if (user != unresolvedUser) { return; } - try { - if (permissionsById.has(UNRESOLVED_KEY)) { - permissionsById.put(user.getObjectId(), permissionsById.get(UNRESOLVED_KEY)); - permissionsById.remove(UNRESOLVED_KEY); - } - unresolvedUser = null; - } catch (JSONException e) { - throw new RuntimeException(e); + if (permissionsById.containsKey(UNRESOLVED_KEY)) { + permissionsById.put(user.getObjectId(), permissionsById.get(UNRESOLVED_KEY)); + permissionsById.remove(UNRESOLVED_KEY); } + unresolvedUser = null; } /* package */ boolean hasUnresolvedUser() { @@ -173,47 +215,12 @@ public ParseACL(ParseUser owner) { } // Helper for setting stuff - private void setAccess(String accessType, String userId, boolean allowed) { - try { - JSONObject permissions = permissionsById.optJSONObject(userId); - if (permissions == null) { - if (!allowed) { - // The user already doesn't have this permission, so no action is - // needed. - return; - } - permissions = new JSONObject(); - permissionsById.put(userId, permissions); - } - - if (allowed) { - permissions.put(accessType, true); - } else { - permissions.remove(accessType); - if (permissions.length() == 0) { - permissionsById.remove(userId); - } - } - } catch (JSONException e) { - throw new RuntimeException("JSON failure with ACL: " + e.getMessage()); + private void setPermissionsIfNonEmpty(String userId, boolean readPermission, boolean writePermission) { + if (!(readPermission || writePermission)) { + permissionsById.remove(userId); } - } - - // Helper for getting stuff - private boolean getAccess(String accessType, String userId) { - try { - JSONObject permissions = permissionsById.optJSONObject(userId); - if (permissions == null) { - return false; - } - - if (!permissions.has(accessType)) { - return false; - } - - return permissions.getBoolean(accessType); - } catch (JSONException e) { - throw new RuntimeException("JSON failure with ACL: " + e.getMessage()); + else { + permissionsById.put(userId, new Permissions(readPermission, writePermission)); } } @@ -252,7 +259,8 @@ public void setReadAccess(String userId, boolean allowed) { if (userId == null) { throw new IllegalArgumentException("cannot setReadAccess for null userId"); } - setAccess(READ_PERMISSION, userId, allowed); + boolean writePermission = getWriteAccess(userId); + setPermissionsIfNonEmpty(userId, allowed, writePermission); } /** @@ -264,7 +272,8 @@ public boolean getReadAccess(String userId) { if (userId == null) { throw new IllegalArgumentException("cannot getReadAccess for null userId"); } - return getAccess(READ_PERMISSION, userId); + Permissions permissions = permissionsById.get(userId); + return permissions != null && permissions.getReadPermission(); } /** @@ -274,7 +283,8 @@ public void setWriteAccess(String userId, boolean allowed) { if (userId == null) { throw new IllegalArgumentException("cannot setWriteAccess for null userId"); } - setAccess(WRITE_PERMISSION, userId, allowed); + boolean readPermission = getReadAccess(userId); + setPermissionsIfNonEmpty(userId, readPermission, allowed); } /** @@ -286,7 +296,8 @@ public boolean getWriteAccess(String userId) { if (userId == null) { throw new IllegalArgumentException("cannot getWriteAccess for null userId"); } - return getAccess(WRITE_PERMISSION, userId); + Permissions permissions = permissionsById.get(userId); + return permissions != null && permissions.getWritePermission(); } /** @@ -513,7 +524,7 @@ public void done(ParseObject object, ParseException e) { } - /* package for tests */ JSONObject getPermissionsById() { + /* package for tests */ Map getPermissionsById() { return permissionsById; } } diff --git a/Parse/src/test/java/com/parse/ParseACLTest.java b/Parse/src/test/java/com/parse/ParseACLTest.java index f10045dac..14c97f59b 100644 --- a/Parse/src/test/java/com/parse/ParseACLTest.java +++ b/Parse/src/test/java/com/parse/ParseACLTest.java @@ -59,7 +59,7 @@ public void tearDown() { public void testConstructor() throws Exception { ParseACL acl = new ParseACL(); - assertEquals(0, acl.getPermissionsById().length()); + assertEquals(0, acl.getPermissionsById().size()); } @Test @@ -90,8 +90,8 @@ public void testCopy() throws Exception { ParseACL copiedACL = acl.copy(); - assertEquals(1, copiedACL.getPermissionsById().length()); - assertTrue(copiedACL.getPermissionsById().has(UNRESOLVED_KEY)); + assertEquals(1, copiedACL.getPermissionsById().size()); + assertTrue(copiedACL.getPermissionsById().containsKey(UNRESOLVED_KEY)); assertTrue(copiedACL.getReadAccess(unresolvedUser)); assertTrue(copiedACL.getWriteAccess(unresolvedUser)); assertFalse(copiedACL.isShared()); @@ -126,12 +126,12 @@ public void testCopyWithSaveListener() throws Exception { // Makre sure we unregister the callback verify(unresolvedUser, times(1)).unregisterSaveListener(any(GetCallback.class)); - assertEquals(1, copiedACL.getPermissionsById().length()); + assertEquals(1, copiedACL.getPermissionsById().size()); assertTrue(copiedACL.getReadAccess(unresolvedUser)); assertTrue(copiedACL.getWriteAccess(unresolvedUser)); assertFalse(copiedACL.isShared()); // No more unresolved permissions since it has been resolved in the callback. - assertFalse(copiedACL.getPermissionsById().has(UNRESOLVED_KEY)); + assertFalse(copiedACL.getPermissionsById().containsKey(UNRESOLVED_KEY)); assertNull(copiedACL.getUnresolvedUser()); } @@ -153,9 +153,11 @@ public void testToJson() throws Exception { JSONObject aclJson = acl.toJSONObject(mockEncoder); assertEquals("unresolvedUserJson", aclJson.getString("unresolvedUser")); - aclJson.remove("unresolvedUser"); - // Without the unresolvedUser, two json objects should be the same - assertEquals(acl.getPermissionsById(), aclJson, JSONCompareMode.NON_EXTENSIBLE); + assertEquals(aclJson.getJSONObject("userId").getBoolean("read"), true); + assertEquals(aclJson.getJSONObject("userId").has("write"), false); + assertEquals(aclJson.getJSONObject("*unresolved").getBoolean("read"), true); + assertEquals(aclJson.getJSONObject("*unresolved").has("write"), false); + assertEquals(aclJson.length(), 3); } //endregion @@ -181,7 +183,7 @@ public void testCreateACLFromJSONObject() throws Exception { assertSame(unresolvedUser, acl.getUnresolvedUser()); assertTrue(acl.getReadAccess("userId")); assertTrue(acl.getWriteAccess("userId")); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } //endregion @@ -216,8 +218,8 @@ public void testResolveUserWithUnresolvedUser() throws Exception { assertNull(acl.getUnresolvedUser()); assertTrue(acl.getReadAccess(unresolvedUser)); assertTrue(acl.getWriteAccess(unresolvedUser)); - assertEquals(1, acl.getPermissionsById().length()); - assertFalse(acl.getPermissionsById().has(UNRESOLVED_KEY)); + assertEquals(1, acl.getPermissionsById().size()); + assertFalse(acl.getPermissionsById().containsKey(UNRESOLVED_KEY)); } //endregion @@ -231,7 +233,7 @@ public void testSetAccessWithNoPermissionAndNotAllowed() throws Exception { acl.setReadAccess("userId", false); // Make sure noting is set - assertEquals(0, acl.getPermissionsById().length()); + assertEquals(0, acl.getPermissionsById().size()); } @Test @@ -241,7 +243,7 @@ public void testSetAccessWithAllowed() throws Exception { acl.setReadAccess("userId", true); assertTrue(acl.getReadAccess("userId")); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test @@ -253,7 +255,7 @@ public void testSetAccessWithPermissionsAndNotAllowed() throws Exception { // Make sure we remove the read access assertFalse(acl.getReadAccess("userId")); - assertEquals(0, acl.getPermissionsById().length()); + assertEquals(0, acl.getPermissionsById().size()); } @Test @@ -272,7 +274,7 @@ public void testSetPublicReadAccessNotAllowed() throws Exception { acl.setPublicReadAccess(false); // Make sure noting is set - assertEquals(0, acl.getPermissionsById().length()); + assertEquals(0, acl.getPermissionsById().size()); } @Test @@ -282,7 +284,7 @@ public void testSetPublicWriteAccessAllowed() throws Exception { acl.setPublicWriteAccess(true); assertTrue(acl.getPublicWriteAccess()); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test @@ -292,7 +294,7 @@ public void testSetPublicWriteAccessNotAllowed() throws Exception { acl.setPublicWriteAccess(false); // Make sure noting is set - assertEquals(0, acl.getPermissionsById().length()); + assertEquals(0, acl.getPermissionsById().size()); } @Test(expected = IllegalArgumentException.class) @@ -330,7 +332,7 @@ public void testSetRoleReadAccess() throws Exception { acl.setRoleReadAccess(role, true); assertTrue(acl.getRoleReadAccess(role)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test(expected = IllegalArgumentException.class) @@ -352,7 +354,7 @@ public void testSetRoleWriteAccess() throws Exception { acl.setRoleWriteAccess(role, true); assertTrue(acl.getRoleWriteAccess(role)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test(expected = IllegalArgumentException.class) @@ -373,9 +375,9 @@ public void testSetUserReadAccessWithLazyUser() throws Exception { assertSame(unresolvedUser, acl.getUnresolvedUser()); verify(unresolvedUser, times(1)).registerSaveListener(any(GetCallback.class)); - assertTrue(acl.getPermissionsById().has(UNRESOLVED_KEY)); + assertTrue(acl.getPermissionsById().containsKey(UNRESOLVED_KEY)); assertTrue(acl.getReadAccess(unresolvedUser)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test @@ -387,7 +389,7 @@ public void testSetUserReadAccessWithNormalUser() throws Exception { acl.setReadAccess(user, true); assertTrue(acl.getReadAccess(user)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test(expected = IllegalArgumentException.class) @@ -408,9 +410,9 @@ public void testSetUserWriteAccessWithLazyUser() throws Exception { assertSame(user, acl.getUnresolvedUser()); verify(user, times(1)).registerSaveListener(any(GetCallback.class)); - assertTrue(acl.getPermissionsById().has(UNRESOLVED_KEY)); + assertTrue(acl.getPermissionsById().containsKey(UNRESOLVED_KEY)); assertTrue(acl.getWriteAccess(user)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } @Test @@ -422,7 +424,7 @@ public void testSetUserWriteAccessWithNormalUser() throws Exception { acl.setWriteAccess(user, true); assertTrue(acl.getWriteAccess(user)); - assertEquals(1, acl.getPermissionsById().length()); + assertEquals(1, acl.getPermissionsById().size()); } //endregion