├── .mvn └── wrapper │ ├── .gitignore │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── doc └── odrl-pap.jpg ├── .dockerignore ├── src ├── test │ ├── resources │ │ ├── examples │ │ │ ├── dome │ │ │ │ ├── 1000 │ │ │ │ │ ├── _1000.json │ │ │ │ │ └── 1000.rego │ │ │ │ ├── 1001 │ │ │ │ │ ├── _1001.json │ │ │ │ │ └── 1001.rego │ │ │ │ ├── 1002 │ │ │ │ │ ├── _1002.json │ │ │ │ │ └── 1002.rego │ │ │ │ ├── 1003 │ │ │ │ │ ├── _1003.json │ │ │ │ │ └── 1003.rego │ │ │ │ ├── 1004 │ │ │ │ │ ├── 1004.json │ │ │ │ │ └── 1004.rego │ │ │ │ ├── 1005 │ │ │ │ │ ├── _1005.json │ │ │ │ │ └── 1005.rego │ │ │ │ ├── 2001 │ │ │ │ │ ├── _2001.json │ │ │ │ │ └── 2001.rego │ │ │ │ ├── 2002 │ │ │ │ │ ├── _2002.json │ │ │ │ │ └── 2002.rego │ │ │ │ ├── 2003 │ │ │ │ │ ├── _2003.json │ │ │ │ │ └── 2003.rego │ │ │ │ ├── 6600 │ │ │ │ │ └── _6600.json │ │ │ │ ├── 6700 │ │ │ │ │ └── _6700.json │ │ │ │ ├── 6800 │ │ │ │ │ └── _6800.json │ │ │ │ ├── 1001-2 │ │ │ │ │ ├── _1001-2.json │ │ │ │ │ └── 1001-2.rego │ │ │ │ ├── 2001-2 │ │ │ │ │ ├── _2001-2.json │ │ │ │ │ └── 2001-2.rego │ │ │ │ └── 2001-3 │ │ │ │ │ ├── _2001-3.json │ │ │ │ │ └── 2001-3.rego │ │ │ ├── odrl │ │ │ │ └── 3000 │ │ │ │ │ ├── 3000.rego │ │ │ │ │ └── _3000.json │ │ │ ├── ngsi-ld │ │ │ │ └── types │ │ │ │ │ ├── properties.json │ │ │ │ │ └── types.json │ │ │ └── gaia-x │ │ │ │ └── ovc-constraint.json │ │ ├── application.properties │ │ └── opa.yaml │ └── java │ │ └── org │ │ └── fiware │ │ └── odrl │ │ ├── model │ │ ├── ValueObject.java │ │ ├── TestContent.java │ │ ├── RolesAndDuties.java │ │ ├── KongOpaInput.java │ │ ├── Request.java │ │ ├── GaiaXAddress.java │ │ ├── RelatedParty.java │ │ ├── OperandObject.java │ │ ├── MockEntity.java │ │ ├── HttpRequest.java │ │ └── Headers.java │ │ ├── resources │ │ ├── InjectOpa.java │ │ ├── InjectMockServerClient.java │ │ ├── MockServerTestResource.java │ │ └── OpenPolicyAgentTestResource.java │ │ └── OdrlTestIT.java └── main │ ├── resources │ ├── rego │ │ ├── vc │ │ │ ├── assignee.rego │ │ │ └── leftOperand.rego │ │ ├── tmf │ │ │ ├── action.rego │ │ │ └── leftOperand.rego │ │ ├── odrl │ │ │ ├── assignee.rego │ │ │ ├── target.rego │ │ │ ├── rightOperand.rego │ │ │ ├── leftOperand.rego │ │ │ ├── action.rego │ │ │ ├── operand.rego │ │ │ └── operator.rego │ │ ├── dome │ │ │ ├── action.rego │ │ │ └── leftOperand.rego │ │ ├── ngsi-ld │ │ │ ├── action.rego │ │ │ └── leftOperand.rego │ │ ├── http │ │ │ ├── operator.rego │ │ │ └── leftOperand.rego │ │ ├── gaia-x │ │ │ ├── constraint.rego │ │ │ └── leftOperand.rego │ │ └── utils │ │ │ ├── kong.rego │ │ │ └── apisix.rego │ └── application.properties │ ├── java │ └── org │ │ └── fiware │ │ └── odrl │ │ ├── rego │ │ ├── RegoPolicyService.java │ │ ├── OdrlPolicy.java │ │ ├── RegoPolicy.java │ │ ├── PolicyWrapper.java │ │ ├── RegoMethod.java │ │ ├── DataResponse.java │ │ ├── Manifest.java │ │ ├── PolicyRepository.java │ │ └── PersistentPolicyRepository.java │ │ ├── persistence │ │ ├── Policy.java │ │ ├── PersistentRepository.java │ │ └── PolicyEntity.java │ │ ├── mapping │ │ ├── PolicyReflectionConfiguration.java │ │ ├── MappingException.java │ │ ├── NamespacedMap.java │ │ ├── MappingConfiguration.java │ │ ├── RegoMap.java │ │ ├── OdrlAttribute.java │ │ ├── EntityMapper.java │ │ ├── TypeMapper.java │ │ ├── OperatorMapper.java │ │ ├── MappingResult.java │ │ ├── OdrlConstants.java │ │ ├── LeftOperandMapper.java │ │ ├── ConstraintMapper.java │ │ └── RightOperandMapper.java │ │ ├── verification │ │ ├── VerificationException.java │ │ ├── TypeVerifier.java │ │ └── OvcConstraintVerifier.java │ │ ├── Pep.java │ │ ├── PathsConfiguration.java │ │ ├── GeneralConfig.java │ │ ├── MappingsResource.java │ │ ├── AppConfig.java │ │ ├── ValidationResource.java │ │ └── PolicyResource.java │ └── docker │ ├── Dockerfile.native │ └── Dockerfile.native-micro ├── charts ├── mockserver │ ├── Chart.yaml │ ├── values.yaml │ └── templates │ │ ├── service.yaml │ │ ├── _helpers.tpl │ │ └── deployment.yaml ├── odrl-pap │ ├── Chart.yaml │ ├── values.yaml │ └── templates │ │ ├── service.yaml │ │ ├── deployment.yaml │ │ └── _helpers.tpl ├── opa │ ├── Chart.yaml │ ├── templates │ │ ├── service.yaml │ │ ├── configmap.yaml │ │ ├── _helpers.tpl │ │ └── deployment.yaml │ └── values.yaml ├── apisix │ ├── Chart.yaml │ └── values.yaml └── postgresql │ ├── Chart.yaml │ └── values.yaml ├── frontend ├── tsconfig.json ├── src │ ├── api │ │ ├── models │ │ │ ├── Page.ts │ │ │ ├── Uid.ts │ │ │ ├── PageSize.ts │ │ │ ├── Id.ts │ │ │ ├── OdrlPolicyJson.ts │ │ │ ├── PolicyList.ts │ │ │ ├── Headers.ts │ │ │ ├── Mapping.ts │ │ │ ├── Policy.ts │ │ │ ├── ValidationRequest.ts │ │ │ ├── ValidationResponse.ts │ │ │ ├── Mappings.ts │ │ │ └── TestRequest.ts │ │ ├── core │ │ │ ├── ApiResult.ts │ │ │ ├── ApiRequestOptions.ts │ │ │ ├── ApiError.ts │ │ │ ├── OpenAPI.ts │ │ │ └── CancelablePromise.ts │ │ ├── index.ts │ │ └── services │ │ │ ├── UiService.ts │ │ │ └── PapService.ts │ ├── main.tsx │ ├── theme │ │ └── theme.css │ ├── App.tsx │ ├── components │ │ ├── Layout.tsx │ │ ├── Baukasten.tsx │ │ ├── TargetEditor.tsx │ │ ├── AssigneeEditor.tsx │ │ └── PolicySummary.tsx │ ├── App.css │ ├── index.css │ ├── services │ │ └── api.ts │ ├── pages │ │ ├── PolicyList.tsx │ │ └── PolicyEditor.tsx │ └── assets │ │ └── react.svg ├── .env ├── .env.example ├── .gitignore ├── index.html ├── Dockerfile ├── eslint.config.js ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── vite.config.ts ├── public │ └── vite.svg └── README.md ├── .github └── workflows │ ├── test.yaml │ ├── it.yaml │ ├── check.yml │ ├── release.yaml │ └── pre-release.yaml ├── scripts ├── create-rego-resource-list.sh └── create-rego-doc.sh ├── api └── bundle.yaml └── k3s └── opa.yaml /.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /doc/odrl-pap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/doc/odrl-pap.jpg -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1004/1004.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1004/1004.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1000/_1000.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1000/_1000.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1001/_1001.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1001/_1001.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1002/_1002.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1002/_1002.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1003/_1003.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1003/_1003.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1005/_1005.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1005/_1005.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001/_2001.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/2001/_2001.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2002/_2002.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/2002/_2002.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2003/_2003.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/2003/_2003.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/6600/_6600.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/6600/_6600.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/6700/_6700.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/6700/_6700.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/6800/_6800.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/6800/_6800.json -------------------------------------------------------------------------------- /src/main/resources/rego/vc/assignee.rego: -------------------------------------------------------------------------------- 1 | package vc.assignee 2 | 3 | import rego.v1 4 | 5 | ## odrl:any 6 | # allows for any user 7 | is_any := true 8 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1001-2/_1001-2.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/1001-2/_1001-2.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001-2/_2001-2.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/2001-2/_2001-2.json -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001-3/_2001-3.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wistefan/odrl-pap/HEAD/src/test/resources/examples/dome/2001-3/_2001-3.json -------------------------------------------------------------------------------- /charts/mockserver/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: mockserver 3 | description: Mock server chart 4 | type: application 5 | version: 5.14.0 6 | appVersion: 5.14.0 -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /charts/odrl-pap/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: odrl-pap 3 | description: Helm Chart to deploy the ODRL PAP to Kubernetes 4 | type: application 5 | version: 0.0.1 6 | appVersion: 0.0.1 -------------------------------------------------------------------------------- /charts/opa/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: opa 3 | description: Helm Chart to deploy the Open Policy Agent to Kubernetes 4 | type: application 5 | version: 0.0.1 6 | appVersion: 0.63.0 -------------------------------------------------------------------------------- /src/main/resources/rego/tmf/action.rego: -------------------------------------------------------------------------------- 1 | package tmf.action 2 | 3 | import rego.v1 4 | 5 | ## tmf:create 6 | # Check if the given request is a creation 7 | is_creation(request) if request.method == "POST" -------------------------------------------------------------------------------- /frontend/src/api/models/Page.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type Page = number; 6 | -------------------------------------------------------------------------------- /frontend/src/api/models/Uid.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type Uid = string; 6 | -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/assignee.rego: -------------------------------------------------------------------------------- 1 | package odrl.assignee 2 | 3 | import rego.v1 4 | 5 | ## odrl:uid,odrl:assignee 6 | # is the given user id the same as the given uid 7 | is_user(user,uid) if user == uid 8 | -------------------------------------------------------------------------------- /frontend/src/api/models/PageSize.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type PageSize = number; 6 | -------------------------------------------------------------------------------- /src/main/resources/rego/dome/action.rego: -------------------------------------------------------------------------------- 1 | package dome.action 2 | 3 | import rego.v1 4 | 5 | ## dome-op:create 6 | # Check if the given request is a creation 7 | is_creation(request) if request.method == "POST" 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/rego/ngsi-ld/action.rego: -------------------------------------------------------------------------------- 1 | package ngsild.action 2 | 3 | import rego.v1 4 | 5 | ## ngsild:create 6 | # Check if the given request is a creation 7 | is_creation(request) if request.method == "POST" 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/target.rego: -------------------------------------------------------------------------------- 1 | package odrl.target 2 | 3 | import rego.v1 4 | 5 | ## odrl:target,odrl:uid 6 | # check that the uid of the target is equal to the given uid 7 | is_target(target, uid) if target == uid -------------------------------------------------------------------------------- /frontend/src/api/models/Id.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { Uid } from './Uid'; 6 | export type Id = Uid; 7 | -------------------------------------------------------------------------------- /frontend/src/api/models/OdrlPolicyJson.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type OdrlPolicyJson = Record; 6 | -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/rightOperand.rego: -------------------------------------------------------------------------------- 1 | package odrl.rightOperand 2 | 3 | import rego.v1 4 | 5 | 6 | ## odrl:policyUsage 7 | # return the current time in ms, e.g. the time that the policy is used 8 | policy_usage := time.now_ns() / 1000000 -------------------------------------------------------------------------------- /frontend/src/api/models/PolicyList.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { Policy } from './Policy'; 6 | export type PolicyList = Array; 7 | -------------------------------------------------------------------------------- /src/main/resources/rego/http/operator.rego: -------------------------------------------------------------------------------- 1 | package http.operator 2 | 3 | import rego.v1 4 | 5 | ## http:isInPath 6 | # check that left operand is in the path of the right operand 7 | is_in_path_operator(leftOperand, rightOperand) if startswith(leftOperand, rightOperand) 8 | -------------------------------------------------------------------------------- /charts/apisix/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: apisix 3 | description: Dependency chart 4 | type: application 5 | version: 3.0.2 6 | appVersion: 3.8.0 7 | 8 | dependencies: 9 | - name: apisix 10 | repository: oci://registry-1.docker.io/bitnamicharts 11 | version: 3.5.1 -------------------------------------------------------------------------------- /charts/postgresql/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: postgresql 3 | description: Dependency chart 4 | type: application 5 | version: 0.0.1 6 | appVersion: 0.0.1 7 | 8 | dependencies: 9 | - name: postgresql 10 | repository: oci://registry-1.docker.io/bitnamicharts 11 | version: 13.1.5 -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/RegoPolicyService.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | /** 4 | * @author Stefan Wiedemann 5 | */ 6 | public interface RegoPolicyService { 7 | 8 | void createPolicy(String packageName, String policy); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/api/models/Headers.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type Headers = { 6 | accept?: string; 7 | authorization?: string; 8 | 'content-type'?: string; 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/api/models/Mapping.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | /** 6 | * A concrete mapping. 7 | */ 8 | export type Mapping = { 9 | name?: string; 10 | description?: string; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/api/models/Policy.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type Policy = { 6 | id?: string; 7 | 'odrl:uid'?: string; 8 | odrl?: string; 9 | rego?: string; 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/OdrlPolicy.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | /** 6 | * @author Stefan Wiedemann 7 | */ 8 | @RegisterForReflection 9 | public record OdrlPolicy(String policy) { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/RegoPolicy.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | /** 6 | * @author Stefan Wiedemann 7 | */ 8 | @RegisterForReflection 9 | public record RegoPolicy(String policy) { 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import './theme/theme.css' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | , 11 | ) -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | # The full URL to your backend API for the dev server proxy 2 | VITE_API_PROXY_TARGET=http://localhost:8080 3 | 4 | # The base path for API calls in the production build. 5 | # If your API is at https://api.example.com, set this to https://api.example.com 6 | # If it's served on the same host, you can leave this as /api 7 | VITE_API_BASE_URL=/api 8 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/ValueObject.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | @AllArgsConstructor 8 | @Data 9 | public class ValueObject { 10 | 11 | @JsonProperty("@value") 12 | private Object theValue; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # The full URL to your backend API for the dev server proxy 2 | VITE_API_PROXY_TARGET=http://localhost:8081 3 | 4 | # The base path for API calls in the production build. 5 | # If your API is at https://api.example.com, set this to https://api.example.com 6 | # If it's served on the same host, you can leave this as /api 7 | VITE_API_BASE_URL=/api 8 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/TestContent.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | @EqualsAndHashCode 8 | @AllArgsConstructor 9 | @Data 10 | public class TestContent { 11 | 12 | private String testString; 13 | private boolean test; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/src/api/core/ApiResult.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type ApiResult = { 6 | readonly url: string; 7 | readonly ok: boolean; 8 | readonly status: number; 9 | readonly statusText: string; 10 | readonly body: any; 11 | }; 12 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/persistence/Policy.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.persistence; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @RegisterForReflection 10 | @Data 11 | public class Policy { 12 | private String policy; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/PolicyWrapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | /** 6 | * @author Stefan Wiedemann 7 | */ 8 | @RegisterForReflection 9 | public record PolicyWrapper(String regoId, String odrlUid, OdrlPolicy odrl, RegoPolicy rego) { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/RolesAndDuties.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author Stefan Wiedemann 9 | */ 10 | @Data 11 | public class RolesAndDuties { 12 | private String target; 13 | private String id; 14 | private List roleNames; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/KongOpaInput.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @Data 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class KongOpaInput { 12 | 13 | private Request request; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001/2001.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import rego.v1 7 | import odrl.action 8 | 9 | default allow := false 10 | 11 | allow if { 12 | eq_operator(related_party(helper.reg.entity()),null) 13 | has_part_operator(role(helper.req.credential()),"seller") 14 | is_modification(helper.req.request()) 15 | } -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001-2/2001-2.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import rego.v1 7 | import odrl.action 8 | 9 | default allow := false 10 | 11 | allow if { 12 | eq_operator(related_party(helper.reg.entity()),null) 13 | has_part_operator(role(helper.req.credential()),"seller") 14 | is_modification(helper.req.request()) 15 | } -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1005/1005.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import odrl.assignee 7 | import rego.v1 8 | import odrl.action 9 | 10 | default allow := false 11 | 12 | allow if { 13 | eq_operator(related_party(helper.reg.entity()),null) 14 | is_user(helper.reg.subject(),"urn:assignee") 15 | is_modification(helper.req.request()) 16 | } -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | if: github.event_name == 'push' 10 | 11 | steps: 12 | 13 | - uses: actions/checkout@v2 14 | 15 | - uses: actions/setup-java@v1 16 | with: 17 | java-version: '17' 18 | java-package: jdk 19 | 20 | - name: Run tests 21 | run: mvn clean test -------------------------------------------------------------------------------- /frontend/src/api/models/ValidationRequest.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { OdrlPolicyJson } from './OdrlPolicyJson'; 6 | import type { TestRequest } from './TestRequest'; 7 | export type ValidationRequest = { 8 | policy?: OdrlPolicyJson; 9 | testRequest?: TestRequest; 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/PolicyReflectionConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | import org.fiware.odrl.model.Policy; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @RegisterForReflection(targets = {Policy.class}) 10 | public class PolicyReflectionConfiguration { 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/Request.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @Data 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class Request { 12 | 13 | private String time; 14 | private HttpRequest http; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1003/1003.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import odrl.target 6 | import helper.req 7 | import rego.v1 8 | import odrl.action 9 | 10 | default allow := false 11 | 12 | allow if { 13 | eq_operator(role(helper.req.credential()),"onboarder") 14 | is_read(helper.req.request()) 15 | is_target(helper.reg.target(),"urn:ngsi-ld:button:onboard") 16 | } -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | frontend 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/verification/VerificationException.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.verification; 2 | 3 | import lombok.Getter; 4 | 5 | public class VerificationException extends Exception { 6 | 7 | @Getter 8 | private final Object verifiedObject; 9 | 10 | public VerificationException(String message, Object verifiedObject) { 11 | super(message); 12 | this.verifiedObject = verifiedObject; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /charts/mockserver/values.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | # we need it accessible from the tests 3 | type: LoadBalancer 4 | port: 1080 5 | test: 6 | image: quay.io/curl/curl:8.1.2 7 | releasenameOverride: mockserver 8 | 9 | app: 10 | logLevel: "INFO" 11 | serverPort: "1080" 12 | propertiesFileName: "mockserver.properties" 13 | 14 | image: 15 | repository: mockserver 16 | snapshot: false 17 | pullPolicy: IfNotPresent 18 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/GaiaXAddress.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | 8 | @Getter 9 | @Setter 10 | @Accessors(fluent = true) 11 | public class GaiaXAddress { 12 | 13 | @JsonProperty("gx:countrySubdivisionCode") 14 | private String countryCode; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2002/2002.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import dome.action 7 | import rego.v1 8 | 9 | default allow := false 10 | 11 | allow if { 12 | and_sequence_operand([eq_operator(id,current_party(helper.req.credential())),eq_operator(role,"Owner")]) 13 | TODO(set_published) 14 | has_part_operator(role(helper.req.credential()),"manager") 15 | } -------------------------------------------------------------------------------- /frontend/src/theme/theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #0B2B40; 3 | --secondary-color: #F07D00; 4 | --background-color: #FFFFFF; 5 | --text-color: #333333; 6 | --font-family: 'Lato', sans-serif; 7 | } 8 | 9 | body { 10 | font-family: var(--font-family); 11 | color: var(--text-color); 12 | background-color: var(--background-color); 13 | } 14 | 15 | .navbar-dark { 16 | background-color: var(--primary-color); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/persistence/PersistentRepository.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.persistence; 2 | 3 | import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; 4 | import jakarta.enterprise.context.ApplicationScoped; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @ApplicationScoped 10 | public class PersistentRepository implements PanacheRepositoryBase { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/MappingException.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | /** 4 | * @author Stefan Wiedemann 5 | */ 6 | public class MappingException extends Exception { 7 | public MappingException(String message) { 8 | super(message); 9 | } 10 | 11 | public MappingException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/NamespacedMap.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * @author Stefan Wiedemann 9 | */ 10 | @RegisterForReflection 11 | public class NamespacedMap extends HashMap { 12 | public NamespacedMap() { 13 | super(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2001-3/2001-3.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import rego.v1 7 | import odrl.action 8 | 9 | default allow := false 10 | 11 | allow if { 12 | and_sequence_operand([eq_operator(id,current_party(helper.req.credential())),eq_operator(role,"Owner")]) 13 | has_part_operator(role(helper.req.credential()),"seller") 14 | is_modification(helper.req.request()) 15 | } -------------------------------------------------------------------------------- /charts/postgresql/values.yaml: -------------------------------------------------------------------------------- 1 | postgresql: 2 | image: 3 | repository: bitnamilegacy/postgresql 4 | primary: 5 | persistence: 6 | enabled: false 7 | initdb: 8 | scripts: 9 | enable.sh: | 10 | psql postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE pap;" 11 | readReplicas: 12 | persistence: 13 | enabled: false 14 | global: 15 | postgresql: 16 | auth: 17 | postgresPassword: postgres -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/resources/InjectOpa.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.resources; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Stefan Wiedemann 10 | */ 11 | @Target(ElementType.FIELD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface InjectOpa { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/RelatedParty.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.experimental.Accessors; 8 | 9 | /** 10 | * @author Stefan Wiedemann 11 | */ 12 | @Getter 13 | @Setter 14 | @Accessors(fluent = true) 15 | public class RelatedParty { 16 | private String id; 17 | private String role; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build the React app 2 | FROM node:18-alpine AS build 3 | 4 | WORKDIR /app 5 | 6 | COPY package*.json ./ 7 | RUN npm install 8 | COPY . . 9 | RUN npm run build 10 | 11 | # Stage 2: Serve the static build artifacts 12 | FROM nginx:1.21.6-alpine 13 | 14 | COPY --from=build /app/dist /usr/share/nginx/html 15 | 16 | # When the container starts, nginx will serve the files from /usr/share/nginx/html 17 | EXPOSE 80 18 | 19 | CMD ["nginx", "-g", "daemon off;"] 20 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/MappingConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * @author Stefan Wiedemann 9 | */ 10 | @RegisterForReflection 11 | public class MappingConfiguration extends HashMap { 12 | 13 | public MappingConfiguration() { 14 | super(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/RegoMap.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | import org.fiware.odrl.rego.RegoMethod; 5 | 6 | import java.util.HashMap; 7 | 8 | /** 9 | * @author Stefan Wiedemann 10 | */ 11 | @RegisterForReflection 12 | public class RegoMap extends HashMap { 13 | public RegoMap() { 14 | super(); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/RegoMethod.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | /** 6 | * @author Stefan Wiedemann 7 | */ 8 | @RegisterForReflection 9 | public record RegoMethod(String regoPackage, String regoMethod, String description) { 10 | public RegoMethod(String regoPackage, String regoMethod) { 11 | this(regoPackage, regoMethod, ""); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/api/models/ValidationResponse.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type ValidationResponse = { 6 | /** 7 | * indicates if the request was allowed by the policy 8 | */ 9 | allow?: boolean; 10 | /** 11 | * Explanation of the detailed error, in case the policy evaluation failed. 12 | */ 13 | explanation?: Array; 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/resources/InjectMockServerClient.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.resources; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Stefan Wiedemann 10 | */ 11 | @Target(ElementType.FIELD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface InjectMockServerClient { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/OperandObject.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | 8 | @AllArgsConstructor 9 | @Data 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class OperandObject { 12 | 13 | @JsonProperty("@value") 14 | private Object value; 15 | @JsonProperty("@id") 16 | private String id; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/2003/2003.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import dome.action 7 | import rego.v1 8 | 9 | default allow := false 10 | 11 | allow if { 12 | is_creation(helper.req.request()) 13 | has_part_operator(role(helper.req.credential()),"seller") 14 | and_sequence_operand([eq_operator(related_party(helper.reg.entity()),current_party(helper.req.credential())),eq_operator(related_party_role(helper.reg.entity()),"Owner")]) 15 | } -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1002/1002.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import odrl.target 4 | import odrl.operator 5 | import helper.req 6 | import odrl.assignee 7 | import rego.v1 8 | import odrl.action 9 | 10 | default allow := false 11 | 12 | allow if { 13 | is_target(helper.reg.target(),"urn:ngsi-ld:product-offering:62d4f929-d29d-4070-ae1f-9fe7dd1de5f6") 14 | lt_operator(41,42) 15 | is_user(helper.reg.subject(),"urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2") 16 | is_use(helper.req.request()) 17 | } -------------------------------------------------------------------------------- /charts/opa/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "opa.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | {{ include "opa.labels" . | nindent 4 }} 8 | spec: 9 | type: {{ .Values.service.type }} 10 | ports: 11 | - port: {{ .Values.service.port }} 12 | targetPort: {{ .Values.deployment.opa.port }} 13 | protocol: TCP 14 | name: {{ include "opa.fullname" . }} 15 | selector: 16 | {{ include "opa.labels" . | nindent 4 }} -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1004/1004.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import dome.leftOperand 4 | import odrl.operator 5 | import helper.req 6 | import odrl.assignee 7 | import rego.v1 8 | import odrl.action 9 | 10 | default allow := false 11 | 12 | allow if { 13 | is_user(helper.reg.subject(),"urn:assignee") 14 | is_read(helper.req.request()) 15 | and_sequence_operand([n_eq_operator(life_cycle_status(helper.reg.entity()),"Retired"),lt_operator(TODO(validFor_startDateTime),null),gt_operator(TODO(validFor_endDateTime),null)]) 16 | } -------------------------------------------------------------------------------- /charts/odrl-pap/values.yaml: -------------------------------------------------------------------------------- 1 | deployment: 2 | image: 3 | repository: quay.io/wi_stefan/odrl-pap 4 | # -- tag of the image to be used 5 | tag: test 6 | # -- specification of the image pull policy 7 | pullPolicy: Never 8 | logLevel: DEBUG 9 | odrlPap: 10 | organizationId: did:web:test-org 11 | port: 8080 12 | db: 13 | kind: postgresql 14 | url: jdbc:postgresql://it-postgresql:5432/pap 15 | username: postgres 16 | password: postgres 17 | 18 | service: 19 | port: 8080 20 | type: LoadBalancer -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1000/1000.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import data.odrl.action as odrl_action 4 | import data.odrl.assignee as odrl_assignee 5 | import data.helper.reg 6 | import rego.v1 7 | import data.odrl.target as odrl_target 8 | 9 | 10 | allow() := if { 11 | odrl_target.is_target(helper.reg.target(),"urn:ngsi-ld:product-offering:62d4f929-d29d-4070-ae1f-9fe7dd1de5f6") 12 | odrl_action.is_use(helper.reg.request()) 13 | odrl_assignee.is_user(helper.reg.subject(),"urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2") 14 | } -------------------------------------------------------------------------------- /charts/odrl-pap/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "odrl-pap.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | {{ include "odrl-pap.labels" . | nindent 4 }} 8 | spec: 9 | type: {{ .Values.service.type }} 10 | ports: 11 | - port: {{ .Values.service.port }} 12 | targetPort: {{ .Values.deployment.odrlPap.port }} 13 | protocol: TCP 14 | name: {{ include "odrl-pap.fullname" . }} 15 | selector: 16 | {{ include "odrl-pap.labels" . | nindent 4 }} -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/DataResponse.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | import java.util.List; 7 | 8 | @RegisterForReflection 9 | public record DataResponse(boolean result, List explanation) { 10 | public DataResponse(boolean result) { 11 | this(result, List.of()); 12 | } 13 | 14 | public DataResponse(List explanation) { 15 | this(false, explanation); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /charts/mockserver/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "release.name" . }} 5 | labels: 6 | app: {{ template "chart.name" . }} 7 | release: {{ .Release.Name }} 8 | chart: {{ template "chart.name_version" . }} 9 | spec: 10 | type: {{ .Values.service.type }} 11 | ports: 12 | - name: serviceport 13 | protocol: TCP 14 | targetPort: serviceport 15 | port: {{ .Values.service.port }} 16 | selector: 17 | app: {{ template "chart.name" . }} 18 | release: {{ .Release.Name }} -------------------------------------------------------------------------------- /scripts/create-rego-resource-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Reads all rego files under the resource directory and adds there paths to a list. 4 | # This is required, since the quarkus native images cannot list resource subdirectories, but are only able to address 5 | # resources directly. 6 | 7 | resource_folder="src/main/resources/" 8 | rego_resources="src/main/resources/rego-resources.txt" 9 | 10 | for rego_file in $(find $resource_folder -name "*.rego" -type f); do 11 | echo "${rego_file#"$resource_folder"}" >> $rego_resources 12 | done 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/MockEntity.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author Stefan Wiedemann 13 | */ 14 | @Getter 15 | @Setter 16 | @Accessors(fluent = true) 17 | public class MockEntity { 18 | private String id; 19 | private String type; 20 | private List relatedParty; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/verification/TypeVerifier.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.verification; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Verifies objects to be compliant with a certain required structure. 7 | * Could for example check that an ovc:constraint contains ovc:leftOperand, odrl:rightOperand, odrl:operator and ovc:subjectType 8 | */ 9 | public interface TypeVerifier { 10 | 11 | void verify(T objectToVerify) throws VerificationException; 12 | 13 | List verifiableTypes(); 14 | 15 | String supportedBaseType(); 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.rest-client.opa_yaml.url=http://localhost:8181 2 | quarkus.rest-client.opa_yaml.scope=jakarta.inject.Singleton 3 | general.pep=kong 4 | quarkus.openapi-generator.codegen.spec.opa_yaml.return-response=true 5 | 6 | quarkus.datasource.db-kind=h2 7 | quarkus.datasource.username=username-default 8 | quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test 9 | quarkus.datasource.jdbc.max-size=13 10 | quarkus.hibernate-orm.database.generation=drop-and-create 11 | quarkus.hibernate-orm.database.generation.create-schemas=true 12 | -------------------------------------------------------------------------------- /src/main/resources/rego/gaia-x/constraint.rego: -------------------------------------------------------------------------------- 1 | package gaiax.constraint 2 | 3 | import rego.v1 4 | 5 | ## ovc:constraints 6 | # evaluates all provided constraints 7 | evaluate(constraints) if { 8 | true_constraints := [c | some c in constraints; c == true] 9 | count(true_constraints) == count(constraints) 10 | } 11 | 12 | ## ovc:credentialSubjectType 13 | # compares the credentials' subject-type with the provided one 14 | credentialSubjectType(verifiable_credential, credentialSubjectType) if { 15 | credentialSubjectType == verifiable_credential.credentialSubject.type 16 | } -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/Pep.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @RegisterForReflection 10 | public enum Pep { 11 | 12 | APISIX("apisix"), 13 | KONG("kong"); 14 | 15 | private final String value; 16 | 17 | Pep(String value) { 18 | this.value = value; 19 | } 20 | 21 | @JsonValue 22 | public String getValue() { 23 | return value; 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @Data 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class HttpRequest { 12 | private String id; 13 | private String method; 14 | private String host; 15 | private String path; 16 | private String protocol; 17 | private Object body; 18 | private Headers headers; 19 | private String entityId; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package odrl.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## odrl:currentTime 6 | # returns the current time in ms 7 | current_time := time.now_ns() / 1000000 8 | 9 | ## odrl:dayOfWeek 10 | # Day of week from timestamp (0=Mon, 6=Sun) 11 | day_of_week(ts) = weekday if { 12 | days := floor(ts / 1000 / 86400) 13 | weekday := (days + 4) % 7 14 | } 15 | 16 | ## odrl:hourOfDay 17 | # Hour of day (UTC) from timestamp (0–23) 18 | hour_of_day(ts) = hour if { 19 | seconds := floor(ts / 1000) 20 | seconds_in_day := seconds % 86400 21 | hour := floor(seconds_in_day / 3600) 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1001/1001.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import odrl.target 4 | import odrl.operator 5 | import helper.req 6 | import odrl.assignee 7 | import rego.v1 8 | import odrl.action 9 | import odrl.leftOperand 10 | 11 | default allow := false 12 | 13 | allow if { 14 | is_target(helper.reg.target(),"urn:ngsi-ld:product-offering:62d4f929-d29d-4070-ae1f-9fe7dd1de5f6") 15 | and_sequence_operand([gt_operator(TODO(dateTime),2023-12-31),lt_operator(TODO(dateTime),2025-01-01)]) 16 | is_user(helper.reg.subject(),"urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2") 17 | is_use(helper.req.request()) 18 | } -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/model/Headers.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author Stefan Wiedemann 9 | */ 10 | @Data 11 | @JsonInclude(JsonInclude.Include.NON_NULL) 12 | public class Headers { 13 | 14 | @JsonProperty(":method") 15 | private String method; 16 | private String accept; 17 | private String authorization; 18 | @JsonProperty("content-type") 19 | private String contentType; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/api/models/Mappings.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { Mapping } from './Mapping'; 6 | /** 7 | * Mappings available to be used for building policies 8 | */ 9 | export type Mappings = { 10 | actions?: Array; 11 | operators?: Array; 12 | operands?: Array; 13 | rightOperands?: Array; 14 | leftOperands?: Array; 15 | assignees?: Array; 16 | targets?: Array; 17 | constraints?: Array; 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 2 | import Layout from './components/Layout'; 3 | import PolicyList from './pages/PolicyList'; 4 | import PolicyEditor from './pages/PolicyEditor'; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | }> 11 | } /> 12 | } /> 13 | } /> 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default App; -------------------------------------------------------------------------------- /.github/workflows/it.yaml: -------------------------------------------------------------------------------- 1 | name: IT 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | if: github.event_name == 'push' 10 | 11 | steps: 12 | 13 | - uses: actions/checkout@v2 14 | 15 | - uses: actions/setup-java@v1 16 | with: 17 | java-version: '17' 18 | java-package: jdk 19 | 20 | - name: Ensure br_netfilter is enabled. 21 | run: | 22 | sudo modprobe br_netfilter 23 | 24 | - name: Run integration tests 25 | run: | 26 | mvn clean package -DskipTests -Dnative -Dquarkus.container-image.build=true 27 | mvn clean install -Pk8s-it -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/action.rego: -------------------------------------------------------------------------------- 1 | package odrl.action 2 | 3 | import rego.v1 4 | 5 | ## odrl:modify 6 | # checks if the given request is a modification 7 | is_modification(request) if request.method == "PATCH" 8 | 9 | ## odrl:delete 10 | # checks if the given request is a deletion 11 | is_deletion(request) if request.method == "DELETE" 12 | 13 | ## odrl:read 14 | # checks if the given request is a read operation 15 | is_read(request) if request.method == "GET" 16 | 17 | ## odrl:use 18 | # checks if the given request is a usage 19 | is_use(request) if { 20 | methods := ["DELETE", "GET", "POST", "PUT", "PATCH"] 21 | request.method in methods 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/opa.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - name: bundle-server 3 | url: http://localhost:8081/bundles/service/v1 4 | 5 | bundles: 6 | policies: 7 | service: bundle-server 8 | resource: policies.tar.gz 9 | polling: 10 | min_delay_seconds: 2 11 | max_delay_seconds: 4 12 | methods: 13 | service: bundle-server 14 | resource: methods.tar.gz 15 | polling: 16 | min_delay_seconds: 1 17 | max_delay_seconds: 3 18 | data: 19 | service: bundle-server 20 | resource: data.tar.gz 21 | polling: 22 | min_delay_seconds: 1 23 | max_delay_seconds: 15 24 | 25 | default_decision: /policy/main/allow -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/PathsConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import io.quarkus.runtime.annotations.StaticInitSafe; 4 | import io.smallrye.config.ConfigMapping; 5 | 6 | import java.io.File; 7 | import java.util.Optional; 8 | 9 | /** 10 | * @author Stefan Wiedemann 11 | */ 12 | @StaticInitSafe 13 | @ConfigMapping(prefix = "paths") 14 | public interface PathsConfiguration { 15 | 16 | // path to an additional @link{MappingConfiguration} to be merged with the defaults 17 | Optional mapping(); 18 | 19 | // Path to additional rego-methods to be added to the built-in methods. Duplications will be overwritten 20 | Optional rego(); 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/examples/dome/1001-2/1001-2.rego: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import odrl.target 4 | import odrl.operator 5 | import helper.req 6 | import odrl.assignee 7 | import rego.v1 8 | import odrl.action 9 | import odrl.leftOperand 10 | 11 | default allow := false 12 | 13 | allow if { 14 | is_target(helper.reg.target(),"urn:ngsi-ld:product-offering:62d4f929-d29d-4070-ae1f-9fe7dd1de5f6") 15 | or_operand([and_operand([gt_operator(TODO(dateTime),2023-12-31),lt_operator(TODO(dateTime),2025-01-01)]),and_operand([gt_operator(TODO(dateTime),2025-12-31),lt_operator(TODO(dateTime),2027-01-01)])]) 16 | is_user(helper.reg.subject(),"urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2") 17 | is_use(helper.req.request()) 18 | } -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | import { defineConfig, globalIgnores } from 'eslint/config' 7 | 8 | export default defineConfig([ 9 | globalIgnores(['dist']), 10 | { 11 | files: ['**/*.{ts,tsx}'], 12 | extends: [ 13 | js.configs.recommended, 14 | tseslint.configs.recommended, 15 | reactHooks.configs['recommended-latest'], 16 | reactRefresh.configs.vite, 17 | ], 18 | languageOptions: { 19 | ecmaVersion: 2020, 20 | globals: globals.browser, 21 | }, 22 | }, 23 | ]) 24 | -------------------------------------------------------------------------------- /frontend/src/api/core/ApiRequestOptions.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export type ApiRequestOptions = { 6 | readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; 7 | readonly url: string; 8 | readonly path?: Record; 9 | readonly cookies?: Record; 10 | readonly headers?: Record; 11 | readonly query?: Record; 12 | readonly formData?: Record; 13 | readonly body?: any; 14 | readonly mediaType?: string; 15 | readonly responseHeader?: string; 16 | readonly errors?: Record; 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/api/models/TestRequest.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { Headers } from './Headers'; 6 | export type TestRequest = { 7 | method?: TestRequest.method; 8 | host?: string; 9 | path?: string; 10 | protocol?: TestRequest.protocol; 11 | body?: Record; 12 | headers?: Headers; 13 | }; 14 | export namespace TestRequest { 15 | export enum method { 16 | POST = 'POST', 17 | PATCH = 'PATCH', 18 | PUT = 'PUT', 19 | GET = 'GET', 20 | DELETE = 'DELETE', 21 | } 22 | export enum protocol { 23 | HTTPS = 'https', 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/Manifest.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | import lombok.Getter; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Stefan Wiedemann 11 | */ 12 | @RegisterForReflection 13 | @Getter 14 | public class Manifest { 15 | 16 | private List roots = new ArrayList<>(); 17 | 18 | public Manifest setRoots(List roots) { 19 | this.roots.clear(); 20 | this.roots.addAll(roots); 21 | return this; 22 | } 23 | public Manifest addRoot(String root){ 24 | this.roots.add(root); 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/rego/gaia-x/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package gaiax.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## ovc:leftOperand 6 | # retrieves the claim from the credential, using the json-path of the claim 7 | getClaim(verifiable_credential, claim_path) := claim if { 8 | # split the path into an array of keys - e.g. "$.my.fancy.claim" becomes ["my","fancy","claim"] 9 | key_array := split(trim_prefix(claim_path, "$."), ".") 10 | # walk through the credential, providing tuples of path array & value - e.g. [["my","fancy","claim"], "value] 11 | walk(verifiable_credential, walked_tuple) 12 | # check that we found the claim keyed by our path 13 | walked_tuple[0] == key_array 14 | # return the actual value 15 | claim = walked_tuple[1] 16 | } -------------------------------------------------------------------------------- /charts/opa/values.yaml: -------------------------------------------------------------------------------- 1 | deployment: 2 | image: 3 | repository: openpolicyagent/opa 4 | # -- tag of the image to be used 5 | tag: 0.63.0 6 | # -- specification of the image pull policy 7 | pullPolicy: IfNotPresent 8 | logLevel: DEBUG 9 | opa: 10 | port: 8181 11 | logLevel: debug 12 | pap: 13 | url: http://it-odrl-pap:8080/bundles/service/v1 14 | policies: 15 | resource: policies.tar.gz 16 | minDelay: 2 17 | maxDelay: 4 18 | methods: 19 | resource: methods.tar.gz 20 | minDelay: 1 21 | maxDelay: 3 22 | data: 23 | resource: data.tar.gz 24 | minDelay: 1 25 | maxDelay: 15 26 | service: 27 | port: 8181 28 | type: LoadBalancer 29 | 30 | fullnameOverride: -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2023", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "types": [], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "erasableSyntaxOnly": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["vite.config.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import { Navbar, Container, Nav } from 'react-bootstrap'; 3 | 4 | const Layout = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ODRL PAP 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Layout; 26 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/rego/http/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package http.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## http:path 6 | # returns the currently requested path 7 | path(http_part) := http_part.path 8 | 9 | ## http:bodyValue 10 | # retrieves the value of the body content at the given path. 11 | body_value(body,value_path) := property if { 12 | # split the path into an array of keys - e.g. "$.my.fancy.property" becomes ["my","fancy","property"] 13 | key_array := split(trim_prefix(value_path, "$."), ".") 14 | # walk through the body, providing tuples of path array & value - e.g. [["my","fancy","property"], "value] 15 | walk(body, walked_tuple) 16 | # check that we found the property keyed by our path 17 | walked_tuple[0] == key_array 18 | # return the actual value 19 | property = walked_tuple[1] 20 | } -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/odrl-poc . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/odrl-poc 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /src/main/resources/rego/vc/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package vc.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## vc:role 6 | # retrieves the roles from the credential, that target the current organization 7 | role(verifiable_credential,organization_id) := r if { 8 | roles := verifiable_credential.credentialSubject.roles 9 | role := [rad | some rad in roles; rad.target = organization_id ] 10 | r = role[_].names; trace(organization_id) 11 | } 12 | 13 | ## vc:currentParty 14 | # the current (organization)party, 15 | current_party(credential) := credential.issuer 16 | 17 | ## vc:type 18 | # the type(s) of the current credential. Converted to array if string type. 19 | types(verifiable_credential) := result if { 20 | is_array(verifiable_credential.type) 21 | result = verifiable_credential.type 22 | } else := [verifiable_credential.type] -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/GeneralConfig.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import io.quarkus.runtime.annotations.StaticInitSafe; 4 | import io.smallrye.config.ConfigMapping; 5 | import io.smallrye.config.WithName; 6 | 7 | import java.net.URL; 8 | import java.util.List; 9 | 10 | /** 11 | * @author Stefan Wiedemann 12 | */ 13 | @StaticInitSafe 14 | @ConfigMapping(prefix = "general") 15 | public interface GeneralConfig { 16 | 17 | String organizationDid(); 18 | 19 | Pep pep(); 20 | 21 | // even thought kebab is the default case, making it more explicit 22 | @WithName("supported-sub-types") 23 | List supportedSubTypes(); 24 | 25 | interface SubType{ 26 | 27 | @WithName("base-type") 28 | String baseType(); 29 | 30 | @WithName("sub-types") 31 | List subTypes(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/OdrlAttribute.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | /** 7 | * @author Stefan Wiedemann 8 | */ 9 | @RegisterForReflection 10 | public enum OdrlAttribute { 11 | 12 | LEFT_OPERAND("leftOperand"), 13 | RIGHT_OPERAND("rightOperand"), 14 | OPERATOR("operator"), 15 | ASSIGNEE("assignee"), 16 | ACTION("action"), 17 | OPERAND("operand"), 18 | TARGET("target"), 19 | CONSTRAINT("constraint"); 20 | 21 | private final String value; 22 | 23 | OdrlAttribute(String value) { 24 | this.value = value; 25 | } 26 | 27 | @JsonValue 28 | public String getValue() { 29 | return value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2022", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "types": ["vite/client"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "moduleDetection": "force", 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "erasableSyntaxOnly": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUncheckedSideEffectImports": true 26 | }, 27 | "include": ["src"] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/rego/tmf/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package tmf.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## tmf:lifecycleStatus 6 | # return the lifeCycleStatus of a given entity 7 | life_cycle_status(entity) := entity.lifeCycleStatus 8 | 9 | ## tmf:resource 10 | # retrieves the type of the resource from the path 11 | resource_type(http_part) := resource if { 12 | path_without_query := split(http_part.path, "?")[0] 13 | path_elements := split(path_without_query, "/") 14 | # reverse the path to get the potential id element first 15 | reversed := array.reverse(path_elements) 16 | # remove the (potential) id element from the path array 17 | non_id_parts := [path_element | some path_element in reversed; not contains(path_element, "ngsi-ld")] 18 | # after removal of the id, the resource is the first one to be retrieved 19 | resource = non_id_parts[0] 20 | } -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/operand.rego: -------------------------------------------------------------------------------- 1 | package odrl.operand 2 | 3 | import rego.v1 4 | 5 | ## odrl:and 6 | # checks if all given constraints are true 7 | and_operand(constraints) if { 8 | true_constraints := [constraint | some constraint in constraints; constraint == true] 9 | count(true_constraints) == count(constraints) 10 | } 11 | 12 | ## odrl:andSequence 13 | # checks if all given constraints are true 14 | and_sequence_operand(constraints) if { 15 | and_operand(constraints) 16 | } 17 | 18 | ## odrl:or 19 | # check that at least one of the constraints is true 20 | or_operand(constraints) if { 21 | true in constraints 22 | } 23 | 24 | ## odrl:xone 25 | # check that exactly one of the constraints is true 26 | only_one_operand(constraints) if { 27 | true_constraints := [constraint | some constraint in constraints; constraint == true] 28 | count(true_constraints) == 1 29 | } -------------------------------------------------------------------------------- /frontend/src/api/core/ApiError.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { ApiRequestOptions } from './ApiRequestOptions'; 6 | import type { ApiResult } from './ApiResult'; 7 | 8 | export class ApiError extends Error { 9 | public readonly url: string; 10 | public readonly status: number; 11 | public readonly statusText: string; 12 | public readonly body: any; 13 | public readonly request: ApiRequestOptions; 14 | 15 | constructor(request: ApiRequestOptions, response: ApiResult, message: string) { 16 | super(message); 17 | 18 | this.name = 'ApiError'; 19 | this.url = response.url; 20 | this.status = response.status; 21 | this.statusText = response.statusText; 22 | this.body = response.body; 23 | this.request = request; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/examples/odrl/3000/3000.rego: -------------------------------------------------------------------------------- 1 | package policy.weekday_hours_cars 2 | 3 | import data.odrl.operand as odrl_operand 4 | import data.odrl.action as odrl_action 5 | import data.utils.helper as helper 6 | import data.odrl.leftOperand as odrl_lo 7 | import data.ngsild.leftOperand as ngsild_lo 8 | import rego.v1 9 | import data.odrl.operator as odrl_operator 10 | import data.vc.assignee as vc_assignee 11 | 12 | is_allowed if { 13 | odrl_action.is_read(helper.http_part) 14 | odrl_operand.and_sequence_operand([odrl_operator.eq_operator(ngsild_lo.entity_type(helper.http_part),"Test_Car")]) 15 | vc_assignee.is_any 16 | odrl_operand.and_sequence_operand([odrl_operator.gt_eq_operator(odrl_lo.day_of_week(odrl_lo.current_time),0),odrl_operator.lt_eq_operator(odrl_lo.day_of_week(odrl_lo.current_time),4),odrl_operator.gt_eq_operator(odrl_lo.hour_of_day(odrl_lo.current_time),8),odrl_operator.lt_eq_operator(odrl_lo.hour_of_day(odrl_lo.current_time),23)]) 17 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | general.organization-did=urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2 2 | general.pep=apisix 3 | 4 | 5 | general.supported-sub-types[0].base-type=odrl:constraint 6 | general.supported-sub-types[0].sub-types[0]=ovc:constraint 7 | 8 | quarkus.rest-client.opa_yaml.url=http://localhost:8181 9 | quarkus.rest-client.opa_yaml.scope=jakarta.inject.Singleton 10 | quarkus.openapi-generator.codegen.spec.opa_yaml.return-response=true 11 | 12 | quarkus.datasource.db-kind=postgresql 13 | quarkus.datasource.username=postgres 14 | quarkus.datasource.password=postgres 15 | quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/pap 16 | quarkus.hibernate-orm.database.generation=update 17 | quarkus.hibernate-orm.database.generation.create-schemas=true 18 | 19 | quarkus.native.resources.includes=mapping.json,rego/**,rego-resources.txt 20 | 21 | quarkus.management.enabled=true 22 | quarkus.micrometer.binder.http-server.enabled=true 23 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./mvnw package -Dnative 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/odrl-poc . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/odrl-poc 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:2.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root target/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview", 11 | "generate-api": "openapi --input ../api/odrl.yaml --output ./src/api --client axios" 12 | }, 13 | "dependencies": { 14 | "axios": "^1.12.2", 15 | "bootstrap": "^5.3.8", 16 | "react": "^19.1.1", 17 | "react-bootstrap": "^2.10.10", 18 | "react-dom": "^19.1.1", 19 | "react-router-dom": "^7.9.3" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.36.0", 23 | "@types/react": "^19.1.13", 24 | "@types/react-dom": "^19.1.9", 25 | "@vitejs/plugin-react": "^5.0.3", 26 | "eslint": "^9.36.0", 27 | "eslint-plugin-react-hooks": "^5.2.0", 28 | "eslint-plugin-react-refresh": "^0.4.20", 29 | "globals": "^16.4.0", 30 | "openapi-typescript-codegen": "^0.29.0", 31 | "typescript": "~5.8.3", 32 | "typescript-eslint": "^8.44.0", 33 | "vite": "^7.1.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /charts/mockserver/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* Chart name truncated at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} 4 | {{- define "chart.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 6 | {{- end -}} 7 | 8 | {{/* Release name truncated at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} 9 | {{- define "release.name" -}} 10 | {{- if .Values.releasenameOverride -}} 11 | {{- .Values.releasenameOverride | trunc 63 | trimSuffix "-" -}} 12 | {{- else if .Values.fullnameOverride -}} 13 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 14 | {{- else -}} 15 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | {{- end -}} 18 | 19 | {{/* Create chart name and version as used by the chart label. */}} 20 | {{- define "chart.name_version" -}} 21 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 22 | {{- end -}} -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /frontend/src/api/core/OpenAPI.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { ApiRequestOptions } from './ApiRequestOptions'; 6 | 7 | type Resolver = (options: ApiRequestOptions) => Promise; 8 | type Headers = Record; 9 | 10 | export type OpenAPIConfig = { 11 | BASE: string; 12 | VERSION: string; 13 | WITH_CREDENTIALS: boolean; 14 | CREDENTIALS: 'include' | 'omit' | 'same-origin'; 15 | TOKEN?: string | Resolver | undefined; 16 | USERNAME?: string | Resolver | undefined; 17 | PASSWORD?: string | Resolver | undefined; 18 | HEADERS?: Headers | Resolver | undefined; 19 | ENCODE_PATH?: ((path: string) => string) | undefined; 20 | }; 21 | 22 | export const OpenAPI: OpenAPIConfig = { 23 | BASE: '', 24 | VERSION: '1.0.0', 25 | WITH_CREDENTIALS: false, 26 | CREDENTIALS: 'include', 27 | TOKEN: undefined, 28 | USERNAME: undefined, 29 | PASSWORD: undefined, 30 | HEADERS: undefined, 31 | ENCODE_PATH: undefined, 32 | }; 33 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/PolicyRepository.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import jakarta.transaction.Transactional; 4 | 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import java.util.Random; 8 | 9 | /** 10 | * @author Stefan Wiedemann 11 | */ 12 | public interface PolicyRepository { 13 | 14 | Random RANDOM = new Random(); 15 | 16 | default String generatePolicyId() { 17 | int leftLimit = 97; // letter 'a' 18 | int rightLimit = 122; // letter 'z' 19 | int targetStringLength = 10; 20 | return RANDOM.ints(leftLimit, rightLimit + 1) 21 | .limit(targetStringLength) 22 | .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) 23 | .toString(); 24 | } 25 | 26 | String createPolicy(String id, String uid, PolicyWrapper policy); 27 | 28 | Optional getPolicy(String id); 29 | 30 | Optional getPolicyByUid(String uid); 31 | 32 | Map getPolicies(); 33 | 34 | Map getPolicies(int page, int pageSize); 35 | 36 | void deletePolicy(String id); 37 | 38 | void deletePolicyByUid(String uid); 39 | } 40 | -------------------------------------------------------------------------------- /src/test/resources/examples/ngsi-ld/types/properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "dc": "http://purl.org/dc/elements/1.1/", 4 | "dct": "http://purl.org/dc/terms/", 5 | "owl": "http://www.w3.org/2002/07/owl#", 6 | "odrl": "http://www.w3.org/ns/odrl/2/", 7 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 8 | "skos": "http://www.w3.org/2004/02/skos/core#" 9 | }, 10 | "@id": "https://mp-operation.org/policy/common/type", 11 | "odrl:uid": "https://mp-operation.org/policy/common/type", 12 | "@type": "odrl:Policy", 13 | "odrl:permission": { 14 | "odrl:assigner": { 15 | "@id": "https://www.mp-operation.org/" 16 | }, 17 | "odrl:target": { 18 | "@type": "odrl:AssetCollection", 19 | "odrl:source": "urn:asset", 20 | "odrl:refinement": [ 21 | { 22 | "@type": "odrl:Constraint", 23 | "http:bodyValue": "$.numNodes.value", 24 | "odrl:operator": { 25 | "@id": "odrl:eq" 26 | }, 27 | "odrl:rightOperand": "3" 28 | } 29 | ] 30 | }, 31 | "odrl:assignee": { 32 | "@id": "vc:any" 33 | }, 34 | "odrl:action": { 35 | "@id": "odrl:use" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/api/index.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export { ApiError } from './core/ApiError'; 6 | export { CancelablePromise, CancelError } from './core/CancelablePromise'; 7 | export { OpenAPI } from './core/OpenAPI'; 8 | export type { OpenAPIConfig } from './core/OpenAPI'; 9 | 10 | export type { Headers } from './models/Headers'; 11 | export type { Id } from './models/Id'; 12 | export type { Mapping } from './models/Mapping'; 13 | export type { Mappings } from './models/Mappings'; 14 | export type { OdrlPolicyJson } from './models/OdrlPolicyJson'; 15 | export type { Page } from './models/Page'; 16 | export type { PageSize } from './models/PageSize'; 17 | export type { Policy } from './models/Policy'; 18 | export type { PolicyList } from './models/PolicyList'; 19 | export { TestRequest } from './models/TestRequest'; 20 | export type { Uid } from './models/Uid'; 21 | export type { ValidationRequest } from './models/ValidationRequest'; 22 | export type { ValidationResponse } from './models/ValidationResponse'; 23 | 24 | export { PapService } from './services/PapService'; 25 | export { UiService } from './services/UiService'; 26 | -------------------------------------------------------------------------------- /src/test/resources/examples/ngsi-ld/types/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "dc": "http://purl.org/dc/elements/1.1/", 4 | "dct": "http://purl.org/dc/terms/", 5 | "owl": "http://www.w3.org/2002/07/owl#", 6 | "odrl": "http://www.w3.org/ns/odrl/2/", 7 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 8 | "skos": "http://www.w3.org/2004/02/skos/core#" 9 | }, 10 | "@id": "https://mp-operation.org/policy/common/type", 11 | "odrl:uid": "https://mp-operation.org/policy/common/type", 12 | "@type": "odrl:Policy", 13 | "odrl:permission": { 14 | "odrl:assigner": { 15 | "@id": "https://www.mp-operation.org/" 16 | }, 17 | "odrl:target": { 18 | "@type": "odrl:AssetCollection", 19 | "odrl:source": "urn:asset", 20 | "odrl:refinement": [ 21 | { 22 | "@type": "odrl:Constraint", 23 | "odrl:leftOperand": { 24 | "@id": "ngsi-ld:entityType" 25 | }, 26 | "odrl:operator": { 27 | "@id": "odrl:eq" 28 | }, 29 | "odrl:rightOperand": "Marketplace" 30 | } 31 | ] 32 | }, 33 | "odrl:assignee": { 34 | "@id": "vc:any" 35 | }, 36 | "odrl:action": { 37 | "@id": "dome-op:read" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/examples/gaia-x/ovc-constraint.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "dc": "http://purl.org/dc/elements/1.1/", 4 | "dct": "http://purl.org/dc/terms/", 5 | "owl": "http://www.w3.org/2002/07/owl#", 6 | "odrl": "http://www.w3.org/ns/odrl/2/", 7 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 8 | "skos": "http://www.w3.org/2004/02/skos/core#" 9 | }, 10 | "@id": "https://mp-operation.org/policy/common/type", 11 | "odrl:uid": "https://mp-operation.org/policy/common/type", 12 | "@type": "odrl:Policy", 13 | "odrl:permission": { 14 | "odrl:assigner": { 15 | "@id": "https://www.mp-operation.org/" 16 | }, 17 | "odrl:target": "urn:ngsi-ld:product-offering:62d4f929-d29d-4070-ae1f-9fe7dd1de5f6", 18 | "odrl:assignee": "urn:ngsi-ld:organization:0b03975e-7ded-4fbd-9c3b-a5d6550df7e2", 19 | "odrl:action": { 20 | "@id": "odrl:use" 21 | }, 22 | "ovc:constraint": [ 23 | { 24 | "ovc:leftOperand": "$.credentialSubject.gx:legalAddress.gx:countrySubdivisionCode", 25 | "odrl:operator": "odrl:anyOf", 26 | "odrl:rightOperand": [ 27 | "FR-HDF", 28 | "BE-BRU" 29 | ], 30 | "ovc:credentialSubjectType": "gx:LegalParticipant" 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(({ mode }) => { 6 | const env = loadEnv(mode, process.cwd(), ''); 7 | const proxyTarget = env.VITE_API_PROXY_TARGET || 'http://localhost:8080'; 8 | 9 | const proxyConfig = { 10 | target: proxyTarget, 11 | changeOrigin: true, 12 | headers: { 13 | 'Accept': 'application/json, text/plain, */*', 14 | }, 15 | configure: (proxy, _options) => { 16 | proxy.on('error', (err, _req, _res) => { 17 | console.log('proxy error', err); 18 | }); 19 | proxy.on('proxyReq', (proxyReq, req, _res) => { 20 | console.log('\n[proxy] Sending Request to the Target:', req.method, req.url); 21 | console.log('[proxy] Target:', proxyTarget + proxyReq.path); 22 | }); 23 | proxy.on('proxyRes', (proxyRes, req, _res) => { 24 | console.log('[proxy] Received Response from the Target:', proxyRes.statusCode, req.url); 25 | }); 26 | }, 27 | }; 28 | 29 | return { 30 | plugins: [react()], 31 | server: { 32 | proxy: { 33 | '/mappings': proxyConfig, 34 | '/policy': proxyConfig, 35 | '/validate': proxyConfig, 36 | }, 37 | }, 38 | }; 39 | }); -------------------------------------------------------------------------------- /api/bundle.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: OPA Bundle API 4 | description: OPA Bundle API 5 | version: 0.63.0 6 | paths: 7 | /bundles/service/v1/policies.tar.gz: 8 | get: 9 | operationId: getPolicies 10 | summary: Return the policies bundle. 11 | responses: 12 | '200': 13 | description: The tar.gz containing the policies. 14 | content: 15 | application/octet-stream: 16 | schema: 17 | type: string 18 | format: binary 19 | /bundles/service/v1/methods.tar.gz: 20 | get: 21 | operationId: getMethods 22 | summary: Return the methods bundle. 23 | responses: 24 | '200': 25 | description: The tar.gz containing the methods. 26 | content: 27 | application/octet-stream: 28 | schema: 29 | type: string 30 | format: binary 31 | /bundles/service/v1/data.tar.gz: 32 | get: 33 | operationId: getData 34 | summary: Return the additional data to be used for policy evaluation. 35 | responses: 36 | '200': 37 | description: The tar.gz containing the data. 38 | content: 39 | application/octet-stream: 40 | schema: 41 | type: string 42 | format: binary -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/persistence/PolicyEntity.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.persistence; 2 | 3 | import io.quarkus.hibernate.orm.panache.PanacheEntity; 4 | import jakarta.persistence.Basic; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.FetchType; 7 | import jakarta.persistence.Lob; 8 | import lombok.Data; 9 | import org.hibernate.annotations.JdbcTypeCode; 10 | import org.hibernate.type.SqlTypes; 11 | 12 | import java.util.Optional; 13 | 14 | /** 15 | * @author Stefan Wiedemann 16 | */ 17 | @Entity(name = PolicyEntity.TABLE_NAME) 18 | @Data 19 | public class PolicyEntity extends PanacheEntity { 20 | 21 | public static final String TABLE_NAME = "policy_entity"; 22 | 23 | // opa-compatible id of the policy 24 | private String policyId; 25 | // uid of the policy as defined by odrl 26 | private String uid; 27 | 28 | @JdbcTypeCode(SqlTypes.JSON) 29 | private Policy odrl; 30 | 31 | @JdbcTypeCode(SqlTypes.JSON) 32 | private Policy rego; 33 | 34 | public static Optional findByPolicyId(String policyId) { 35 | return Optional.ofNullable(find("policyId", policyId).firstResult()); 36 | } 37 | 38 | public static Optional findByPolicyUid(String uid) { 39 | return Optional.ofNullable(find("uid", uid).firstResult()); 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /k3s/opa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: opa 6 | labels: 7 | app.kubernetes.io/name: opa 8 | app.kubernetes.io/instance: opa 9 | spec: 10 | type: LoadBalancer 11 | ports: 12 | - port: 8181 13 | targetPort: 8181 14 | protocol: TCP 15 | name: opa 16 | selector: 17 | app.kubernetes.io/name: opa 18 | app.kubernetes.io/instance: opa 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: opa 24 | labels: 25 | app.kubernetes.io/name: opa 26 | app.kubernetes.io/instance: opa 27 | spec: 28 | replicas: 1 29 | revisionHistoryLimit: 3 30 | selector: 31 | matchLabels: 32 | app.kubernetes.io/name: opa 33 | app.kubernetes.io/instance: opa 34 | template: 35 | metadata: 36 | labels: 37 | app.kubernetes.io/name: opa 38 | app.kubernetes.io/instance: opa 39 | spec: 40 | serviceAccountName: default 41 | containers: 42 | - name: opa 43 | imagePullPolicy: IfNotPresent 44 | image: "openpolicyagent/opa:1.2.0" 45 | ports: 46 | - name: http 47 | containerPort: 8181 48 | protocol: TCP 49 | args: 50 | - "run" 51 | - "--ignore=.*" # exclude hidden dirs created by Kubernetes 52 | - "--server" -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check PR 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | - labeled 10 | - unlabeled 11 | branches: 12 | - master 13 | 14 | jobs: 15 | check: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: actions/setup-java@v1 22 | with: 23 | java-version: '11' 24 | java-package: jdk 25 | 26 | - id: bump 27 | uses: zwaldowski/match-label-action@v1 28 | with: 29 | allowed: major,minor,patch 30 | 31 | - uses: zwaldowski/semver-release-action@v2 32 | with: 33 | dry_run: true 34 | bump: ${{ steps.bump.outputs.match }} 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | comment: 38 | runs-on: ubuntu-latest 39 | if: always() 40 | steps: 41 | - uses: technote-space/workflow-conclusion-action@v2 42 | - name: Checkout 43 | uses: actions/checkout@v1 44 | 45 | - name: Comment PR 46 | if: env.WORKFLOW_CONCLUSION == 'failure' 47 | uses: thollander/actions-comment-pull-request@1.0.2 48 | with: 49 | message: "Please apply one of the following labels to the PR: 'patch', 'minor', 'major'." 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /charts/opa/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "opa.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | {{- include "opa.labels" . | nindent 4 }} 8 | data: 9 | opa.yaml: |- 10 | services: 11 | - name: bundle-server 12 | url: {{ .Values.deployment.opa.pap.url }} 13 | bundles: 14 | policies: 15 | service: bundle-server 16 | resource: {{ .Values.deployment.opa.pap.policies.resource }} 17 | polling: 18 | min_delay_seconds: {{ .Values.deployment.opa.pap.policies.minDelay }} 19 | max_delay_seconds: {{ .Values.deployment.opa.pap.policies.maxDelay }} 20 | methods: 21 | service: bundle-server 22 | resource: {{ .Values.deployment.opa.pap.methods.resource }} 23 | polling: 24 | min_delay_seconds: {{ .Values.deployment.opa.pap.methods.minDelay }} 25 | max_delay_seconds: {{ .Values.deployment.opa.pap.methods.maxDelay }} 26 | data: 27 | service: bundle-server 28 | resource: {{ .Values.deployment.opa.pap.data.resource }} 29 | polling: 30 | min_delay_seconds: {{ .Values.deployment.opa.pap.data.minDelay }} 31 | max_delay_seconds: {{ .Values.deployment.opa.pap.data.maxDelay }} 32 | 33 | default_decision: /policy/main/allow -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/api/services/UiService.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { Mappings } from '../models/Mappings'; 6 | import type { ValidationRequest } from '../models/ValidationRequest'; 7 | import type { ValidationResponse } from '../models/ValidationResponse'; 8 | import type { CancelablePromise } from '../core/CancelablePromise'; 9 | import { OpenAPI } from '../core/OpenAPI'; 10 | import { request as __request } from '../core/request'; 11 | export class UiService { 12 | /** 13 | * Validates a policy with a demo request 14 | * @param requestBody 15 | * @returns ValidationResponse Validation result 16 | * @throws ApiError 17 | */ 18 | public static validatePolicy( 19 | requestBody: ValidationRequest, 20 | ): CancelablePromise { 21 | return __request(OpenAPI, { 22 | method: 'POST', 23 | url: '/validate', 24 | body: requestBody, 25 | mediaType: 'application/json', 26 | }); 27 | } 28 | /** 29 | * Gets the supported by the PAP. 30 | * @returns Mappings Successfully retrieved the Mappings. 31 | * @throws ApiError 32 | */ 33 | public static getMappings(): CancelablePromise { 34 | return __request(OpenAPI, { 35 | method: 'GET', 36 | url: '/mappings', 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/rego/utils/kong.rego: -------------------------------------------------------------------------------- 1 | package utils.helper 2 | 3 | import rego.v1 4 | 5 | # did of the organization running the PAP 6 | organization_did := data.data.organizationDid 7 | 8 | # the request as part of the policy input 9 | request := input.request 10 | 11 | # the request body as json object 12 | body := json.unmarshal(http_part.body) 13 | 14 | # the http request 15 | http_part := request.http 16 | 17 | # the headers of the request 18 | headers := http_part.headers 19 | 20 | # the (undecoded) authorization header 21 | authorization := headers.authorization 22 | 23 | # the decoded authorization jwt 24 | decoded_authorization := io.jwt.decode(token) 25 | 26 | # the decoded payload of the jwt 27 | decoded_token_payload := decoded_authorization[1] 28 | 29 | # the verifiable credential received as part of the token 30 | verifiable_credential := decoded_token_payload.verifiableCredential 31 | 32 | # the issuer of the credential 33 | issuer := verifiable_credential.issuer 34 | 35 | # the unprefixed bearer token 36 | token := t if { 37 | output := replace(authorization, "bearer ", "") 38 | t = replace(output, "Bearer ", "") 39 | } 40 | 41 | # the entity provided as http-body 42 | entity := body 43 | 44 | # the target of the request, found as the last part of the path 45 | target := p if { 46 | # split the path 47 | path_parts := split(http_part.path, "/") 48 | # get the last part of the path, without the query parameters 49 | p = split(path_parts[count(path_parts)-1], "?")[0] 50 | } -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/EntityMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import jakarta.enterprise.context.ApplicationScoped; 6 | import jakarta.inject.Inject; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.fiware.odrl.persistence.Policy; 9 | import org.fiware.odrl.persistence.PolicyEntity; 10 | import org.fiware.odrl.rego.OdrlPolicy; 11 | import org.fiware.odrl.rego.PolicyWrapper; 12 | import org.fiware.odrl.rego.RegoPolicy; 13 | 14 | /** 15 | * @author Stefan Wiedemann 16 | */ 17 | @ApplicationScoped 18 | @Slf4j 19 | public class EntityMapper { 20 | 21 | @Inject 22 | private ObjectMapper objectMapper; 23 | 24 | public PolicyEntity map(String id, String uid, PolicyWrapper policyWrapper) { 25 | Policy rego = new Policy(); 26 | rego.setPolicy(policyWrapper.rego().policy()); 27 | Policy odrl = new Policy(); 28 | odrl.setPolicy(policyWrapper.odrl().policy()); 29 | PolicyEntity policy = new PolicyEntity(); 30 | policy.setPolicyId(id); 31 | policy.setUid(uid); 32 | policy.setRego(rego); 33 | policy.setOdrl(odrl); 34 | return policy; 35 | } 36 | 37 | public PolicyWrapper map(PolicyEntity policyEntity) { 38 | return new PolicyWrapper( 39 | policyEntity.getPolicyId(), 40 | policyEntity.getUid(), 41 | new OdrlPolicy(policyEntity.getOdrl().getPolicy()), 42 | new RegoPolicy(policyEntity.getRego().getPolicy())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/services/api.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPI } from '../api'; 2 | // Explicitly import and re-export what the app uses 3 | import type { Mappings, OdrlPolicyJson, Policy, TestRequest, ValidationResponse } from '../api'; 4 | import axios from 'axios'; // Re-add axios import 5 | 6 | // This can be extended to read the token from a store 7 | const getAuthToken = () => { 8 | return localStorage.getItem('authToken'); 9 | } 10 | 11 | // For production, the VITE_API_BASE_URL can be used to set a full URL 12 | // For development, requests are made to relative paths and handled by the proxy 13 | OpenAPI.BASE = import.meta.env.VITE_API_BASE_URL || ''; 14 | 15 | OpenAPI.WITH_CREDENTIALS = true; 16 | 17 | // Store the original request function 18 | const originalRequest = OpenAPI.request; 19 | 20 | // Override the request function to inject the Accept header 21 | OpenAPI.request = async (options) => { 22 | const token = getAuthToken(); 23 | const headers: Record = { 24 | 'Accept': 'application/json, text/plain, */*', // Force the Accept header here 25 | }; 26 | if (token) { 27 | headers['Authorization'] = `Bearer ${token}`; 28 | } 29 | 30 | // Merge with any existing headers from the request options, prioritizing our Accept header 31 | options.headers = { ...options.headers, ...headers }; 32 | 33 | // Call the original request function 34 | return originalRequest(options); 35 | }; 36 | 37 | // Re-export 38 | export { OpenAPI }; 39 | export type { Mappings, OdrlPolicyJson, Policy, TestRequest, ValidationResponse }; 40 | -------------------------------------------------------------------------------- /charts/odrl-pap/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "odrl-pap.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | {{ include "odrl-pap.labels" . | nindent 4 }} 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 3 11 | selector: 12 | matchLabels: 13 | {{ include "odrl-pap.labels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | labels: 17 | {{ include "odrl-pap.labels" . | nindent 8 }} 18 | spec: 19 | serviceAccountName: default 20 | containers: 21 | - name: {{ .Chart.Name }} 22 | imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} 23 | image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" 24 | ports: 25 | - name: http 26 | containerPort: {{ .Values.deployment.odrlPap.port }} 27 | protocol: TCP 28 | env: 29 | - name: GENERAL_ORGANIZATION_DID 30 | value: {{ .Values.deployment.odrlPap.organizationId | quote }} 31 | - name: QUARKUS_DATASOURCE_DB_KIND 32 | value: {{ .Values.deployment.db.kind | quote }} 33 | - name: QUARKUS_DATASOURCE_JDBC_URL 34 | value: {{ .Values.deployment.db.url | quote }} 35 | - name: QUARKUS_DATASOURCE_USERNAME 36 | value: {{ .Values.deployment.db.username | quote }} 37 | - name: QUARKUS_DATASOURCE_PASSWORD 38 | value: {{ .Values.deployment.db.password | quote }} -------------------------------------------------------------------------------- /charts/opa/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "opa.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "opa.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "opa.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 31 | {{- end -}} 32 | 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "opa.labels" -}} 38 | app.kubernetes.io/name: {{ include "opa.name" . }} 39 | helm.sh/chart: {{ include "opa.chart" . }} 40 | app.kubernetes.io/instance: {{ .Release.Name }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/resources/MockServerTestResource.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.resources; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.quarkus.test.common.DevServicesContext; 5 | import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.mockserver.client.MockServerClient; 8 | import org.testcontainers.containers.GenericContainer; 9 | 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author Stefan Wiedemann 15 | */ 16 | @Slf4j 17 | public class MockServerTestResource implements QuarkusTestResourceLifecycleManager { 18 | 19 | private GenericContainer mockServerContainer; 20 | private MockServerClient mockServerClient; 21 | 22 | @Override 23 | public Map start() { 24 | mockServerContainer = new GenericContainer("mockserver/mockserver:5.15.0") 25 | .withEnv("MOCKSERVER_SERVER_PORT", "1080") 26 | .withNetworkMode("host"); 27 | mockServerContainer.start(); 28 | mockServerClient = new MockServerClient(mockServerContainer.getHost(), 1080); 29 | return Map.of(); 30 | } 31 | 32 | @Override 33 | public void stop() { 34 | log.info(mockServerContainer.getLogs()); 35 | mockServerContainer.stop(); 36 | } 37 | 38 | @Override 39 | public void inject(TestInjector testInjector) { 40 | testInjector.injectIntoFields(mockServerClient, new TestInjector.Annotated(InjectMockServerClient.class)); 41 | } 42 | 43 | 44 | } -------------------------------------------------------------------------------- /charts/odrl-pap/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "odrl-pap.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "odrl-pap.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "odrl-pap.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 31 | {{- end -}} 32 | 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "odrl-pap.labels" -}} 38 | app.kubernetes.io/name: {{ include "odrl-pap.name" . }} 39 | helm.sh/chart: {{ include "odrl-pap.chart" . }} 40 | app.kubernetes.io/instance: {{ .Release.Name }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} -------------------------------------------------------------------------------- /charts/opa/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "opa.fullname" . }} 5 | namespace: {{ .Release.Namespace | quote }} 6 | labels: 7 | {{ include "opa.labels" . | nindent 4 }} 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 3 11 | selector: 12 | matchLabels: 13 | {{ include "opa.labels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | labels: 17 | {{ include "opa.labels" . | nindent 8 }} 18 | spec: 19 | serviceAccountName: default 20 | containers: 21 | - name: {{ .Chart.Name }} 22 | imagePullPolicy: {{ .Values.deployment.image.pullPolicy }} 23 | image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" 24 | ports: 25 | - name: http 26 | containerPort: {{ .Values.deployment.opa.port }} 27 | protocol: TCP 28 | args: 29 | - "run" 30 | - "--ignore=.*" # exclude hidden dirs created by Kubernetes 31 | - "--server" 32 | - "-l" 33 | - {{ .Values.deployment.opa.logLevel | quote }} 34 | - "-c" 35 | - "/config/opa.yaml" 36 | - "--addr" 37 | - "0.0.0.0:{{ .Values.deployment.opa.port }}" 38 | volumeMounts: 39 | - name: config-volume 40 | mountPath: /config/ 41 | volumes: 42 | - name: config-volume 43 | configMap: 44 | name: {{ include "opa.fullname" . }} 45 | items: 46 | - key: opa.yaml 47 | path: opa.yaml -------------------------------------------------------------------------------- /charts/apisix/values.yaml: -------------------------------------------------------------------------------- 1 | apisix: 2 | global: 3 | security: 4 | allowInsecureImages: true 5 | waitContainer: 6 | image: 7 | repository: bitnamilegacy/os-shell 8 | image: 9 | repository: bitnamilegacy/apisix 10 | controlPlane: 11 | enabled: true 12 | resourcesPreset: small 13 | ingressController: 14 | enabled: false 15 | dashboard: 16 | enabled: false 17 | etcd: 18 | global: 19 | security: 20 | allowInsecureImages: true 21 | image: 22 | repository: bitnamilegacy/etcd 23 | enabled: true 24 | persistence: 25 | enabled: false 26 | dataPlane: 27 | resourcesPreset: micro 28 | service: 29 | ports: 30 | http: 8082 31 | https: 8443 32 | extraEnvVars: [] 33 | extraConfig: 34 | deployment: 35 | role_data_plane: 36 | config_provider: yaml 37 | extraVolumes: 38 | - name: routes 39 | configMap: 40 | name: apisix-routes 41 | extraVolumeMounts: 42 | - name: routes 43 | mountPath: /usr/local/apisix/conf/apisix.yaml 44 | subPath: apisix.yaml 45 | extraDeploy: 46 | - apiVersion: v1 47 | kind: ConfigMap 48 | metadata: 49 | name: apisix-routes 50 | data: 51 | apisix.yaml: |- 52 | routes: 53 | - uri: /* 54 | upstream: 55 | nodes: 56 | "mockserver:1080": 1 57 | type: roundrobin 58 | plugins: 59 | opa: 60 | host: "http://it-opa:8181" 61 | policy: policy/main 62 | #END -------------------------------------------------------------------------------- /frontend/src/pages/PolicyList.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Table, Button } from 'react-bootstrap'; 3 | import { PapService } from '../api/services/PapService'; 4 | import type { Policy } from '../services/api'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | const PolicyList = () => { 8 | const [policies, setPolicies] = useState([]); 9 | 10 | useEffect(() => { 11 | PapService.getPolicies() 12 | .then(setPolicies) 13 | .catch(console.error); 14 | }, []); 15 | 16 | const handleDelete = (id: string) => { 17 | PapService.deletePolicyById(id) 18 | .then(() => { 19 | setPolicies(policies.filter(p => p.id !== id)); 20 | }) 21 | .catch(console.error); 22 | }; 23 | 24 | return ( 25 | <> 26 |

