How to return error messages per object in rego response when using nested objects - open-policy-agent

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.

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

I'm trying to group all occurrences of an ID's data into a JSON payload as an array

I'm new to KSQL and I feel like there should be a way to group and add data into an array
I'm getting these individual objects from the stream, for example
{
"ENRLMT_ID": "I12345",
"STUS_CD": "06",
"STUS_RSN_CD": "081",
"STUS_RSN_DESC": "APPROVED FOR REVALIDATION",
"STUS_DESC": "APPROVED"
}
{
"ENRLMT_ID": "I12345",
"STUS_CD": "13",
"SRC_ENRLMT_STUS_HSTRY_SK": "OxdP6jOQnr/o+UfE4q0zr5p7lMvK0Fh9N",
"STUS_RSN_CD": "029",
"STUS_RSN_DESC": "THE PROVIDER OR SUPPLIER IS VOLUNTARILY WITHDRAWING",
"STUS_DESC": "DEACTIVATED"
}
the results I'm looking to get is:
{
"ENRLMT_ID": "I12345",
PAYLOAD: [
{
"STUS_CD": "06",
"STUS_RSN_CD": "081",
"STUS_RSN_DESC": "APPROVED FOR REVALIDATION",
"STUS_DESC": "APPROVED"
},
{
"STUS_CD": "13",
"STUS_RSN_CD": "029",
"STUS_RSN_DESC": "THE PROVIDER OR SUPPLIER IS VOLUNTARILY WITHDRAWING",
"STUS_DESC": "DEACTIVATED"
}
]
}
This is the KSQL I've used to get as close as I could:
CREATE STREAM ENROLLMENT_STATUS_STREAM AS SELECT
ENRLMT_ID AS ENRLMT_ID,
STRUCT(
"STUS_CD":= ESJ.STUS_CD,
"STUS_RSN_CD" := ESJ.STUS_RSN_CD,
"STUS_RSN_DESC":= ESJ.STUS_RSN_DESC,
"STUS_DESC":= ESJ.STUS_DESC
) AS PAYLOAD
FROM ENROLLMENT_STATUS_DATA ESJ;
Resulting in this output from the stream:
{
"ENRLMT_ID": "I12345",
"PAYLOAD": {
"STUS_RSN_CD": "029",
"STUS_RSN_DESC": THE PROVIDER OR SUPPLIER IS VOLUNTARILY WITHDRAWING",
"STUS_CD": "13",
"STUS_DESC": "DEACTIVATED"
}
}
Yes it is possible using collect_list aggregate function
CREATE STREAM ENROLLMENT_STATUS_STREAM
AS
SELECT
ENRLMT_ID,
collect_list(struct(
STUS_CD := ESJ.STUS_CD,
STUS_RSN_CD := ESJ.STUS_RSN_CD,
STUS_RSN_DESC := ESJ.STUS_RSN_DESC,
STUS_DESC := ESJ.STUS_DESC
)) AS PAYLOAD
FROM ENROLLMENT_STATUS_DATA ESJ
GROUP BY ENRLMT_ID;

Union an array of objects in rego

How do I union an array of objects into one object in rego?
I know how to union two objects, but how do I loop through an array of objects?
This sample works, but it is (obvious) not scalable ;)
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test4": "012"}]
b := {
"my-property": object.union(object.union(a[0], a[1]), a[2])
}
Expected output:
{
"test1": "123",
"test2": "456",
"test3": "789",
"test4": "012"
}
Thanks!
Casper
TLDR; assuming the object keys are unique (like in your example), you can simply use a comprehension:
b := {k: v |
some i, k
v := a[i][k]
}
Since the variable i is only used once, you can replace it with _ to avoid having to come up with a name:
b := {k: v |
some k
v := a[_][k]
}
If the object keys are not unique then it's slightly more complicated because you need to decide how to resolve conflicts when two objects contain the same key with different values. How you decide to resolve conflicts will depend on the use case. One way to resolve them is to just group values by their keys. For example:
# the 'test2' key appears in more than one object with a different value
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test2": "012"}]
To group the values by keys, you can write a nested comprehension:
{k: vs |
# for each key 'k'
some k
conflicting[_][k]
# find all values for key 'k' and group as a set
vs := {v | v := conflicting[_][k]}
}
Output:
{
"test1": [
"123"
],
"test2": [
"012",
"456"
],
"test3": [
"789"
]
}

