diff --git a/charts/digital-product-pass/Chart.yaml b/charts/digital-product-pass/Chart.yaml index 36015a71c..c1ca0a13a 100644 --- a/charts/digital-product-pass/Chart.yaml +++ b/charts/digital-product-pass/Chart.yaml @@ -40,10 +40,10 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.4.0 +version: 1.5.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.4.0" +appVersion: "1.5.0" diff --git a/charts/digital-product-pass/README.md b/charts/digital-product-pass/README.md index c038f05da..d95b9ee81 100644 --- a/charts/digital-product-pass/README.md +++ b/charts/digital-product-pass/README.md @@ -1,6 +1,6 @@ # digital-product-pass -![Version: 1.3.1](https://img.shields.io/badge/Version-1.3.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.3.1](https://img.shields.io/badge/AppVersion-1.3.1-informational?style=flat-square) +![Version: 1.5.0](https://img.shields.io/badge/Version-1.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.5.0](https://img.shields.io/badge/AppVersion-1.5.0-informational?style=flat-square) A Helm chart for Tractus-X Digital Product Pass Kubernetes @@ -15,36 +15,57 @@ A Helm chart for Tractus-X Digital Product Pass Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | -| backend | object | `{"application":{"yml":"# -- spring boot configuration\nspring:\n name: \"Catena-X Product Passport Consumer Backend\"\n main:\n allow-bean-definition-overriding: true\n devtools:\n add-properties: false\n jackson:\n serialization:\n indent_output: true\nlogging:\n level:\n # -- general logging level\n root: INFO\n # -- logging for the util components\n utils: INFO\nconfiguration:\n # -- max retries for the backend services\n maxRetries: 5\n # -- keycloak configuration\n keycloak:\n realm: CX-Central\n resource: Cl13-CX-Battery\n tokenUri: 'https:///auth/realms//protocol/openid-connect/token'\n userInfoUri: 'https:///auth/realms//protocol/openid-connect/userinfo'\n # -- edc consumer connection configuration\n edc:\n endpoint: 'https://'\n management: '/management/v2'\n catalog: '/catalog/request'\n negotiation: '/contractnegotiations'\n transfer: '/transferprocesses'\n receiverEndpoint: 'https:///endpoint'\n delay: 100 # -- Negotiation status Delay in milliseconds in between async requests [<= 500]\n # -- security configuration\n security:\n check:\n enabled: false\n bpn: false\n edc: false\n # -- irs configuration\n irs:\n enabled: true # -- Enable search for children in the requests\n endpoint: \"https://\" # -- IRS endpoint\n paths:\n job: \"/irs/jobs\" # -- API path for calling in the IRS endpoints and staring/getting jobs\n tree:\n fileName: \"treeDataModel\" # -- Tree dataModel filename created in the processId directory\n indent: true # -- Indent tree file\n callbackUrl: \"https:///api/irs\" # -- Backend call back base url for the irs controller\n # -- digital twin registry configuration\n dtr:\n central: false\n # -- central digital twin registry url\n centralUrl: 'https://'\n # -- asset type to search for the registry in the edc\n assetType: 'data.core.digitalTwinRegistry'\n # -- submodel endpoint interface to search\n endpointInterface: 'SUBMODEL-3.0'\n # -- dsp endpoint key inside submodel body\n dspEndpointKey: 'dspEndpoint'\n # -- decentral digital twin apis\n decentralApis:\n search: \"/lookup/shells\"\n digitalTwin: \"/shell-descriptors\"\n subModel: \"/submodel-descriptors\"\n # -- timeouts for the digital twin registry async negotiation\n timeouts:\n search: 10\n negotiation: 40\n transfer: 10\n digitalTwin: 20\n # -- temporary storage of dDTRs for optimization\n temporaryStorage: true\n # -- discovery configuration\n discovery:\n # -- discovery finder configuration\n endpoint: \"https:///discoveryfinder/api/v1.0/administration/connectors/discovery/search\"\n # -- bpn discovery configuration\n bpn:\n key: \"manufacturerPartId\"\n searchPath: \"/api/v1.0/administration/connectors/bpnDiscovery/search\"\n timeout: 2000 # -- timeout in milliseconds for the bpn discovery APIs to respond\n # -- edc discovery configuration\n edc:\n key: \"bpn\"\n timeout: 2000 # -- timeout in milliseconds for the bpn discovery APIs to respond\n # -- process configuration\n process:\n # -- directory for storing the contract negotiation files\n dir: \"process\"\n # -- indent the process negotiation files\n indent: true\n # -- unique sha512 hash key used for the passport encryption\n signKey: \"\"\n # -- passport data transfer configuration\n passport:\n # -- configure the data transfer\n dataTransfer:\n # -- encrypt the passport when he arrives from the edc data plane\n encrypt: true\n # -- the indent from the passport\n indent: true\n # -- directory to store the passport when is not linked to a process\n dir: \"data/transfer\"\n # -- passport versions and aspects allowed\n aspects:\n - \"urn:bamm:io.catenax.generic.digital_product_passport:1.0.0#DigitalProductPassport\"\n - \"urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass\"\n# -- configuration of the spring boot server\nserver:\n # -- configuration of backend errors\n error:\n include-message: ALWAYS\n include-binding-errors: ALWAYS\n include-stacktrace: ON_PARAM\n include-exception: false\n # -- listening port for the backend\n port: 8888\n # -- maximum allowed connections\n tomcat:\n max-connections: 10000"},"edc":{"clientId":"","clientSecret":"","participantId":"","xApiKey":""},"image":{"pullPolicy":"Always","repository":"docker.io/tractusx/digital-product-pass-backend"},"imagePullSecrets":[],"ingress":{"enabled":false},"name":"dpp-backend","service":{"port":8888,"type":"ClusterIP"}}` | Backend configuration | -| backend.application | object | `{"yml":"# -- spring boot configuration\nspring:\n name: \"Catena-X Product Passport Consumer Backend\"\n main:\n allow-bean-definition-overriding: true\n devtools:\n add-properties: false\n jackson:\n serialization:\n indent_output: true\nlogging:\n level:\n # -- general logging level\n root: INFO\n # -- logging for the util components\n utils: INFO\nconfiguration:\n # -- max retries for the backend services\n maxRetries: 5\n # -- keycloak configuration\n keycloak:\n realm: CX-Central\n resource: Cl13-CX-Battery\n tokenUri: 'https:///auth/realms//protocol/openid-connect/token'\n userInfoUri: 'https:///auth/realms//protocol/openid-connect/userinfo'\n # -- edc consumer connection configuration\n edc:\n endpoint: 'https://'\n management: '/management/v2'\n catalog: '/catalog/request'\n negotiation: '/contractnegotiations'\n transfer: '/transferprocesses'\n receiverEndpoint: 'https:///endpoint'\n delay: 100 # -- Negotiation status Delay in milliseconds in between async requests [<= 500]\n # -- security configuration\n security:\n check:\n enabled: false\n bpn: false\n edc: false\n # -- irs configuration\n irs:\n enabled: true # -- Enable search for children in the requests\n endpoint: \"https://\" # -- IRS endpoint\n paths:\n job: \"/irs/jobs\" # -- API path for calling in the IRS endpoints and staring/getting jobs\n tree:\n fileName: \"treeDataModel\" # -- Tree dataModel filename created in the processId directory\n indent: true # -- Indent tree file\n callbackUrl: \"https:///api/irs\" # -- Backend call back base url for the irs controller\n # -- digital twin registry configuration\n dtr:\n central: false\n # -- central digital twin registry url\n centralUrl: 'https://'\n # -- asset type to search for the registry in the edc\n assetType: 'data.core.digitalTwinRegistry'\n # -- submodel endpoint interface to search\n endpointInterface: 'SUBMODEL-3.0'\n # -- dsp endpoint key inside submodel body\n dspEndpointKey: 'dspEndpoint'\n # -- decentral digital twin apis\n decentralApis:\n search: \"/lookup/shells\"\n digitalTwin: \"/shell-descriptors\"\n subModel: \"/submodel-descriptors\"\n # -- timeouts for the digital twin registry async negotiation\n timeouts:\n search: 10\n negotiation: 40\n transfer: 10\n digitalTwin: 20\n # -- temporary storage of dDTRs for optimization\n temporaryStorage: true\n # -- discovery configuration\n discovery:\n # -- discovery finder configuration\n endpoint: \"https:///discoveryfinder/api/v1.0/administration/connectors/discovery/search\"\n # -- bpn discovery configuration\n bpn:\n key: \"manufacturerPartId\"\n searchPath: \"/api/v1.0/administration/connectors/bpnDiscovery/search\"\n timeout: 2000 # -- timeout in milliseconds for the bpn discovery APIs to respond\n # -- edc discovery configuration\n edc:\n key: \"bpn\"\n timeout: 2000 # -- timeout in milliseconds for the bpn discovery APIs to respond\n # -- process configuration\n process:\n # -- directory for storing the contract negotiation files\n dir: \"process\"\n # -- indent the process negotiation files\n indent: true\n # -- unique sha512 hash key used for the passport encryption\n signKey: \"\"\n # -- passport data transfer configuration\n passport:\n # -- configure the data transfer\n dataTransfer:\n # -- encrypt the passport when he arrives from the edc data plane\n encrypt: true\n # -- the indent from the passport\n indent: true\n # -- directory to store the passport when is not linked to a process\n dir: \"data/transfer\"\n # -- passport versions and aspects allowed\n aspects:\n - \"urn:bamm:io.catenax.generic.digital_product_passport:1.0.0#DigitalProductPassport\"\n - \"urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass\"\n# -- configuration of the spring boot server\nserver:\n # -- configuration of backend errors\n error:\n include-message: ALWAYS\n include-binding-errors: ALWAYS\n include-stacktrace: ON_PARAM\n include-exception: false\n # -- listening port for the backend\n port: 8888\n # -- maximum allowed connections\n tomcat:\n max-connections: 10000"}` | specific backend and spring boot configurations | -| backend.edc | object | `{"clientId":"","clientSecret":"","participantId":"","xApiKey":""}` | in this section we configure the values that are inserted as secrets in the backend | -| backend.edc.clientId | string | `""` | note: this credentials need to have access to the Discovery Finder, BPN Discovery and EDC Discovery | +| backend | object | `{"digitalTwinRegistry":{"endpoints":{"digitalTwin":"/shell-descriptors","search":"/lookup/shells","subModel":"/submodel-descriptors"},"temporaryStorage":{"enabled":true},"timeouts":{"digitalTwin":20,"negotiation":40,"search":10,"transfer":10}},"discovery":{"bpnDiscovery":{"key":"manufacturerPartId","path":"/api/v1.0/administration/connectors/bpnDiscovery/search"},"edcDiscovery":{"key":"bpn"},"hostname":""},"edc":{"apis":{"catalog":"/catalog/request","management":"/management/v2","negotiation":"/contractnegotiations","transfer":"/transferprocesses"},"delay":100,"endpoint":"","participantId":"","xApiKey":""},"hostname":"localhost","image":{"pullPolicy":"Always","repository":"docker.io/tractusx/digital-product-pass-backend"},"imagePullSecrets":[],"ingress":{"enabled":false,"hosts":[{"host":"localhost","paths":[{"path":"/","pathType":"Prefix"}]}]},"irs":{"enabled":false,"hostname":""},"logging":{"level":{"root":"INFO","utils":"INFO"}},"maxRetries":5,"name":"dpp-backend","passport":{"aspects":["urn:bamm:io.catenax.generic.digital_product_passport:1.0.0#DigitalProductPassport","urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass","urn:bamm:io.catenax.transmission.transmission_pass:1.0.0#TransmissionPass"]},"process":{"encryptionKey":""},"securityCheck":{"bpn":false,"edc":false,"enabled":false},"serverPort":8888,"service":{"port":8888,"type":"ClusterIP"}}` | Backend configuration | +| backend.digitalTwinRegistry.temporaryStorage | object | `{"enabled":true}` | temporary storage of dDTRs for optimization | +| backend.digitalTwinRegistry.timeouts | object | `{"digitalTwin":20,"negotiation":40,"search":10,"transfer":10}` | timeouts for the digital twin registry async negotiation | +| backend.discovery | object | `{"bpnDiscovery":{"key":"manufacturerPartId","path":"/api/v1.0/administration/connectors/bpnDiscovery/search"},"edcDiscovery":{"key":"bpn"},"hostname":""}` | discovery configuration | +| backend.discovery.bpnDiscovery | object | `{"key":"manufacturerPartId","path":"/api/v1.0/administration/connectors/bpnDiscovery/search"}` | bpn discovery configuration | +| backend.discovery.edcDiscovery | object | `{"key":"bpn"}` | edc discovery configuration | +| backend.discovery.hostname | string | `""` | discovery finder configuration | +| backend.edc | object | `{"apis":{"catalog":"/catalog/request","management":"/management/v2","negotiation":"/contractnegotiations","transfer":"/transferprocesses"},"delay":100,"endpoint":"","participantId":"","xApiKey":""}` | in this section we configure the values that are inserted as secrets in the backend | +| backend.edc.endpoint | string | `""` | edc consumer connection configuration | | backend.edc.participantId | string | `""` | BPN Number | | backend.edc.xApiKey | string | `""` | the secret for assesing the edc management API | +| backend.hostname | string | `"localhost"` | backend hostname (without protocol prefix [DEFAULT HTTPS] for security ) | | backend.imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | -| backend.ingress | object | `{"enabled":false}` | ingress declaration to expose the dpp-backend service | +| backend.ingress | object | `{"enabled":false,"hosts":[{"host":"localhost","paths":[{"path":"/","pathType":"Prefix"}]}]}` | ingress declaration to expose the dpp-backend service | +| backend.irs | object | `{"enabled":false,"hostname":""}` | irs configuration | +| backend.logging.level.root | string | `"INFO"` | general logging level | +| backend.logging.level.utils | string | `"INFO"` | logging for the util components | +| backend.maxRetries | int | `5` | max retries for the backend services | +| backend.passport | object | `{"aspects":["urn:bamm:io.catenax.generic.digital_product_passport:1.0.0#DigitalProductPassport","urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass","urn:bamm:io.catenax.transmission.transmission_pass:1.0.0#TransmissionPass"]}` | passport data transfer configuration | +| backend.passport.aspects | list | `["urn:bamm:io.catenax.generic.digital_product_passport:1.0.0#DigitalProductPassport","urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass","urn:bamm:io.catenax.transmission.transmission_pass:1.0.0#TransmissionPass"]` | passport versions and aspects allowed | +| backend.process | object | `{"encryptionKey":""}` | digital twin registry configuration | +| backend.process.encryptionKey | string | `""` | unique sha512 hash key used for the passport encryption | +| backend.securityCheck | object | `{"bpn":false,"edc":false,"enabled":false}` | security configuration | +| backend.serverPort | int | `8888` | configuration of the spring boot server | | backend.service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service | +| frontend.api | object | `{"delay":1000,"max_retries":30,"timeout":90000}` | api timeouts | +| frontend.api.delay | int | `1000` | delay from getting status | +| frontend.api.max_retries | int | `30` | max retries for getting status | +| frontend.api.timeout | int | `90000` | default timeout - 90 seconds in milliseconds | +| frontend.backend | object | `{"hostname":""}` | url of the digital product pass backend service | | frontend.image.pullPolicy | string | `"Always"` | | | frontend.image.repository | string | `"docker.io/tractusx/digital-product-pass-frontend"` | | | frontend.imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | | frontend.ingress | object | `{"enabled":false,"hosts":[]}` | ingress declaration to expose the dpp-frontend service | +| frontend.irs | object | `{"maxWaitingTime":30,"requestDelay":30000}` | irs api timeouts | +| frontend.irs.maxWaitingTime | int | `30` | maximum waiting time to get the irs job status | +| frontend.irs.requestDelay | int | `30000` | request timeout delay | | frontend.name | string | `"dpp-frontend"` | | -| frontend.productpass | object | `{"api":{"delay":1000,"max_retries":30,"timeout":90000},"backend_url":"","idp_url":"","irs":{"maxWaitingTime":30,"requestDelay":30000},"keycloak":{"clientId":"","onLoad":"login-required","realm":""}}` | product passport UI configuration | -| frontend.productpass.api | object | `{"delay":1000,"max_retries":30,"timeout":90000}` | api timeouts | -| frontend.productpass.api.delay | int | `1000` | delay from getting status | -| frontend.productpass.api.max_retries | int | `30` | max retries for getting status | -| frontend.productpass.api.timeout | int | `90000` | default timeout - 90 seconds in milliseconds | -| frontend.productpass.backend_url | string | `""` | url of the digital product pass backend service | -| frontend.productpass.idp_url | string | `""` | url of the identity provider service | -| frontend.productpass.irs | object | `{"maxWaitingTime":30,"requestDelay":30000}` | irs api timeouts | -| frontend.productpass.irs.maxWaitingTime | int | `30` | maximum waiting time to get the irs job status | -| frontend.productpass.irs.requestDelay | int | `30000` | request timeout delay | -| frontend.productpass.keycloak | object | `{"clientId":"","onLoad":"login-required","realm":""}` | keycloak specific configuration for frontend authentication | +| frontend.portal.hostname | string | `""` | | | frontend.service.port | int | `8080` | | | frontend.service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service | +| frontend.supportContact.adminEmail | string | `"admin@example.com"` | | | name | string | `"digital-product-pass"` | | | namespace | string | `""` | | | nodeSelector | object | `{}` | | +| oauth | object | `{"appId":"","bpnCheck":{"bpn":"","enabled":false},"hostname":"","onLoad":"login-required","realm":"","roleCheck":{"enabled":false},"techUser":{"clientId":"","clientSecret":""}}` | oauth configuration | +| oauth.bpnCheck | object | `{"bpn":"","enabled":false}` | configure here the bpn check for the application | +| oauth.bpnCheck.bpn | string | `""` | this bpn needs to be included in the user login information when the check is enabled | +| oauth.hostname | string | `""` | url of the identity provider service | +| oauth.roleCheck | object | `{"enabled":false}` | the role check checks if the user has access roles for the appId | +| oauth.techUser | object | `{"clientId":"","clientSecret":""}` | note: this credentials need to have access to the Discovery Finder, BPN Discovery and EDC Discovery | | replicaCount | int | `1` | | | resources.limits.cpu | string | `"500m"` | | | resources.limits.memory | string | `"512Mi"` | | diff --git a/charts/digital-product-pass/templates/configmap-backend.yaml b/charts/digital-product-pass/templates/configmap-backend.yaml index 3dac8a6b2..f02201c93 100644 --- a/charts/digital-product-pass/templates/configmap-backend.yaml +++ b/charts/digital-product-pass/templates/configmap-backend.yaml @@ -54,7 +54,7 @@ data: userInfoUri: "https://{{ tpl (.Values.oauth.hostname | default "http://localhost") . }}/auth/realms/{{ .Values.oauth.realm }}/protocol/openid-connect/userinfo" # -- edc consumer connection configuration edc: - endpoint: "https://{{ .Values.backend.edc.endpoint }}" + endpoint: "https://{{ .Values.backend.edc.hostname }}" management: {{ .Values.backend.edc.apis.management }} catalog: {{ .Values.backend.edc.apis.catalog }} negotiation: {{ .Values.backend.edc.apis.negotiation }} @@ -63,10 +63,14 @@ data: delay: {{ .Values.backend.edc.delay }} # -- Negotiation status Delay in milliseconds in between async requests [<= 500] # -- security configuration security: - check: - enabled: {{ .Values.backend.securityCheck.enabled }} - bpn: {{ .Values.backend.securityCheck.bpn }} - edc: {{ .Values.backend.securityCheck.edc }} + # -- authorization configuration about bpn and role checks + authorization: + bpnAuth: {{ .Values.oauth.bpnCheck.enabled }} + roleAuth: {{ .Values.oauth.roleCheck.enabled }} + # -- checkups done in the startup + startUpChecks: + bpnCheck: {{ .Values.backend.securityCheck.bpn }} + edcCheck: {{ .Values.backend.securityCheck.edc }} # -- irs configuration irs: enabled: {{ .Values.backend.irs.enabled }} # -- Enable search for children in the requests diff --git a/charts/digital-product-pass/templates/deployment-backend.yaml b/charts/digital-product-pass/templates/deployment-backend.yaml index 8be65d12f..4ddacb504 100644 --- a/charts/digital-product-pass/templates/deployment-backend.yaml +++ b/charts/digital-product-pass/templates/deployment-backend.yaml @@ -54,17 +54,22 @@ spec: allowPrivilegeEscalation: false runAsUser: 10000 runAsGroup: 3000 - env: + env: + - name: "appId" + valueFrom: + secretKeyRef: + key: appId + name: avp-consumer-backend-auth - name: "client.id" valueFrom: secretKeyRef: key: clientId - name: avp-consumer-backend-cx-registry-auth + name: avp-consumer-backend-auth - name: "client.secret" valueFrom: secretKeyRef: key: clientSecret - name: avp-consumer-backend-cx-registry-auth + name: avp-consumer-backend-auth - name: "edc.apiKey" valueFrom: secretKeyRef: diff --git a/charts/digital-product-pass/templates/deployment-frontend.yaml b/charts/digital-product-pass/templates/deployment-frontend.yaml index 3e09753dd..4c318f77a 100644 --- a/charts/digital-product-pass/templates/deployment-frontend.yaml +++ b/charts/digital-product-pass/templates/deployment-frontend.yaml @@ -69,6 +69,21 @@ spec: - name: "KEYCLOAK_ONLOAD" value: "{{ .Values.oauth.onLoad }}" + - name: "AUTH_ROLE_CHECK" + value: "{{ .Values.oauth.roleCheck.enabled | default false }}" + + - name: "AUTH_BPN_CHECK" + value: "{{ .Values.oauth.bpnCheck.enabled | default false }}" + + - name: "AUTH_BPN_NUMBER" + value: "{{ .Values.oauth.bpnCheck.bpn }}" + + - name: "APP_PORTAL_URL" + value: "https://{{ .Values.frontend.portal.hostname }}" + + - name: "APP_ADMIN_EMAIL" + value: "{{ .Values.frontend.supportContact.adminEmail }}" + - name: "BACKEND_URL" value: "https://{{ .Values.frontend.backend.hostname }}" diff --git a/charts/digital-product-pass/templates/secret-backend.yaml b/charts/digital-product-pass/templates/secret-backend.yaml index 5d2159e61..25d9b2bc8 100644 --- a/charts/digital-product-pass/templates/secret-backend.yaml +++ b/charts/digital-product-pass/templates/secret-backend.yaml @@ -23,12 +23,13 @@ apiVersion: v1 kind: Secret metadata: - name: avp-consumer-backend-cx-registry-auth + name: avp-consumer-backend-auth labels: {{- include "chart.labels" . | nindent 4 }} namespace: {{ .Values.namespace }} type: Opaque stringData: + appId: {{ .Values.oauth.appId }} clientId: {{ .Values.oauth.techUser.clientId }} clientSecret: {{ .Values.oauth.techUser.clientSecret }} --- diff --git a/charts/digital-product-pass/values-beta.yaml b/charts/digital-product-pass/values-beta.yaml index 4172b088c..7fd787c92 100644 --- a/charts/digital-product-pass/values-beta.yaml +++ b/charts/digital-product-pass/values-beta.yaml @@ -42,12 +42,11 @@ backend: edc: xApiKey: - participantId: - endpoint: "materialpass.beta.demo.catena-x.net/consumer" + participantId: &bpn + hostname: "materialpass.beta.demo.catena-x.net/consumer" hostname: *hostname securityCheck: - enabled: true bpn: true edc: true @@ -83,17 +82,25 @@ frontend: hosts: - materialpass.beta.demo.catena-x.net - edc: - xApiKey: - # Product Passport UI Configuration backend: hostname: "materialpass.beta.demo.catena-x.net" + + supportContact: + adminEmail: "admin@example.com" + portal: + hostname: "portal.beta.demo.catena-x.net" + oauth: - hostname: "centralidp.int.demo.catena-x.net" + hostname: "centralidp.data.demo.catena-x.net" techUser: - clientId: - clientSecret: - realm: - appId: \ No newline at end of file + clientId: + clientSecret: + realm: + appId: + bpnCheck: + enabled: true + bpn: *bpn + roleCheck: + enabled: false \ No newline at end of file diff --git a/charts/digital-product-pass/values-dev.yaml b/charts/digital-product-pass/values-dev.yaml index 04210dbd1..09a58dc17 100644 --- a/charts/digital-product-pass/values-dev.yaml +++ b/charts/digital-product-pass/values-dev.yaml @@ -42,13 +42,12 @@ backend: edc: xApiKey: - participantId: - endpoint: "materialpass.dev.demo.catena-x.net/consumer" + participantId: &bpn + hostname: "materialpass.dev.demo.catena-x.net/consumer" hostname: *hostname securityCheck: - enabled: true bpn: true edc: true @@ -84,17 +83,26 @@ frontend: hosts: - materialpass.dev.demo.catena-x.net - edc: - xApiKey: - # Product Passport UI Configuration backend: hostname: *hostname + supportContact: + adminEmail: "admin@example.com" + + portal: + hostname: "portal.dev.demo.catena-x.net" + oauth: hostname: "centralidp.dev.demo.catena-x.net" techUser: - clientId: - clientSecret: - realm: - appId: \ No newline at end of file + clientId: + clientSecret: + realm: + appId: + bpnCheck: + enabled: true + bpn: *bpn + roleCheck: + enabled: false + diff --git a/charts/digital-product-pass/values-int.yaml b/charts/digital-product-pass/values-int.yaml index e82db3bb6..4792ea105 100644 --- a/charts/digital-product-pass/values-int.yaml +++ b/charts/digital-product-pass/values-int.yaml @@ -31,7 +31,7 @@ backend: nginx.ingress.kubernetes.io/ssl-passthrough: "false" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" hosts: - - host: &hostname "materialpass.int.demo.catena-x.net" + - host: &hostname "dpp.int.demo.catena-x.net" paths: - path: / pathType: Prefix @@ -41,12 +41,11 @@ backend: - *hostname edc: xApiKey: - participantId: - endpoint: "materialpass.int.demo.catena-x.net/consumer" + participantId: &bpn + hostname: "materialpass.int.demo.catena-x.net/consumer" hostname: *hostname securityCheck: - enabled: true bpn: true edc: true @@ -73,21 +72,25 @@ frontend: nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/service-upstream: "true" hosts: - - host: materialpass.int.demo.catena-x.net + - host: dpp.int.demo.catena-x.net paths: - path: /passport(/|$)(.*) pathType: Prefix tls: - secretName: tls-secret hosts: - - materialpass.int.demo.catena-x.net - - edc: - xApiKey: + - dpp.int.demo.catena-x.net backend: hostname: *hostname + supportContact: + adminEmail: "admin@example.com" + + portal: + hostname: "portal.int.demo.catena-x.net" + + oauth: hostname: "centralidp.int.demo.catena-x.net" techUser: @@ -95,3 +98,8 @@ oauth: clientSecret: realm: appId: + bpnCheck: + enabled: true + bpn: *bpn + roleCheck: + enabled: false diff --git a/charts/digital-product-pass/values.yaml b/charts/digital-product-pass/values.yaml index 5e2e7d9ee..d7ea17dd8 100644 --- a/charts/digital-product-pass/values.yaml +++ b/charts/digital-product-pass/values.yaml @@ -28,59 +28,6 @@ name: "digital-product-pass" replicaCount: 1 namespace: "" -frontend: - name: "dpp-frontend" - image: - repository: docker.io/tractusx/digital-product-pass-frontend - pullPolicy: Always - # -- Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) - imagePullSecrets: [] - - service: - # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service - type: ClusterIP - port: 8080 - - # -- ingress declaration to expose the dpp-frontend service - ingress: - enabled: false - hosts: [] - - # -- product passport UI configuration - - # -- url of the digital product pass backend service - backend: - hostname: "" - - # -- api timeouts - api: - # -- max retries for getting status - max_retries: 30 - # -- default timeout - 90 seconds in milliseconds - timeout: 90000 - # -- delay from getting status - delay: 1000 - - # -- irs api timeouts - irs: - # -- request timeout delay - requestDelay: 30000 - # -- maximum waiting time to get the irs job status - maxWaitingTime: 30 - -# -- oauth configuration -oauth: - # -- url of the identity provider service - hostname: "" - # -- technical user keycloak central id credentials - # -- note: this credentials need to have access to the Discovery Finder, BPN Discovery and EDC Discovery - techUser: - clientId: "" - clientSecret: "" - realm: "" - appId: "" - onLoad: "login-required" - # -- Backend configuration backend: name: "dpp-backend" @@ -110,9 +57,9 @@ backend: # -- the secret for assesing the edc management API xApiKey: "" # -- BPN Number - participantId: "" + participantId: &bpn "" # -- edc consumer connection configuration - endpoint: "" + hostname: "" apis: management: '/management/v2' catalog: '/catalog/request' @@ -138,7 +85,6 @@ backend: # -- security configuration securityCheck: - enabled: false bpn: false edc: false @@ -187,6 +133,73 @@ backend: edcDiscovery: key: "bpn" +frontend: + name: "dpp-frontend" + image: + repository: docker.io/tractusx/digital-product-pass-frontend + pullPolicy: Always + # -- Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) + imagePullSecrets: [] + + service: + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service + type: ClusterIP + port: 8080 + + # -- ingress declaration to expose the dpp-frontend service + ingress: + enabled: false + hosts: [] + + # -- product passport UI configuration + + # -- url of the digital product pass backend service + backend: + hostname: "" + + supportContact: + adminEmail: "admin@example.com" + + portal: + hostname: "" + + # -- api timeouts + api: + # -- max retries for getting status + max_retries: 30 + # -- default timeout - 90 seconds in milliseconds + timeout: 90000 + # -- delay from getting status + delay: 1000 + + # -- irs api timeouts + irs: + # -- request timeout delay + requestDelay: 30000 + # -- maximum waiting time to get the irs job status + maxWaitingTime: 30 + +# -- oauth configuration +oauth: + # -- url of the identity provider service + hostname: "" + # -- technical user keycloak central id credentials + # -- note: this credentials need to have access to the Discovery Finder, BPN Discovery and EDC Discovery + techUser: + clientId: "" + clientSecret: "" + realm: "" + appId: "" + onLoad: "login-required" + # -- configure here the bpn check for the application + bpnCheck: + enabled: false + # -- this bpn needs to be included in the user login information when the check is enabled + bpn: *bpn + # -- the role check checks if the user has access roles for the appId + roleCheck: + enabled: false + # Following Catena-X Helm Best Practices @url: https://catenax-ng.github.io/docs/kubernetes-basics/helm # @url: https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits resources: @@ -204,4 +217,4 @@ nodeSelector: {} tolerations: [] # [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on -affinity: {} \ No newline at end of file +affinity: {} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/SecurityConfig.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/SecurityConfig.java new file mode 100644 index 000000000..a23b38baf --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/SecurityConfig.java @@ -0,0 +1,136 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * This class consists exclusively to define the attributes and methods needed for the Vault service configuration. + **/ +@Configuration +@ConfigurationProperties(prefix="configuration.security") +public class SecurityConfig { + + /** ATTRIBUTES **/ + private AuthorizationConfig authorization; + + private StartUpCheckConfig startUpChecks; + + public SecurityConfig(AuthorizationConfig authorization, StartUpCheckConfig startUpChecks) { + this.authorization = authorization; + this.startUpChecks = startUpChecks; + } + + public SecurityConfig() { + } + + /** GETTERS AND SETTERS **/ + public AuthorizationConfig getAuthorization() { + return authorization; + } + + public void setAuthorization(AuthorizationConfig authorization) { + this.authorization = authorization; + } + + public StartUpCheckConfig getStartUpChecks() { + return startUpChecks; + } + + public void setStartUpChecks(StartUpCheckConfig startUpChecks) { + this.startUpChecks = startUpChecks; + } + /** INNER CLASSES **/ + + /** + * This class defined the authorization configuration + **/ + public static class AuthorizationConfig{ + + private Boolean bpnAuth; + + private Boolean roleAuth; + + public AuthorizationConfig(Boolean bpnAuth, Boolean roleAuth) { + this.bpnAuth = bpnAuth; + this.roleAuth = roleAuth; + } + + public AuthorizationConfig() { + } + + public Boolean getBpnAuth() { + return bpnAuth; + } + + public void setBpnAuth(Boolean bpnAuth) { + this.bpnAuth = bpnAuth; + } + + public Boolean getRoleAuth() { + return roleAuth; + } + + public void setRoleAuth(Boolean roleAuth) { + this.roleAuth = roleAuth; + } + } + /** + * This class defined the StartUpChecks configuration + **/ + public static class StartUpCheckConfig{ + + private Boolean bpnCheck; + + private Boolean edcCheck; + + public StartUpCheckConfig(Boolean bpnCheck, Boolean edcCheck) { + this.bpnCheck = bpnCheck; + this.edcCheck = edcCheck; + } + + public StartUpCheckConfig() { + } + + public Boolean getBpnCheck() { + return bpnCheck; + } + + public void setBpnCheck(Boolean bpnCheck) { + this.bpnCheck = bpnCheck; + } + + public Boolean getEdcCheck() { + return edcCheck; + } + + public void setEdcCheck(Boolean edcCheck) { + this.edcCheck = edcCheck; + } + } + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/listeners/AppListener.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/listeners/AppListener.java index 0f5d9e534..146e30f27 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/listeners/AppListener.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/listeners/AppListener.java @@ -24,6 +24,7 @@ package org.eclipse.tractusx.productpass.listeners; import org.eclipse.tractusx.productpass.config.DtrConfig; +import org.eclipse.tractusx.productpass.config.SecurityConfig; import org.eclipse.tractusx.productpass.models.auth.JwtToken; import org.eclipse.tractusx.productpass.models.catenax.Discovery; import org.eclipse.tractusx.productpass.models.edc.Jwt; @@ -45,12 +46,13 @@ import utils.HttpUtil; import utils.LogUtil; - /** - * This class consists exclusively of methods to operate on the Application's Event listeners. - * - *

