Skip to content

Commit

Permalink
Merge pull request #6 from wistefan/ngsi-ld
Browse files Browse the repository at this point in the history
add some basic ngsi-ld support
  • Loading branch information
wistefan authored May 31, 2024
2 parents 0752194 + 60ef5fc commit 45d5c15
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 15 deletions.
17 changes: 17 additions & 0 deletions doc/REGO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<property> | # | 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:<property>_observedAt | # | retrieves the observedAt of the property The method should be concretized in the mapping.json, to match a concrete property. |
| leftOperand | ngsi-ld:<property>_modifiedAt | # | retrieves the modifiedAt of the property The method should be concretized in the mapping.json, to match a concrete property. |
| leftOperand | ngsi-ld:<relationship> | # | 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. |
48 changes: 36 additions & 12 deletions src/main/java/org/fiware/odrl/mapping/OdrlMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> assigneeMap = convertToMap(theAssignee);
Expand Down Expand Up @@ -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<String, Object> 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 {
Expand All @@ -408,7 +432,7 @@ private void mapStringTarget(NamespacedValue target, String targetId) throws Map
private void mapAssigneeParty(Map<String, Object> theParty) throws MappingException {
Optional<Object> 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.");
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/resources/mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
}
},
Expand Down
38 changes: 38 additions & 0 deletions src/main/resources/rego-resources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
49 changes: 49 additions & 0 deletions src/main/resources/rego/ngsi-ld/leftOperand.rego
Original file line number Diff line number Diff line change
@@ -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:<property>
# 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:<property>_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:<property>_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:<relationship>
# 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

7 changes: 6 additions & 1 deletion src/main/resources/rego/odrl/assignee.rego
Original file line number Diff line number Diff line change
Expand Up @@ -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
is_user(user,uid) if user == uid

## odrl:any
# allows for any user
is_any := true

6 changes: 5 additions & 1 deletion src/main/resources/rego/utils/apisix.rego
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions src/main/resources/rego/vc/leftOperand.rego
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion src/test/java/org/fiware/odrl/OdrlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public abstract class OdrlTest {

public void mockEntity(MockServerClient mockServerClient, MockEntity mockEntity) {


Map<String, Object> theOffering = Map.of("id", mockEntity.id(), "relatedParty",
mockEntity.relatedParty());
mockServerClient
Expand All @@ -62,6 +61,12 @@ public void mockEntity(MockServerClient mockServerClient, MockEntity mockEntity)

public static Stream<Arguments> 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",
Expand Down Expand Up @@ -155,6 +160,7 @@ public static Stream<Arguments> odrlPolicies() {

public static Stream<Arguments> 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"),
Expand Down
39 changes: 39 additions & 0 deletions src/test/resources/examples/ngsi-ld/types/types.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}

0 comments on commit 45d5c15

Please sign in to comment.