Delphi SuperObject - is there a (recursive) search function that tells where a value can be found?

I'm using SuperObject to create and manipulate a simple hierarchical structure in JSON.
My goal is to transform a set of objects {"id":..., "name":..., "parent":...} into a hierarchical structure. Example:
I want to transform this
{"id": "0001","name": "item0001", "parent":""},
{"id": "0002","name": "item0002", "parent":""},
{"id": "0003","name": "item0003", "parent":""},
{"id": "0003.1","name": "item0003.1", "parent":"0003"},
{"id": "0003.1.1","name": "item0003.1.1", "parent":"0003.1"},
into this
{
"items": [
{
"id": "0001",
"name": "item0001"
},
{
"id": "0002",
"name": "item0002"
},
{
"id": "0003",
"name": "item0003",
"items": [
{
"id": "0003.1",
"name": "item0003.1",
"items": [
{
"id": "0003.1.1",
"name": "item0003.1.1"
}
]
}
]
}
]
}
(This structure can vary, i.e. there is no fixed model. Which probably means the solution must be recursive).
I think the way to achieve this is:
for each object to add,
if there is no parent, add it to the output json, at the top;
if there is a parent, find where the parent is in the output json.
add the object to the output json under the parent.
To do this, I was looking for a way to retrieve the path of an object, like
function findpathinObject(key:string, value:string, object:iSuperObject):string
which would return the "path" of the value found.
In my example, findpathinObject("parent", "0003.1", newObject) would return 'items[2].items[0]'
Is this a good approach? Is there something that resolves my issue without making a new function?
the closest I've seen is this
SuperObject - Extract All
but I don't know if that can be changed to return the path it is looking in, or the path where it finally found the value...
Thanks
Got this from Python:
Sorting JSON object(s) into a Hierarchy
In Delphi (it works, here is an extract for guidance):
function ProcessObject(const aAsObject: iSuperObject): iSuperObject;
var
var KeyedObject: iSuperObject
item: iSuperObject;
ArrayItem: iSuperObject;
parent, tgt: iSuperObject;
begin
KeyedObject := SO('{}');
for ArrayItem in aAsObject do
begin
KeyedObject[ArrayItem['id'].AsString] := ArrayItem;
end;
// iterate through each item in the `myJson` list.
for item in aAsObject do
begin
// does the item have a parent?
if assigned(item['parent.id']) then
begin
// get the parent item
if (assigned(item['parent']) and assigned(item['parent.id'])) then
begin
if (assigned(KeyedObject[item['parent'].AsString])) then
parent := KeyedObject[item['parent.id'].AsString];
// if the parent item doesn't have a "children" member,
// we must create one.
if not(assigned(parent['children'])) then
parent['children'] := SO('{[]}');
// add the item to its parent's "children" list.
parent['children[]'] := item;
end;
end;
end;
tgt := SO('{}');
for item in aAsObject do
if not assigned(item['parent']) then
tgt[] := item;
result := tgt;
end;
SuperObject is a JSON access library, not a data processing library. So there is nothing like this available in the box.
You just need to implement the extraction logic in pascal code, using SuperObject for reading the input, and creating the nested output.

accessing list from json and iterating in postgres

