0

In OPA, I take an input of type { roles, action, object }, and I return a matching permission which contains data regarding what the user is allowed to do based on the input

This is rather simple if no two roles have the same permission:

package policies

import data.role_permissions

matching_permission := permission {
  role := input.roles[_]
  permissions := role_permissions[role]
  permission := permissions[_]

  permission.action == input.action
  permission.object == input.object
}

The problem arises if the input roles has multiple roles that match the same permission, in that case I get an error: complete rules must not produce multiple outputs. Which makes sense, so I modified the policy to match an array of permissions:

matching_perms[permission] {
  role := input.roles[_]
  permissions := role_permissions[role]
  permission := permissions[_]

  permission.action == input.action
  permission.object == input.object
}

But I still need to return a singe permission object. What I want to do is merge any extra keys into a single item, with my own logic for each merge conflict
However, I cannot seem to merge the arrays of extra keys in this case.
How would I, for instance, get the following output with this data?:

// data.json
{
  "role_permissions": {
    "a": [{ "action": "read", "object": "threats", "levels": [1, 2, 3] }],
    "b": [{ "action": "read", "object": "threats", "levels": [4, 5] }]
  }
}

// input
{
  "roles": ["a", "b"],
  "action": "read",
  "object": "threats"
}

// desired output
{
  "action": "read",
  "object": "threats",
  "levels": [1, 2, 3, 4, 5] // <-- Merged into one result
}

Basically, how can I merge the permission objects' array fields to create a single object?

1 Answer 1

1

TL;DR - This is the solution I arrived at:

package policies

import data.role_permissions

extra_keys(permission) := [key |
    some key
    extra = object.remove(permission, ["action", "object"])
    _ = extra[key]
]

matching_perms[permission] {
    role := input.roles[_]
    permissions := role_permissions[role]
    permission := permissions[_]

    permission.action == input.action
    permission.object == input.object
}

merged_data[key] := result {
    perm := matching_perms[_]
    keys := extra_keys(perm)
    key := keys[_]

    result := {v |
        perm := matching_perms[_]
        permission_data = perm[key]
        v = permission_data[_]
    }
}

permission := {
    "action": input.action,
    "object": input.object,
    "data": merged_data,
}

And, if I absolutely need levels to sit alongside action and object rather than nested inside data, I use:

permission := object.union(
    {
        "action": input.action,
        "object": input.object,
    },
    merged_data,
)

The solution was to use a set comprehension for each extra key in my permission object. Using merged_data[key] := ... I define an array of data, where I iterate over all extra keys (key := keys[_]), then use a set comprehension on result := {v | to combine all values in the matching permissions, removing duplicates

Sign up to request clarification or add additional context in comments.

1 Comment

Nice! Yeah, custom merge logic can certainly be a challenge given immutable vars.. but you found a good way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.