diff --git a/doc/REGO.md b/doc/REGO.md index d55bf13..28c5625 100644 --- a/doc/REGO.md +++ b/doc/REGO.md @@ -61,3 +61,20 @@ | helper | ## | token | the unprefixed bearer token | | helper | ## | entity | the entity provided as http-body | | helper | ## | target | the target of the request, found as the last part of the path | + +## vc + +| ODRL Class | ODRL Key | Rego-Method | Description | +| --- | --- | --- | --- | +| leftOperand | vc:role | role(verifiable_credential,organization_id) | retrieves the roles from the credential, that target the current organization | +| leftOperand | vc:currentParty | current_party(credential) | the current (organization)party, | + +## ngsild + +| ODRL Class | ODRL Key | Rego-Method | Description | +| --- | --- | --- | --- | +| leftOperand | ngsi-ld:entityType | entity_type(http_part) | retrieves the type from an entity, either from the request path or from the body | +| leftOperand | ngsi-ld: | # | retrieves the value of the property, only applies to properties of type "Property". The method should be concretized in the mapping.json, to match a concrete property. | +| leftOperand | ngsi-ld:_observedAt | # | retrieves the observedAt of the property The method should be concretized in the mapping.json, to match a concrete property. | +| leftOperand | ngsi-ld:_modifiedAt | # | retrieves the modifiedAt of the property The method should be concretized in the mapping.json, to match a concrete property. | +| leftOperand | ngsi-ld: | # | retrieves the object of the relationship, only applies to properties of type "Relationship". The method should be concretized in the mapping.json, to match a concrete property. | diff --git a/src/main/java/org/fiware/odrl/mapping/OdrlMapper.java b/src/main/java/org/fiware/odrl/mapping/OdrlMapper.java index fab9b64..bbdfaa7 100644 --- a/src/main/java/org/fiware/odrl/mapping/OdrlMapper.java +++ b/src/main/java/org/fiware/odrl/mapping/OdrlMapper.java @@ -151,15 +151,9 @@ private void mapTarget(Object theTarget) throws MappingException { } } - private void mapAssignee(Object theAssignee) throws MappingException { - NamespacedValue assignee = getNamespaced(ASSIGNEE_KEY); - - try { - mapStringAssignee(assignee, getStringOrByKey(theAssignee, ID_KEY)); + if (mapStringAssignee(theAssignee)) { return; - } catch (MappingException e) { - // no-op, its a map } Map assigneeMap = convertToMap(theAssignee); @@ -392,10 +386,40 @@ private String getStringOrByKey(Object theObject, String theKey) throws MappingE throw new MappingException(String.format("Was not able to extract a valid %s.", theKey)); } - private void mapStringAssignee(NamespacedValue assignee, String assigneeId) throws MappingException { - RegoMethod regoMethod = getFromConfig(OdrlAttribute.ASSIGNEE, assignee); - mappingResult.addImport(regoMethod.regoPackage()); - mappingResult.addRule(String.format(regoMethod.regoMethod(), String.format(STRING_ESCAPE_TEMPLATE, assigneeId))); + private boolean mapStringAssignee(Object theAssignee) throws MappingException { + boolean result = false; + var assigneeString = ""; + + if (theAssignee instanceof String theString) { + assigneeString = theString; + result = true; + } else { + Map assigneeMap = convertToMap(theAssignee); + if (assigneeMap.containsKey(VALUE_KEY) && assigneeMap.get(VALUE_KEY) instanceof String theString) { + assigneeString = theString; + result = true; + } else if (assigneeMap.containsKey(ID_KEY) && assigneeMap.get(ID_KEY) instanceof String theString) { + assigneeString = theString; + result = true; + } + } + + if (result) { + NamespacedValue namespacedAssignee = null; + var assigneeId = ""; + try { + getFromConfig(OdrlAttribute.ASSIGNEE, getNamespaced(assigneeString)); + namespacedAssignee = getNamespaced(assigneeString); + } catch (MappingException mappingException) { + namespacedAssignee = getNamespaced(ASSIGNEE_KEY); + assigneeId = assigneeString; + } + RegoMethod regoMethod = getFromConfig(OdrlAttribute.ASSIGNEE, namespacedAssignee); + mappingResult.addImport(regoMethod.regoPackage()); + mappingResult.addRule(String.format(regoMethod.regoMethod(), String.format(STRING_ESCAPE_TEMPLATE, assigneeId))); + } + + return result; } private void mapStringTarget(NamespacedValue target, String targetId) throws MappingException { @@ -408,7 +432,7 @@ private void mapStringTarget(NamespacedValue target, String targetId) throws Map private void mapAssigneeParty(Map theParty) throws MappingException { Optional optionalUid = Optional.ofNullable(theParty.get(UID_KEY)); if (optionalUid.isPresent() && optionalUid.get() instanceof String uid) { - mapStringAssignee(getNamespaced(ASSIGNEE_KEY), uid); + mapStringAssignee(uid); } else { mappingResult.addFailure("The party does not contain a valid uid."); } diff --git a/src/main/resources/mapping.json b/src/main/resources/mapping.json index 9a5fcaf..844147e 100644 --- a/src/main/resources/mapping.json +++ b/src/main/resources/mapping.json @@ -124,6 +124,22 @@ } }, "leftOperand": { + "vc": { + "role": { + "regoPackage": "vc.leftOperand as vc_lo", + "regoMethod": "vc_lo.role(helper.verifiable_credential, helper.organization_did)" + }, + "currentParty": { + "regoPackage": "vc.leftOperand as vc_lo", + "regoMethod": "vc_lo.current_party(helper.verifiable_credential)" + } + }, + "ngsi-ld": { + "entityType": { + "regoPackage": "ngsild.leftOperand as ngsild_lo", + "regoMethod": "ngsild_lo.entity_type(helper.http_part)" + } + }, "odrl": { "dateTime": { "regoPackage": "odrl.leftOperand as odrl_lo", @@ -176,6 +192,10 @@ "assignee": { "regoPackage": "odrl.assignee as odrl_assignee", "regoMethod": "odrl_assignee.is_user(helper.issuer,%s)" + }, + "any": { + "regoPackage": "odrl.assignee as odrl_assignee", + "regoMethod": "odrl_assignee.is_any" } } }, diff --git a/src/main/resources/rego-resources.txt b/src/main/resources/rego-resources.txt index 634c2ff..c80b3de 100644 --- a/src/main/resources/rego-resources.txt +++ b/src/main/resources/rego-resources.txt @@ -19,3 +19,41 @@ rego/odrl/action.rego rego/odrl/assignee.rego rego/utils/kong.rego rego/utils/apisix.rego +rego/dome/leftOperand.rego +rego/dome/action.rego +rego/odrl/operand.rego +rego/odrl/rightOperand.rego +rego/odrl/operator.rego +rego/odrl/leftOperand.rego +rego/odrl/target.rego +rego/odrl/action.rego +rego/odrl/assignee.rego +rego/utils/kong.rego +rego/utils/apisix.rego +rego/ngsi-ld/leftOperand.rego +rego/dome/leftOperand.rego +rego/dome/action.rego +rego/odrl/operand.rego +rego/odrl/rightOperand.rego +rego/odrl/operator.rego +rego/odrl/leftOperand.rego +rego/odrl/target.rego +rego/odrl/action.rego +rego/odrl/assignee.rego +rego/utils/kong.rego +rego/utils/apisix.rego +rego/vc/leftOperand.rego +rego/ngsi-ld/leftOperand.rego +rego/dome/leftOperand.rego +rego/dome/action.rego +rego/odrl/operand.rego +rego/odrl/rightOperand.rego +rego/odrl/operator.rego +rego/odrl/leftOperand.rego +rego/odrl/target.rego +rego/odrl/action.rego +rego/odrl/assignee.rego +rego/utils/kong.rego +rego/utils/apisix.rego +rego/vc/leftOperand.rego +rego/ngsi-ld/leftOperand.rego diff --git a/src/main/resources/rego/ngsi-ld/leftOperand.rego b/src/main/resources/rego/ngsi-ld/leftOperand.rego new file mode 100644 index 0000000..3fb774b --- /dev/null +++ b/src/main/resources/rego/ngsi-ld/leftOperand.rego @@ -0,0 +1,49 @@ +package ngsild.leftOperand + +import rego.v1 + + +# helper method to retrieve the type from the path +type_from_path(path) := tfe if { + path_without_query := split(path, "?")[0] + path_elements := split(path_without_query, "/") + id_elements := split(path_elements[count(path_elements) - 1], ":") + tfe = id_elements[2] +} else := tfq if { + query := split(path, "?")[1] + query_parts := split(query, "&") + type_query := [query_part | some query_part in query_parts; contains(query_part, "type=")] + tfq = split(type_query[0], "=")[1] +} + +# helper to retrieve the type from the body +type_from_body(body) := body.type + +## ngsi-ld:entityType +# retrieves the type from an entity, either from the request path or from the body +entity_type(http_part) := tfp if { + tfp = type_from_path(http_part.path) +} else := tfb if { + tfb = type_from_body(http_part.body) +} + +## ngsi-ld: +# retrieves the value of the property, only applies to properties of type "Property". The method should be concretized in the mapping.json, to match a concrete property. +# F.e.: ngsi-ld:brandName = property_value("brandName", http_part.body) +property_value(property_name, body) := body[property_name].value + +## ngsi-ld:_observedAt +# retrieves the observedAt of the property The method should be concretized in the mapping.json, to match a concrete property. +# F.e.: ngsi-ld:brandName_observedAt = property_value("brandName", http_part.body) +property_observed_at(property_name,body) := body[property_name].observedAt + +## ngsi-ld:_modifiedAt +# retrieves the modifiedAt of the property The method should be concretized in the mapping.json, to match a concrete property. +# F.e.: ngsi-ld:brandName_modifiedAt= property_value("brandName", http_part.body) +property_observed_at(property_name,body) := body[property_name].modifiedAt + +## ngsi-ld: +# retrieves the object of the relationship, only applies to properties of type "Relationship". The method should be concretized in the mapping.json, to match a concrete property. +# F.e.: ngsi-ld:owningCompany = relationship_object("owningCompany", http_part.body) +relationship_object(relationship_name, body):= body[relationship_name].object + diff --git a/src/main/resources/rego/odrl/assignee.rego b/src/main/resources/rego/odrl/assignee.rego index 1f2420f..cf03d72 100644 --- a/src/main/resources/rego/odrl/assignee.rego +++ b/src/main/resources/rego/odrl/assignee.rego @@ -4,4 +4,9 @@ import rego.v1 ## odrl:uid,odrl:assignee # is the given user id the same as the given uid -is_user(user,uid) if user == uid \ No newline at end of file +is_user(user,uid) if user == uid + +## odrl:any +# allows for any user +is_any := true + diff --git a/src/main/resources/rego/utils/apisix.rego b/src/main/resources/rego/utils/apisix.rego index a466054..b68481b 100644 --- a/src/main/resources/rego/utils/apisix.rego +++ b/src/main/resources/rego/utils/apisix.rego @@ -36,7 +36,11 @@ decoded_token_payload := decoded_authorization[1] ## # the verifiable credential received as part of the token -verifiable_credential := decoded_token_payload.verifiableCredential +verifiable_credential := verfiableCredential if{ + verfiableCredential = decoded_token_payload.verifiableCredential +} else := vc if { + vc = decoded_token_payload.vc +} ## # the issuer of the credential diff --git a/src/main/resources/rego/vc/leftOperand.rego b/src/main/resources/rego/vc/leftOperand.rego new file mode 100644 index 0000000..c088b03 --- /dev/null +++ b/src/main/resources/rego/vc/leftOperand.rego @@ -0,0 +1,15 @@ +package vc.leftOperand + +import rego.v1 + +## vc:role +# retrieves the roles from the credential, that target the current organization +role(verifiable_credential,organization_id) := r if { + roles := verifiable_credential.credentialSubject.roles + role := [rad | some rad in roles; rad.target = organization_id ] + r = role[_].names; trace(organization_id) +} + +## vc:currentParty +# the current (organization)party, +current_party(credential) := credential.issuer \ No newline at end of file diff --git a/src/test/java/org/fiware/odrl/OdrlTest.java b/src/test/java/org/fiware/odrl/OdrlTest.java index 501601a..9a6b616 100644 --- a/src/test/java/org/fiware/odrl/OdrlTest.java +++ b/src/test/java/org/fiware/odrl/OdrlTest.java @@ -44,7 +44,6 @@ public abstract class OdrlTest { public void mockEntity(MockServerClient mockServerClient, MockEntity mockEntity) { - Map theOffering = Map.of("id", mockEntity.id(), "relatedParty", mockEntity.relatedParty()); mockServerClient @@ -62,6 +61,12 @@ public void mockEntity(MockServerClient mockServerClient, MockEntity mockEntity) public static Stream validCombinations() { return Stream.of( + Arguments.of( + List.of("/examples/ngsi-ld/types/types.json"), + getRequest("urn:ngsi-ld:participant:1", + "/ngsi-ld/v1/entities/urn:ngsi-ld:Marketplace:test", + "GET"), + new MockEntity().id("urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2").relatedParty(List.of(new RelatedParty()))), Arguments.of( List.of("/examples/dome/1000/_1000.json"), getRequest("urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2", @@ -155,6 +160,7 @@ public static Stream odrlPolicies() { public static Stream odrlPolicyPath() { return Stream.of( + Arguments.of("/examples/ngsi-ld/types/types.json"), Arguments.of("/examples/dome/1000/_1000.json"), Arguments.of("/examples/dome/1001/_1001.json"), Arguments.of("/examples/dome/1001-2/_1001-2.json"), diff --git a/src/test/resources/examples/ngsi-ld/types/types.json b/src/test/resources/examples/ngsi-ld/types/types.json new file mode 100644 index 0000000..4ae8ac6 --- /dev/null +++ b/src/test/resources/examples/ngsi-ld/types/types.json @@ -0,0 +1,39 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "dct": "http://purl.org/dc/terms/", + "owl": "http://www.w3.org/2002/07/owl#", + "odrl": "http://www.w3.org/ns/odrl/2/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "skos": "http://www.w3.org/2004/02/skos/core#" + }, + "@id": "https://mp-operation.org/policy/common/type", + "@type": "odrl:Policy", + "odrl:permission": { + "odrl:assigner": { + "@id": "https://www.mp-operation.org/" + }, + "odrl:target": { + "@type": "odrl:AssetCollection", + "odrl:source": "urn:asset", + "odrl:refinement": [ + { + "@type": "odrl:Constraint", + "odrl:leftOperand": { + "@id": "ngsi-ld:entityType" + }, + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "Marketplace" + } + ] + }, + "odrl:assignee": { + "@id": "odrl:any" + }, + "odrl:action": { + "@id": "dome-op:read" + } + } +}