I have a json as mentioned below,
{
"list": [{
"notificationId": 123,
"userId": 444
},
{
"notificationId": 456,
"userId": 789
}
]
}
I need to write a postgres procedure which interates through the list and perform either update/insert based on notification id is already present or not in DB.
I have a notification table which has notificationid and userID as columns.
Can anyone please tell me on how to perform this using postgres json operators.
Try this query:
SELECT *
FROM yourTable
WHERE col->'list'#>'[{"notificationId":123}]';
You may replace the value 123 with whatever notificationId you want to search. Follow the link below for a demo showing that this logic works:
Demo
Assuming you have a unique constraint on notificationid (e.g. because it's the primary key, there is no need for stored function or loop:
with data (j) as (
values ('
{
"list": [{
"notificationId": 123,
"userId": 444
},
{
"notificationId": 456,
"userId": 789
}
]
}'::jsonb)
)
insert into notification (notificationid, userid)
select (e.r ->> 'notificationId')::int, (e.r ->> 'userId')::int
from data d, jsonb_array_elements(d.j -> 'list') as e(r)
on conflict (notificationid) do update
set userid = excluded.userid;
The first step in that statement is to turn the array into a list of rows, this is what:
select e.*
from data d, jsonb_array_elements(d.j -> 'list') as e(r)
does. Given your sample JSON, this returns two rows with a JSON value in each:
r
--------------------------------------
{"userId": 444, "notificationId": 123}
{"userId": 789, "notificationId": 456}
This is then split into two integer columns:
select (e.r ->> 'notificationId')::int, (e.r ->> 'userId')::int
from data d, jsonb_array_elements(d.j -> 'list') as e(r)
So we get:
int4 | int4
-----+-----
123 | 444
456 | 789
And this result is used as the input for an INSERT statement.
The on conflict clause then does an insert or update depending on the presence of the row identified by the column notificationid which has to have a unique index.
Meanwhile i tried this,
CREATE OR REPLACE FUNCTION insert_update_notifications(notification_ids jsonb) RETURNS void AS
$$
DECLARE
allNotificationIds text[];
indJson jsonb;
notIdCount int;
i json;
BEGIN
FOR i IN SELECT * FROM jsonb_array_elements(notification_ids)
LOOP
select into notIdCount count(notification_id) from notification_table where notification_id = i->>'notificationId' ;
IF(notIdCount = 0 ) THEN
insert into notification_table(notification_id,userid) values(i->>'notificationId',i->>'userId');
ELSE
update notification_table set userid = i->>'userId' where notification_id = i->>'notificationId';
END IF;
END LOOP;
END;
$$
language plpgsql;
select * from insert_update_notifications('[{
"notificationId": "123",
"userId": "444"
},
{
"notificationId": "456",
"userId": "789"
}
]');
It works.. Please review this.

Rest Assured Body handling ArrayList

I has a response body like this
enter code here
{
"applicationName": "Service MyService",
"someData": [
{
"name": "check1",
"props": [
"AAaa"
]
},
{
"name": "check2",
"props": [
"BBbb",
"CCcc"
]
}
]
}
Now I can use the following code and the test passes.
given().log().all()
.accept(JSON).expect().statusCode(SC_OK)
.when().log().all()
.get(contextPath + "/test")
.then().log().all()
.body("someData.name",
IsCollectionWithSize.hasSize(2))
.body("someData.name",
allOf(hasItems("check1", "check2")))
.body("someData.findAll {it.name == 'check1'}.props",
IsCollectionWithSize.hasSize(1))
.body("healthReports.findAll {it.name == 'check2'}.props",
IsCollectionWithSize.hasSize(2)));
However if I then attempt to check the values in the props field it fails I think because a ArrayList is returned and the matchers are checking on String.
given().log().all()
.accept(JSON).expect().statusCode(SC_OK)
.when().log().all()
.get(contextPath + "/test")
.then().log().all()
.body("someData.name",
IsCollectionWithSize.hasSize(2))
.body("healthReports.findAll {it.name == 'check1'}.props",
IsCollectionContaining.hasItems(startsWith("AA")));
I'm not sure how from the findAll ...props I can check the contents of the ArrayList.
The error displayed is:
JSON path someData.findAll {it.name == 'check1'}.props doesn't match.
Expected: (a collection containing a string starting with "AA")
Actual: [[AAaa]]
Any idea's ?
The findall return an Array of Array containing one element which is AA (which is why you have [[AAaa]] instead of [AAaa].
You have to flatten or extract one level of array to solve the problem I think.

Resources