How to return error messages per object in rego response when using nested objects

Viewed 265

I am creating a policy to validate access to a collection of Records. These records are passed as input and have a collection of permissions attached to them. I validate them against permissions data stored in the OPA.

For instance, I can return the collection of Records that are accessible by doing something like this

isAllowed[id] {
   permissionSet := {x | x := permissions.groups[_].name}
   id := input.records[i].id
   input.operation == "update"
   input.records[i].acls.owners[j]==permissionSet[k]
}{
   id := input.records[i].id
   input.operation == "create" }

Which would return something like

"isAllowed": ["123"]

when the input is like the following and the 'permissions' data included "service.legal.user"

  "input": { 
        "operation": "update", 
        "records": [
            { "id": "123", "acls": { "owners": ["service.legal.user"] }},
            { "id": "456", "acls": { "owners": ["service.storage.viewer"] }}
        ]
   }

However I want to return something like the following where I list all input records and assign error messages to ones that have failed with all the reasons it failed

"records":[
   {"id": "123", "errors": ""}, 
   {"id": "456", "errors": "You must have owner permission to update a record"}
]

I have tried an incremental rule but I get the error message from OPA 'complete rules must not produce multiple outputs'

isAllowed = response {
    #owner permission checked for update operation on all records
    some i
    response := {
      "id" : input.records[i].id,
      "errors" : CheckErrors
    }
}
CheckErrors[reason] {
    reason := "Must be an owner to update a record"
    input.operation == "update"
    permissionSet := {x | x := permissions.groups[_].name}
    input.records[i].acls.owners[j]==permissionSet[k]
}
CheckErrors[reason]{
    #no permission checked for create operation on all records
    reason := "Anyone can create"
    input.operation == "create"
}

Any help would be welcome.

1 Answers

Not sure I followed entirely, and you didn't provide the permissions object, but assuming groups is just a list of objects like {"name": "service.legal.user"} something like the below would produce your desired output.

records[response] {    
    id := input.records[_].id
    errors := [r | e := check_errors[_]
                   e.id == id
                   r := e.reason]
    
    response := {
        "id" : id,
        "errors" : errors
    }
}

check_errors[{"id": id, "reason": "Must be an owner to update a record"}] {
    input.operation == "update"
    id := input.records[x].id
    permissionSet := {x | x := permissions.groups[_].name}
    owners := {o | o := input.records[x].acls.owners[_]}
    
    count(owners & permissionSet) == 0
}

Full example here.

Related