Add tutorial example for multi-decisions 18/123018/1
authorPamela Dragosh <pd1248@att.com>
Fri, 30 Jul 2021 17:17:12 +0000 (13:17 -0400)
committerPamela Dragosh <pd1248@att.com>
Fri, 30 Jul 2021 17:18:17 +0000 (13:18 -0400)
Issue-ID: POLICY-3514
Change-Id: Ifcaa56d35ef359f2b24cc111ca9af4021000514f
Signed-off-by: Pamela Dragosh <pd1248@att.com>
tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialRequest.java
tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponse.java [new file with mode: 0644]
tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponsePermission.java [new file with mode: 0644]
tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialTranslator.java
tutorials/tutorial-xacml-application/src/test/java/org/onap/policy/tutorial/tutorial/TutorialApplicationTest.java
tutorials/tutorial-xacml-application/src/test/resources/tutorial-decision-multi-request.json [new file with mode: 0644]

index 356480b..cda1b43 100644 (file)
 
 package org.onap.policy.tutorial.tutorial;
 
+import com.att.research.xacml.api.DataTypeException;
+import com.att.research.xacml.api.Identifier;
+import com.att.research.xacml.api.Request;
+import com.att.research.xacml.api.XACML3;
+import com.att.research.xacml.std.IdentifierImpl;
+import com.att.research.xacml.std.StdAttributeValue;
+import com.att.research.xacml.std.StdMutableAttribute;
+import com.att.research.xacml.std.StdMutableRequest;
+import com.att.research.xacml.std.StdMutableRequestAttributes;
+import com.att.research.xacml.std.StdMutableRequestReference;
+import com.att.research.xacml.std.StdRequestAttributesReference;
+import com.att.research.xacml.std.annotations.RequestParser;
 import com.att.research.xacml.std.annotations.XACMLAction;
 import com.att.research.xacml.std.annotations.XACMLRequest;
 import com.att.research.xacml.std.annotations.XACMLResource;
 import com.att.research.xacml.std.annotations.XACMLSubject;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import lombok.Getter;