Policies

27 | New Policy 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {policies.map(policy => ( 38 | 39 | 40 | 41 | 45 | 46 | ))} 47 | 48 |
IDODRL UIDActions
{policy.id}{policy['odrl:uid']} 42 | Edit 43 | 44 |
49 | 50 | ); 51 | }; 52 | 53 | export default PolicyList; -------------------------------------------------------------------------------- /src/main/resources/rego/utils/apisix.rego: -------------------------------------------------------------------------------- 1 | package utils.helper 2 | 3 | import rego.v1 4 | 5 | ## 6 | # did of the organization running the PAP 7 | organization_did := data.data.organizationDid 8 | 9 | ## 10 | # the request as part of the policy input 11 | request := input.request 12 | 13 | ## 14 | # the request body as json object 15 | body := json.unmarshal(http_part.body) 16 | 17 | ## 18 | # the http request 19 | http_part := request 20 | 21 | ## 22 | # the headers of the request 23 | headers := http_part.headers 24 | 25 | ## 26 | # the (undecoded) authorization header 27 | authorization := headers.authorization 28 | 29 | ## 30 | # the decoded authorization jwt 31 | decoded_authorization := io.jwt.decode(token) 32 | 33 | ## 34 | # the decoded payload of the jwt 35 | decoded_token_payload := decoded_authorization[1] 36 | 37 | ## 38 | # the verifiable credential received as part of the token 39 | verifiable_credential := verfiableCredential if{ 40 | verfiableCredential = decoded_token_payload.verifiableCredential 41 | } else := vc if { 42 | vc = decoded_token_payload.vc 43 | } 44 | 45 | ## 46 | # the issuer of the credential 47 | issuer := verifiable_credential.issuer 48 | 49 | ## 50 | # the unprefixed bearer token 51 | token := t if { 52 | output := replace(authorization, "bearer ", "") 53 | t = replace(output, "Bearer ", "") 54 | } 55 | 56 | ## 57 | # the entity provided as http-body 58 | entity := body 59 | 60 | ## 61 | # the target of the request, found as the last part of the path 62 | target := p if { 63 | # split the path 64 | path_parts := split(http_part.path, "/") 65 | # get the last part of the path, without the query parameters 66 | p = split(path_parts[count(path_parts)-1], "?")[0] 67 | } -------------------------------------------------------------------------------- /scripts/create-rego-doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # generates the documenatation for the rego methods, based on their doc. 4 | # Every method should have: 5 | # - first a line with all its ODRL-Keys, prefixed with `## ` 6 | # - second a line with the method documentation 7 | # - the method itself 8 | 9 | resource_folder="src/main/resources/" 10 | rego_doc_file="doc/REGO.md" 11 | 12 | echo "# REGO Methods" > $rego_doc_file 13 | echo "" >> $rego_doc_file 14 | 15 | declare -A package_map 16 | for rego_file in $(find $resource_folder -name "*.rego" -type f); do 17 | package_line=$(head -n 1 $rego_file) 18 | package=$(echo "${package_line#"package "}") 19 | readarray -d "." -t newarr <<< "$package" 20 | if [[ -v package_map[$newarr[0]] ]]; then 21 | echo "Already added" 22 | else 23 | package_map[$newarr[0]]=package 24 | echo "" >> $rego_doc_file 25 | echo "## ${newarr[0]}" >> $rego_doc_file 26 | echo "" >> $rego_doc_file 27 | echo "| ODRL Class | ODRL Key | Rego-Method | Description |" >> $rego_doc_file 28 | echo "| --- | --- | --- | --- |" >> $rego_doc_file 29 | fi 30 | 31 | class=$(echo "${newarr[1]//[$'\t\r\n']}") 32 | readarray -t line_array < $rego_file 33 | 34 | for i in ${!line_array[@]}; do 35 | current_line="${line_array[$i]}" 36 | if [[ $current_line == \#\#* ]] ; 37 | then 38 | odrl_key=$(echo "${current_line#"## "}") 39 | j=$((i+1)) 40 | k=$((i+2)) 41 | doc_line="${line_array[$j]}" 42 | doc=$(echo "${doc_line#"# "}") 43 | method_line="${line_array[$k]}" 44 | readarray -d " " -t methodarr <<< "$method_line" 45 | method="${methodarr[0]}" 46 | echo "| $class | $odrl_key | $method | $doc |" >> $rego_doc_file 47 | fi 48 | done 49 | done 50 | 51 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/TypeMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.fiware.odrl.rego.RegoMethod; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | @Slf4j 15 | @RequiredArgsConstructor 16 | public abstract class TypeMapper { 17 | 18 | public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); 19 | public static final String STRING_ESCAPE_TEMPLATE = "\"%s\""; 20 | protected final ObjectMapper objectMapper; 21 | protected final Map mappings; 22 | 23 | protected Map convertToMap(Object theObject) { 24 | return objectMapper.convertValue(theObject, new TypeReference>() { 25 | }); 26 | } 27 | 28 | public RegoMethod getMethod(String type) { 29 | log.warn(String.format("Get %s from %s", type, mappings)); 30 | return mappings.get(type); 31 | } 32 | 33 | protected static Map getMappings(MappingConfiguration mappingConfiguration, OdrlAttribute key) { 34 | if (!mappingConfiguration.containsKey(key)) { 35 | return Map.of(); 36 | } 37 | return mappingConfiguration.get(key) 38 | .entrySet() 39 | .stream() 40 | .flatMap(entry -> entry 41 | .getValue() 42 | .entrySet() 43 | .stream() 44 | .map(e -> Map.entry(String.format("%s:%s", entry.getKey(), ((Map.Entry) e).getKey()), e.getValue())) 45 | ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/resources/OpenPolicyAgentTestResource.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.resources; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.quarkus.test.common.DevServicesContext; 5 | import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.utility.MountableFile; 9 | 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author Stefan Wiedemann 15 | */ 16 | @Slf4j 17 | public class OpenPolicyAgentTestResource implements QuarkusTestResourceLifecycleManager { 18 | 19 | private Optional containerNetworkId; 20 | private GenericContainer opaContainer; 21 | 22 | @Override 23 | public Map start() { 24 | opaContainer = new GenericContainer("openpolicyagent/opa:1.2.0") 25 | .withReuse(false) 26 | .withCopyToContainer(MountableFile.forClasspathResource("opa.yaml"), "/opa.yaml") 27 | .withCommand("run", "--server", "-l", "debug", "-c", "/opa.yaml", "--addr", "localhost:8181") 28 | .withNetworkMode("host"); 29 | opaContainer.start(); 30 | return ImmutableMap.of("quarkus.rest-client.opa_yaml.url", String.format("http://%s:%s/", opaContainer.getHost(), 8181)); 31 | } 32 | 33 | @Override 34 | public void stop() { 35 | log.info(opaContainer.getLogs()); 36 | opaContainer.stop(); 37 | } 38 | 39 | @Override 40 | public void inject(TestInjector testInjector) { 41 | testInjector.injectIntoFields(opaContainer, new TestInjector.Annotated(InjectOpa.class)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/rego/dome/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package dome.leftOperand 2 | 3 | import rego.v1 4 | 5 | ## dome-op:role 6 | # retrieves the roles from the (lear) credential, that target the current organization 7 | role(verifiable_credential,organization_id) := r if { 8 | rolesAndDuties := verifiable_credential.credentialSubject.rolesAndDuties 9 | roleAndDuty := [rad | some rad in rolesAndDuties; rad.target = organization_id ] 10 | r = roleAndDuty[_].roleNames; trace(organization_id) 11 | } 12 | 13 | ## dome-op:currentParty 14 | # the current (organization)party, 15 | current_party(credential) := credential.issuer 16 | 17 | ## dome-op:relatedParty 18 | # get the entity from tm-forum and extract related party 19 | related_party(http_part) := rp if { 20 | path_without_query := split(http_part.path, "?")[0] 21 | ## will be one or multiple entities 22 | responseBody := http.send({"method": "get", "url": sprintf("%v%v", [http_part.host, path_without_query])}).body 23 | rp = responseBody.relatedParty 24 | } 25 | 26 | ## dome-op:owner 27 | # filter the given list of related_party(ies) for one with role "Owner" 28 | owner(related_party) := o_id if { 29 | owner_rp := [rp | some rp in related_party; rp.role = "Owner"] 30 | o_id = owner_rp[_].id 31 | } 32 | 33 | ## dome-op:relatedParty_role 34 | # return the role from the related party of an entity 35 | related_party_role(entity) := related_party(entity).role 36 | 37 | ## dome-op:validFor_endDateTime 38 | # return the end of the validity of an entity 39 | valid_for_end_date_time(entity) := time.parse_rfc3339_ns(entity.validFor.endDataTime) 40 | 41 | ## dome-op:validFor_startDateTime 42 | # return the start of the validity of an entity 43 | valid_for_start_date_time(entity) := time.parse_rfc3339_ns(entity.validFor.startDataTime) -------------------------------------------------------------------------------- /src/main/resources/rego/odrl/operator.rego: -------------------------------------------------------------------------------- 1 | package odrl.operator 2 | 3 | import rego.v1 4 | 5 | ## odrl:eq 6 | # check that both operands are equal 7 | eq_operator(leftOperand, rightOperand) if leftOperand == rightOperand 8 | 9 | ## odrl:hasPart 10 | # check that the rightOperand is in the leftOperand 11 | has_part_operator(leftOperand, rightOperand) if rightOperand in leftOperand 12 | 13 | ## odrl:gt 14 | # check that the leftOperand is greater than the rightOperand 15 | gt_operator(leftOperand, rightOperand) if leftOperand > rightOperand 16 | 17 | ## odrl:gteq 18 | # check that the leftOperand is greater or equal to the rightOperand 19 | gt_eq_operator(leftOperand, rightOperand) if leftOperand >= rightOperand 20 | 21 | ## odrl:isAllOf 22 | # check that the given sets are equal 23 | is_all_of_operator(leftOperand, rightOperand) if leftOperand == rightOperand 24 | 25 | ## odrl:isAnyOf 26 | # check that the leftOperand is contained in the rightOperand set 27 | is_any_of_operator(leftOperand, rightOperand) if leftOperand in rightOperand 28 | 29 | ## odrl:isNoneOf 30 | # check that the leftOperand is not contained in the rightOperand set 31 | is_none_of_operator(leftOperand, rightOperand) if not leftOperand in rightOperand 32 | 33 | ## odrl:isPartOf 34 | # check that the rightOperand is contained in the leftOperand set 35 | is_part_of_operator(leftOperand, rightOperand) if rightOperand in leftOperand 36 | 37 | ## odrl:lt 38 | # check that the leftOperand is less than the rightOperand 39 | lt_operator(leftOperand, rightOperand) if rightOperand > leftOperand 40 | 41 | ## odrl:lteq 42 | # check that the leftOperand is less or equal to the rightOperand 43 | lt_eq_operator(leftOperand, rightOperand) if rightOperand >= leftOperand 44 | 45 | ## odrl:neq 46 | # check that the operands are unequal 47 | n_eq_operator(leftOperand, rightOperand) if leftOperand != rightOperand 48 | 49 | -------------------------------------------------------------------------------- /src/test/resources/examples/odrl/3000/_3000.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "odrl": "http://www.w3.org/ns/odrl/2/", 4 | "ngsi-ld": "https://uri.etsi.org/ngsi-ld/" 5 | }, 6 | "@type": "odrl:Policy", 7 | "odrl:uid": "https://mp-operation.org/policy/common/type", 8 | "odrl:permission": { 9 | "odrl:assigner": { 10 | "@id": "https://www.mp-operation.org/" 11 | }, 12 | "odrl:assignee": { 13 | "@id": "vc:any" 14 | }, 15 | "odrl:action": { 16 | "@id": "odrl:read" 17 | }, 18 | "odrl:target": { 19 | "@type": "odrl:AssetCollection", 20 | "odrl:source": "urn:asset", 21 | "odrl:refinement": [ 22 | { 23 | "@type": "odrl:Constraint", 24 | "odrl:leftOperand": "ngsi-ld:entityType", 25 | "odrl:operator": { 26 | "@id": "odrl:eq" 27 | }, 28 | "odrl:rightOperand": "Test_Car" 29 | } 30 | ] 31 | }, 32 | "odrl:constraint": [ 33 | { 34 | "odrl:leftOperand": "odrl:dayOfWeek", 35 | "odrl:operator": "odrl:gteq", 36 | "odrl:rightOperand": { 37 | "@value": 0, 38 | "@type": "xsd:integer" 39 | } 40 | }, 41 | { 42 | "odrl:leftOperand": "odrl:dayOfWeek", 43 | "odrl:operator": "odrl:lteq", 44 | "odrl:rightOperand": { 45 | "@value": 4, 46 | "@type": "xsd:integer" 47 | } 48 | }, 49 | { 50 | "odrl:leftOperand": "odrl:hourOfDay", 51 | "odrl:operator": "odrl:gteq", 52 | "odrl:rightOperand": { 53 | "@value": 8, 54 | "@type": "xsd:integer" 55 | } 56 | }, 57 | { 58 | "odrl:leftOperand": "odrl:hourOfDay", 59 | "odrl:operator": "odrl:lteq", 60 | "odrl:rightOperand": { 61 | "@value": 23, 62 | "@type": "xsd:integer" 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /charts/mockserver/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "release.name" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ template "chart.name" . }} 8 | release: {{ .Release.Name }} 9 | chart: {{ template "chart.name_version" . }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app: {{ template "chart.name" . }} 15 | release: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | name: {{ template "release.name" . }} 19 | labels: 20 | app: {{ template "chart.name" . }} 21 | release: {{ .Release.Name }} 22 | spec: 23 | serviceAccountName: {{ .Values.app.serviceAccountName }} 24 | containers: 25 | - name: {{ template "release.name" . }} 26 | image: {{ if .Values.image.repositoryNameAndTag }}{{ .Values.image.repositoryNameAndTag }}{{- else }}{{ .Values.image.repository }}/mockserver:mockserver-{{- if .Values.image.snapshot }}snapshot{{- else }}{{ .Chart.AppVersion }}{{- end }}{{- end }} 27 | imagePullPolicy: Always 28 | ports: 29 | - name: serviceport 30 | containerPort: {{ .Values.app.serverPort }} 31 | protocol: TCP 32 | readinessProbe: 33 | tcpSocket: 34 | port: serviceport 35 | initialDelaySeconds: 2 36 | periodSeconds: 2 37 | successThreshold: 1 38 | failureThreshold: 10 39 | livenessProbe: 40 | tcpSocket: 41 | port: serviceport 42 | initialDelaySeconds: 10 43 | periodSeconds: 5 44 | successThreshold: 1 45 | failureThreshold: 10 46 | env: 47 | - name: MOCKSERVER_LOG_LEVEL 48 | value: {{ .Values.app.logLevel | quote }} 49 | - name: SERVER_PORT 50 | value: {{ .Values.app.serverPort | quote }} -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/OperatorMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.fiware.odrl.rego.RegoMethod; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Provides capabilites for mapping odrl:operator 10 | */ 11 | public class OperatorMapper extends TypeMapper { 12 | 13 | public OperatorMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 14 | super(objectMapper, getMappings(mappingConfiguration, OdrlAttribute.OPERATOR)); 15 | } 16 | 17 | // package private, since it's only to fulfill cdi requirements 18 | OperatorMapper() { 19 | super(null, null); 20 | } 21 | 22 | /** 23 | * Is the given key a sub-class of odrl:operator? 24 | */ 25 | public boolean isOperator(String key) { 26 | if (key.equalsIgnoreCase(OdrlConstants.OPERATOR_KEY)) { 27 | return true; 28 | } 29 | return mappings.containsKey(key); 30 | } 31 | 32 | /** 33 | * Retrieves the key of the operator from the given constraint. Throws an exception if none is found 34 | */ 35 | public String getOperatorKey(Map theConstraint) throws MappingException { 36 | return theConstraint.keySet().stream().filter(this::isOperator).findFirst() 37 | .orElseThrow(() -> new MappingException("The provided constraint does not contain an operator.")); 38 | } 39 | 40 | /** 41 | * Get the type of the operator at the given key in the provided object. 42 | */ 43 | public String getType(String key, Object operatorObject) throws MappingException { 44 | // case "my:customOperator": {} 45 | if (!key.equalsIgnoreCase(OdrlConstants.OPERATOR_KEY)) { 46 | return key; 47 | } 48 | 49 | // case "odrl:operator": "my:customOperator" 50 | if (operatorObject instanceof String typeString) { 51 | return typeString; 52 | } 53 | 54 | Map theOperatorMap = convertToMap(operatorObject); 55 | if (theOperatorMap.containsKey(OdrlConstants.ID_KEY)) { 56 | return (String) theOperatorMap.get(OdrlConstants.ID_KEY); 57 | } 58 | throw new MappingException("The constraint does not contain a valid operator."); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/MappingResult.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import io.quarkus.qute.ImmutableList; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.StringJoiner; 12 | 13 | /** 14 | * @author Stefan Wiedemann 15 | */ 16 | public class MappingResult { 17 | 18 | 19 | @Getter 20 | private boolean failed = false; 21 | private final List failureReasons = new ArrayList<>(); 22 | 23 | private final Set imports = new HashSet<>(); 24 | 25 | // set default allow := false 26 | private final Set rules = new HashSet<>(); 27 | 28 | @Getter 29 | @Setter 30 | private String uid; 31 | 32 | public MappingResult() { 33 | imports.add("import rego.v1"); 34 | imports.add("import data.utils.helper as helper"); 35 | } 36 | 37 | public MappingResult addFailure(String reason, String... parameters) { 38 | failureReasons.add(String.format(reason, parameters)); 39 | failed = true; 40 | return this; 41 | } 42 | 43 | public List getFailureReasons() { 44 | return ImmutableList.copyOf(failureReasons); 45 | } 46 | 47 | public MappingResult addImport(String importPackage) { 48 | imports.add(String.format("import data.%s", importPackage)); 49 | return this; 50 | } 51 | 52 | public MappingResult addRule(String rule) { 53 | rules.add(rule); 54 | return this; 55 | } 56 | 57 | public String getRego(String packageName) { 58 | 59 | StringJoiner regoJoiner = new StringJoiner(System.getProperty("line.separator")); 60 | 61 | regoJoiner.add(String.format("package %s", packageName)); 62 | regoJoiner.add(""); 63 | imports.forEach(regoJoiner::add); 64 | regoJoiner.add(""); 65 | regoJoiner.add("is_allowed if {"); 66 | rules.forEach(regoJoiner::add); 67 | regoJoiner.add("}"); 68 | 69 | return regoJoiner.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/OdrlConstants.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | /** 4 | * @author Stefan Wiedemann 5 | */ 6 | public class OdrlConstants { 7 | 8 | public static final String CONTEXT_KEY = "@context"; 9 | public static final String TYPE_KEY = "@type"; 10 | public static final String ID_KEY = "@id"; 11 | public static final String VALUE_KEY = "@value"; 12 | public static final String GRAPH_KEY = "@graph"; 13 | public static final String LIST_KEY = "@list"; 14 | 15 | public static final String STRING_TYPE = "xsd:string"; 16 | public static final String DATE_TYPE = "xsd:date"; 17 | public static final String TYPE_POLICY = "odrl:Policy"; 18 | public static final String TYPE_PERMISSION = "odrl:Permission"; 19 | public static final String TYPE_PARTY = "odrl:Party"; 20 | public static final String TYPE_PARTY_COLLECTION = "odrl:PartyCollection"; 21 | public static final String TYPE_CONSTRAINT = "odrl:Constraint"; 22 | public static final String TYPE_LOGICAL_CONSTRAINT = "odrl:LogicalConstraint"; 23 | public static final String TYPE_ASSET_COLLECTION = "odrl:AssetCollection"; 24 | public static final String TYPE_ASSET = "odrl:Asset"; 25 | 26 | 27 | public static final String PROFILE_KEY = "odrl:profile"; 28 | public static final String ASSIGNER_KEY = "odrl:assigner"; 29 | public static final String PERMISSION_KEY = "odrl:permission"; 30 | public static final String ASSIGNEE_KEY = "odrl:assignee"; 31 | public static final String SOURCE_KEY = "odrl:source"; 32 | public static final String REFINEMENT_KEY = "odrl:refinement"; 33 | public static final String LEFT_OPERAND_KEY = "odrl:leftOperand"; 34 | public static final String OPERATOR_KEY = "odrl:operator"; 35 | public static final String RIGHT_OPERAND_KEY = "odrl:rightOperand"; 36 | public static final String ODRL_UID_KEY = "odrl:uid"; 37 | public static final String TARGET_KEY = "odrl:target"; 38 | public static final String ACTION_KEY = "odrl:action"; 39 | public static final String CONSTRAINT_KEY = "odrl:constraint"; 40 | 41 | private OdrlConstants() { 42 | // prevent instantiation 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/rego/ngsi-ld/leftOperand.rego: -------------------------------------------------------------------------------- 1 | package ngsild.leftOperand 2 | 3 | import rego.v1 4 | 5 | # helper method to retrieve the type from the path 6 | type_from_path(http_part) := tfe if { 7 | path_without_query := split(http_part.path, "?")[0] 8 | path_elements := split(path_without_query, "/") 9 | id_elements := split(path_elements[count(path_elements) - 1], ":") 10 | tfe = id_elements[2] 11 | } else := tfq if { 12 | tfq = http_part.query.type 13 | } 14 | 15 | # helper to retrieve the type from the body 16 | type_from_body(body) := body.type 17 | 18 | ## ngsi-ld:entityType 19 | # retrieves the type from an entity, either from the request path or from the body 20 | entity_type(http_part) := tfp if { 21 | tfp = type_from_path(http_part) 22 | } else := tfb if { 23 | tfb = type_from_body(http_part.body) 24 | } 25 | 26 | ## ngsi-ld: 27 | # 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. 28 | # F.e.: ngsi-ld:brandName = property_value("brandName", http_part.body) 29 | property_value(property_name, body) := body[property_name].value 30 | 31 | ## ngsi-ld:_observedAt 32 | # retrieves the observedAt of the property The method should be concretized in the mapping.json, to match a concrete property. 33 | # F.e.: ngsi-ld:brandName_observedAt = property_value("brandName", http_part.body) 34 | property_observed_at(property_name,body) := body[property_name].observedAt 35 | 36 | ## ngsi-ld:_modifiedAt 37 | # retrieves the modifiedAt of the property The method should be concretized in the mapping.json, to match a concrete property. 38 | # F.e.: ngsi-ld:brandName_modifiedAt= property_value("brandName", http_part.body) 39 | property_observed_at(property_name,body) := body[property_name].modifiedAt 40 | 41 | ## ngsi-ld: 42 | # 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. 43 | # F.e.: ngsi-ld:owningCompany = relationship_object("owningCompany", http_part.body) 44 | relationship_object(relationship_name, body):= body[relationship_name].object 45 | 46 | ## ngsi-ld:entityTypeGroup 47 | # returns the entity type - let the operator do the comparison 48 | entity_type_group(http_part) := entity_type(http_part) 49 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # ODRL PAP Frontend 2 | 3 | > :warning: This is currently just an experimental frontend, without any proper tests provided. DO NOT use it in production. 4 | 5 | This is a React-based frontend for the ODRL Policy Administration Point (PAP). 6 | 7 | ## Local Development 8 | 9 | To run the frontend locally for development, follow these steps: 10 | 11 | 1. **Start the environment** 12 | From the base dir, run 13 | ```bash 14 | mvn clean install -Pfrontend 15 | ``` 16 | 17 | 2. **Configure the Backend** 18 | Create a `.env` file in the `frontend` directory by copying the `.env.example` file. Adjust the variables to point to your backend API. 19 | ```bash 20 | cp .env.example .env 21 | ``` 22 | 23 | 3. **Install Dependencies** 24 | Make sure you have Node.js and npm installed. Then, install the project dependencies: 25 | ```bash 26 | npm install 27 | ``` 28 | 29 | 4. **Run the Development Server** 30 | This command starts the Vite development server. It will use the `VITE_API_PROXY_TARGET` from your `.env` file to proxy API requests. 31 | ```bash 32 | npm run dev 33 | ``` 34 | The frontend will be available at `http://localhost:5173`. 35 | 36 | 5. **Generate API Client** 37 | If you make changes to the `api/odrl.yaml` file, you need to regenerate the API client: 38 | ```bash 39 | npm run generate-api 40 | ``` 41 | 42 | ## Configuration 43 | 44 | You can configure the application using environment variables defined in a `.env` file in the `frontend` directory. 45 | 46 | - `VITE_API_PROXY_TARGET`: The full URL of your backend, used to proxy requests from the development server (e.g., `http://localhost:8080`). 47 | - `VITE_API_BASE_URL`: The base URL for API calls in the production build. For a backend on a different domain, this would be the full URL (e.g., `https://api.example.com`). If the frontend is served from the same host as the backend, this can be a relative path like `/api`. 48 | 49 | 50 | ## Building for Production 51 | 52 | To create a production build of the application, run: 53 | ```bash 54 | npm run build 55 | ``` 56 | This will create a `dist` directory with the optimized static assets. 57 | 58 | ## Docker 59 | 60 | A Dockerfile is provided to containerize the frontend application. 61 | 62 | 1. **Build the Docker Image** 63 | ```bash 64 | docker build -t odrl-pap-frontend . 65 | ``` 66 | 67 | 2. **Run the Docker Container** 68 | ```bash 69 | docker run -p 8080:80 odrl-pap-frontend 70 | ``` 71 | The frontend will be available at `http://localhost:8080`. -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/MappingsResource.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import jakarta.inject.Inject; 4 | import jakarta.ws.rs.core.Response; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.http.HttpStatus; 7 | import org.checkerframework.checker.units.qual.A; 8 | import org.fiware.odrl.api.MappingsApi; 9 | import org.fiware.odrl.mapping.MappingConfiguration; 10 | import org.fiware.odrl.mapping.NamespacedMap; 11 | import org.fiware.odrl.mapping.OdrlAttribute; 12 | import org.fiware.odrl.model.Mapping; 13 | import org.fiware.odrl.model.Mappings; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | 19 | /** 20 | * Implementation of the mapping api to provide access to the currently supported mappings. Can be used for frontend-integration. 21 | */ 22 | @Slf4j 23 | public class MappingsResource implements MappingsApi { 24 | 25 | @Inject 26 | private MappingConfiguration mappingConfiguration; 27 | 28 | @Override 29 | public Response getMappings() { 30 | return Response.ok(getMappingsFromConfig()).build(); 31 | } 32 | 33 | private Mappings getMappingsFromConfig() { 34 | Mappings mappings = new Mappings(); 35 | Arrays.stream(OdrlAttribute.values()) 36 | .forEach(attribute -> { 37 | List mappingList = toMappingList(mappingConfiguration.get(attribute)); 38 | switch (attribute) { 39 | case LEFT_OPERAND -> mappings.leftOperands(mappingList); 40 | case RIGHT_OPERAND -> mappings.rightOperands(mappingList); 41 | case OPERATOR -> mappings.operators(mappingList); 42 | case CONSTRAINT -> mappings.constraints(mappingList); 43 | case OPERAND -> mappings.operands(mappingList); 44 | case ASSIGNEE -> mappings.assignees(mappingList); 45 | case ACTION -> mappings.actions(mappingList); 46 | case TARGET -> mappings.targets(mappingList); 47 | } 48 | 49 | }); 50 | return mappings; 51 | } 52 | 53 | private List toMappingList(NamespacedMap namespacedMap) { 54 | List mappings = new ArrayList<>(); 55 | namespacedMap.forEach((namespace, value) -> value.entrySet() 56 | .stream() 57 | .map(regoMapEntry -> new Mapping() 58 | .description(regoMapEntry.getValue().description()) 59 | .name(String.format("%s:%s", namespace, regoMapEntry.getKey()))) 60 | .forEach(mappings::add)); 61 | return mappings; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/AppConfig.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.enterprise.context.ApplicationScoped; 5 | import jakarta.inject.Inject; 6 | import jakarta.ws.rs.Produces; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.fiware.odrl.mapping.*; 9 | import org.fiware.odrl.rego.RegoMethod; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.Map; 14 | import java.util.stream.Collectors; 15 | 16 | /** 17 | * @author Stefan Wiedemann 18 | */ 19 | @Slf4j 20 | @ApplicationScoped 21 | public class AppConfig { 22 | 23 | private static final String DEFAULT_MAPPING_PATH = "mapping.json"; 24 | 25 | @Inject 26 | private ObjectMapper objectMapper; 27 | 28 | @Inject 29 | private PathsConfiguration pathsConfiguration; 30 | 31 | @Produces 32 | @ApplicationScoped 33 | public MappingConfiguration mappingConfiguration() { 34 | MappingConfiguration mappingConfiguration = new MappingConfiguration(); 35 | InputStream defaultMappingInputStream = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_MAPPING_PATH); 36 | try { 37 | mappingConfiguration.putAll(objectMapper.readValue(defaultMappingInputStream, MappingConfiguration.class)); 38 | } catch (IOException e) { 39 | throw new IllegalArgumentException("Was not able to read the default mapping.", e); 40 | } 41 | 42 | if (pathsConfiguration.mapping().isPresent() && pathsConfiguration.mapping().get().exists()) { 43 | try { 44 | mappingConfiguration.putAll(objectMapper.readValue(pathsConfiguration.mapping().get(), MappingConfiguration.class)); 45 | } catch (IOException e) { 46 | log.warn("Was not able to load the additional mappings.", e); 47 | } 48 | } 49 | return mappingConfiguration; 50 | } 51 | 52 | 53 | 54 | @Produces 55 | @ApplicationScoped 56 | public ConstraintMapper constraintMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 57 | return new ConstraintMapper(objectMapper, mappingConfiguration); 58 | } 59 | 60 | @Produces 61 | @ApplicationScoped 62 | public LeftOperandMapper leftOperandMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 63 | return new LeftOperandMapper(objectMapper, mappingConfiguration); 64 | } 65 | 66 | @Produces 67 | @ApplicationScoped 68 | public OperatorMapper operatorMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 69 | return new OperatorMapper(objectMapper, mappingConfiguration); 70 | } 71 | 72 | @Produces 73 | @ApplicationScoped 74 | public RightOperandMapper rightOperandMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 75 | return new RightOperandMapper(objectMapper, mappingConfiguration); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | REGISTRY: quay.io 10 | REPOSITORY: fiware 11 | 12 | jobs: 13 | generate-version: 14 | runs-on: ubuntu-latest 15 | 16 | outputs: 17 | version: ${{ steps.out.outputs.version }} 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - id: pr 23 | uses: actions-ecosystem/action-get-merged-pull-request@v1.0.1 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: zwaldowski/semver-release-action@v2 28 | with: 29 | dry_run: true 30 | bump: ${{ steps.pr.outputs.labels }} 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Set version output 34 | id: out 35 | run: echo "::set-output name=version::$(echo ${VERSION})" 36 | 37 | # image build&push 38 | build-and-push: 39 | needs: [ "generate-version" ] 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - uses: actions/checkout@v2 44 | 45 | - uses: actions/setup-java@v1 46 | with: 47 | java-version: '17' 48 | java-package: jdk 49 | 50 | - name: Log into quay.io 51 | run: docker login -u "${{ secrets.QUAY_USERNAME }}" -p "${{ secrets.QUAY_PASSWORD }}" ${{ env.REGISTRY }} 52 | 53 | - name: Build image 54 | run: | 55 | mvn versions:set -DnewVersion=${{ needs.generate-version.outputs.version }} 56 | mvn clean package -Dquarkus.package.type=native -Dquarkus.container-image.build=true -Dquarkus.container-image.tag=${{ needs.generate-version.outputs.version }} -Dquarkus.container-image.registry="${{ env.REGISTRY }}" -Dquarkus.container-image.group="${{ env.REPOSITORY }}" 57 | 58 | - name: Ensure br_netfilter is enabled. 59 | run: | 60 | sudo modprobe br_netfilter 61 | 62 | - name: Test image 63 | run: | 64 | mvn integration-test -Pk8s-it -DskipTests -Dquarkus.container-image.tag=${{ needs.generate-version.outputs.version }} -Dquarkus.container-image.registry="${{ env.REGISTRY }}" -Dquarkus.container-image.group="${{ env.REPOSITORY }}" 65 | 66 | - name: Push 67 | run: | 68 | docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/odrl-pap:${{ needs.generate-version.outputs.version }} 69 | 70 | git-release: 71 | needs: ["generate-version", "build-and-push"] 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | 76 | - uses: actions/checkout@v2 77 | 78 | - uses: "marvinpinto/action-automatic-releases@latest" 79 | with: 80 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 81 | automatic_release_tag: ${{ needs.generate-version.outputs.version }} 82 | prerelease: false 83 | title: ${{ needs.generate-version.outputs.version }} 84 | files: | 85 | LICENSE 86 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yaml: -------------------------------------------------------------------------------- 1 | name: Pre-Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | - labeled 10 | - unlabeled 11 | env: 12 | REGISTRY: quay.io 13 | REPOSITORY: fiware 14 | 15 | jobs: 16 | 17 | generate-version: 18 | runs-on: ubuntu-latest 19 | 20 | outputs: 21 | version: ${{ steps.out.outputs.version }} 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - id: bump 27 | uses: zwaldowski/match-label-action@v1 28 | with: 29 | allowed: major,minor,patch 30 | 31 | - uses: zwaldowski/semver-release-action@v2 32 | with: 33 | dry_run: true 34 | bump: ${{ steps.bump.outputs.match }} 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Get PR Number 38 | id: pr_number 39 | run: echo "::set-output name=nr::$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')" 40 | 41 | - name: Set version output 42 | id: out 43 | run: echo "::set-output name=version::$(echo ${VERSION}-PRE-${{ steps.pr_number.outputs.nr }})" 44 | 45 | # image build&push 46 | build-and-push: 47 | needs: [ "generate-version" ] 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v2 52 | 53 | - uses: actions/setup-java@v1 54 | with: 55 | java-version: '17' 56 | java-package: jdk 57 | 58 | - name: Log into quay.io 59 | run: docker login -u "${{ secrets.QUAY_USERNAME }}" -p "${{ secrets.QUAY_PASSWORD }}" ${{ env.REGISTRY }} 60 | 61 | - name: Build image 62 | run: | 63 | mvn versions:set -DnewVersion=${{ needs.generate-version.outputs.version }} 64 | mvn clean package -Dquarkus.package.type=native -Dquarkus.container-image.build=true -Dquarkus.container-image.tag=${{ needs.generate-version.outputs.version }} -Dquarkus.container-image.registry="${{ env.REGISTRY }}" -Dquarkus.container-image.group="${{ env.REPOSITORY }}" 65 | 66 | - name: Ensure br_netfilter is enabled. 67 | run: | 68 | sudo modprobe br_netfilter 69 | 70 | - name: Test image 71 | run: | 72 | mvn integration-test -Pk8s-it -DskipTests -Dquarkus.container-image.tag=${{ needs.generate-version.outputs.version }} -Dquarkus.container-image.registry="${{ env.REGISTRY }}" -Dquarkus.container-image.group="${{ env.REPOSITORY }}" 73 | 74 | - name: Push 75 | run: | 76 | docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/odrl-pap:${{ needs.generate-version.outputs.version }} 77 | 78 | 79 | git-release: 80 | needs: ["generate-version","build-and-push"] 81 | runs-on: ubuntu-latest 82 | 83 | steps: 84 | 85 | - uses: actions/checkout@v2 86 | 87 | - uses: "marvinpinto/action-automatic-releases@latest" 88 | with: 89 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 90 | automatic_release_tag: ${{ needs.generate-version.outputs.version }} 91 | prerelease: true 92 | title: ${{ needs.generate-version.outputs.version }} 93 | files: | 94 | LICENSE 95 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/rego/PersistentPolicyRepository.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.rego; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.quarkus.hibernate.orm.panache.PanacheEntityBase; 5 | import io.quarkus.hibernate.orm.panache.PanacheQuery; 6 | import io.quarkus.panache.common.Page; 7 | import io.quarkus.panache.common.Sort; 8 | import jakarta.enterprise.context.ApplicationScoped; 9 | import jakarta.inject.Inject; 10 | import jakarta.transaction.Transactional; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.fiware.odrl.mapping.EntityMapper; 13 | import org.fiware.odrl.persistence.PolicyEntity; 14 | 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Optional; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * @author Stefan Wiedemann 23 | */ 24 | @Slf4j 25 | @ApplicationScoped 26 | public class PersistentPolicyRepository implements PolicyRepository { 27 | 28 | private static final String DEFAULT_SORT = "id"; 29 | 30 | @Inject 31 | private EntityMapper entityMapper; 32 | 33 | 34 | @Override 35 | @Transactional 36 | public String createPolicy(String id, String uid, PolicyWrapper policy) { 37 | if (PolicyEntity.findByPolicyId(id).isPresent()) { 38 | throw new IllegalArgumentException(String.format("Policy with id %s already exists.", id)); 39 | } 40 | if (PolicyEntity.findByPolicyUid(uid).isPresent()) { 41 | throw new IllegalArgumentException(String.format("Policy with uid %s already exists.", uid)); 42 | } 43 | entityMapper.map(id, uid, policy).persist(); 44 | return id; 45 | } 46 | 47 | @Override 48 | public Optional getPolicy(String id) { 49 | return PolicyEntity.findByPolicyId(id).map(entityMapper::map); 50 | } 51 | 52 | @Override 53 | public Optional getPolicyByUid(String uid) { 54 | return PolicyEntity.findByPolicyUid(uid).map(entityMapper::map); 55 | } 56 | 57 | private String getUniqueId() { 58 | String generatedId = generatePolicyId(); 59 | if (getPolicy(generatedId).isPresent()) { 60 | return getUniqueId(); 61 | } 62 | return generatedId; 63 | } 64 | 65 | public Map getPolicies() { 66 | Map policies = new HashMap<>(); 67 | 68 | List policyEntityList = PolicyEntity.listAll(); 69 | policyEntityList.forEach(e -> policies.put(e.getPolicyId(), entityMapper.map(e))); 70 | 71 | return ImmutableMap.copyOf(policies); 72 | } 73 | 74 | public Map getPolicies(int page, int pageSize) { 75 | PanacheQuery policyEntities = PolicyEntity.findAll(Sort.ascending(DEFAULT_SORT)); 76 | List policyEntityList = policyEntities.page(Page.of(page, pageSize)).list(); 77 | 78 | return policyEntityList.stream().collect(Collectors.toMap(PolicyEntity::getPolicyId, e -> entityMapper.map(e), (e1, e2) -> e1)); 79 | } 80 | 81 | @Override 82 | @Transactional 83 | public void deletePolicy(String id) { 84 | log.warn("Try to delete {}", id); 85 | PolicyEntity.findByPolicyId(id) 86 | .ifPresent(PanacheEntityBase::delete); 87 | } 88 | 89 | @Override 90 | @Transactional 91 | public void deletePolicyByUid(String uid) { 92 | log.warn("Try to delete {}", uid); 93 | PolicyEntity.findByPolicyUid(uid) 94 | .ifPresent(PanacheEntityBase::delete); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/ValidationResource.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.inject.Inject; 5 | import jakarta.ws.rs.core.Response; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.eclipse.microprofile.rest.client.inject.RestClient; 8 | import org.fiware.odrl.api.ValidateApi; 9 | import org.fiware.odrl.mapping.MappingResult; 10 | import org.fiware.odrl.mapping.OdrlMapper; 11 | import org.fiware.odrl.model.ValidationRequest; 12 | import org.fiware.odrl.model.ValidationResponse; 13 | import org.fiware.odrl.rego.DataResponse; 14 | import org.fiware.odrl.rego.PolicyRepository; 15 | import org.openapi.quarkus.opa_yaml.api.DataApiApi; 16 | import org.openapi.quarkus.opa_yaml.api.PolicyApiApi; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * Implementation of the validation api to support testing of policies 23 | */ 24 | @Slf4j 25 | public class ValidationResource implements ValidateApi { 26 | 27 | @RestClient 28 | public PolicyApiApi opaPolicyApi; 29 | 30 | @RestClient 31 | private DataApiApi dataApiApi; 32 | 33 | @Inject 34 | private PolicyRepository policyRepository; 35 | 36 | @Inject 37 | private OdrlMapper odrlMapper; 38 | 39 | @Override 40 | public Response validatePolicy(ValidationRequest validationRequest) { 41 | if (dataApiApi == null) { 42 | throw new UnsupportedOperationException("Policy validation is not enabled."); 43 | } 44 | String tempId = policyRepository.generatePolicyId(); 45 | try { 46 | MappingResult mappingResult = odrlMapper.mapOdrl(validationRequest.getPolicy()); 47 | if (mappingResult.isFailed()) { 48 | throw new IllegalArgumentException(String.format("Was not able to map the policy. Reason: %s", mappingResult.getFailureReasons())); 49 | } 50 | Response creation = opaPolicyApi.putPolicyModule(tempId, mappingResult.getRego(tempId), false, false); 51 | if (creation.getStatus() != 200) { 52 | throw new IllegalArgumentException(String.format("Cannot create policy. Reason: %s", creation.readEntity(String.class))); 53 | } 54 | Map request = new HashMap<>(); 55 | request.put("request", validationRequest.getTestRequest()); 56 | Map input = new HashMap<>(); 57 | input.put("input", request); 58 | Response dataResponse = dataApiApi.getDocumentWithPath(String.format("%s/is_allowed", tempId), input, true, false, "full", false, false); 59 | DataResponse dataResponseObject = dataResponse.readEntity(DataResponse.class); 60 | ValidationResponse validationResponse = new ValidationResponse().allow(dataResponseObject.result()); 61 | if (!dataResponseObject.result()) { 62 | // it failed 63 | validationResponse.explanation(dataResponseObject.explanation()); 64 | } 65 | return Response.ok(validationResponse).build(); 66 | } finally { 67 | Response deletionResponse = opaPolicyApi.deletePolicyModule(tempId, false); 68 | if (deletionResponse.getStatus() != 200) { 69 | log.warn("Was not able to delete the policy {}. Reason: {}", tempId, deletionResponse.readEntity(String.class)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /frontend/src/components/Baukasten.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Form, Row, Col } from 'react-bootstrap'; 3 | import { UiService } from '../api/services/UiService'; 4 | import type { Mappings, OdrlPolicyJson } from '../services/api'; 5 | import ConstraintBuilder from './ConstraintBuilder'; 6 | import TargetEditor from './TargetEditor'; 7 | import AssigneeEditor from './AssigneeEditor'; 8 | import PolicySummary from './PolicySummary'; 9 | 10 | interface BaukastenProps { 11 | policy: OdrlPolicyJson; 12 | setPolicy: (policy: OdrlPolicyJson) => void; 13 | } 14 | 15 | const Baukasten = ({ policy, setPolicy }: BaukastenProps) => { 16 | const [mappings, setMappings] = useState(null); 17 | 18 | useEffect(() => { 19 | UiService.getMappings().then(setMappings).catch(console.error); 20 | }, []); 21 | 22 | const handlePermissionChange = (field: string, value: any) => { 23 | const newPolicy = { ...policy }; 24 | if (!newPolicy['odrl:permission']) { 25 | newPolicy['odrl:permission'] = {}; 26 | } 27 | newPolicy['odrl:permission'][field] = value; 28 | setPolicy(newPolicy); 29 | }; 30 | 31 | const setPermission = (newPermission: any) => { 32 | setPolicy({ ...policy, 'odrl:permission': newPermission }); 33 | } 34 | 35 | if (!mappings) { 36 | return