The methods defined here are the event listeners needed to run the application. + * This class consists exclusively of methods to operate on the Application's + * Event listeners. * + *

+ * The methods defined here are the event listeners needed to run the + * application. */ @Component @Configuration @@ -58,13 +60,15 @@ @ConfigurationProperties public class AppListener { - /** ATTRIBUTES **/ + /** + * ATTRIBUTES + **/ @Autowired BuildProperties buildProperties; @Autowired CatenaXService catenaXService; @Autowired - DtrConfig dtrConfig; + SecurityConfig securityConfig; @Autowired AuthenticationService authService; @Autowired @@ -76,62 +80,105 @@ public class AppListener { @Autowired DataTransferService dataTransferService; - /** METHODS **/ + /** + * METHODS + **/ @EventListener(ApplicationStartedEvent.class) public void started() { - Boolean preChecks = env.getProperty("configuration.security.check.enabled", Boolean.class, true); - if (!preChecks) { - return; - } - - Boolean bpnCheck = env.getProperty("configuration.security.check.bpn", Boolean.class, true); - Boolean edcCheck = env.getProperty("configuration.security.check.edc", Boolean.class, true); - if (!bpnCheck && !edcCheck) { - return; - } try { - LogUtil.printMessage("========= [ EXECUTING PRE-CHECKS ] ================================"); - String participantId = (String) vaultService.getLocalSecret("edc.participantId"); - if (participantId.isEmpty()) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] ParticipantId configuration does not exists in Vault File!"); - } - if (edcCheck) { + SecurityConfig.StartUpCheckConfig startUpConfig = securityConfig.getStartUpChecks(); + Boolean bpnCheck = startUpConfig.getBpnCheck(); + Boolean edcCheck = startUpConfig.getEdcCheck(); + + if (bpnCheck || edcCheck) { try { - LogUtil.printMessage("[ EDC Connection Test ] Testing connection with the EDC Consumer, this may take some seconds..."); - String bpnNumber = dataTransferService.checkEdcConsumerConnection(); - if (!participantId.equals(bpnNumber)) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] Incorrect BPN Number configuration, expected the same participant id as the EDC consumer connector!"); + LogUtil.printMessage("========= [ EXECUTING PRE-CHECKS ] ================================"); + String participantId = (String) vaultService.getLocalSecret("edc.participantId"); + if (participantId.isEmpty()) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] ParticipantId configuration does not exists in Vault File!"); } - LogUtil.printMessage("[ EDC Connection Test ] The EDC consumer is available for receiving connections!"); + if (edcCheck) { + try { + LogUtil.printMessage( + "[ EDC Connection Test ] Testing connection with the EDC Consumer, this may take some seconds..."); + String bpnNumber = dataTransferService.checkEdcConsumerConnection(); + if(bpnNumber == null){ + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] The EDC Consumer configured is not reachable!"); + } + if (bpnCheck && !participantId.equals(bpnNumber)) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] Incorrect BPN Number configuration, expected the same participant id as the EDC consumer connector!"); + } + LogUtil.printMessage( + "[ EDC Connection Test ] The EDC consumer is available for receiving connections!"); + } catch (Exception e) { + throw new IncompatibleConfigurationException(e.getMessage()); + } + } + if (bpnCheck) { + try { + LogUtil.printMessage("[ BPN Number Check ] Checking the token from the technical user..."); + JwtToken token = authService.getToken(); + if (token == null) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] Not possible to get technical user credentials!"); + } + Jwt jwtToken = httpUtil.parseToken(token.getAccessToken()); + if (jwtToken == null) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] The technical user JwtToken is empty!"); + } + if (!jwtToken.getPayload().containsKey("bpn")) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] The technical user JwtToken does not specify any BPN number!"); + } + String techUserBpn = (String) jwtToken.getPayload().get("bpn"); + if (!techUserBpn.equals(participantId)) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] The technical user does not has the same BPN number as the EDC Consumer and the Backend! Access not allowed!"); + } + LogUtil.printMessage( + "[ BPN Number Check ] Technical User BPN matches the EDC Consumer and the Backend participantId!"); + } catch (Exception e) { + throw new IncompatibleConfigurationException(e.getMessage()); + } + } + LogUtil.printMessage("========= [ PRE-CHECKS COMPLETED ] ================================"); } catch (Exception e) { throw new IncompatibleConfigurationException(e.getMessage()); } } - if (!bpnCheck) { + SecurityConfig.AuthorizationConfig authorizationConfig = securityConfig.getAuthorization(); + Boolean bpnAuth = authorizationConfig.getBpnAuth(); + Boolean roleAuth = authorizationConfig.getRoleAuth(); + + if (!bpnAuth && !roleAuth) { return; } - try { - LogUtil.printMessage("[ BPN Number Check ] Checking the token from the technical user..."); - JwtToken token = authService.getToken(); - if (token == null) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] Not possible to get technical user credentials!"); - } - Jwt jwtToken = httpUtil.parseToken(token.getAccessToken()); - if (jwtToken == null) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] The technical user JwtToken is empty!"); - } - if (!jwtToken.getPayload().containsKey("bpn")) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] The technical user JwtToken does not specify any BPN number!"); + + LogUtil.printMessage("========= [ EXECUTING AUTHORIZATION PRE-CHECKS ] ================================"); + if (bpnAuth) { + String participantId = (String) vaultService.getLocalSecret("edc.participantId"); + if (participantId.isEmpty()) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] ParticipantId configuration does not exists in Vault File!"); } - String techUserBpn = (String) jwtToken.getPayload().get("bpn"); - if (!techUserBpn.equals(participantId)) { - throw new Exception("[" + this.getClass().getName() + ".onStartUp] The technical user does not has the same BPN number as the EDC Consumer and the Backend! Access not allowed!"); + LogUtil.printMessage("[ BPN AUTHORIZATION CHECK ] The following bpn [ " + participantId + + " ] is required in authenticated tokens"); + } + if (roleAuth) { + String appId = (String) vaultService.getLocalSecret("appId"); + if (appId.isEmpty()) { + throw new Exception("[" + this.getClass().getName() + + ".onStartUp] The appId configuration does not exists in Vault File!"); } - LogUtil.printMessage("[ BPN Number Check ] Technical User BPN matches the EDC Consumer and the Backend participantId!"); - } catch (Exception e) { - throw new IncompatibleConfigurationException(e.getMessage()); + LogUtil.printMessage( + "[ ROLE AUTHORIZATION CHECK ] The authenticated tokens in requests shall contain roles within this appId [ " + + appId + " ]"); } - LogUtil.printMessage("========= [ PRE-CHECKS COMPLETED ] ================================"); + LogUtil.printMessage("========= [ AUTHORIZATION PRE-CHECKS COMPLETED ] ================================"); } catch (Exception e) { throw new IncompatibleConfigurationException(e.getMessage()); } @@ -141,38 +188,48 @@ public void started() { @EventListener(ApplicationReadyEvent.class) public void onStartUp() { String ascii = "\n" + - " ____ _ _ __ __ ____ __ __ ____ \n" + - " / __ \\(_)___ _(_) /_____ _/ / / __ \\_________ ____/ /_ _______/ /_ / __ \\____ ___________ \n" + - " / / / / / __ `/ / __/ __ `/ / / /_/ / ___/ __ \\/ __ / / / / ___/ __/ / /_/ / __ `/ ___/ ___/ \n" + - " / /_/ / / /_/ / / /_/ /_/ / / / ____/ / / /_/ / /_/ / /_/ / /__/ /_ / ____/ /_/ (__ |__ ) \n" + - "/_____/_/\\__, /_/\\__/\\__,_/_/ /_/ /_/ \\____/\\__,_/\\__,_/\\___/\\__/ /_/ \\__,_/____/____/ \n" + - " /____/ \n" + - " \\\\/ersion: v" + buildProperties.getVersion() + "\n\n"; + " ____ _ _ __ __ ____ __ __ ____ \n" + + + " / __ \\(_)___ _(_) /_____ _/ / / __ \\_________ ____/ /_ _______/ /_ / __ \\____ ___________ \n" + + + " / / / / / __ `/ / __/ __ `/ / / /_/ / ___/ __ \\/ __ / / / / ___/ __/ / /_/ / __ `/ ___/ ___/ \n" + + + " / /_/ / / /_/ / / /_/ /_/ / / / ____/ / / /_/ / /_/ / /_/ / /__/ /_ / ____/ /_/ (__ |__ ) \n" + + + "/_____/_/\\__, /_/\\__/\\__,_/_/ /_/ /_/ \\____/\\__,_/\\__,_/\\___/\\__/ /_/ \\__,_/____/____/ \n" + + + " /____/ \n" + + + " \\\\/ersion: v" + + buildProperties.getVersion() + "\n\n"; System.out.print(ascii); String serverStartUpMessage = "\n\n" + "**********************************************************************\n\n" + - " "+buildProperties.getName() + "\n" + + " " + buildProperties.getName() + "\n" + " Copyright (c) 2022, 2023: BASF SE, BMW AG, Henkel AG & Co. KGaA\n" + " Copyright (c) 2022, 2023: Contributors to the Eclipse Foundation.\n\n" + "**********************************************************************\n\n"; System.out.print(serverStartUpMessage); - System.out.print("\n========= [ APPLICATION STARTED ] ====================================\n"+ + System.out.print("\n========= [ APPLICATION STARTED ] ====================================\n" + "Listening to requests...\n\n"); - Discovery discovery = catenaXService.start(); // Start the CatenaX service (we need the bpnDiscovery and edcDiscovery addresses) + Discovery discovery = catenaXService.start(); // Start the CatenaX service (we need the bpnDiscovery and + // edcDiscovery addresses) if (discovery == null) { - LogUtil.printError("\n*************************************[CRITICAL ERROR]*************************************" + - "\nIt was not possible to start the application correctly..." + - "\nPlease configure the Discovery Service Endpoint property:" + - "\n\t- [application.configuration.discovery.endpoint]" + - "\nMake sure that the Technical User Credentials are correctly configured:" + - "\n\t- [avp.helm.clientId]" + - "\n\t- [avp.helm.clientSecret]" + - "\nThis user should be able to retrieve the token from the following Keycloak Endpoint:" + - "\n\t- [application.configuration.keycloak.tokenUri]" + - "\n*****************************************************************************************\n" - ); + LogUtil.printError( + "\n*************************************[CRITICAL ERROR]*************************************" + + "\nIt was not possible to start the application correctly..." + + "\nPlease configure the Discovery Service Endpoint property:" + + "\n\t- [backend.discovery.hostname]" + + "\nMake sure that the Keycloak App Id is available" + + "\n\t- [oauth.appId]" + + "\nMake sure that the Technical User Credentials are correctly configured:" + + "\n\t- [oauth.techUser.clientId]" + + "\n\t- [oauth.techUser.clientSecret]" + + "\nThis user should be able to retrieve the token from the following Keycloak Endpoint:" + + "\n\t- [backend.configuration.tokenUrl]" + + "\n\t- [backend.configuration.userInfoUrl]" + + "\n*****************************************************************************************\n"); } - } - // Store the process manager in memory } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/auth/UserInfo.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/auth/UserInfo.java index 7879c6d8a..9b357da9b 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/auth/UserInfo.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/auth/UserInfo.java @@ -23,12 +23,14 @@ package org.eclipse.tractusx.productpass.models.auth; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** * This class consists exclusively to define attributes and methods related to the needed User's information. **/ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class UserInfo { @@ -37,6 +39,8 @@ public class UserInfo { String sub; @JsonProperty("email_verified") String email_verified; + @JsonProperty("bpn") + String bpn; @JsonProperty("name") String name; @JsonProperty("preferred_username") @@ -51,10 +55,15 @@ public class UserInfo { String email; /** CONSTRUCTOR(S) **/ + @SuppressWarnings("Unused") - public UserInfo(String sub, String email_verified, String name, String preferred_username, String locale, String given_name, String family_name, String email) { + public UserInfo() { + } + + public UserInfo(String sub, String email_verified, String bpn, String name, String preferred_username, String locale, String given_name, String family_name, String email) { this.sub = sub; this.email_verified = email_verified; + this.bpn = bpn; this.name = name; this.preferred_username = preferred_username; this.locale = locale; @@ -62,8 +71,16 @@ public UserInfo(String sub, String email_verified, String name, String preferred this.family_name = family_name; this.email = email; } - @SuppressWarnings("Unused") - public UserInfo() { + + public UserInfo(String sub, String email_verified, String name, String preferred_username, String locale, String given_name, String family_name, String email) { + this.sub = sub; + this.email_verified = email_verified; + this.name = name; + this.preferred_username = preferred_username; + this.locale = locale; + this.given_name = given_name; + this.family_name = family_name; + this.email = email; } /** GETTERS AND SETTERS **/ @@ -129,4 +146,12 @@ public String getEmail() { public void setEmail(String email) { this.email = email; } + + public String getBpn() { + return bpn; + } + + public void setBpn(String bpn) { + this.bpn = bpn; + } } diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AuthenticationService.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AuthenticationService.java index a603cc08f..b7fe666bb 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AuthenticationService.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AuthenticationService.java @@ -25,10 +25,12 @@ import com.fasterxml.jackson.databind.JsonNode; import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.tractusx.productpass.config.SecurityConfig; import org.eclipse.tractusx.productpass.exceptions.ServiceException; import org.eclipse.tractusx.productpass.exceptions.ServiceInitializationException; import org.eclipse.tractusx.productpass.models.auth.JwtToken; import org.eclipse.tractusx.productpass.models.auth.UserInfo; +import org.eclipse.tractusx.productpass.models.edc.Jwt; import org.eclipse.tractusx.productpass.models.service.BaseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -37,6 +39,7 @@ import org.springframework.stereotype.Service; import utils.HttpUtil; import utils.JsonUtil; +import utils.LogUtil; import java.util.ArrayList; import java.util.List; @@ -50,17 +53,21 @@ public class AuthenticationService extends BaseService { private final Environment env; private final HttpUtil httpUtil; private final JsonUtil jsonUtil; + + private final SecurityConfig securityConfig; + public String tokenUri; public String clientId; public String clientSecret; /** CONSTRUCTOR(S) **/ @Autowired - public AuthenticationService(VaultService vaultService, Environment env, HttpUtil httpUtil, JsonUtil jsonUtil) throws ServiceInitializationException { + public AuthenticationService(VaultService vaultService, Environment env, HttpUtil httpUtil, JsonUtil jsonUtil, SecurityConfig securityConfig) throws ServiceInitializationException { this.vaultService = vaultService; this.env = env; this.httpUtil = httpUtil; this.jsonUtil = jsonUtil; + this.securityConfig = securityConfig; this.init(env); this.checkEmptyVariables(List.of("clientId", "clientSecret")); } @@ -131,6 +138,63 @@ public Boolean isAuthenticated(String jwtToken){ return userInfo != null; } + public Boolean isUserInfoAvailable(String token){ + UserInfo userInfo = null; + try { + userInfo = this.getUserInfo(token); + }catch (Exception e){ + return false; + } + return userInfo != null; + } + + public Boolean tokenContainsSameBpn(Jwt jwtToken){ + try { + if (!jwtToken.getPayload().containsKey("bpn")) { + return false; + } + + return jwtToken.getPayload().get("bpn").equals(vaultService.getLocalSecret("edc.participantId")); + }catch (Exception e){ + return false; + } + } + /** + * Checks if the user is authenticated. + *