@@ -34,6 +47,15 @@ import org.onap.policy.models.decisions.concepts.DecisionRequest;
 @ToString
 @XACMLRequest(ReturnPolicyIdList = true)
 public class TutorialRequest {
+    public static final Identifier ATTRIBUTE_ID_MULTIID = new IdentifierImpl("urn:org:onap:tutorial-multi-id");
+    public static final Identifier ATTRIBUTE_ID_USER = new IdentifierImpl("urn:org:onap:tutorial-user");
+    public static final Identifier ATTRIBUTE_ID_ENTITY = new IdentifierImpl("urn:org:onap:tutorial-entity");
+    public static final Identifier ATTRIBUTE_ID_PERMISSION = new IdentifierImpl("urn:org:onap:tutorial-permission");
+    //
+    // For use with a multi request
+    //
+    private static final StdRequestAttributesReference refSubjects = new StdRequestAttributesReference("subjects1");
+    private static final StdRequestAttributesReference refActions = new StdRequestAttributesReference("actions1");
     //
     // Excluding from results to demonstrate control as to which attributes can be returned.
     //
@@ -46,7 +68,7 @@ public class TutorialRequest {
     @XACMLSubject(attributeId = "urn:org:onap:onap-instance", includeInResults = false)
     private String onapInstance;
 
-    @XACMLAction()
+    @XACMLAction
     private String action;
 
     //
@@ -65,9 +87,13 @@ public class TutorialRequest {
      * createRequest.
      *
      * @param decisionRequest Incoming
-     * @return TutorialRequest object
+     * @return Request XACML Request object
+     * @throws DataTypeException DataTypeException
+     * @throws IllegalAccessException IllegalAccessException
      */
-    public static TutorialRequest createRequest(DecisionRequest decisionRequest) {
+    @SuppressWarnings("unchecked")
+    public static Request createRequest(DecisionRequest decisionRequest)
+            throws IllegalAccessException, DataTypeException {
         //
         // Create our object
         //
@@ -86,6 +112,18 @@ public class TutorialRequest {
         // Add the resource attributes
         //
         Map<String, Object> resources = decisionRequest.getResource();
+        //
+        // Check if this is a multi-request
+        //
+        if (resources.containsKey("users")) {
+            //
+            // Setup the multi-request
+            //
+            return buildMultiRequest(request, (List<Object>) resources.get("users"));
+        }
+        //
+        // Continue as a single request
+        //
         for (Entry<String, Object> entrySet : resources.entrySet()) {
             if ("user".equals(entrySet.getKey())) {
                 request.user = entrySet.getValue().toString();
@@ -98,6 +136,87 @@ public class TutorialRequest {
             }
         }
 
-        return request;
+        return RequestParser.parseRequest(request);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected static Request buildMultiRequest(TutorialRequest existingRequest, List<Object> users)
+            throws IllegalAccessException, DataTypeException {
+        //
+        // Create a single request absorbing the existing attributes, we will copy
+        // them over and assign them an ID.
+        //
+        StdMutableRequest singleRequest = new StdMutableRequest(RequestParser.parseRequest(existingRequest));
+        //
+        // Copy the attributes and assign ID's
+        //
+        StdMutableRequest multiRequest = addMultiRequestIds(singleRequest);
+        //
+        // Iterate and add in the requests
+        //
+        users.forEach(user -> addUser(multiRequest, (Map<String, Object>) user));
+        //
+        // Done
+        //
+        return multiRequest;
+    }
+
+    protected static StdMutableRequest addMultiRequestIds(StdMutableRequest singleRequest) {
+        StdMutableRequest multiRequest = new StdMutableRequest();
+        singleRequest.getRequestAttributes().forEach(attributes -> {
+            StdMutableRequestAttributes newAttributes = new StdMutableRequestAttributes();
+            if (attributes.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
+                newAttributes.setCategory(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT);
+                newAttributes.setXmlId(refSubjects.getReferenceId());
+            } else if (attributes.getCategory().equals(XACML3.ID_ATTRIBUTE_CATEGORY_ACTION)) {
+                newAttributes.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_ACTION);
+                newAttributes.setXmlId(refActions.getReferenceId());
+            }
+            attributes.getAttributes().forEach(newAttributes::add);
+            multiRequest.add(newAttributes);
+        });
+        return multiRequest;
+    }
+
+    protected static void addUser(StdMutableRequest multiRequest, Map<String, Object> user) {
+        StdMutableRequestAttributes attributes = new StdMutableRequestAttributes();
+        attributes.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
+        for (Entry<String, Object> entrySet : user.entrySet()) {
+            StdAttributeValue<String> value =
+                    new StdAttributeValue<>(XACML3.ID_DATATYPE_STRING, entrySet.getValue().toString());
+            StdMutableAttribute attribute = new StdMutableAttribute();
+            attribute.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
+            attribute.setIncludeInResults(true);
+            attribute.addValue(value);
+            if ("multiId".equals(entrySet.getKey())) {
+                attributes.setXmlId(entrySet.getValue().toString());
+                attribute.setAttributeId(ATTRIBUTE_ID_MULTIID);
+            } else if ("user".equals(entrySet.getKey())) {
+                attribute.setAttributeId(ATTRIBUTE_ID_USER);
+            } else if ("entity".equals(entrySet.getKey())) {
+                attribute.setAttributeId(ATTRIBUTE_ID_ENTITY);
+            } else if ("permission".equals(entrySet.getKey())) {
+                attribute.setAttributeId(ATTRIBUTE_ID_PERMISSION);
+            } else {
+                throw new IllegalArgumentException("Unknown request attribute given");
+            }
+            attributes.add(attribute);
+        }
+        //
+        // Add the attributes to the Multi-Request
+        //
+        multiRequest.add(attributes);
+        //
+        // Create the references
+        //
+        StdRequestAttributesReference attributesReference = new StdRequestAttributesReference(attributes.getXmlId());
+        StdMutableRequestReference reference = new StdMutableRequestReference();
+        reference.add(refSubjects);
+        reference.add(refActions);
+        reference.add(attributesReference);
+        //
+        // Add the reference to this request
+        //
+        multiRequest.add(reference);
     }
 }
diff --git a/tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponse.java b/tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponse.java
new file mode 100644 (file)
index 0000000..7ef6362
--- /dev/null
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.tutorial.tutorial;
+
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.onap.policy.models.decisions.concepts.DecisionResponse;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TutorialResponse extends DecisionResponse {
+    private List<TutorialResponsePermission> permissions;
+}
diff --git a/tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponsePermission.java b/tutorials/tutorial-xacml-application/src/main/java/org/onap/policy/tutorial/tutorial/TutorialResponsePermission.java
new file mode 100644 (file)
index 0000000..5dd03cf
--- /dev/null
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.tutorial.tutorial;
+
+import java.util.Map;
+import lombok.Data;
+
+@Data
+public class TutorialResponsePermission {
+    private String status;
+    private Map<String, Object> attributes;
+}
index 31bb103..327a507 100644 (file)
@@ -28,7 +28,7 @@ import com.att.research.xacml.api.Response;
 import com.att.research.xacml.api.Result;
 import com.att.research.xacml.api.XACML3;
 import com.att.research.xacml.std.IdentifierImpl;
-import com.att.research.xacml.std.annotations.RequestParser;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -93,9 +93,9 @@ public class TutorialTranslator extends StdBaseTranslator {
         //
         // For simplicity, let's just match on the action "authorize" and the user
         //
-        var matchAction = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
-                XACML3.ID_FUNCTION_STRING_EQUAL, "authorize", XACML3.ID_DATATYPE_STRING,
-                XACML3.ID_ACTION_ACTION_ID, XACML3.ID_ATTRIBUTE_CATEGORY_ACTION);
+        var matchAction =
+                ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(XACML3.ID_FUNCTION_STRING_EQUAL, "authorize",
+                        XACML3.ID_DATATYPE_STRING, XACML3.ID_ACTION_ACTION_ID, XACML3.ID_ATTRIBUTE_CATEGORY_ACTION);
         Map<String, Object> props = toscaPolicy.getProperties();
         var user = props.get("user").toString();
         var matchUser = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(XACML3.ID_FUNCTION_STRING_EQUAL, user,
@@ -119,9 +119,9 @@ public class TutorialTranslator extends StdBaseTranslator {
                     ((Map<String, String>) permission).get("entity"), XACML3.ID_DATATYPE_STRING, ID_TUTORIAL_ENTITY,
                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
 
-            var matchPermission = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
-                    XACML3.ID_FUNCTION_STRING_EQUAL, ((Map<String, String>) permission).get("permission"),
-                    XACML3.ID_DATATYPE_STRING, ID_TUTORIAL_PERM, XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
+            var matchPermission = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(XACML3.ID_FUNCTION_STRING_EQUAL,
+                    ((Map<String, String>) permission).get("permission"), XACML3.ID_DATATYPE_STRING, ID_TUTORIAL_PERM,
+                    XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
             anyOf = new AnyOfType();
             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(matchEntity, matchPermission));
             target = new TargetType();
@@ -147,7 +147,7 @@ public class TutorialTranslator extends StdBaseTranslator {
     @Override
     public Request convertRequest(DecisionRequest request) {
         try {
-            return RequestParser.parseRequest(TutorialRequest.createRequest(request));
+            return TutorialRequest.createRequest(request);
         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
             // Empty
         }
@@ -156,15 +156,59 @@ public class TutorialTranslator extends StdBaseTranslator {
 
     @Override
     public DecisionResponse convertResponse(Response xacmlResponse) {
+        //
+        // Single or Multi?
+        //
+        if (xacmlResponse.getResults().size() > 1) {
+            return convertMultiResponse(xacmlResponse);
+        } else {
+            return convertSingleResponse(xacmlResponse.getResults().iterator().next());
+        }
+    }
+
+    protected DecisionResponse convertSingleResponse(Result xacmlResult) {
         var decisionResponse = new DecisionResponse();
         //
         // Setup policies
         //
         decisionResponse.setPolicies(new HashMap<>());
         //
-        // Iterate through all the results
+        // Check the result
+        //
+        if (xacmlResult.getDecision() == Decision.PERMIT) {
+            //
+            // This tutorial will simply set the status to Permit
+            //
+            decisionResponse.setStatus(Decision.PERMIT.toString());
+        } else {
+            //
+            // This tutorial will simply set the status to Deny
+            //
+            decisionResponse.setStatus(Decision.DENY.toString());
+        }
+        //
+        // Add attributes use the default scanAttributes. Note that one
+        // could override that method and return the structure as desired.
+        // The attributes returned by default method are in the format
+        // of XACML syntax. It may be more desirable to map them back to
+        // the original request name-value.
+        //
+        if (booleanReturnAttributes) {
+            scanAttributes(xacmlResult.getAttributes(), decisionResponse);
+        }
+        return decisionResponse;
+    }
+
+    protected DecisionResponse convertMultiResponse(Response xacmlResponse) {
+        TutorialResponse decisionResponse = new TutorialResponse();
         //
+        // Setup policies
+        //
+        decisionResponse.setPolicies(new HashMap<>());
+        decisionResponse.setStatus("multi");
+        List<TutorialResponsePermission> permissions = new ArrayList<>();
         for (Result xacmlResult : xacmlResponse.getResults()) {
+            TutorialResponsePermission permission = new TutorialResponsePermission();
             //
             // Check the result
             //
@@ -172,12 +216,12 @@ public class TutorialTranslator extends StdBaseTranslator {
                 //
                 // This tutorial will simply set the status to Permit
                 //
-                decisionResponse.setStatus(Decision.PERMIT.toString());
+                permission.setStatus(Decision.PERMIT.toString());
             } else {
                 //
                 // This tutorial will simply set the status to Deny
                 //
-                decisionResponse.setStatus(Decision.DENY.toString());
+                permission.setStatus(Decision.DENY.toString());
             }
             //
             // Add attributes use the default scanAttributes. Note that one
@@ -187,10 +231,22 @@ public class TutorialTranslator extends StdBaseTranslator {
             // the original request name-value.
             //
             if (booleanReturnAttributes) {
+                //
+                // Call existing method
+                //
                 scanAttributes(xacmlResult.getAttributes(), decisionResponse);
+                //
+                // Move from overall response to the individual permission
+                //
+                permission.setAttributes(decisionResponse.getAttributes());
+                decisionResponse.setAttributes(null);
             }
+            //
+            // Add it
+            //
+            permissions.add(permission);
         }
-
+        decisionResponse.setPermissions(permissions);
         return decisionResponse;
     }
 
index 6600126..a6f0e94 100644 (file)
@@ -25,6 +25,7 @@ import com.att.research.xacml.api.Response;
 import com.att.research.xacml.api.XACML3;
 import java.io.File;
 import java.io.IOException;
+import java.util.Map;
 import java.util.Properties;
 import java.util.ServiceLoader;
 import org.apache.commons.lang3.tuple.Pair;
@@ -65,8 +66,8 @@ public class TutorialApplicationTest {
         // Setup our temporary folder
         //
         XacmlPolicyUtils.FileCreator myCreator = (String filename) -> policyFolder.newFile(filename);
-        propertiesFile = XacmlPolicyUtils.copyXacmlPropertiesContents("src/test/resources/xacml.properties",
-                properties, myCreator);
+        propertiesFile = XacmlPolicyUtils.copyXacmlPropertiesContents("src/test/resources/xacml.properties", properties,
+                myCreator);
         //
         // Load XacmlApplicationServiceProvider service
         //
@@ -88,21 +89,20 @@ public class TutorialApplicationTest {
         // we just built for it.
         //
         service.initialize(propertiesFile.toPath().getParent(), null);
-    }
-
-    @Test
-    public void test() throws CoderException, XacmlApplicationException, IOException {
         //
         // Now load the tutorial policies.
         //
         TestUtils.loadPolicies("src/test/resources/tutorial-policies.yaml", service);
+    }
+
+    @Test
+    public void testSingleDecision() throws CoderException, XacmlApplicationException, IOException {
         //
         // Load a Decision request
         //
-        DecisionRequest decisionRequest = gson.decode(
-                TextFileUtils
-                .getTextFileAsString("src/test/resources/tutorial-decision-request.json"),
-                DecisionRequest.class);
+        DecisionRequest decisionRequest =
+                gson.decode(TextFileUtils.getTextFileAsString("src/test/resources/tutorial-decision-request.json"),
+                        DecisionRequest.class);
         LOGGER.info("{}", gson.encode(decisionRequest, true));
         //
         // Test a decision - should start with a permit
@@ -114,7 +114,7 @@ public class TutorialApplicationTest {
         // Check that there are attributes
         //
         assertThat(decision.getLeft().getAttributes()).isNotNull().hasSize(1)
-            .containsKey(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
+                .containsKey(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
         //
         // This should be a deny
         //
@@ -127,7 +127,73 @@ public class TutorialApplicationTest {
         // Check that there are attributes
         //
         assertThat(decision.getLeft().getAttributes()).isNotNull().hasSize(1)
-            .containsKey(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
+                .containsKey(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
     }
 
+
+    @Test
+    public void testMultiDecision() throws CoderException, XacmlApplicationException, IOException {
+        //
+        // Load a Decision request
+        //
+        DecisionRequest decisionRequest = gson.decode(
+                TextFileUtils.getTextFileAsString("src/test/resources/tutorial-decision-multi-request.json"),
+                DecisionRequest.class);
+        LOGGER.info("{}", gson.encode(decisionRequest, true));
+        //
+        // Test a decision - should start with a permit
+        //
+        Pair<DecisionResponse, Response> decision = service.makeDecision(decisionRequest, null);
+        LOGGER.info("{}", gson.encode(decision.getLeft(), true));
+        assertEquals("multi", decision.getLeft().getStatus());
+        //
+        // Check that there no attributes for the overall response
+        //
+        assertThat(decision.getLeft().getAttributes()).isNull();
+        //
+        // Check that there are 7 decisions with attributes
+        //
+        assertThat(decision.getLeft()).isInstanceOf(TutorialResponse.class);
+        TutorialResponse tutorialResponse = (TutorialResponse) decision.getLeft();
+        assertThat(tutorialResponse.getPermissions()).hasSize(7);
+        tutorialResponse.getPermissions().forEach(p -> checkPermission(p));
+    }
+
+    private void checkPermission(TutorialResponsePermission permission) {
+        assertThat(permission.getAttributes()).hasSize(1);
+        Object resourceAttributes = permission.getAttributes().get(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
+        assertThat(resourceAttributes).isNotNull().isInstanceOf(Map.class);
+        @SuppressWarnings("unchecked")
+        String multiId = ((Map<String, String>) resourceAttributes).get("urn:org:onap:tutorial-multi-id").toString();
+        assertThat(Integer.parseInt(multiId)).isBetween(1, 7);
+        switch (multiId) {
+            case "1":
+                assertThat(permission.getStatus()).isEqualTo("Permit");
+                return;
+            case "2":
+                assertThat(permission.getStatus()).isEqualTo("Permit");
+                return;
+            case "3":
+                assertThat(permission.getStatus()).isEqualTo("Deny");
+                return;
+            case "4":
+                assertThat(permission.getStatus()).isEqualTo("Permit");
+                return;
+            case "5":
+                assertThat(permission.getStatus()).isEqualTo("Deny");
+                return;
+            case "6":
+                assertThat(permission.getStatus()).isEqualTo("Deny");
+                return;
+            case "7":
+                assertThat(permission.getStatus()).isEqualTo("Deny");
+                return;
+            default:
+                //
+                // Should not get here as we check the value range in line 168.
+                // But CodeStyle wants a default.
+                //
+                break;
+        }
+    }
 }
diff --git a/tutorials/tutorial-xacml-application/src/test/resources/tutorial-decision-multi-request.json b/tutorials/tutorial-xacml-application/src/test/resources/tutorial-decision-multi-request.json
new file mode 100644 (file)
index 0000000..9a7425d
--- /dev/null
@@ -0,0 +1,53 @@
+{
+  "ONAPName": "TutorialPEP",
+  "ONAPComponent": "TutorialPEPComponent",
+  "ONAPInstance": "TutorialPEPInstance",
+  "requestId": "unique-request-id-tutorial",
+  "action": "authorize",
+  "resource": {
+    "users": [
+      {
+        "multiId": 1,
+        "user": "demo",
+        "entity": "foo",
+        "permission" : "write"
+      },
+      {
+        "multiId": 2,
+        "user": "demo",
+        "entity": "foo",
+        "permission" : "read"
+      },
+      {
+        "multiId": 3,
+        "user": "audit",
+        "entity": "foo",
+        "permission" : "write"
+      },
+      {
+        "multiId": 4,
+        "user": "audit",
+        "entity": "foo",
+        "permission" : "read"
+      },
+      {
+        "multiId": 5,
+        "user": "demo",
+        "entity": "bar",
+        "permission" : "read"
+      },
+      {
+        "multiId": 6,
+        "user": "demo",
+        "entity": "bar",
+        "permission" : "admin"
+      },
+      {
+        "multiId": 7,
+        "user": "ceo",
+        "entity": "bar",
+        "permission" : "read"
+      }
+    ]
+  }
+}