Loading mappings...

; 37 | } 38 | 39 | const permission = policy['odrl:permission'] || {}; 40 | 41 | return ( 42 | 43 | 44 |
45 | 46 | Target 47 | 48 | handlePermissionChange('odrl:target', target)} 51 | mappings={mappings} 52 | /> 53 | 54 | 55 | 56 | 57 | Assignee 58 | 59 | handlePermissionChange('odrl:assignee', assignee)} 62 | mappings={mappings} 63 | /> 64 | 65 | 66 | 67 | 68 | Action 69 | 70 | handlePermissionChange('odrl:action', e.target.value)} 73 | > 74 | 75 | {(mappings as any).actions?.map((action: any) => ( 76 | 77 | ))} 78 | 79 | 80 | 81 | 82 |
83 |
Constraints
84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | ); 97 | }; 98 | 99 | export default Baukasten; -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URL; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.nio.file.StandardCopyOption; 29 | 30 | public final class MavenWrapperDownloader 31 | { 32 | private static final String WRAPPER_VERSION = "3.2.0"; 33 | 34 | private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); 35 | 36 | public static void main( String[] args ) 37 | { 38 | log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); 39 | 40 | if ( args.length != 2 ) 41 | { 42 | System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); 43 | System.exit( 1 ); 44 | } 45 | 46 | try 47 | { 48 | log( " - Downloader started" ); 49 | final URL wrapperUrl = new URL( args[0] ); 50 | final String jarPath = args[1].replace( "..", "" ); // Sanitize path 51 | final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); 52 | downloadFileFromURL( wrapperUrl, wrapperJarPath ); 53 | log( "Done" ); 54 | } 55 | catch ( IOException e ) 56 | { 57 | System.err.println( "- Error downloading: " + e.getMessage() ); 58 | if ( VERBOSE ) 59 | { 60 | e.printStackTrace(); 61 | } 62 | System.exit( 1 ); 63 | } 64 | } 65 | 66 | private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) 67 | throws IOException 68 | { 69 | log( " - Downloading to: " + wrapperJarPath ); 70 | if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) 71 | { 72 | final String username = System.getenv( "MVNW_USERNAME" ); 73 | final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); 74 | Authenticator.setDefault( new Authenticator() 75 | { 76 | @Override 77 | protected PasswordAuthentication getPasswordAuthentication() 78 | { 79 | return new PasswordAuthentication( username, password ); 80 | } 81 | } ); 82 | } 83 | try ( InputStream inStream = wrapperUrl.openStream() ) 84 | { 85 | Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); 86 | } 87 | log( " - Downloader complete" ); 88 | } 89 | 90 | private static void log( String msg ) 91 | { 92 | if ( VERBOSE ) 93 | { 94 | System.out.println( msg ); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/LeftOperandMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | import org.fiware.odrl.rego.RegoMethod; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | /** 11 | * Provides capabilities for mapping odrl:leftOperand 12 | */ 13 | @RegisterForReflection 14 | public class LeftOperandMapper extends TypeMapper { 15 | 16 | public LeftOperandMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 17 | super(objectMapper, getMappings(mappingConfiguration, OdrlAttribute.LEFT_OPERAND)); 18 | } 19 | 20 | // package private, since it's only to fulfill cdi requirements 21 | LeftOperandMapper() { 22 | super(null, null); 23 | } 24 | 25 | /** 26 | * Is the given key a sub-class of odrl:leftOperand? 27 | */ 28 | public boolean isLeftOperand(String key) { 29 | if (key.equalsIgnoreCase(OdrlConstants.LEFT_OPERAND_KEY)) { 30 | return true; 31 | } 32 | return mappings.containsKey(key); 33 | } 34 | 35 | /** 36 | * is the given type just the base-type, e.g. odrl:leftOperand? 37 | */ 38 | public boolean isBaseType(String type) { 39 | return type.equalsIgnoreCase(OdrlConstants.LEFT_OPERAND_KEY); 40 | } 41 | 42 | /** 43 | * Retrieves the key of the leftOperand from the given constraint. Throws an exception if none is found 44 | */ 45 | public String getLeftOperandKey(Map theConstraint) throws MappingException { 46 | return theConstraint.keySet().stream().filter(this::isLeftOperand).findFirst() 47 | .orElseThrow(() -> new MappingException("The provided constraint does not contain a left operand.")); 48 | } 49 | 50 | /** 51 | * Get the type of the leftOperand at the given key in the provided object. 52 | */ 53 | public String getType(String key, Object leftOperandObject) { 54 | // case "my:customOperand": {} 55 | if (!key.equalsIgnoreCase(OdrlConstants.LEFT_OPERAND_KEY)) { 56 | return key; 57 | } 58 | 59 | // case "odrl:leftOperand" : "my:customOperand" 60 | if (leftOperandObject instanceof String typeString) { 61 | // it's a mapping 62 | if (isLeftOperand(typeString)) { 63 | return typeString; 64 | } else { 65 | // it's only a value 66 | return OdrlConstants.LEFT_OPERAND_KEY; 67 | } 68 | } 69 | 70 | Map theLeftOperandMap = convertToMap(leftOperandObject); 71 | // case "odrl:leftOperand": {"@value":"somethingStatic"} 72 | if (!theLeftOperandMap.containsKey(OdrlConstants.ID_KEY)) { 73 | return OdrlConstants.LEFT_OPERAND_KEY; 74 | } else 75 | // case "odrl:leftOperand": {"@id":"my:customOperand"} 76 | { 77 | return (String) theLeftOperandMap.get(OdrlConstants.ID_KEY); 78 | } 79 | } 80 | 81 | /** 82 | * Get the value of the leftOperand with the given type and object. 83 | */ 84 | public Optional getValue(String type, Object leftOperandObject) { 85 | // case "odrl:leftOperand" : "my:customOperand" 86 | // or "my:customOperand": "static-value" 87 | if (leftOperandObject instanceof String valueString) { 88 | if (valueString.equals(type)) { 89 | return Optional.empty(); 90 | } else { 91 | return Optional.ofNullable(valueString); 92 | } 93 | } 94 | 95 | Map theLeftOperandMap = convertToMap(leftOperandObject); 96 | // case "odrl:leftOperand" : {"@Value": "something"} 97 | // or "my:customOperand": {"@Value": "something"} 98 | if (theLeftOperandMap.containsKey(OdrlConstants.VALUE_KEY)) { 99 | return Optional.ofNullable(theLeftOperandMap.get(OdrlConstants.VALUE_KEY)); 100 | } 101 | return Optional.empty(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/verification/OvcConstraintVerifier.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.verification; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import jakarta.enterprise.context.ApplicationScoped; 6 | import jakarta.inject.Inject; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.fiware.odrl.mapping.LeftOperandMapper; 9 | import org.fiware.odrl.mapping.MappingException; 10 | import org.fiware.odrl.mapping.OperatorMapper; 11 | import org.fiware.odrl.mapping.RightOperandMapper; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | 17 | /** 18 | * Verifies that an ovc:constraint contains all required attributes. 19 | */ 20 | @Slf4j 21 | @ApplicationScoped 22 | public class OvcConstraintVerifier implements TypeVerifier { 23 | 24 | private static final String MESSAGE_TEMPLATE = "An ovc:constraint needs to contain an %s."; 25 | 26 | private static final String TYPE_NAME = "ovc:constraint"; 27 | private static final String BASE_TYPE = "odrl:constraint"; 28 | 29 | private static final String OVC_LEFT_OPERAND = "ovc:leftOperand"; 30 | private static final String OVC_CREDENTIAL_TYPE_SUBJECT = "ovc:credentialSubjectType"; 31 | 32 | private static final String ODRL_TYPE_KEY = "@type"; 33 | private static final String ODRL_LEFT_OPERAND = "odrl:leftOperand"; 34 | private static final String ODRL_RIGHT_OPERAND = "odrl:rightOperand"; 35 | private static final String ODRL_OPERATOR = "odrl:operator"; 36 | 37 | @Inject 38 | private ObjectMapper objectMapper; 39 | 40 | @Inject 41 | private LeftOperandMapper leftOperandMapper; 42 | 43 | @Inject 44 | private OperatorMapper operatorMapper; 45 | 46 | @Inject 47 | private RightOperandMapper rightOperandMapper; 48 | 49 | 50 | @Override 51 | public void verify(T objectToVerify) throws VerificationException { 52 | if (objectToVerify instanceof List objectList) { 53 | for (Object o : objectList) { 54 | verifyTheObject(o); 55 | } 56 | } else { 57 | verifyTheObject(objectToVerify); 58 | } 59 | 60 | } 61 | 62 | private Map toMap(Object theObject) { 63 | return objectMapper.convertValue(theObject, new TypeReference<>() { 64 | }); 65 | } 66 | 67 | public void verifyTheObject(Object objectToVerify) throws VerificationException { 68 | 69 | Map theObject = toMap(objectToVerify); 70 | // find leftOperand 71 | try { 72 | String leftKey = leftOperandMapper.getLeftOperandKey(theObject); 73 | if (!leftOperandMapper.getType(leftKey, theObject.get(leftKey)).equals(OVC_LEFT_OPERAND)) { 74 | throw new VerificationException(String.format("For ovc:constraint the leftOperand can only be of type %s.", OVC_LEFT_OPERAND), theObject); 75 | } 76 | } catch (MappingException e) { 77 | throw new VerificationException("The ovc:constraint does not contain a leftOperand.", theObject); 78 | } 79 | try { 80 | rightOperandMapper.getRightOperandKey(theObject); 81 | } catch (MappingException e) { 82 | throw new VerificationException("The ovc:constraint does not contain a rightOperand.", theObject); 83 | } 84 | 85 | try { 86 | operatorMapper.getOperatorKey(theObject); 87 | } catch (MappingException e) { 88 | throw new VerificationException("The ovc:constraint does not contain an operator.", theObject); 89 | } 90 | 91 | // find typeSubject 92 | if (!theObject.containsKey(OVC_CREDENTIAL_TYPE_SUBJECT)) { 93 | throw new VerificationException(String.format(MESSAGE_TEMPLATE, ODRL_OPERATOR), theObject); 94 | } 95 | } 96 | 97 | @Override 98 | public List verifiableTypes() { 99 | return List.of(TYPE_NAME); 100 | } 101 | 102 | @Override 103 | public String supportedBaseType() { 104 | return BASE_TYPE; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/java/org/fiware/odrl/OdrlTestIT.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import jakarta.ws.rs.client.ClientBuilder; 6 | import jakarta.ws.rs.client.ClientRequestContext; 7 | import jakarta.ws.rs.client.ClientRequestFilter; 8 | import jakarta.ws.rs.core.Response; 9 | import lombok.Data; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.fiware.odrl.api.PolicyApi; 12 | import org.fiware.odrl.model.HttpRequest; 13 | import org.fiware.odrl.model.KongOpaInput; 14 | import org.fiware.odrl.model.MockEntity; 15 | import org.fiware.odrl.model.Request; 16 | import org.fiware.tmforum.api.ProductOfferingApi; 17 | import org.jboss.resteasy.client.jaxrs.ResteasyClient; 18 | import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; 19 | import org.junit.jupiter.api.Assertions; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.params.ParameterizedTest; 22 | import org.junit.jupiter.params.provider.MethodSource; 23 | import org.mockserver.client.MockServerClient; 24 | import org.mockserver.model.JsonBody; 25 | 26 | import java.io.IOException; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | import static org.mockserver.model.HttpRequest.request; 31 | import static org.mockserver.model.HttpResponse.response; 32 | 33 | /** 34 | * @author Stefan Wiedemann 35 | */ 36 | @Slf4j 37 | public class OdrlTestIT extends OdrlTest { 38 | 39 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 40 | private static PolicyApi policyApi; 41 | private static ProductOfferingApi productOfferingApi; 42 | private static MockServerClient mockServerClient = new MockServerClient("localhost", 1080); 43 | 44 | private final TokenProvider tokenProvider = new TokenProvider(); 45 | 46 | @BeforeEach 47 | public void prepare() { 48 | ResteasyClient resteasyClient = (ResteasyClient) ClientBuilder.newClient(); 49 | ResteasyWebTarget policyTarget = resteasyClient.target("http://localhost:8080"); 50 | policyApi = policyTarget.proxy(PolicyApi.class); 51 | 52 | ResteasyWebTarget productOfferingTarget = resteasyClient.target("http://localhost:8082"); 53 | productOfferingTarget.register(new BearerTokenFilter(tokenProvider)); 54 | productOfferingApi = productOfferingTarget.proxy(ProductOfferingApi.class); 55 | } 56 | 57 | 58 | @ParameterizedTest 59 | @MethodSource("validCombinations") 60 | public void test(List policyPaths, HttpRequest theRequest, MockEntity mockEntity) throws IOException { 61 | for (String path : policyPaths) { 62 | Map theOdrl = getJsonFromResource(OBJECT_MAPPER, path); 63 | Assertions.assertEquals(200, policyApi.createPolicy(theOdrl).getStatus(), "Policy should have been created successfully."); 64 | } 65 | mockEntity(mockServerClient, mockEntity); 66 | 67 | tokenProvider.setToken(theRequest.getHeaders().getAuthorization()); 68 | Response offeringResponse = productOfferingApi.retrieveProductOffering(mockEntity.id(), null); 69 | Assertions.assertEquals(200, offeringResponse.getStatus()); 70 | } 71 | 72 | public Map getJsonFromResource(ObjectMapper objectMapper, String path) throws IOException { 73 | return objectMapper.readValue(this.getClass().getResourceAsStream(path), new TypeReference>() { 74 | }); 75 | } 76 | 77 | class BearerTokenFilter implements ClientRequestFilter { 78 | 79 | private final TokenProvider tokenProvider; 80 | 81 | BearerTokenFilter(TokenProvider tokenProvider) { 82 | this.tokenProvider = tokenProvider; 83 | } 84 | 85 | 86 | @Override 87 | public void filter(ClientRequestContext clientRequestContext) throws IOException { 88 | clientRequestContext.getHeaders().putSingle("Authorization", tokenProvider.getToken()); 89 | } 90 | } 91 | 92 | @Data 93 | class TokenProvider { 94 | private String token; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /frontend/src/components/TargetEditor.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Row, Col, Stack } from 'react-bootstrap'; 2 | import { useState } from 'react'; 3 | import type { Mappings } from '../services/api'; 4 | import ConstraintBuilder from './ConstraintBuilder'; 5 | 6 | interface TargetEditorProps { 7 | target: any; 8 | setTarget: (target: any) => void; 9 | mappings: Mappings; 10 | } 11 | 12 | const TargetEditor = ({ target, setTarget, mappings }: TargetEditorProps) => { 13 | const [simpleTargetType, setSimpleTargetType] = useState('text'); // 'text' or 'dropdown' 14 | 15 | const isCollection = typeof target === 'object' && target !== null && target['@type'] === 'AssetCollection'; 16 | 17 | const setType = (type: 'simple' | 'collection') => { 18 | if (type === 'simple') { 19 | setTarget(''); // Reset to a simple string 20 | setSimpleTargetType('text'); // Default to text input for simple 21 | } else { 22 | setTarget({ '@type': 'AssetCollection', 'odrl:refinement': [] }); 23 | } 24 | }; 25 | 26 | const anyMappings = mappings as any; 27 | const availableTargets = anyMappings.targets; 28 | 29 | return ( 30 | 31 | 32 | 33 | setType('simple')} 39 | /> 40 | setType('collection')} 46 | /> 47 | 48 | 49 | 50 | {!isCollection ? ( 51 | 52 | 53 | 54 | { 60 | setSimpleTargetType('text'); 61 | setTarget(''); // Clear value when switching type 62 | }} 63 | /> 64 | { 70 | setSimpleTargetType('dropdown'); 71 | setTarget({ '@id': '' }); // Clear value when switching type 72 | }} 73 | disabled={!availableTargets || availableTargets.length === 0} 74 | /> 75 | 76 | 77 | 78 | 79 | {simpleTargetType === 'text' ? ( 80 | setTarget(e.target.value)} 85 | /> 86 | ) : ( 87 | setTarget({ '@id': e.target.value })} 90 | > 91 | 92 | {availableTargets?.map((t: any) => ( 93 | 94 | ))} 95 | 96 | )} 97 | 98 | 99 | 100 | ) : ( 101 | 102 |
Asset Collection
103 |
104 |
Refinements
105 | 110 |
111 | )} 112 |
113 | ); 114 | }; 115 | 116 | export default TargetEditor; 117 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/ConstraintMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.fiware.odrl.rego.RegoMethod; 7 | 8 | import java.text.ParseException; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | 14 | import static org.fiware.odrl.mapping.RightOperandMapper.DATE_FORMAT; 15 | 16 | /** 17 | * Provides capabilites for mapping odrl:constraint 18 | */ 19 | @Slf4j 20 | @RegisterForReflection 21 | public class ConstraintMapper extends TypeMapper { 22 | 23 | public ConstraintMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 24 | super(objectMapper, getMappings(mappingConfiguration, OdrlAttribute.CONSTRAINT)); 25 | } 26 | 27 | // package private, since it's only to fulfill cdi requirements 28 | ConstraintMapper() { 29 | super(null, null); 30 | } 31 | 32 | /** 33 | * Is the given key a sub-class of odrl:constraint? 34 | */ 35 | public boolean isConstraint(String key) { 36 | if (key == null) { 37 | return false; 38 | } 39 | if (key.equalsIgnoreCase(OdrlConstants.TYPE_LOGICAL_CONSTRAINT) || key.equalsIgnoreCase(OdrlConstants.TYPE_CONSTRAINT)) { 40 | return true; 41 | } 42 | return mappings.containsKey(key); 43 | } 44 | 45 | /** 46 | * Retrieves the concrete type from the constraint. If nothing is specified, odrl:constraint is returend 47 | */ 48 | public String getTypeFromConstraint(Map theConstraint) { 49 | if (theConstraint.containsKey(OdrlConstants.TYPE_KEY)) { 50 | return (String) theConstraint.get(OdrlConstants.TYPE_KEY); 51 | } 52 | return OdrlConstants.TYPE_CONSTRAINT; 53 | } 54 | 55 | /** 56 | * Get the type of the constraint at the given key in the provided object. 57 | */ 58 | public String getType(String key, Object theObject) { 59 | // case "my:customConstraint": {} 60 | if (!key.equalsIgnoreCase(OdrlConstants.CONSTRAINT_KEY) && !key.equalsIgnoreCase(OdrlConstants.TYPE_LOGICAL_CONSTRAINT) && !key.equalsIgnoreCase(OdrlConstants.REFINEMENT_KEY)) { 61 | return key; 62 | } 63 | if (theObject instanceof List) { 64 | return OdrlConstants.TYPE_LOGICAL_CONSTRAINT; 65 | } 66 | Map theObjectMap = convertToMap(theObject); 67 | if (theObjectMap.containsKey(OdrlConstants.TYPE_KEY)) { 68 | return (String) theObjectMap.get(OdrlConstants.TYPE_KEY); 69 | } else { 70 | return OdrlConstants.TYPE_CONSTRAINT; 71 | } 72 | } 73 | 74 | /** 75 | * Get the value of the constraint with the given type and object. 76 | * Only constraints are supported, no custom logical constraints. 77 | */ 78 | public Optional getValue(String type, Object constraintObject) { 79 | // case "odrl:constraint" : "my:customConstraint" 80 | // or "my:customConstraint": "static-value" 81 | if (constraintObject instanceof String valueString) { 82 | if (valueString.equals(type)) { 83 | return Optional.empty(); 84 | } else { 85 | return Optional.ofNullable(String.format(STRING_ESCAPE_TEMPLATE, valueString)); 86 | } 87 | } 88 | 89 | Map theConstraintMap = convertToMap(constraintObject); 90 | // case "odrl:constraint" : {"@Value": "something"} 91 | // or "my:customConstraint": {"@Value": "something"} 92 | if (theConstraintMap.containsKey(OdrlConstants.VALUE_KEY)) { 93 | Optional optionalType = Optional.ofNullable(theConstraintMap.get(OdrlConstants.TYPE_KEY)).map(String.class::cast); 94 | 95 | return Optional.ofNullable(theConstraintMap.get(OdrlConstants.VALUE_KEY)).map(v -> { 96 | 97 | if (v instanceof String valueString) { 98 | if (optionalType.isPresent() && optionalType.get().equals(OdrlConstants.DATE_TYPE)) { 99 | try { 100 | Date parsedDate = DATE_FORMAT.parse(valueString); 101 | return parsedDate.getTime(); 102 | } catch (ParseException e) { 103 | log.warn("The date {} is not valid", valueString, e); 104 | } 105 | } 106 | return String.format(STRING_ESCAPE_TEMPLATE, valueString); 107 | } 108 | return v; 109 | }); 110 | } 111 | return Optional.empty(); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /frontend/src/components/AssigneeEditor.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Row, Col, Stack } from 'react-bootstrap'; 2 | import { useState } from 'react'; 3 | import type { Mappings } from '../services/api'; 4 | import ConstraintBuilder from './ConstraintBuilder'; 5 | 6 | interface AssigneeEditorProps { 7 | assignee: any; 8 | setAssignee: (assignee: any) => void; 9 | mappings: Mappings; 10 | } 11 | 12 | const AssigneeEditor = ({ assignee, setAssignee, mappings }: AssigneeEditorProps) => { 13 | const [simpleAssigneeType, setSimpleAssigneeType] = useState('text'); // 'text' or 'dropdown' 14 | 15 | const isCollection = typeof assignee === 'object' && assignee !== null && assignee['@type'] === 'PartyCollection'; 16 | 17 | const setType = (type: 'simple' | 'collection') => { 18 | if (type === 'simple') { 19 | setAssignee(''); // Reset to a simple string 20 | setSimpleAssigneeType('text'); // Default to text input for simple 21 | } else { 22 | setAssignee({ '@type': 'PartyCollection', 'odrl:refinement': [] }); 23 | } 24 | }; 25 | 26 | const anyMappings = mappings as any; 27 | const availableAssignees = anyMappings.assignees; 28 | 29 | return ( 30 | 31 | 32 | 33 | setType('simple')} 39 | /> 40 | setType('collection')} 46 | /> 47 | 48 | 49 | 50 | {!isCollection ? ( 51 | 52 | 53 | 54 | { 60 | setSimpleAssigneeType('text'); 61 | setAssignee(''); // Clear value when switching type 62 | }} 63 | /> 64 | { 70 | setSimpleAssigneeType('dropdown'); 71 | setAssignee({ '@id': '' }); // Clear value when switching type 72 | }} 73 | disabled={!availableAssignees || availableAssignees.length === 0} 74 | /> 75 | 76 | 77 | 78 | 79 | {simpleAssigneeType === 'text' ? ( 80 | setAssignee(e.target.value)} 85 | /> 86 | ) : ( 87 | setAssignee({ '@id': e.target.value })} 90 | > 91 | 92 | {availableAssignees?.map((a: any) => ( 93 | 94 | ))} 95 | 96 | )} 97 | 98 | 99 | 100 | ) : ( 101 | 102 |
Party Collection
103 |
104 |
Refinements
105 | 110 |
111 | )} 112 |
113 | ); 114 | }; 115 | 116 | export default AssigneeEditor; 117 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/mapping/RightOperandMapper.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl.mapping; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.fiware.odrl.rego.RegoMethod; 5 | 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | import static org.fiware.odrl.mapping.OdrlConstants.*; 14 | 15 | /** 16 | * Provides capabilities for mapping odrl:rightOperand 17 | */ 18 | public class RightOperandMapper extends TypeMapper { 19 | 20 | public RightOperandMapper(ObjectMapper objectMapper, MappingConfiguration mappingConfiguration) { 21 | super(objectMapper, getMappings(mappingConfiguration, OdrlAttribute.RIGHT_OPERAND)); 22 | } 23 | 24 | // package private, since it's only to fulfill cdi requirements 25 | RightOperandMapper() { 26 | super(null, null); 27 | } 28 | 29 | /** 30 | * Is the given key a sub-class of odrl:rightOperand? 31 | */ 32 | public boolean isRightOperand(String key) { 33 | if (key.equalsIgnoreCase(OdrlConstants.RIGHT_OPERAND_KEY)) { 34 | return true; 35 | } 36 | return mappings.containsKey(key); 37 | } 38 | 39 | /** 40 | * Retrieves the key of the rightOperand from the given constraint. Throws an exception if none is found 41 | */ 42 | public String getRightOperandKey(Map theConstraint) throws MappingException { 43 | return theConstraint.keySet().stream().filter(this::isRightOperand).findFirst() 44 | .orElseThrow(() -> new MappingException("The provided constraint does not contain a right operand.")); 45 | } 46 | 47 | /** 48 | * Get the type of the rightOperand at the given key in the provided object. 49 | */ 50 | public String getType(String key, Object rightOperandObject) { 51 | // case "my:customOperand": {} 52 | if (!key.equalsIgnoreCase(OdrlConstants.RIGHT_OPERAND_KEY)) { 53 | return key; 54 | } 55 | 56 | // case "odrl:rightOperand" : "my:customOperand" 57 | if (rightOperandObject instanceof String typeString) { 58 | // it's a mapping 59 | if (isRightOperand(typeString)) { 60 | return typeString; 61 | } else { 62 | // it's only a value 63 | return OdrlConstants.RIGHT_OPERAND_KEY; 64 | } 65 | } 66 | 67 | // case "odrl:rightOperand" : [..,] 68 | if (rightOperandObject instanceof List) { 69 | return OdrlConstants.RIGHT_OPERAND_KEY; 70 | } 71 | 72 | Map theRightOperandMap = convertToMap(rightOperandObject); 73 | // case "odrl:rightOperand": {"@value":"somethingStatic"} 74 | if (!theRightOperandMap.containsKey(OdrlConstants.ID_KEY)) { 75 | return OdrlConstants.RIGHT_OPERAND_KEY; 76 | } else 77 | // case "odrl:rightOperand": {"@id":"my:customOperand"} 78 | { 79 | return (String) theRightOperandMap.get(OdrlConstants.ID_KEY); 80 | } 81 | } 82 | 83 | /** 84 | * Get the value of the rightOperand with the given type and object. 85 | */ 86 | public Optional getValue(String type, Object rightOperandObject) throws MappingException { 87 | // case "odrl:rightOperand" : "my:customOperand" 88 | // or "my:customOperand": "static-value" 89 | if (rightOperandObject instanceof String valueString) { 90 | if (valueString.equals(type)) { 91 | return Optional.empty(); 92 | } else { 93 | return Optional.ofNullable(valueString); 94 | } 95 | } 96 | 97 | if (rightOperandObject instanceof List valueList) { 98 | return Optional.of(valueList); 99 | } 100 | 101 | Map theRightOperandMap = convertToMap(rightOperandObject); 102 | // case "odrl:leftOperand" : {"@Value": "something"} 103 | // or "my:customOperand": {"@Value": "something"} 104 | if (!theRightOperandMap.containsKey(OdrlConstants.VALUE_KEY)) { 105 | return Optional.empty(); 106 | } 107 | if (theRightOperandMap.containsKey(OdrlConstants.TYPE_KEY) && theRightOperandMap.get(TYPE_KEY).equals(DATE_TYPE)) { 108 | String dateString = (String) theRightOperandMap.get(VALUE_KEY); 109 | try { 110 | Date parsedDate = DATE_FORMAT.parse(dateString); 111 | return Optional.of(parsedDate.getTime()); 112 | } catch (ParseException e) { 113 | throw new MappingException(String.format("The date %s is not valid", dateString), e); 114 | } 115 | } 116 | return Optional.ofNullable(theRightOperandMap.get(OdrlConstants.VALUE_KEY)); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /frontend/src/api/core/CancelablePromise.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | export class CancelError extends Error { 6 | 7 | constructor(message: string) { 8 | super(message); 9 | this.name = 'CancelError'; 10 | } 11 | 12 | public get isCancelled(): boolean { 13 | return true; 14 | } 15 | } 16 | 17 | export interface OnCancel { 18 | readonly isResolved: boolean; 19 | readonly isRejected: boolean; 20 | readonly isCancelled: boolean; 21 | 22 | (cancelHandler: () => void): void; 23 | } 24 | 25 | export class CancelablePromise implements Promise { 26 | #isResolved: boolean; 27 | #isRejected: boolean; 28 | #isCancelled: boolean; 29 | readonly #cancelHandlers: (() => void)[]; 30 | readonly #promise: Promise; 31 | #resolve?: (value: T | PromiseLike) => void; 32 | #reject?: (reason?: any) => void; 33 | 34 | constructor( 35 | executor: ( 36 | resolve: (value: T | PromiseLike) => void, 37 | reject: (reason?: any) => void, 38 | onCancel: OnCancel 39 | ) => void 40 | ) { 41 | this.#isResolved = false; 42 | this.#isRejected = false; 43 | this.#isCancelled = false; 44 | this.#cancelHandlers = []; 45 | this.#promise = new Promise((resolve, reject) => { 46 | this.#resolve = resolve; 47 | this.#reject = reject; 48 | 49 | const onResolve = (value: T | PromiseLike): void => { 50 | if (this.#isResolved || this.#isRejected || this.#isCancelled) { 51 | return; 52 | } 53 | this.#isResolved = true; 54 | if (this.#resolve) this.#resolve(value); 55 | }; 56 | 57 | const onReject = (reason?: any): void => { 58 | if (this.#isResolved || this.#isRejected || this.#isCancelled) { 59 | return; 60 | } 61 | this.#isRejected = true; 62 | if (this.#reject) this.#reject(reason); 63 | }; 64 | 65 | const onCancel = (cancelHandler: () => void): void => { 66 | if (this.#isResolved || this.#isRejected || this.#isCancelled) { 67 | return; 68 | } 69 | this.#cancelHandlers.push(cancelHandler); 70 | }; 71 | 72 | Object.defineProperty(onCancel, 'isResolved', { 73 | get: (): boolean => this.#isResolved, 74 | }); 75 | 76 | Object.defineProperty(onCancel, 'isRejected', { 77 | get: (): boolean => this.#isRejected, 78 | }); 79 | 80 | Object.defineProperty(onCancel, 'isCancelled', { 81 | get: (): boolean => this.#isCancelled, 82 | }); 83 | 84 | return executor(onResolve, onReject, onCancel as OnCancel); 85 | }); 86 | } 87 | 88 | get [Symbol.toStringTag]() { 89 | return "Cancellable Promise"; 90 | } 91 | 92 | public then( 93 | onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, 94 | onRejected?: ((reason: any) => TResult2 | PromiseLike) | null 95 | ): Promise { 96 | return this.#promise.then(onFulfilled, onRejected); 97 | } 98 | 99 | public catch( 100 | onRejected?: ((reason: any) => TResult | PromiseLike) | null 101 | ): Promise { 102 | return this.#promise.catch(onRejected); 103 | } 104 | 105 | public finally(onFinally?: (() => void) | null): Promise { 106 | return this.#promise.finally(onFinally); 107 | } 108 | 109 | public cancel(): void { 110 | if (this.#isResolved || this.#isRejected || this.#isCancelled) { 111 | return; 112 | } 113 | this.#isCancelled = true; 114 | if (this.#cancelHandlers.length) { 115 | try { 116 | for (const cancelHandler of this.#cancelHandlers) { 117 | cancelHandler(); 118 | } 119 | } catch (error) { 120 | console.warn('Cancellation threw an error', error); 121 | return; 122 | } 123 | } 124 | this.#cancelHandlers.length = 0; 125 | if (this.#reject) this.#reject(new CancelError('Request aborted')); 126 | } 127 | 128 | public get isCancelled(): boolean { 129 | return this.#isCancelled; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /frontend/src/api/services/PapService.ts: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | import type { OdrlPolicyJson } from '../models/OdrlPolicyJson'; 6 | import type { Policy } from '../models/Policy'; 7 | import type { PolicyList } from '../models/PolicyList'; 8 | import type { Uid } from '../models/Uid'; 9 | import type { CancelablePromise } from '../core/CancelablePromise'; 10 | import { OpenAPI } from '../core/OpenAPI'; 11 | import { request as __request } from '../core/request'; 12 | export class PapService { 13 | /** 14 | * Creates a new policy from the given odrl-json 15 | * @param requestBody 16 | * @returns string The rego policy 17 | * @throws ApiError 18 | */ 19 | public static createPolicy( 20 | requestBody: OdrlPolicyJson, 21 | ): CancelablePromise { 22 | return __request(OpenAPI, { 23 | method: 'POST', 24 | url: '/policy', 25 | body: requestBody, 26 | mediaType: 'application/json', 27 | }); 28 | } 29 | /** 30 | * Get the policies in the ODRL-Format 31 | * @param page 32 | * @param pageSize 33 | * @returns PolicyList Successfully retrieved the policis. 34 | * @throws ApiError 35 | */ 36 | public static getPolicies( 37 | page?: number, 38 | pageSize: number = 25, 39 | ): CancelablePromise { 40 | return __request(OpenAPI, { 41 | method: 'GET', 42 | url: '/policy', 43 | query: { 44 | 'page': page, 45 | 'pageSize': pageSize, 46 | }, 47 | errors: { 48 | 404: `No such policy exists`, 49 | }, 50 | }); 51 | } 52 | /** 53 | * Creates or overwrites the given policy. 54 | * @param id 55 | * @param requestBody 56 | * @returns string The rego policy 57 | * @throws ApiError 58 | */ 59 | public static createPolicyWithId( 60 | id: Uid, 61 | requestBody: OdrlPolicyJson, 62 | ): CancelablePromise { 63 | return __request(OpenAPI, { 64 | method: 'PUT', 65 | url: '/policy/{id}', 66 | path: { 67 | 'id': id, 68 | }, 69 | body: requestBody, 70 | mediaType: 'application/json', 71 | errors: { 72 | 409: `Should be returned in case the policy is not allowed to be modified`, 73 | }, 74 | }); 75 | } 76 | /** 77 | * Return the given policy by its ID. 78 | * @param id 79 | * @returns Policy Successfully retrieved the policy. 80 | * @throws ApiError 81 | */ 82 | public static getPolicyById( 83 | id: Uid, 84 | ): CancelablePromise { 85 | return __request(OpenAPI, { 86 | method: 'GET', 87 | url: '/policy/{id}', 88 | path: { 89 | 'id': id, 90 | }, 91 | errors: { 92 | 404: `No such policy exists`, 93 | }, 94 | }); 95 | } 96 | /** 97 | * Delete the given policy. 98 | * @param id 99 | * @returns void 100 | * @throws ApiError 101 | */ 102 | public static deletePolicyById( 103 | id: Uid, 104 | ): CancelablePromise { 105 | return __request(OpenAPI, { 106 | method: 'DELETE', 107 | url: '/policy/{id}', 108 | path: { 109 | 'id': id, 110 | }, 111 | errors: { 112 | 404: `No such policy exists`, 113 | }, 114 | }); 115 | } 116 | /** 117 | * Return the given policy by its ID. 118 | * @param id 119 | * @returns Policy Successfully retrieved the policy. 120 | * @throws ApiError 121 | */ 122 | public static getPolicyByUid( 123 | id: Uid, 124 | ): CancelablePromise { 125 | return __request(OpenAPI, { 126 | method: 'GET', 127 | url: '/policy/odrl/{id}', 128 | path: { 129 | 'id': id, 130 | }, 131 | errors: { 132 | 404: `No such policy exists`, 133 | }, 134 | }); 135 | } 136 | /** 137 | * Delete the given policy. 138 | * @param id 139 | * @returns void 140 | * @throws ApiError 141 | */ 142 | public static deletePolicyByUid( 143 | id: Uid, 144 | ): CancelablePromise { 145 | return __request(OpenAPI, { 146 | method: 'DELETE', 147 | url: '/policy/odrl/{id}', 148 | path: { 149 | 'id': id, 150 | }, 151 | errors: { 152 | 404: `No such policy exists`, 153 | }, 154 | }); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/org/fiware/odrl/PolicyResource.java: -------------------------------------------------------------------------------- 1 | package org.fiware.odrl; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import jakarta.enterprise.inject.Instance; 6 | import jakarta.inject.Inject; 7 | import jakarta.ws.rs.core.Response; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.http.HttpStatus; 10 | import org.fiware.odrl.api.PolicyApi; 11 | import org.fiware.odrl.mapping.*; 12 | import org.fiware.odrl.model.Policy; 13 | import org.fiware.odrl.rego.OdrlPolicy; 14 | import org.fiware.odrl.rego.PolicyRepository; 15 | import org.fiware.odrl.rego.PolicyWrapper; 16 | import org.fiware.odrl.rego.RegoPolicy; 17 | import org.fiware.odrl.verification.TypeVerifier; 18 | import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Optional; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * @author Stefan Wiedemann 27 | */ 28 | @Slf4j 29 | public class PolicyResource implements PolicyApi { 30 | 31 | private static final String MAIN_POLICY_ID = "main"; 32 | private static final String LOCATION_HEADER = "Location"; 33 | 34 | @Inject 35 | private ObjectMapper objectMapper; 36 | 37 | @Inject 38 | private PolicyRepository policyRepository; 39 | 40 | @Inject 41 | private GeneralConfig generalConfig; 42 | 43 | @Inject 44 | private OdrlMapper odrlMapper; 45 | 46 | @Override 47 | public Response createPolicy(Map requestBody) { 48 | return createPolicyWithId(policyRepository.generatePolicyId(), requestBody); 49 | } 50 | 51 | @Override 52 | public Response createPolicyWithId(String id, Map policy) { 53 | if (id.equals(MAIN_POLICY_ID)) { 54 | return Response.status(HttpStatus.SC_CONFLICT).entity("Policy `main` cannot be manually modified.").build(); 55 | } 56 | 57 | MappingResult mappingResult = odrlMapper.mapOdrl(policy); 58 | if (mappingResult.isFailed()) { 59 | throw new IllegalArgumentException(String.join(",", mappingResult.getFailureReasons())); 60 | } 61 | String packagedId = String.format("policy.%s", id); 62 | String regoPolicy = mappingResult.getRego(packagedId); 63 | try { 64 | PolicyWrapper thePolicy = new PolicyWrapper(id, mappingResult.getUid(), new OdrlPolicy(objectMapper.writeValueAsString(policy)), new RegoPolicy(regoPolicy)); 65 | policyRepository.createPolicy(id, mappingResult.getUid(), thePolicy); 66 | } catch (JsonProcessingException e) { 67 | throw new IllegalArgumentException("Was not able to persist the odrl representation.", e); 68 | } 69 | return Response.ok(regoPolicy).header(LOCATION_HEADER, id).build(); 70 | } 71 | 72 | @Override 73 | public Response deletePolicyById(String id) { 74 | policyRepository.deletePolicy(id); 75 | return Response.noContent().build(); 76 | } 77 | 78 | @Override 79 | public Response deletePolicyByUid(String uid) { 80 | policyRepository.deletePolicyByUid(uid); 81 | return Response.noContent().build(); 82 | } 83 | 84 | 85 | @Override 86 | public Response getPolicies(Integer page, Integer pageSize) { 87 | List policyList = policyRepository 88 | .getPolicies(Optional.ofNullable(page).orElse(0), Optional.ofNullable(pageSize).orElse(25)) 89 | .entrySet() 90 | .stream() 91 | .map(policyEntry -> new Policy() 92 | .id(policyEntry.getKey()) 93 | .odrlColonUid(policyEntry.getValue().odrlUid()) 94 | .odrl(policyEntry.getValue().odrl().policy()) 95 | .rego(policyEntry.getValue().rego().policy())) 96 | .toList(); 97 | 98 | return Response.ok(policyList).build(); 99 | } 100 | 101 | 102 | @Override 103 | public Response getPolicyById(String id) { 104 | return policyWrapperToResponse(policyRepository 105 | .getPolicy(id)); 106 | } 107 | 108 | @Override 109 | public Response getPolicyByUid(String id) { 110 | return policyWrapperToResponse(policyRepository 111 | .getPolicyByUid(id)); 112 | 113 | } 114 | 115 | private Response policyWrapperToResponse(Optional optionalPolicyWrapper) { 116 | return optionalPolicyWrapper 117 | .map(pw -> new Policy() 118 | .id(pw.regoId()) 119 | .odrlColonUid(pw.odrlUid()) 120 | .odrl(pw.odrl().policy()) 121 | .rego(pw.rego().policy())) 122 | .map(Response::ok) 123 | .map(Response.ResponseBuilder::build) 124 | .orElse(Response.status(Response.Status.NOT_FOUND).build()); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /frontend/src/components/PolicySummary.tsx: -------------------------------------------------------------------------------- 1 | import { Card, ListGroup, Badge, Stack, Button, Collapse } from 'react-bootstrap'; 2 | import { useState } from 'react'; 3 | import type { OdrlPolicyJson } from '../services/api'; 4 | 5 | interface PolicySummaryProps { 6 | policy: OdrlPolicyJson; 7 | } 8 | 9 | // Helper to render any kind of operand (string, @id, @value) 10 | const renderOperand = (operand: any): string => { 11 | if (!operand) return '(not set)'; 12 | if (typeof operand === 'string') return operand; 13 | if (operand['@id']) return operand['@id'].replace('odrl:', ''); 14 | if (operand['@value']) return `'${operand['@value']}' (${operand['@type']})`; 15 | return JSON.stringify(operand); 16 | } 17 | 18 | const renderConstraint = (constraint: any, index: number) => ( 19 | 20 |
21 |
Constraint
22 | 23 | {renderOperand(constraint['odrl:leftOperand'])} 24 | {renderOperand(constraint['odrl:operator'])} 25 | {renderOperand(constraint['odrl:rightOperand'])} 26 | 27 |
28 |
29 | ); 30 | 31 | const renderRefinements = (refinements: any[]) => { 32 | if (!refinements || refinements.length === 0) return null; 33 | return ( 34 |
35 |
Refinements:
36 | 37 | {refinements.map(renderConstraint)} 38 | 39 |
40 | ); 41 | }; 42 | 43 | const PolicySummary = ({ policy }: PolicySummaryProps) => { 44 | const [showJson, setShowJson] = useState(false); 45 | 46 | if (!policy) return null; 47 | 48 | const permission = policy['odrl:permission'] || {}; 49 | const { 'odrl:target': target, 'odrl:assignee': assignee, 'odrl:action': action, 'odrl:constraint': constraint } = permission; 50 | 51 | const renderEntity = (entity: any, name: string) => { 52 | if (!entity) return <>{name}: (not set); 53 | if (typeof entity === 'string') return <>{name}: {entity}; 54 | if (entity['@type']) { 55 | return ( 56 | <> 57 | {name}: {entity['@type']} 58 | {renderRefinements(entity['odrl:refinement'])} 59 | 60 | ); 61 | } 62 | return <>{name}: {JSON.stringify(entity)}; 63 | } 64 | 65 | const renderConstraints = () => { 66 | if (!constraint) return null; 67 | 68 | // Logical Constraint 69 | if (constraint['@type'] === 'odrl:LogicalConstraint') { 70 | const operator = Object.keys(constraint).find(k => k.startsWith('odrl:')); 71 | const constraints = operator ? constraint[operator] : []; 72 | return ( 73 | <> 74 |
Constraints {operator?.replace('odrl:', '').toUpperCase()}
75 | {constraints.map(renderConstraint)} 76 | 77 | ) 78 | } 79 | 80 | // Array of constraints (implicit AND) 81 | if (Array.isArray(constraint)) { 82 | return ( 83 | <> 84 |
Constraints AND
85 | {constraint.map(renderConstraint)} 86 | 87 | ) 88 | } 89 | 90 | // Single constraint 91 | return ( 92 | <> 93 |
Constraint
94 | {renderConstraint(constraint, 0)} 95 | 96 | ) 97 | } 98 | 99 | return ( 100 | 101 | 102 | Policy Summary 103 | 106 | 107 | 108 | 109 |
110 | 111 | UID: {policy['odrl:uid'] || '(not set)'} 112 | 113 |
114 |
Permission
115 | 116 | {renderEntity(target, 'Target')} 117 | {renderEntity(assignee, 'Assignee')} 118 | Action: {action || '(not set)'} 119 | 120 | {renderConstraints()} 121 |
122 |
123 | 124 |
125 |
{JSON.stringify(policy, null, 2)}
126 |
127 |
128 |
129 |
130 | ); 131 | }; 132 | 133 | export default PolicySummary; 134 | -------------------------------------------------------------------------------- /frontend/src/pages/PolicyEditor.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useParams, useNavigate } from 'react-router-dom'; 3 | import { Form, Button, Tabs, Tab, Modal, Alert } from 'react-bootstrap'; 4 | import { PapService } from '../api/services/PapService'; 5 | import { UiService } from '../api/services/UiService'; 6 | import type { OdrlPolicyJson, Policy, TestRequest, ValidationResponse } from '../services/api'; 7 | import Baukasten from '../components/Baukasten'; 8 | import ValidationEditor from '../components/ValidationEditor'; 9 | 10 | const NEW_POLICY_TEMPLATE = { 11 | '@context': 'http://www.w3.org/ns/odrl/2/', 12 | '@type': 'odrl:Policy', 13 | 'odrl:permission': {}, 14 | }; 15 | 16 | const DEFAULT_TEST_REQUEST: TestRequest = { 17 | method: 'GET', 18 | host: 'example.com', 19 | path: '/', 20 | headers: { 21 | 'content-type': 'application/json' 22 | }, 23 | body: {} 24 | } 25 | 26 | const PolicyEditor = () => { 27 | const { id } = useParams(); 28 | const navigate = useNavigate(); 29 | const [policy, setPolicy] = useState({}); 30 | const [key, setKey] = useState('baukasten'); 31 | 32 | // Validation state 33 | const [showValidation, setShowValidation] = useState(false); 34 | const [testRequest, setTestRequest] = useState(DEFAULT_TEST_REQUEST); 35 | const [validationResult, setValidationResult] = useState(null); 36 | const [validationError, setValidationError] = useState(''); 37 | 38 | useEffect(() => { 39 | if (id) { 40 | PapService.getPolicyById(id) 41 | .then((p: Policy) => setPolicy(JSON.parse(p.odrl!))) 42 | .catch(console.error); 43 | } else { 44 | // Create a new policy with a generated UID 45 | const newPolicy = { 46 | ...NEW_POLICY_TEMPLATE, 47 | 'odrl:uid': crypto.randomUUID(), 48 | }; 49 | setPolicy(newPolicy); 50 | } 51 | }, [id]); 52 | 53 | const handleSave = () => { 54 | const requestBody = policy; 55 | if (id) { 56 | PapService.createPolicyWithId(id, requestBody) 57 | .then(() => navigate('/')) 58 | .catch(console.error); 59 | } else { 60 | PapService.createPolicy(requestBody) 61 | .then(() => navigate('/')) 62 | .catch(console.error); 63 | } 64 | }; 65 | 66 | const handleValidate = () => { 67 | try { 68 | // The body from the textarea is a string, try to parse it. 69 | const body = typeof testRequest.body === 'string' ? JSON.parse(testRequest.body) : testRequest.body; 70 | const finalTestRequest = { ...testRequest, body }; 71 | 72 | const requestBody = { policy, testRequest: finalTestRequest }; 73 | UiService.validatePolicy(requestBody) 74 | .then(setValidationResult) 75 | .catch(err => setValidationError(err.message)); 76 | } catch (e: any) { 77 | setValidationError('Invalid JSON in body'); 78 | } 79 | }; 80 | 81 | const handleCloseValidation = () => { 82 | setShowValidation(false); 83 | setValidationResult(null); 84 | setValidationError(''); 85 | } 86 | 87 | return ( 88 | <> 89 |

{id ? 'Edit Policy' : 'New Policy'}

90 | setKey(k!)} 94 | className="mb-3" 95 | > 96 | 97 | 98 | 99 | 100 | setPolicy(JSON.parse(e.target.value))} 105 | /> 106 | 107 | 108 |
109 | 110 | 111 | 112 | 113 | {/* Validation Modal */} 114 | 115 | 116 | Validate Policy 117 | 118 | 119 | 120 | {validationResult && ( 121 | 122 | Validation Result 123 |
{JSON.stringify(validationResult, null, 2)}
124 |
125 | )} 126 | {validationError && ( 127 | {validationError} 128 | )} 129 |
130 | 131 | 132 | 133 | 134 |
135 | 136 | ); 137 | }; 138 | 139 | export default PolicyEditor; 140 | --------------------------------------------------------------------------------