+ * @param jwtToken + * the {@code Jwt} token from the request + * + * @return true if the token contains any role in the client id, false otherwise. + * + */ + public Boolean tokenContainsAnyRole(Jwt jwtToken){ + try { + if (!jwtToken.getPayload().containsKey("resource_access")) { + return false; + } + String appId = (String) vaultService.getLocalSecret("appId"); + if(appId == null || appId.equals("")){ + return false; + } + Map resourceAccess = (Map) jsonUtil.toMap(jwtToken.getPayload().get("resource_access")); + + if (!resourceAccess.containsKey(appId)) { + return false; + } + Map appIdResource = (Map) jsonUtil.toMap(resourceAccess.get(appId)); + + if (!appIdResource.containsKey("roles")) { + return false; + } + + List roles = (List) appIdResource.get("roles"); + return roles.size() > 0; + }catch (Exception e){ + return false; + } + } + /** * Checks if the user is authenticated. *

@@ -145,25 +209,39 @@ public Boolean isAuthenticated(HttpServletRequest httpRequest){ if(token == null){ return false; } - /* - Jwt jwtToken = httpUtil.parseToken(token); - // If the end user has no bpn available block - if(jwtToken.getPayload().containsKey("bpn")){ - return false; + + SecurityConfig.AuthorizationConfig authorizationConfig = this.securityConfig.getAuthorization(); + // In this block starts the Authorization configuration for all the Secure APIs + + Boolean bpnAuth = authorizationConfig.getBpnAuth(); + Boolean roleAuth = authorizationConfig.getRoleAuth(); + + if(!bpnAuth && !roleAuth){ + return this.isUserInfoAvailable(token); } + // Check the authorization based on the jwt token received + Jwt jwtToken = httpUtil.parseToken(token); - if(jwtToken.getPayload().get("bpn") != vaultService.getLocalSecret("edc.participantId")){ - return false; + Boolean containsSameBpn = this.tokenContainsSameBpn(jwtToken); + Boolean containsAnyRole = this.tokenContainsAnyRole(jwtToken); + + boolean authorized = false; + + // Cross the authentication following the configuration rules + if((bpnAuth && roleAuth) && (containsSameBpn && containsAnyRole)){ + authorized = true; + } else if ((!bpnAuth && roleAuth) && containsAnyRole) { + authorized = true; + } else if ((bpnAuth && !roleAuth) && containsSameBpn) { + authorized = true; } - */ - UserInfo userInfo = null; - try { - userInfo = this.getUserInfo(token); - }catch (Exception e){ - return false; + // If authorized check if the user info is available + if(authorized){ + return this.isUserInfoAvailable(token); } - return userInfo != null; + + return false; } /** diff --git a/consumer-backend/productpass/src/main/java/utils/JsonUtil.java b/consumer-backend/productpass/src/main/java/utils/JsonUtil.java index cfbc17419..f4c442660 100644 --- a/consumer-backend/productpass/src/main/java/utils/JsonUtil.java +++ b/consumer-backend/productpass/src/main/java/utils/JsonUtil.java @@ -702,7 +702,6 @@ public String translatePathSep(String path, String pathSep, String newPathSep){ } } - /** * Parses the JSON object to a Map type object. *

@@ -722,7 +721,6 @@ public Map toMap(Object obj){ throw new UtilException(JsonUtil.class, "It was not possible to parse json -> [" + e.getMessage() + "]"); } } - /** * Binds the JsonNode object to the given class type object. *

diff --git a/consumer-backend/productpass/src/main/resources/application.yml b/consumer-backend/productpass/src/main/resources/application.yml index a0a524bbf..7294a64c6 100644 --- a/consumer-backend/productpass/src/main/resources/application.yml +++ b/consumer-backend/productpass/src/main/resources/application.yml @@ -55,10 +55,12 @@ configuration: delay: 100 # -- Negotiation status Delay in milliseconds in between async requests [<= 500] security: - check: - enabled: false - bpn: true - edc: true + authorization: + bpnAuth: true + roleAuth: true + startUpChecks: + bpnCheck: true + edcCheck: true irs: enabled: true @@ -124,6 +126,7 @@ configuration: indent: 2 defaultValue: '' attributes: + - "appId" - "client.id" - "client.secret" - "edc.apiKey" diff --git a/entrypoint.sh b/entrypoint.sh index a665e93ce..4f157bb3e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -39,7 +39,11 @@ do sed -i 's|KEYCLOAK_ONLOAD|'${KEYCLOAK_ONLOAD}'|g' $file sed -i 's|REPO_COMMIT_ID|'${REPO_COMMIT_ID}'|g' $file sed -i 's|REPO_ENDPOINT_URL|'${REPO_ENDPOINT_URL}'|g' $file - + sed -i 's|AUTH_ROLE_CHECK|'${AUTH_BPN_CHECK}'|g' $file + sed -i 's|AUTH_BPN_CHECK|'${AUTH_BPN_CHECK}'|g' $file + sed -i 's|AUTH_BPN_NUMBER|'${AUTH_BPN_NUMBER}'|g' $file + sed -i 's|APP_PORTAL_URL|'${APP_PORTAL_URL}'|g' $file + sed -i 's|APP_ADMIN_EMAIL|'${APP_ADMIN_EMAIL}'|g' $file done exec "$@" diff --git a/src/App.vue b/src/App.vue index df3e810e1..9603bb281 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,14 +23,28 @@ @@ -49,7 +63,7 @@ export default { display: flex; flex-direction: column; min-height: 100vh; - background-image: url(https://portal.dev.demo.catena-x.net/assets/images/frame/Home.png); + background-image: url('@/media/Home.png'); background-repeat: no-repeat; background-size: cover; background-position: center; diff --git a/src/components/general/ErrorComponent.vue b/src/components/general/ErrorComponent.vue index 1810612aa..f8f51950f 100644 --- a/src/components/general/ErrorComponent.vue +++ b/src/components/general/ErrorComponent.vue @@ -57,7 +57,7 @@ + diff --git a/src/components/general/NotAvailableComponent.vue b/src/components/general/NotAvailableComponent.vue new file mode 100644 index 000000000..eedde9339 --- /dev/null +++ b/src/components/general/NotAvailableComponent.vue @@ -0,0 +1,87 @@ + + + + + + + diff --git a/src/main.js b/src/main.js index a7dbfde51..fa657e6ec 100644 --- a/src/main.js +++ b/src/main.js @@ -36,6 +36,10 @@ app.use(vuetify); app.use(store); app.use(router); -let auth = new authentication(); +var auth = new authentication(); app.provide('authentication', auth); +app.config.globalProperties.$authProperties = { + loginReachable: false, + isAuthorized: false +}; auth.keycloakInit(app); diff --git a/src/media/Home.png b/src/media/Home.png new file mode 100644 index 000000000..66af6a806 Binary files /dev/null and b/src/media/Home.png differ diff --git a/src/services/Authentication.js b/src/services/Authentication.js index b0a1334ca..081a32cea 100644 --- a/src/services/Authentication.js +++ b/src/services/Authentication.js @@ -20,81 +20,152 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { REDIRECT_URI, INIT_OPTIONS } from "@/services/service.const"; +import { REDIRECT_URI, INIT_OPTIONS, BPN_CHECK, BPN, ROLE_CHECK } from "@/services/service.const"; import Keycloak from 'keycloak-js'; - +import authUtil from "@/utils/authUtil"; +import jsonUtil from "@/utils/jsonUtil"; export default class Authentication { - constructor() { - this.keycloak = new Keycloak(INIT_OPTIONS); + constructor() { + this.keycloak = new Keycloak(INIT_OPTIONS); + } + isAuthorized(parsedToken) { + if (!BPN_CHECK && !ROLE_CHECK) { + return true; } - keycloakInit(app) { - this.keycloak.init({ onLoad: INIT_OPTIONS.onLoad }).then((auth) => { - if (!auth) { - window.location.reload(); - } - else { - app.mount('#app'); - } - //Token Refresh - setInterval(() => { - this.updateToken(60); - }, 60000); - }).catch((e) => { - console.log(e); - console.error("keycloakInit -> Login Failure"); - }); - } - getAccessToken() { - return this.keycloak.token; + if (!parsedToken) { + return false; } - getRefreshedToken() { - return this.keycloak.refreshToken; + // Get conditions for authorization + let bpnAuthorized = authUtil.checkBpn(parsedToken, BPN); + let roleAuthorized = this.hasRoles(); + + // Authorize according to configuration + if ((BPN_CHECK && ROLE_CHECK) && (bpnAuthorized && roleAuthorized)) { // In case both a valid everything needs to be true + return true; + } else if ((!BPN_CHECK && ROLE_CHECK) && roleAuthorized) { // In case just the role is valid just the role needs to be true + return true; + } else if ((BPN_CHECK && !ROLE_CHECK) && bpnAuthorized) { // In case the + return true; } - updateToken(minimumValidity) { - this.keycloak.updateToken(minimumValidity).then((refreshed) => { - if (refreshed) { - console.info('Token refreshed' + refreshed); - } else { - console.warn('Token not refreshed, valid for ' - + Math.round(this.keycloak.tokenParsed.exp + this.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); + return false; + + } + keycloakInit(app) { + var authProperties = app.config.globalProperties.$authProperties; + this.keycloak.init({ onLoad: INIT_OPTIONS.onLoad }).then((auth) => { + if (!auth) { + window.location.reload(); + } + else { + // Check if the refresh token is valid and authenticated + if(this.keycloak.tokenParsed){ + authProperties.loginReachable = true; + } + authProperties.isAuthorized = this.isAuthorized(this.keycloak.tokenParsed); + } + + app.config.globalProperties.$authProperties = authProperties; + app.mount('#app'); + //Token Refresh + setInterval(() => { + this.updateToken(60, app); + }, 60000); + }).catch((e) => { + console.log(e); + authProperties.loginReachable = false; + authProperties.isAuthorized = false; + app.config.globalProperties.$authProperties = authProperties; + app.mount('#app'); + }); + } + getAccessToken() { + return this.keycloak.token; + } + + getRefreshedToken() { + return this.keycloak.refreshToken; + } + + updateToken(minimumValidity, app) { + this.keycloak.updateToken(minimumValidity).then((refreshed) => { + if (refreshed) { + // Check if the refresh token is valid and authenticated + if(this.keycloak.parsedToken){ + app.config.globalProperties.$authProperties.loginReachable = true; + }else{ + app.config.globalProperties.$authProperties.loginReachable = false; } - }).catch(() => { - console.error("updateToken -> Failed to refresh token"); - }); - } + app.config.globalProperties.$authProperties.isAuthorized = this.isAuthorized(this.keycloak.parsedToken); + console.info('Token refreshed ' + refreshed); + } else { + console.warn('Token not refreshed, valid for ' + + Math.round(this.keycloak.tokenParsed.exp + this.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); + } + }).catch(() => { + console.error("updateToken -> Failed to refresh token"); + }); + } - isUserAuthenticated() { - return this.keycloak.authenticated; - } - getClientId() { - return this.keycloak.clientId; - } - decodeAccessToken() { - return JSON.parse(window.atob(this.keycloak.token.split(".")[1])); - } - getUserName() { - return this.decodeAccessToken().email; - } - getName() { - return this.decodeAccessToken().name; - } - getSessionId() { - return this.keycloak.sessionId; - } - getRole() { - let clientRoles = ''; - clientRoles = this.keycloak.resourceAccess[this.getClientId()].roles; - return clientRoles.length == 1 ? clientRoles[0] : clientRoles; + isUserAuthenticated() { + return this.keycloak.authenticated; + } + getClientId() { + return this.keycloak.clientId; + } + getUserName() { + return this.keycloak.tokenParsed.email; + } + getName() { + return this.keycloak.tokenParsed.name; + } + getBpn() { + return this.keycloak.tokenParsed.bpn; + } + getSessionId() { + return this.keycloak.sessionId; + } + hasRoles() { + try { + let clientId = this.getClientId(); + if (!clientId || !this.keycloak.resourceAccess) { + return false; + } + + if (!jsonUtil.exists(clientId, this.keycloak.resourceAccess)) { + return false; + } + + let appIdResource = jsonUtil.get(clientId, this.keycloak.resourceAccess, ".", null); + if (appIdResource==null || !jsonUtil.exists("roles", appIdResource)) { + return false; + } + + let roleList = jsonUtil.get("roles", appIdResource, ".", null); + if(roleList == null){ + return false; + } + return roleList.length > 0; + } catch (e) { + return false; } - logout() { - let logoutOptions = { redirectUri: REDIRECT_URI }; - this.keycloak.logout(logoutOptions).then((success) => { - console.log("--> log: logout success ", success); - }).catch((error) => { - console.log("--> log: logout error ", error); - }); + } + getRole() { + let clientRoles = ""; + let clientId = this.getClientId(); + if (this.hasRoles()) { + clientRoles = this.keycloak.resourceAccess[clientId].roles; } + return clientRoles.length == 1 ? clientRoles[0] : clientRoles; + } + logout() { + let logoutOptions = { redirectUri: REDIRECT_URI }; + this.keycloak.logout(logoutOptions).then((success) => { + console.log("--> log: logout success ", success); + }).catch((error) => { + console.log("--> log: logout error ", error); + }); + } } diff --git a/src/services/service.const.js b/src/services/service.const.js index 329e09f8c..6c6ddde46 100644 --- a/src/services/service.const.js +++ b/src/services/service.const.js @@ -42,6 +42,11 @@ let realm = "KEYCLOAK_REALM"; let onLoad = "KEYCLOAK_ONLOAD"; let commitId = "REPO_COMMIT_ID"; let repoEndpoint = "REPO_ENDPOINT_URL"; +let roleCheck = "AUTH_ROLE_CHECK"; +let bpnCheck = "AUTH_BPN_CHECK"; +let bpn = "AUTH_BPN_NUMBER"; +let portalUrl = "APP_PORTAL_URL"; +let adminEmail = "APP_ADMIN_EMAIL"; // Default values if the value is not specified serverUrl = (serverUrl != null && serverUrl !== "") ? serverUrl : "https://materialpass.int.demo.catena-x.net" @@ -49,6 +54,11 @@ backendUrl = (backendUrl != null && backendUrl !== "") ? backendUrl : serverUrl clientId = (clientId != null && clientId !== "") ? clientId : "Cl13-CX-Battery" realm = (realm != null && realm !== "") ? realm : "CX-Central" onLoad = (onLoad != null && onLoad !== "") ? onLoad : "login-required" +adminEmail = (adminEmail != null && adminEmail !== "") ? adminEmail : "admin@example.com" +portalUrl = (portalUrl != null && portalUrl !== "") ? portalUrl : "https://portal.int.demo.catena-x.net" +bpnCheck = (bpnCheck === "true") +roleCheck = (roleCheck === "true") + // Default Variables if value is not specified or is not a integer timeout = numberUtil.parseInt(timeout, 60000); @@ -70,6 +80,11 @@ const REALM = realm; const ONLOAD = onLoad; const COMMIT_ID = commitId; const REPO_ENDPOINT = repoEndpoint; +const ROLE_CHECK = roleCheck; +const BPN_CHECK = bpnCheck; +const BPN = bpn; +const PORTAL_URL = portalUrl; +const ADMIN_EMAIL = adminEmail; // Initialize configuration objects let INIT_OPTIONS = { @@ -92,4 +107,4 @@ if (window.location.href.includes("localhost")) { //Modify credentials for local REDIRECT_URI = SERVER_URL; } // Export all the CONSTANTS and VARIABLES -export { INIT_OPTIONS, REDIRECT_URI, SERVER_URL, IDP_URL, BACKEND_URL, VERSION, API_TIMEOUT, API_DELAY, API_MAX_RETRIES, COMMIT_ID, REPO_ENDPOINT,IRS_DELAY, IRS_MAX_WAITING_TIME }; +export { INIT_OPTIONS, REDIRECT_URI, SERVER_URL, IDP_URL, BACKEND_URL, VERSION, API_TIMEOUT, API_DELAY, API_MAX_RETRIES, COMMIT_ID, REPO_ENDPOINT,IRS_DELAY, IRS_MAX_WAITING_TIME, BPN_CHECK, BPN, PORTAL_URL, ADMIN_EMAIL, ROLE_CHECK}; diff --git a/src/utils/authUtil.js b/src/utils/authUtil.js new file mode 100644 index 000000000..71703b695 --- /dev/null +++ b/src/utils/authUtil.js @@ -0,0 +1,41 @@ +/** + * Catena-X - Product Passport Consumer Frontend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import jsonUtil from "@/utils/jsonUtil.js"; +import cryptUtil from "@/utils/cryptUtil"; +export default { + decodeToken(token){ + return jsonUtil.toJson(cryptUtil.fromBase64(String(token).split(".")[1])) + }, + checkBpn(parsedToken, bpn){ + if(!jsonUtil.exists("bpn", parsedToken)){ + return false; + } + let tokenBpn = jsonUtil.get("bpn",parsedToken, ".", null); + if(bpn == null){ + return false; + } + + return bpn === tokenBpn; + } + +} \ No newline at end of file diff --git a/src/utils/cryptUtil.js b/src/utils/cryptUtil.js new file mode 100644 index 000000000..2bd5ce4de --- /dev/null +++ b/src/utils/cryptUtil.js @@ -0,0 +1,37 @@ +/** + * Catena-X - Product Passport Consumer Frontend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +export default { + toBase64(string){ + return btoa(string); + }, + fromBase64(base64){ + return atob(base64); + }, + decodeUrl(url){ + return decodeURIComponent(url); + }, + encodeUrl(url){ + return encodeURIComponent(url) + } +} \ No newline at end of file diff --git a/src/utils/jsonUtil.js b/src/utils/jsonUtil.js index df53625b8..31f02165c 100644 --- a/src/utils/jsonUtil.js +++ b/src/utils/jsonUtil.js @@ -76,6 +76,9 @@ export default { } return uniqueKey; //Return unique id }, + toJson(json){ + return JSON.parse(json); + }, flatternJsonAttributes(json, attributes=[], sep=".", allowNull = false, allowEmpty = false) { if (json == null) return null; if (!(json instanceof Object)) return json;