diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 2db8fc6185c54f0595c7b170273d8d9778252712..4d33cc34e54ec5818e14827803b14006029e5a4e 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -78,11 +78,12 @@ services: restart: "no" container_name: dbrepo-auth-service hostname: auth-service - image: bitnami/keycloak:26.0.0-debian-12-r1 + image: bitnami/keycloak:24.0.5-debian-12-r8 volumes: - ./config/import-realms.sh:/docker-entrypoint-initdb.d/import-realms.sh - ./config/master-realm.json:/opt/keycloak/data/import/master-realm.json - ./config/dbrepo-realm.json:/opt/keycloak/data/import/dbrepo-realm.json + - ./config/create-event-listener.jar:/opt/bitnami/keycloak/providers/create-event-listener.jar ports: - "8080:8080" environment: @@ -92,6 +93,9 @@ services: KEYCLOAK_DATABASE_NAME: "${AUTH_DB_NAME:-keycloak}" KEYCLOAK_DATABASE_USER: "${AUTH_DB_USERNAME:-keycloak}" KEYCLOAK_DATABASE_PASSWORD: "${AUTH_DB_PASSWORD:-dbrepo}" + METADATA_SERVICE_ENDPOINT: "${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080}/api/user" + SYSTEM_USERNAME: "${SYSTEM_USERNAME:-admin}" + SYSTEM_PASSWORD: "${SYSTEM_PASSWORD:-admin}" healthcheck: test: curl -fsS http://localhost:8080/realms/master interval: 10s @@ -109,7 +113,7 @@ services: init: true restart: "no" container_name: dbrepo-auth-service-init - image: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.3 environment: AUTH_SERVICE_ADMIN: ${AUTH_SERVICE_ADMIN:-admin} AUTH_SERVICE_ADMIN_PASSWORD: ${AUTH_SERVICE_ADMIN_PASSWORD:-admin} @@ -130,7 +134,7 @@ services: restart: "no" container_name: dbrepo-metadata-service hostname: metadata-service - image: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.3 volumes: - "${SHARED_VOLUME:-/tmp}:/tmp" environment: @@ -193,7 +197,7 @@ services: restart: "no" container_name: dbrepo-analyse-service hostname: analyse-service - image: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.6.3 environment: AUTH_SERVICE_CLIENT: ${AUTH_SERVICE_CLIENT:-dbrepo-client} AUTH_SERVICE_CLIENT_SECRET: ${AUTH_SERVICE_CLIENT:-MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG} @@ -248,7 +252,7 @@ services: restart: "no" container_name: dbrepo-search-db hostname: search-db - image: registry.datalab.tuwien.ac.at/dbrepo/search-db:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/search-db:1.6.3 healthcheck: test: curl -sSL localhost:9200/_plugins/_security/health | jq .status | grep UP interval: 10s @@ -272,7 +276,7 @@ services: restart: "no" container_name: dbrepo-search-service hostname: search-service - image: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.6.3 environment: AUTH_SERVICE_CLIENT: ${AUTH_SERVICE_CLIENT:-dbrepo-client} AUTH_SERVICE_CLIENT_SECRET: ${AUTH_SERVICE_CLIENT_SECRET:-MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG} @@ -296,11 +300,15 @@ services: restart: "no" container_name: dbrepo-ui hostname: ui - image: registry.datalab.tuwien.ac.at/dbrepo/ui:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/ui:1.6.3 environment: NUXT_PUBLIC_API_CLIENT: "${BASE_URL:-http://localhost}" - NUXT_PUBLIC_API_SERVER: "${BASE_URL:-http://localhost}" + NUXT_PUBLIC_API_SERVER: "${BASE_URL:-http://gateway-service}" NUXT_PUBLIC_UPLOAD_CLIENT: "${BASE_URL:-http://localhost}/api/upload/files" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_ID: "${AUTH_SERVICE_CLIENT:-dbrepo-client}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_SECRET: "${AUTH_SERVICE_CLIENT:-MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_REDIRECT_URI: "${BASE_URL:-http://localhost}/auth/keycloak/callback" + NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_REDIRECT_URI: "${BASE_URL:-http://localhost}" depends_on: dbrepo-search-service: condition: service_healthy @@ -311,6 +319,8 @@ services: interval: 10s timeout: 5s retries: 12 + extra_hosts: + - "localhost:host-gateway" logging: driver: json-file @@ -365,7 +375,7 @@ services: init: true container_name: dbrepo-search-service-init hostname: search-service-init - image: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.6.3 environment: LOG_LEVEL: ${LOG_LEVEL:-info} METADATA_SERVICE_ENDPOINT: ${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080} @@ -422,7 +432,7 @@ services: restart: "no" container_name: dbrepo-dashboard-service hostname: dashboard-service - image: registry.datalab.tuwien.ac.at/dbrepo/dashboard-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/dashboard-service:1.6.3 ports: - "3000:3000" volumes: @@ -449,7 +459,7 @@ services: init: true container_name: dbrepo-storage-service-init hostname: storage-service-init - image: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.6.3 environment: S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-seaweedfsadmin} S3_BUCKET: "${S3_BUCKET:-dbrepo}" @@ -479,6 +489,7 @@ services: AWS_ACCESS_KEY_ID: "${S3_ACCESS_KEY_ID:-seaweedfsadmin}" AWS_SECRET_ACCESS_KEY: "${S3_SECRET_ACCESS_KEY:-seaweedfsadmin}" AWS_REGION: "${STORAGE_REGION_NAME:-default}" + METADATA_SERVICE_ENDPOINT: "${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080}" depends_on: dbrepo-storage-service: condition: service_healthy @@ -494,7 +505,7 @@ services: restart: "no" container_name: dbrepo-data-service hostname: data-service - image: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.6.2 + image: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.6.3 volumes: - "${SHARED_VOLUME:-/tmp}:/tmp" environment: diff --git a/.docs/api/auth-service.md b/.docs/api/auth-service.md index 93e87beaf280b5ed9b96a2aca508308098676173..7b28901a9b9d49a01d48a313187f86832509a03b 100644 --- a/.docs/api/auth-service.md +++ b/.docs/api/auth-service.md @@ -88,10 +88,6 @@ which is imported into Keycloak on startup. ## Limitations -* No support for sending e-mails through Keycloak by default. -* No support for temporary passwords. -* No support for multi-factor authentication. - !!! question "Do you miss functionality? Do these limitations affect you?" We strongly encourage you to help us implement it as we are welcoming contributors to open-source software and get diff --git a/.docs/api/ui.md b/.docs/api/ui.md index 30b32c0a0ccde771c8bde22f21ca630d6354a9b3..b82058c19bae4c4e629e0f1e06eca29985c94f67 100644 --- a/.docs/api/ui.md +++ b/.docs/api/ui.md @@ -101,6 +101,7 @@ See the [API Overview](..) page for detailed examples. ## Limitations +* Changing the OIDC provider URL requires to build the UI from scratch. * When developing locally, the `axios` module does not parse custom headers (such as `X-Count`, `X-Headers`) and/or blocks CORS requests wrongfully. diff --git a/.docs/changelog.md b/.docs/changelog.md index e2bb59c3748fde0b9da1db9099e013fd6b928db8..efdb4dd291dbbc3194ffc3ec3168ed71befffee3 100644 --- a/.docs/changelog.md +++ b/.docs/changelog.md @@ -2,6 +2,17 @@ author: Martin Weise --- +## v1.6.3 (2025-02-05) + +[:simple-gitlab: GitLab Release](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/tags/v1.6.3) + +### What's Changed + +#### Changes + +* Refactored the UI to support OIDC and added an event listener to the Auth Service that syncs users on creation to the + Metadata DB in [#488](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/488). + ## v1.6.2 (2025-01-24) [:simple-gitlab: GitLab Release](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/tags/v1.6.2) diff --git a/.docs/concepts/data-visibility.md b/.docs/concepts/data-visibility.md index 04f37c6979bd9019d954859535a757c72da4b63e..31c6ca3682780c4353c6aa5baaae666047893635 100644 --- a/.docs/concepts/data-visibility.md +++ b/.docs/concepts/data-visibility.md @@ -8,28 +8,40 @@ subset of a database. ## Visibility -In total there are three possible scenarios: +In total there are four possible visibility settings that can be applied on database level and then at the subsequent +levels (table, view, subset). We give two examples for better understanding: -#### Public +!!! example "Example: Database that is hidden but certain views are visible" + + Database *Airquality* has the settings to hide all data and schema by default. + + * Table `sensor` inherits the settings from the database by default and therefore is also **hidden**. Nobody can + read/write to this database by default. Only designated users that the database owner allows to read/write can do + so. + * View `v_sensor` inherits the settings from the database by default and therefore is also **hidden**. The database + owner wants the data to be visible to the public (anonymously), so he changes the settings to data=visible, + schema=hidden. Now everybody can see the data but not the table(s) that contain the data. + +#### Visible !!! info "Possible use-case: data publication supplement to an open-access publication" -Where the database's data and metadata is set to be *visible*. This means everything in the database (tables, views, -subsets) are visible by anyone from the public. +Where the resource's data and schema is set to be visible. -#### Private +#### Data-only !!! info "Possible use-case: private sensor measurements with timed embargo" -Where the database's data set to be *hidden* but the schema to be *visible*. This means everything in the database -(tables, views, subsets) are by default not visible by anyone from the public. You can however make specific views that -join tables and/or filter certain columns and apply a 14-day delay-embargo. +Where the resource's schema visibility is hidden but the data is visible. -<figure markdown> - -<figcaption>Figure 1: Public view that joins two private tables and applies a time-embargo</figcaption> -</figure> +#### Schema-only + +!!! info "Possible use-case: publish data for reviewers before the final publication" + +Where the resource's data visibility is hidden but the schema is visible. #### Draft -!!! info "Possible use-case: project data storage before publication" \ No newline at end of file +!!! info "Possible use-case: project data storage before publication" + +Where the resource's data and schema visibility is hidden. It will not be findable even in the search. \ No newline at end of file diff --git a/.docs/images/architecture.drawio b/.docs/images/architecture.drawio index 71536a321290f6c8cb39d147529a8b00fb458eed..b1aed4cb4035dfa1cbc08818c44ca5d7fac5a803 100644 --- a/.docs/images/architecture.drawio +++ b/.docs/images/architecture.drawio @@ -1,4 +1,4 @@ -<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/25.0.2 Chrome/128.0.6613.186 Electron/32.2.5 Safari/537.36" version="25.0.2" pages="9"> +<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.0.9 Chrome/128.0.6613.186 Electron/32.2.5 Safari/537.36" version="26.0.9" pages="9"> <diagram id="mvBsv1rP8O80Qe3yGnn_" name="docker-compose"> <mxGraphModel dx="683" dy="391" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> <root> @@ -1109,47 +1109,10 @@ </mxGraphModel> </diagram> <diagram id="7HywRA3nQAgvNxZjCRq2" name="private-embargo"> - <mxGraphModel dx="985" dy="394" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> + <mxGraphModel dx="1434" dy="822" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> <root> <mxCell id="0" /> <mxCell id="1" parent="0" /> - <mxCell id="n6nk3BLY6128t3IB6Ma7-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-1" target="n6nk3BLY6128t3IB6Ma7-5"> - <mxGeometry relative="1" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-11" value="<span style="text-wrap: wrap; background-color: rgb(251, 251, 251);">value,loc_id</span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;" vertex="1" connectable="0" parent="n6nk3BLY6128t3IB6Ma7-8"> - <mxGeometry x="0.0303" relative="1" as="geometry"> - <mxPoint as="offset" /> - </mxGeometry> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-1" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1"> - <mxGeometry x="250" y="170" width="80" height="80" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-2" value="<b>table</b>: sensor (private)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> - <mxGeometry x="227.5" y="150" width="125" height="20" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;curved=1;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-3" target="n6nk3BLY6128t3IB6Ma7-5"> - <mxGeometry relative="1" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-12" value="id,name,lat,lng" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;" vertex="1" connectable="0" parent="n6nk3BLY6128t3IB6Ma7-9"> - <mxGeometry x="0.1455" relative="1" as="geometry"> - <mxPoint as="offset" /> - </mxGeometry> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-3" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1"> - <mxGeometry x="430" y="170" width="80" height="80" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-4" value="<b>table</b>: location (private)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> - <mxGeometry x="405" y="150" width="130" height="20" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-5" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;fontSize=8;" vertex="1" parent="1"> - <mxGeometry x="340" y="290" width="80" height="80" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-6" value="<b>view</b>: validated_sensor (public)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> - <mxGeometry x="290" y="370" width="180" height="20" as="geometry" /> - </mxCell> - <mxCell id="n6nk3BLY6128t3IB6Ma7-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-4" target="n6nk3BLY6128t3IB6Ma7-4"> - <mxGeometry relative="1" as="geometry" /> - </mxCell> </root> </mxGraphModel> </diagram> diff --git a/.docs/index.md b/.docs/index.md index 64b807cae27ed8db6f928633bbd1f957887b3b0c..d86224726f28fb78b3022601e02b00859a5ecc7d 100644 --- a/.docs/index.md +++ b/.docs/index.md @@ -14,7 +14,7 @@ author: Martin Weise   -Documentation for version: [v1.6.2](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/releases). +Documentation for version: [v1.6.3](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/releases). DBRepo is a repository for data in databases that are used from the beginning until the end of a research project supporting data evolution, -citation and -versioning. It implements the query store of the diff --git a/.docs/installation.md b/.docs/installation.md index ee0d9b88faa47ca33f74c12c5cf63200e7ff7ffc..1c6db304ccf3d760c44e56b7d49aef49d76330ff 100644 --- a/.docs/installation.md +++ b/.docs/installation.md @@ -31,11 +31,11 @@ settings. - min. 200GB free SSD storage Since DBRepo is intended to be a publicly available repository, an optional fixed/static IP-address with optional -SSL/TLS certificate is recommended. Follow the [secure install](#secure-install) guide. +SSL/TLS certificate is recommended. Follow the [secure installation](#secure-installation) guide. ## Secure Installation -Execute the install script to download only the environment and save it to `dist`. +Execute the installation script to download only the environment and save it to `dist`. ```shell curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/install.sh | DOWNLOAD_ONLY=1 bash @@ -50,6 +50,8 @@ Update the rest of the default secrets in the `.env` file to secure passwords. Y `openssl rand -hex 16`. Set `auth_ldap.dn_lookup_bind.password` in `dist/rabbitmq.conf` to the value of `SYSTEM_PASSWORD`. +Only set the `BASE_URL` environment variable in `.env` when your hostname is **not** `localhost`. + ### Runtime Configuration The [Auth Service](../api/auth-service) can be configured easily when DBRepo is running. Start DBRepo temporarily: diff --git a/.docs/kubernetes.md b/.docs/kubernetes.md index 60f87eb6d4e8047ee0b767e08a6ccecda64a6a81..170bc863f58206778bf4488c48f322e6ad801996 100644 --- a/.docs/kubernetes.md +++ b/.docs/kubernetes.md @@ -14,7 +14,7 @@ helm upgrade --install dbrepo \ -n dbrepo \ "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" \ --values ./values.yaml \ - --version "1.6.2" \ + --version "1.6.3" \ --create-namespace \ --cleanup-on-fail ``` diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3661711509265e6d7889e13a24dd2a2e24f7a689..85e5d640bbce875195f6c560d3e60e0d210f650e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,8 +10,8 @@ variables: SONARQUBE_VERSION: "10.0" BUN_VERSION: "1.1.40" DOC_VERSION: "1.6" - APP_VERSION: "1.6.2" - CHART_VERSION: "1.6.2" + APP_VERSION: "1.6.3" + CHART_VERSION: "1.6.3" CACHE_FALLBACK_KEY: "${CI_DEFAULT_BRANCH}" # This will supress any download for dependencies and plugins or upload messages which would clutter the console log. # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work. diff --git a/.gitlab/agents/dev/values.yaml b/.gitlab/agents/dev/values.yaml index aa241d7f0eef507e104fb2ea49c9c01e37390ed5..5841a5e97bd5d9f9ddde4d13a2b74623a91cf4fb 100644 --- a/.gitlab/agents/dev/values.yaml +++ b/.gitlab/agents/dev/values.yaml @@ -26,6 +26,9 @@ authservice: client: id: dbrepo-client secret: MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG + setupJob: + image: + name: registry.datalab.tuwien.ac.at/dbrepo/auth-service-init:1.6.3 persistence: enabled: true @@ -33,6 +36,7 @@ brokerservice: enabled: true ldap: bindpw: b8534187c9adf9618e7bd1c79c7f4639 + identityservice: enabled: true global: @@ -66,9 +70,13 @@ searchdb: analyseservice: enabled: true + image: + name: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.6.3 metadataservice: enabled: true + image: + name: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.3 admin: email: noreply@example.com deletedRecord: permanent @@ -83,6 +91,8 @@ metadataservice: dataservice: enabled: true + image: + name: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.6.3 rabbitmq: consumer: username: admin @@ -95,9 +105,17 @@ dataservice: searchservice: enabled: true + image: + name: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.6.3 + init: + image: + name: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.6.3 storageservice: enabled: true + init: + image: + name: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.6.3 uploadservice: enabled: true @@ -113,13 +131,15 @@ metricdb: ui: enabled: true + image: + name: registry.datalab.tuwien.ac.at/dbrepo/ui:1.6.3 public: api: client: https://s155.datalab.tuwien.ac.at server: https://s155.datalab.tuwien.ac.at title: "Database Repository" logo: "https://ec.tuwien.ac.at/~weise/images/DS_white_hiRes.png" - icon: "https://ec.tuwien.ac.at/~weise/images/DS-icon_white_hiRes.png" + icon: "https://ec.tuwien.ac.at/~weise/images/favicon.ico" touch: "https://ec.tuwien.ac.at/~weise/images/DS-icon_white_hiRes.png" broker: host: s155.datalab.tuwien.ac.at diff --git a/Makefile b/Makefile index e5d0dbec5fdf080bc5224e52531b6fc3706bddeb..0dd2ae5e0a756aa713a101ddfff2ff8513222fa4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all -APP_VERSION ?= 1.6.2 -CHART_VERSION ?= 1.6.2 +APP_VERSION ?= 1.6.3 +CHART_VERSION ?= 1.6.3 REPOSITORY_URL ?= registry.datalab.tuwien.ac.at/dbrepo .PHONY: all diff --git a/dbrepo-analyse-service/Pipfile b/dbrepo-analyse-service/Pipfile index 9c7b7095205c5fce88254ac94439db010a03bef0..22a8e79b3a232b73784971df01dc283b8171388d 100644 --- a/dbrepo-analyse-service/Pipfile +++ b/dbrepo-analyse-service/Pipfile @@ -21,7 +21,7 @@ numpy = "*" pandas = "*" minio = "*" pydantic = "*" -dbrepo = {path = "./lib/dbrepo-1.6.2.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.6.3.tar.gz"} opensearch-py = "*" [dev-packages] diff --git a/dbrepo-analyse-service/Pipfile.lock b/dbrepo-analyse-service/Pipfile.lock index f177d904cb25228548dbc3ee5e0bef46919d6698..5d2ace3655c32086fe018e0267ab207a9f1703b6 100644 --- a/dbrepo-analyse-service/Pipfile.lock +++ b/dbrepo-analyse-service/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5fbd87c094d93565d64444fc1734d9183e7095d47447d30d6493dfc6bb7e8201" + "sha256": "9cc4c161729b642069bbf4ab379c0f4a9122035afcb3ac7b5b1bfc13281f76aa" }, "pipfile-spec": 6, "requires": { @@ -159,11 +159,11 @@ }, "attrs": { "hashes": [ - "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", - "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" + "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", + "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a" ], "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "version": "==25.1.0" }, "blinker": { "hashes": [ @@ -175,20 +175,20 @@ }, "boto3": { "hashes": [ - "sha256:53a5307f6a3526ee2f8590e3c45efa504a3ea4532c1bfe4926c0c19bf188d141", - "sha256:f9843a5d06f501d66ada06f5a5417f671823af2cf319e36ceefa1bafaaaaa953" + "sha256:7f61c9d0ea64f484a17c1e3115fdf90fd7b17ab6771e07cb4549f42b9fd28fb9", + "sha256:ac47215d320b0c2534340db58d6d5284cb1860b7bff172b4dd6eee2dee1d5779" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.36.3" + "version": "==1.36.8" }, "botocore": { "hashes": [ - "sha256:536ab828e6f90dbb000e3702ac45fd76642113ae2db1b7b1373ad24104e89255", - "sha256:775b835e979da5c96548ed1a0b798101a145aec3cd46541d62e27dda5a94d7f8" + "sha256:59d3fdfbae6d916b046e973bebcbeb70a102f9e570ca86d5ba512f1854b78fc2", + "sha256:81c88e5566cf018e1411a68304dc1fb9e4156ca2b50a3a0f0befc274299e67fa" ], "markers": "python_version >= '3.8'", - "version": "==1.36.3" + "version": "==1.36.8" }, "certifi": { "hashes": [ @@ -412,9 +412,9 @@ }, "dbrepo": { "hashes": [ - "sha256:a41ca60353510cbecf8fb647cf2483acb100258743794a16bc8ad6f8e9ea4481" + "sha256:ac99f4bd19961f08665abd513e4d9452fcea5554f122457840e95f90698bab4d" ], - "path": "./lib/dbrepo-1.6.2.tar.gz" + "path": "./lib/dbrepo-1.6.3.tar.gz" }, "events": { "hashes": [ @@ -838,11 +838,11 @@ }, "mistune": { "hashes": [ - "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", - "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667" + "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", + "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c" ], "markers": "python_version >= '3.8'", - "version": "==3.1.0" + "version": "==3.1.1" }, "multidict": { "hashes": [ @@ -1230,12 +1230,12 @@ }, "pydantic": { "hashes": [ - "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff", - "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53" + "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", + "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.10.5" + "version": "==2.10.6" }, "pydantic-core": { "hashes": [ @@ -1427,11 +1427,11 @@ }, "referencing": { "hashes": [ - "sha256:363d9c65f080d0d70bc41c721dce3c7f3e77fc09f269cd5c8813da18069a6794", - "sha256:ca2e6492769e3602957e9b831b94211599d2aade9477f5d44110d2530cf9aade" + "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", + "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0" ], "markers": "python_version >= '3.9'", - "version": "==0.36.1" + "version": "==0.36.2" }, "requests": { "hashes": [ @@ -1553,11 +1553,11 @@ }, "s3transfer": { "hashes": [ - "sha256:3f25c900a367c8b7f7d8f9c34edc87e300bde424f779dc9f0a8ae4f9df9264f6", - "sha256:8fa0aa48177be1f3425176dfe1ab85dcd3d962df603c3dbfc585e6bf857ef0ff" + "sha256:3b39185cb72f5acc77db1a58b6e25b977f28d20496b6e58d6813d75f464d632f", + "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc" ], "markers": "python_version >= '3.8'", - "version": "==0.11.1" + "version": "==0.11.2" }, "setuptools": { "hashes": [ diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.2-py3-none-any.whl b/dbrepo-analyse-service/lib/dbrepo-1.6.2-py3-none-any.whl index 24256263e2fb3156ac0eea01079116e4b40e36fd..256d325e8bdbdacd8c967d852c98e39d8d3b9eb9 100644 Binary files a/dbrepo-analyse-service/lib/dbrepo-1.6.2-py3-none-any.whl and b/dbrepo-analyse-service/lib/dbrepo-1.6.2-py3-none-any.whl differ diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz index 2ae1ea50b1610050f5bd5746f7e9596b1c483c9d..ad4d6f9c5590836360d1a919f4be84b5cc5f9ade 100644 Binary files a/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz and b/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz differ diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.3-py3-none-any.whl b/dbrepo-analyse-service/lib/dbrepo-1.6.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b7f45eecc067d496a9d39d189e619ac7524c66b1 Binary files /dev/null and b/dbrepo-analyse-service/lib/dbrepo-1.6.3-py3-none-any.whl differ diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.3.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.6.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2aa4f75ed8dd08245bd29d34c151dbe9b7eb2253 Binary files /dev/null and b/dbrepo-analyse-service/lib/dbrepo-1.6.3.tar.gz differ diff --git a/dbrepo-auth-service/dbrepo-realm.json b/dbrepo-auth-service/dbrepo-realm.json index b48be9a6bdc607bbfe2f7190b3733238f31f29b8..1c703b83750c5aa21d4da06c7895d74211122ae9 100644 --- a/dbrepo-auth-service/dbrepo-realm.json +++ b/dbrepo-auth-service/dbrepo-realm.json @@ -27,7 +27,7 @@ "oauth2DevicePollingInterval" : 5, "enabled" : true, "sslRequired" : "none", - "registrationAllowed" : false, + "registrationAllowed" : true, "registrationEmailAsUsername" : false, "rememberMe" : false, "verifyEmail" : true, @@ -38,6 +38,7 @@ "bruteForceProtected" : false, "permanentLockout" : false, "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", "maxFailureWaitSeconds" : 900, "minimumQuickLoginWaitSeconds" : 60, "waitIncrementSeconds" : 60, @@ -1308,8 +1309,8 @@ "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "client.use.lightweight.access.token.enabled" : "true" + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : true, @@ -1383,6 +1384,38 @@ "fullScopeAllowed" : true, "nodeReRegistrationTimeout" : -1, "protocolMappers" : [ { + "id" : "266edf62-a19a-483b-b594-81428e4af792", + "name" : "orcid", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "ORCID", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "orcid", + "jsonType.label" : "String" + } + }, { + "id" : "1a21798a-38b6-4df5-89f0-86942415246f", + "name" : "theme", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "THEME", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "theme", + "jsonType.label" : "String" + } + }, { "id" : "da0b27c1-ae2e-4baa-bf78-db233e15c78d", "name" : "preferred_username", "protocol" : "openid-connect", @@ -1396,18 +1429,66 @@ "userinfo.token.claim" : "true" } }, { - "id" : "7c94de93-f60f-487b-b4b7-1891c67f74cc", - "name" : "aud", + "id" : "1bc6a1f4-4be2-439c-8c7f-b3fb0bb9956a", + "name" : "affiliation", "protocol" : "openid-connect", - "protocolMapper" : "oidc-hardcoded-claim-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "claim.value" : "dbrepo", + "introspection.token.claim" : "true", "userinfo.token.claim" : "true", + "user.attribute" : "AFFILIATION", "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", - "claim.name" : "aud", - "access.tokenResponse.claim" : "false" + "claim.name" : "affiliation", + "jsonType.label" : "String" + } + }, { + "id" : "7cbf6dc6-653e-40a9-9974-0e5bf7a363c3", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "70bbd779-d085-4204-ac4b-3a40abab9d88", + "name" : "language", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "LANGUAGE", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "language", + "jsonType.label" : "String" + } + }, { + "id" : "b817424d-7f91-43d8-b7d0-6a32582377fb", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" } }, { "id" : "030a1cd9-53d1-4a62-a375-94d50a2dc6fc", @@ -1424,9 +1505,26 @@ "access.token.claim" : "true", "claim.name" : "uid" } + }, { + "id" : "c304ed2f-5952-4772-838d-91998a45f154", + "name" : "aud", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-hardcoded-claim-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "claim.value" : "account", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "aud", + "jsonType.label" : "String", + "access.tokenResponse.claim" : "false" + } } ], - "defaultClientScopes" : [ "roles", "attributes", "basic" ], - "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] + "defaultClientScopes" : [ "roles", "basic" ], + "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "attributes", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] }, { "id" : "25741f6b-4867-4138-8238-6345c6ba8702", "clientId" : "rabbitmq-client", @@ -1471,12 +1569,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "false", "user.attribute" : "username", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "client_id", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "false" } }, { "id" : "f1afc22d-f595-403b-ba2e-6ab19d98205e", @@ -1485,11 +1583,11 @@ "protocolMapper" : "oidc-hardcoded-claim-mapper", "consentRequired" : false, "config" : { - "claim.value" : "rabbitmq", - "userinfo.token.claim" : "false", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "aud", + "claim.value" : "rabbitmq", + "userinfo.token.claim" : "false", "access.tokenResponse.claim" : "false" } } ], @@ -1548,8 +1646,8 @@ "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, "authenticationFlowBindingOverrides" : { }, @@ -1562,12 +1660,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ], "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], @@ -1591,8 +1689,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "782819fe-ba5d-4ddb-9f95-cabb69d79c8d", @@ -1601,12 +1699,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "emailVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } }, { "id" : "ca613fc8-bbf2-4240-8b33-a1874f1559f3", @@ -1615,12 +1713,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "email", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1630,8 +1728,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "84f0487a-1d7d-470c-9b8e-5835294ae235", @@ -1640,12 +1738,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "preferred_username", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bbdcdb36-3ec0-443d-b1af-9993d40f0567", @@ -1654,12 +1752,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "gender", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "gender", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "9faa870b-5491-4ce9-b27d-c9ce07d6a95e", @@ -1668,12 +1766,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "birthdate", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "birthdate", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f0e3c012-9523-4076-83ae-e466e2d08220", @@ -1693,12 +1791,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "profile", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "profile", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "18cfbf4b-0a8e-45c7-a832-c0f72c92f3f3", @@ -1707,12 +1805,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "updatedAt", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "updated_at", - "jsonType.label" : "long" + "jsonType.label" : "long", + "userinfo.token.claim" : "true" } }, { "id" : "841ea785-26ab-429a-a420-09ce3948924d", @@ -1721,12 +1819,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "lastName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "family_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bfba13ff-f952-4e89-bbb1-a693fdebfae8", @@ -1735,12 +1833,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "website", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "website", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "475f071d-5149-4379-b928-76482f5f519c", @@ -1749,12 +1847,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "zoneinfo", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "zoneinfo", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "b8bebfed-b5e9-4604-a0ee-9817f7d439ac", @@ -1763,12 +1861,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "middleName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "middle_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "445232c8-6830-476c-a6f1-8bbef167595a", @@ -1777,12 +1875,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "picture", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "picture", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "65f2e474-6ede-4872-86e4-e49504dd0f2a", @@ -1791,12 +1889,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "16cd5a27-ccf3-453c-ae1e-8621813ab73c", @@ -1805,12 +1903,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "firstName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "given_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f9efedfc-3388-457c-b10a-1dff4525ff9b", @@ -1819,12 +1917,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "nickname", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "nickname", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1858,12 +1956,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "upn", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1905,8 +2003,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "dae802fb-9138-408a-b80e-a40eb0f56814", @@ -1915,12 +2013,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumber", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "feb06a8d-b0eb-4911-8464-368d93f566fa", @@ -1929,12 +2027,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumberVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } } ] }, { @@ -1944,8 +2042,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" + "consent.screen.text" : "", + "display.on.consent.screen" : "false" }, "protocolMappers" : [ { "id" : "c6411e3b-6478-453d-b530-5fe175a4d786", @@ -2025,6 +2123,61 @@ "gui.order" : "", "consent.screen.text" : "" } + }, { + "id" : "aa5c6ca7-812d-4fff-80b9-f5095ca82ce6", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "bb359b0f-97dc-4d6a-9a2f-89458b53c512", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "7aa3a4d2-3dd1-48dd-8886-562906eadb2a", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "c4882d39-e815-49f5-8a73-eb8b83572eae", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ] }, { "id" : "210cc792-6c07-45a6-a77e-827cdf3b41ba", "name" : "offline_access", @@ -2041,8 +2194,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "8d4ffe4d-1d01-4ca1-8ff4-44eacca61b30", @@ -2115,8 +2268,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "3b6b6914-8ad1-4a71-88ec-444f754aaacb", @@ -2132,11 +2285,15 @@ "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "false", + "multivalued" : "true", "user.attribute" : "foo", + "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" + "jsonType.label" : "String" } }, { "id" : "a7bd6723-e58e-47f7-95c0-2925ce99283d", @@ -2166,8 +2323,12 @@ "strictTransportSecurity" : "max-age=31536000; includeSubDomains" }, "smtpServer" : { }, + "loginTheme" : "keycloak.v2", + "accountTheme" : "", + "adminTheme" : "", + "emailTheme" : "", "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], + "eventsListeners" : [ "create-event-listener", "jboss-logging" ], "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "VERIFY_PROFILE_ERROR", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "OAUTH2_DEVICE_VERIFY_USER_CODE", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "TOKEN_EXCHANGE", "AUTHREQID_TO_TOKEN", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "OAUTH2_DEVICE_AUTH", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "OAUTH2_DEVICE_CODE_TO_TOKEN", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "AUTHREQID_TO_TOKEN_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "OAUTH2_DEVICE_AUTH_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "VERIFY_PROFILE", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], "adminEventsEnabled" : false, "adminEventsDetailsEnabled" : false, @@ -2215,7 +2376,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ] } }, { "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", @@ -2241,7 +2402,15 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "a407a1d6-a7f6-4a72-ba3a-149de03d5a43", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] } } ], "org.keycloak.storage.UserStorageProvider" : [ { @@ -2257,8 +2426,8 @@ "config" : { "ldap.attribute" : [ "createTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "true" ], "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], "user.model.attribute" : [ "createTimestamp" ] } }, { @@ -2269,8 +2438,8 @@ "config" : { "ldap.attribute" : [ "sn" ], "is.mandatory.in.ldap" : [ "true" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "lastName" ] } }, { @@ -2293,8 +2462,8 @@ "config" : { "ldap.attribute" : [ "mail" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "email" ] } }, { @@ -2303,19 +2472,19 @@ "providerId" : "group-ldap-mapper", "subComponents" : { }, "config" : { + "mode" : [ "LDAP_ONLY" ], "membership.attribute.type" : [ "DN" ], + "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "group.name.ldap.attribute" : [ "cn" ], - "preserve.group.inheritance" : [ "false" ], "membership.user.ldap.attribute" : [ "uid" ], - "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], - "mode" : [ "LDAP_ONLY" ], - "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], - "membership.ldap.attribute" : [ "member" ], "ignore.missing.groups" : [ "false" ], + "preserve.group.inheritance" : [ "false" ], + "membership.ldap.attribute" : [ "member" ], "memberof.ldap.attribute" : [ "memberOf" ], "group.object.classes" : [ "groupOfNames" ], - "drop.non.existing.groups.during.sync" : [ "false" ], - "groups.path" : [ "/" ] + "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], + "groups.path" : [ "/" ], + "drop.non.existing.groups.during.sync" : [ "false" ] } }, { "id" : "b6ff3285-35af-4e86-8bb4-d94b8e0d70bb", @@ -2339,15 +2508,15 @@ "is.mandatory.in.ldap" : [ "true" ], "attribute.force.default" : [ "false" ], "is.binary.attribute" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "username" ] } } ] }, "config" : { - "pagination" : [ "false" ], "fullSyncPeriod" : [ "-1" ], + "pagination" : [ "false" ], "startTls" : [ "false" ], "connectionPooling" : [ "true" ], "usersDn" : [ "ou=users,dc=dbrepo,dc=at" ], @@ -2355,15 +2524,15 @@ "useKerberosForPasswordAuthentication" : [ "false" ], "importEnabled" : [ "true" ], "enabled" : [ "true" ], + "bindCredential" : [ "admin" ], "changedSyncPeriod" : [ "-1" ], - "bindDn" : [ "cn=admin,dc=dbrepo,dc=at" ], "usernameLDAPAttribute" : [ "uid" ], - "bindCredential" : [ "admin" ], + "bindDn" : [ "cn=admin,dc=dbrepo,dc=at" ], "lastSync" : [ "1719252666" ], "vendor" : [ "other" ], "uuidLDAPAttribute" : [ "entryUUID" ], - "connectionUrl" : [ "ldap://identity-service:1389" ], "allowKerberosAuthentication" : [ "false" ], + "connectionUrl" : [ "ldap://identity-service:1389" ], "syncRegistrations" : [ "true" ], "authType" : [ "simple" ], "useTruststoreSpi" : [ "always" ], @@ -2375,14 +2544,6 @@ "validatePasswordPolicy" : [ "false" ] } } ], - "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "a407a1d6-a7f6-4a72-ba3a-149de03d5a43", - "providerId" : "declarative-user-profile", - "subComponents" : { }, - "config" : { - "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] - } - } ], "org.keycloak.keys.KeyProvider" : [ { "id" : "2f53ccf3-37b0-4d34-83e7-ed497499ee51", "name" : "rsa-enc-generated", @@ -2995,10 +3156,12 @@ "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email" : "", "parRequestUriLifespan" : "60", "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false", "shortVerificationUri" : "" }, - "keycloakVersion" : "24.0.5", + "keycloakVersion" : "26.0.4", "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, "clientProfiles" : { "profiles" : [ ] }, diff --git a/dbrepo-auth-service/init/Pipfile.lock b/dbrepo-auth-service/init/Pipfile.lock index c8224a7844942ed36a6fef30185dc094f516378d..57631a05559948613a5c9a63b37463c95a48da9a 100644 --- a/dbrepo-auth-service/init/Pipfile.lock +++ b/dbrepo-auth-service/init/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "certifi": { "hashes": [ - "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", - "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2024.12.14" + "version": "==2025.1.31" }, "charset-normalizer": { "hashes": [ @@ -175,11 +175,11 @@ "develop": { "certifi": { "hashes": [ - "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", - "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2024.12.14" + "version": "==2025.1.31" }, "charset-normalizer": { "hashes": [ diff --git a/dbrepo-auth-service/init/app.py b/dbrepo-auth-service/init/app.py index 270e959fecb30c9f200ba96c8186357cb94599b9..5e42003a192d4b6536ccd8f6c6ccc1be9dcedd6d 100644 --- a/dbrepo-auth-service/init/app.py +++ b/dbrepo-auth-service/init/app.py @@ -7,7 +7,7 @@ endpoint = os.getenv('AUTH_SERVICE_ENDPOINT', 'http://localhost:8080') system_username = os.getenv('SYSTEM_USERNAME', 'admin') -def fetch() -> str: +def fetch() -> (str, str): print(f'Fetching user id of internal user with username: {system_username}') response = post(url=f'{endpoint}/realms/master/protocol/openid-connect/token', data=dict({ 'username': os.getenv('AUTH_SERVICE_ADMIN', 'admin'), @@ -25,7 +25,8 @@ def fetch() -> str: if response.status_code != 200 or len(response.json()) != 1: raise FileNotFoundError(f'Failed to obtain user') ldap_user = response.json()[0] - print(f'Successfully fetched user id: {ldap_user["id"]}') + user_id = ldap_user["id"] + print(f'Successfully fetched user id: {user_id}') if 'attributes' not in ldap_user or ldap_user['attributes'] is None: raise ModuleNotFoundError(f'Failed to obtain user attributes: {ldap_user}') ldap_user_attrs = ldap_user['attributes'] @@ -35,10 +36,10 @@ def fetch() -> str: raise EnvironmentError(f'Failed to obtain ldap id: wrong length {len(ldap_user_attrs["LDAP_ID"])} != 1') ldap_user_id = ldap_user_attrs['LDAP_ID'][0] print(f'Successfully fetched ldap user id: {ldap_user_id}') - return ldap_user_id + return (ldap_user_id, user_id) -def save(user_id: str) -> None: +def save(user_id: str, keycloak_id: str) -> None: conn = mariadb.connect(user=os.getenv('METADATA_USERNAME', 'root'), password=os.getenv('METADATA_DB_PASSWORD', 'dbrepo'), host="metadata-db", @@ -46,12 +47,13 @@ def save(user_id: str) -> None: database=os.getenv('METADATA_DB', 'dbrepo')) cursor = conn.cursor() cursor.execute( - "INSERT IGNORE INTO `mdb_users` (`id`, `username`, `email`, `mariadb_password`, `is_internal`) VALUES (?, ?, LEFT(UUID(), 20), PASSWORD(LEFT(UUID(), 20)), true)", - (user_id, system_username)) + "INSERT IGNORE INTO `mdb_users` (`id`, `keycloak_id`, `username`, `mariadb_password`, `is_internal`) VALUES (?, ?, ?, PASSWORD(LEFT(UUID(), 20)), true)", + (user_id, keycloak_id, system_username)) conn.commit() conn.close() if __name__ == '__main__': - save(fetch()) + user_id, keycloak_id = fetch() + save(user_id, keycloak_id) print(f'Successfully inserted user') diff --git a/dbrepo-auth-service/init/test/test_unit_app.py b/dbrepo-auth-service/init/test/test_unit_app.py index 624b7d8d53e7393d2077c214278bdb98f32297ba..af6aed379a3780157718d760bdabd79475e8d249 100644 --- a/dbrepo-auth-service/init/test/test_unit_app.py +++ b/dbrepo-auth-service/init/test/test_unit_app.py @@ -16,38 +16,6 @@ class AppUnitTest(unittest.TestCase): "session_state": "ae64d2bd-3225-4e05-9943-2bb91fb8fe52", "scope": "profile email" } - user_res = [ - {"id": "5b516520-67cb-4aa0-86a6-d12f8b8f1a20", - "username": "admin", - "firstName": "User1", - "lastName": "Bar1", - "emailVerified": False, - "attributes": {"LDAP_ENTRY_DN": ["cn=admin,ou=users,dc=dbrepo,dc=at"], - "createTimestamp": ["20250120141013Z"], - "modifyTimestamp": ["20250120141013Z"], - "LDAP_ID": ["02b6e096-6b84-103f-81f6-1f6da137f2bb"]}, - "createdTimestamp": 1737382606939, - "enabled": True, - "totp": False, - "federationLink": "c109d473-5ce1-4032-af7b-02e5442f5c07", - "disableableCredentialTypes": [], - "requiredActions": [], - "notBefore": 0, - "access": {"manageGroupMembership": True, - "view": True, - "mapRoles": True, - "impersonate": True, - "manage": True}}] - - def test_fetch_succeeds(self): - with requests_mock.Mocker() as mock: - # mock - mock.post(f'{endpoint}/realms/master/protocol/openid-connect/token', json=self.token_res, status_code=200) - mock.get(f'{endpoint}/admin/realms/dbrepo/users/?username=admin', json=self.user_res, status_code=200) - - # test - user_id = fetch() - self.assertEqual("02b6e096-6b84-103f-81f6-1f6da137f2bb", user_id) def test_fetch_token_bad_request_fails(self): with requests_mock.Mocker() as mock: diff --git a/dbrepo-auth-service/listeners/.gitignore b/dbrepo-auth-service/listeners/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5d6e1ae3b181727bbd28cdb0107283899c3a0fd2 --- /dev/null +++ b/dbrepo-auth-service/listeners/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +target/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/dbrepo-auth-service/listeners/pom.xml b/dbrepo-auth-service/listeners/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..e70201b96ac3d853a0274c7f06499d336f1c27cf --- /dev/null +++ b/dbrepo-auth-service/listeners/pom.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-parent</artifactId> + <version>24.0.5</version> + </parent> + + <groupId>at.tuwien</groupId> + <artifactId>create-event-listener</artifactId> + <name>dbrepo-auth-service</name> + <version>24.0.5</version> + + <description>Create event listener</description> + + <url>https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/</url> + <developers> + <developer> + <name>Martin Weise</name> + <email>martin.weise@tuwien.ac.at</email> + <organization>TU Wien</organization> + </developer> + </developers> + + <properties> + <java.version>17</java.version> + <maven.version>3.9.8</maven.version> + <maven.compiler.source>${java.version}</maven.compiler.source> + <maven.compiler.target>${java.version}</maven.compiler.target> + <maven.compiler.release>${java.version}</maven.compiler.release> + <maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version> + <testcontainers.version>1.19.1</testcontainers.version> + <keycloak-testcontainer.version>3.2.0</keycloak-testcontainer.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-server-spi</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-server-spi-private</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-services</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-saml-core-public</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.jboss.logging</groupId> + <artifactId>jboss-logging</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.jboss.spec.javax.ws.rs</groupId> + <artifactId>jboss-jaxrs-api_2.1_spec</artifactId> + </dependency> + <!-- Tests --> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${testcontainers.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.github.dasniko</groupId> + <artifactId>testcontainers-keycloak</artifactId> + <version>${keycloak-testcontainer.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <finalName>create-event-listener</finalName> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven-compiler-plugin.version}</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + <plugin> + <groupId>org.wildfly.plugins</groupId> + <artifactId>wildfly-maven-plugin</artifactId> + <configuration> + <skip>false</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/dbrepo-auth-service/listeners/src/main/java/at/tuwien/Client.java b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/Client.java new file mode 100644 index 0000000000000000000000000000000000000000..769ec49097223e5fd49f76d855d9acef1cfbe35c --- /dev/null +++ b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/Client.java @@ -0,0 +1,65 @@ +package at.tuwien; + +import org.jboss.logging.Logger; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Base64; + +public class Client { + private static final Logger log = Logger.getLogger(Client.class); + + public static void postService(String data) throws IOException { + try { + final String urlString = System.getenv("METADATA_SERVICE_ENDPOINT"); + log.debugf("METADATA_SERVICE_ENDPOINT: %s", urlString); + if (urlString == null || urlString.isEmpty()) { + throw new IllegalArgumentException("Environment variable METADATA_SERVICE_ENDPOINT is not set or is empty."); + } + final String systemUsername = System.getenv("SYSTEM_USERNAME"); + if (systemUsername == null || systemUsername.isEmpty()) { + throw new IllegalArgumentException("Environment variable SYSTEM_USERNAME is not set or is empty."); + } + log.debugf("SYSTEM_USERNAME: %s", systemUsername); + final String systemPassword = System.getenv("SYSTEM_PASSWORD"); + if (systemPassword == null || systemPassword.isEmpty()) { + throw new IllegalArgumentException("Environment variable SYSTEM_PASSWORD is not set or is empty."); + } + + URL url = URI.create(urlString).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + final String token = systemUsername + ":" + systemPassword; + conn.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(token.getBytes( + Charset.defaultCharset()))); + conn.setRequestProperty("Content-Type", "application/json; utf-8"); + + OutputStream os = conn.getOutputStream(); + os.write(data.getBytes()); + os.flush(); + + final int responseCode = conn.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_CREATED && responseCode != HttpURLConnection.HTTP_OK) { + throw new RuntimeException("Failed : HTTP error code : " + responseCode); + } + + final BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String output; + log.debugf("Output from Server .... \n"); + while ((output = br.readLine()) != null) { + System.out.println(output); + log.debugf("Input from Server: %s", output); + } + conn.disconnect(); + } catch (IOException e) { + throw new IOException("Failed to post service: " + e.getMessage(), e); + } + } +} diff --git a/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProvider.java b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2b5d9221a7beb49385e129f6ccb3fff0aed73002 --- /dev/null +++ b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProvider.java @@ -0,0 +1,139 @@ +package at.tuwien; + +import org.jboss.logging.Logger; +import org.keycloak.events.Event; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventType; +import org.keycloak.events.admin.AdminEvent; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RealmProvider; +import org.keycloak.models.UserModel; + +import java.util.StringJoiner; + +public class CreateEventListenerProvider implements EventListenerProvider { + + private static final Logger log = Logger.getLogger(CreateEventListenerProvider.class); + + private final KeycloakSession session; + private final RealmProvider model; + + public CreateEventListenerProvider(KeycloakSession session) { + this.session = session; + this.model = session.realms(); + } + + @Override + public void onEvent(Event event) { + + log.debugf("New %s Event", event.getType()); + log.debugf("onEvent-> %s", toString(event)); + + if (EventType.REGISTER.equals(event.getType())) { + + event.getDetails().forEach((key, value) -> log.debugf("%s : %s", key, value)); + + RealmModel realm = this.model.getRealm(event.getRealmId()); + UserModel user = this.session.users().getUserById(realm, event.getUserId()); + sendUserData(user); + } + + } + + @Override + public void onEvent(AdminEvent adminEvent, boolean b) { + log.debug("onEvent(AdminEvent)"); + log.debugf("Resource path: %s", adminEvent.getResourcePath()); + log.debugf("Resource type: %s", adminEvent.getResourceType()); + log.debugf("Operation type: %s", adminEvent.getOperationType()); + log.debugf("AdminEvent.toString(): %s", toString(adminEvent)); + if (ResourceType.USER.equals(adminEvent.getResourceType()) + && OperationType.CREATE.equals(adminEvent.getOperationType())) { + RealmModel realm = this.model.getRealm(adminEvent.getRealmId()); + UserModel user = this.session.users().getUserById(realm, adminEvent.getResourcePath().substring(6)); + + sendUserData(user); + } + } + + private void sendUserData(UserModel user) { + final String userData = "{" + + quoteAttr("id", user.getId()) + ", " + + quoteAttr("username", user.getUsername()) + ", " + + quoteAttr("email", user.getEmail()) + ", " + + quoteAttr("ldap_id", user.getFirstAttribute("LDAP_ID")) + ", " + + quoteAttr("given_name", user.getFirstName()) + ", " + + quoteAttr("family_name", user.getLastName()) + + "}"; + try { + log.debugf("create new user in API: %s", userData); + Client.postService(userData); + } catch (Exception e) { + log.errorf("Failed to call API: %s", e); + } + } + + private static String quoteAttr(String key, String value) { + if (value == null || value.isBlank() || value.isEmpty() || value.contentEquals(" ")) { + return "\"" + key + "\": null"; + } + return "\"" + key + "\": \"" + value + "\""; + } + + @Override + public void close() { + } + + private String toString(Event event) { + final StringJoiner joiner = new StringJoiner(", "); + joiner.add("type=" + event.getType()) + .add("realmId=" + event.getRealmId()) + .add("clientId=" + event.getClientId()) + .add("userId=" + event.getUserId()) + .add("ipAddress=" + event.getIpAddress()); + if (event.getError() != null) { + joiner.add("error=" + event.getError()); + } + if (event.getDetails() != null) { + event.getDetails().forEach((key, value) -> { + if (value == null || !value.contains(" ")) { + joiner.add(key + "=" + value); + } else { + joiner.add(key + "='" + value + "'"); + } + }); + } + return joiner.toString(); + } + + private String toString(AdminEvent event) { + RealmModel realm = this.model.getRealm(event.getRealmId()); + UserModel newRegisteredUser = this.session.users().getUserById(realm, event.getAuthDetails().getUserId()); + + StringJoiner joiner = new StringJoiner(", "); + + joiner.add("operationType=" + event.getOperationType()) + .add("realmId=" + event.getAuthDetails().getRealmId()) + .add("clientId=" + event.getAuthDetails().getClientId()) + .add("userId=" + event.getAuthDetails().getUserId()); + + if (newRegisteredUser != null) { + joiner.add("email=" + newRegisteredUser.getEmail()) + .add("username=" + newRegisteredUser.getUsername()) + .add("firstName=" + newRegisteredUser.getFirstName()) + .add("lastName=" + newRegisteredUser.getLastName()); + } + + joiner.add("ipAddress=" + event.getAuthDetails().getIpAddress()) + .add("resourcePath=" + event.getResourcePath()); + + if (event.getError() != null) { + joiner.add("error=" + event.getError()); + } + + return joiner.toString(); + } +} diff --git a/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProviderFactory.java b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProviderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..61477ffa33169504a3368d11d0750c9c1404e160 --- /dev/null +++ b/dbrepo-auth-service/listeners/src/main/java/at/tuwien/CreateEventListenerProviderFactory.java @@ -0,0 +1,36 @@ +package at.tuwien; + +import org.keycloak.Config; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventListenerProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +public class CreateEventListenerProviderFactory implements EventListenerProviderFactory { + + @Override + public EventListenerProvider create(KeycloakSession keycloakSession) { + return new CreateEventListenerProvider(keycloakSession); + } + + @Override + public void init(Config.Scope scope) { + + } + + @Override + public void postInit(KeycloakSessionFactory keycloakSessionFactory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "create-event-listener"; + } + +} diff --git a/dbrepo-auth-service/listeners/src/main/resources/META-INF/jboss-deployment-structure.xml b/dbrepo-auth-service/listeners/src/main/resources/META-INF/jboss-deployment-structure.xml new file mode 100644 index 0000000000000000000000000000000000000000..c0330ba082479a3bd9d0caf86508b5067251ed84 --- /dev/null +++ b/dbrepo-auth-service/listeners/src/main/resources/META-INF/jboss-deployment-structure.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<jboss-deployment-structure> + <deployment> + <dependencies> + <module name="org.keycloak.keycloak-services" /> + </dependencies> + </deployment> +</jboss-deployment-structure> \ No newline at end of file diff --git a/dbrepo-auth-service/listeners/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/dbrepo-auth-service/listeners/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory new file mode 100644 index 0000000000000000000000000000000000000000..df3c5521f0958fed5fadb4f006d8ee6eb50f97c2 --- /dev/null +++ b/dbrepo-auth-service/listeners/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory @@ -0,0 +1 @@ +at.tuwien.CreateEventListenerProviderFactory \ No newline at end of file diff --git a/dbrepo-auth-service/listeners/src/test/java/at/tuwien/EventListenerIntegrationTest.java b/dbrepo-auth-service/listeners/src/test/java/at/tuwien/EventListenerIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3d6ee94ccd764d16d589b33b51d501e2a2a3d82 --- /dev/null +++ b/dbrepo-auth-service/listeners/src/test/java/at/tuwien/EventListenerIntegrationTest.java @@ -0,0 +1,18 @@ +package at.tuwien; + +import dasniko.testcontainers.keycloak.KeycloakContainer; +import org.testcontainers.images.PullPolicy; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class EventListenerIntegrationTest { + + @Container + private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") + .withImagePullPolicy(PullPolicy.alwaysPull()) + .withAdminUsername("admin") + .withAdminPassword("admin") + .withRealmImportFile("dbrepo-realm.json") + .withEnv("KC_HOSTNAME_STRICT_HTTPS", "false"); +} diff --git a/dbrepo-auth-service/listeners/src/test/resources/dbrepo-realm.json b/dbrepo-auth-service/listeners/src/test/resources/dbrepo-realm.json new file mode 100644 index 0000000000000000000000000000000000000000..56f2003e961a14d09bcd56832437f915cae04dea --- /dev/null +++ b/dbrepo-auth-service/listeners/src/test/resources/dbrepo-realm.json @@ -0,0 +1,2798 @@ +{ + "id" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "realm" : "dbrepo", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 1, + "accessTokenLifespan" : 900, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 864000, + "ssoSessionMaxLifespan" : 2592000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 1800, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "none", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : true, + "loginWithEmailAllowed" : false, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "48f38342-1e3f-427a-995d-c436eaee65cb", + "name" : "default-user-handling", + "description" : "${default-user-handling}", + "composite" : true, + "composites" : { + "realm" : [ "modify-user-theme", "modify-user-information" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "9bb4a8dc-28e0-4645-b62f-cc94425f0cb0", + "name" : "default-maintenance-handling", + "description" : "${default-maintenance-handling}", + "composite" : true, + "composites" : { + "realm" : [ "create-maintenance-message", "find-maintenance-message", "update-maintenance-message", "delete-maintenance-message", "list-maintenance-messages" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "7ee1c424-11b0-46a9-b0ed-725e9b7fc40c", + "name" : "default-system-roles", + "description" : "${default-system-roles}", + "composite" : true, + "composites" : { + "realm" : [ "delete-database-view", "update-semantic-unit", "export-query-data", "default-data-steward-roles", "execute-query", "default-user-handling", "delete-table-data", "find-query", "list-database-views", "persist-query", "update-search-index", "delete-database-access", "view-table-history", "create-ontology", "update-ontology", "modify-user-theme", "default-system-roles", "create-semantic-concept", "default-container-handling", "create-container", "create-table", "default-broker-handling", "default-maintenance-handling", "execute-semantic-query", "uma_authorization", "table-semantic-analyse", "list-containers", "check-database-access", "escalated-query-handling", "delete-identifier", "modify-database-owner", "list-tables", "export-table-data", "create-database-access", "delete-container", "re-execute-query", "create-semantic-unit", "escalated-identifier-handling", "system", "update-table-statistic", "escalated-semantics-handling", "default-database-handling", "delete-ontology", "find-database", "find-database-view", "update-semantic-concept", "find-user", "import-database-data", "publish-identifier", "default-roles-dbrepo", "find-foreign-user", "create-database", "create-maintenance-message", "find-maintenance-message", "escalated-container-handling", "default-researcher-roles", "default-identifier-handling", "escalated-user-handling", "modify-user-information", "create-database-view", "update-maintenance-message", "delete-foreign-table", "offline_access", "modify-foreign-table-column-semantics", "delete-maintenance-message", "find-container", "insert-table-data", "modify-identifier-metadata", "modify-database-image", "escalated-broker-handling", "modify-table-column-semantics", "escalated-database-handling", "default-semantics-handling", "update-database-access", "default-query-handling", "find-table", "list-queries", "default-developer-roles", "create-identifier", "escalated-table-handling", "find-identifier", "view-table-data", "list-licenses", "default-table-handling", "list-identifiers", "create-foreign-identifier", "list-databases", "list-ontologies", "modify-database-visibility", "list-maintenance-messages", "delete-table" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "143ba359-5fa2-451e-8296-43ecf20bb251", + "name" : "update-semantic-concept", + "description" : "${update-semantic-concept}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "5136d7a3-e3f0-4585-bacd-15cb8a56095c", + "name" : "escalated-container-handling", + "description" : "${escalated-container-handling}", + "composite" : true, + "composites" : { + "realm" : [ "create-container", "delete-container" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "b0bc8649-7d84-4dd3-84f0-7f174425babe", + "name" : "list-tables", + "description" : "${list-tables}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "bfd85d9c-2772-4660-a8f0-cdc0cd8252b3", + "name" : "default-database-handling", + "description" : "${default-database-handling}", + "composite" : true, + "composites" : { + "realm" : [ "modify-database-image", "modify-database-owner", "update-database-access", "create-database", "list-databases", "create-database-access", "find-database", "modify-database-visibility", "import-database-data", "delete-database-access", "check-database-access" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "74648f9a-777e-4ef9-b97b-4c5d749d862f", + "name" : "update-search-index", + "description" : "${update-search-index}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "22492b64-c633-48a0-9678-b28669f2885b", + "name" : "execute-semantic-query", + "description" : "${execute-semantic-query}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "4ed919fa-edc5-44e5-9411-607786e4a86d", + "name" : "view-table-history", + "description" : "${view-table-history}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d89a2881-b642-4abb-b990-196e71372f6b", + "name" : "default-table-handling", + "description" : "${default-table-handling}", + "composite" : true, + "composites" : { + "realm" : [ "modify-table-column-semantics", "list-tables", "update-table-statistic", "find-table", "create-table", "delete-table" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "b0d66d3d-59b4-4aae-aa66-e3d5a49f28e3", + "name" : "view-database-view-data", + "description" : "${view-database-view-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "f5ea431a-9b2c-4195-bcb4-9511f38e4b44", + "name" : "create-database-view", + "description" : "${create-database-view}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a5ffc20e-8b11-498c-9f3b-b5740aec24c7", + "name" : "default-semantics-handling", + "description" : "${default-semantics-handling}", + "composite" : true, + "composites" : { + "realm" : [ "create-semantic-unit", "create-semantic-concept", "execute-semantic-query", "table-semantic-analyse" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "fe4a01f3-6590-4df6-9ade-5a9c1fae4736", + "name" : "create-semantic-unit", + "description" : "${create-semantic-unit}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "0e12eedf-545d-4d32-ac4d-2821dcb118b8", + "name" : "update-table-statistic", + "description" : "${update-table-statistic}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e63e61a2-d852-4ad3-bfb5-92d9ceafef6a", + "name" : "escalated-user-handling", + "description" : "${escalated-user-handling}", + "composite" : true, + "composites" : { + "realm" : [ "find-user" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "be4e1aba-e276-4241-b6ea-01dce6c52f8b", + "name" : "find-container", + "description" : "${find-container}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "3a801b48-f3c2-4bc6-aa25-c7a91d5b32a7", + "name" : "default-researcher-roles", + "description" : "${default-researcher-roles}", + "composite" : true, + "composites" : { + "realm" : [ "default-table-handling", "default-semantics-handling", "default-container-handling", "default-query-handling", "default-user-handling", "default-database-handling", "default-broker-handling", "default-identifier-handling" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "3d8104fb-8307-40f0-b4b2-c3e518957110", + "name" : "view-table-data", + "description" : "${view-table-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "fe71b907-7020-44ab-9964-da2b87264582", + "name" : "create-database", + "description" : "${create-database}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e51b63c2-48dd-4bd6-95fb-d257d21b26ba", + "name" : "import-database-data", + "description" : "${import-database-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "1f0a9b13-c2b8-474c-bc08-59dbd71835a6", + "name" : "modify-database-image", + "description" : "${modify-database-image}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a7ad038c-5c06-42fc-951c-15ac09d4df66", + "name" : "modify-database-owner", + "description" : "${modify-database-owner}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "c12c1f4e-186f-4153-a795-26e79fb623d6", + "name" : "create-ontology", + "description" : "${create-ontology}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "b60a5694-4099-4f7d-a7e9-4c433e0eb9c9", + "name" : "update-semantic-unit", + "description" : "${update-semantic-unit}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e9854bbb-4580-4757-b1ae-305934173249", + "name" : "create-database-access", + "description" : "${create-database-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "50c604c1-7c6e-43f3-9c43-2398f5eff66e", + "name" : "list-databases", + "description" : "${list-databases}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "535f1484-4514-4d24-8d97-e3f6c11a426b", + "name" : "create-container", + "description" : "${create-container}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "f4116230-8642-4bb7-bbc8-db9c5c07b558", + "name" : "create-maintenance-message", + "description" : "${create-maintenance-message}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "973f0999-cc70-4b28-9f43-979c470bea8e", + "name" : "default-data-steward-roles", + "description" : "${default-data-steward-roles}", + "composite" : true, + "composites" : { + "realm" : [ "escalated-identifier-handling", "default-semantics-handling", "escalated-semantics-handling", "default-user-handling" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e1383fb7-d54c-4732-9146-93030eb2ca50", + "name" : "escalated-query-handling", + "description" : "${escalated-query-handling}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "993b5c69-9eb2-42af-ac28-b4a46c6b61f2", + "name" : "find-user", + "description" : "${find-user}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e4cfdc4d-2373-477b-a8df-161db99aba00", + "name" : "create-foreign-identifier", + "description" : "${create-foreign-identifier}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "6a5872a5-2b51-415d-ae2d-25a6db4a35df", + "name" : "escalated-semantics-handling", + "description" : "${escalated-semantics-handling}", + "composite" : true, + "composites" : { + "realm" : [ "update-semantic-unit", "create-ontology", "update-ontology", "list-ontologies", "delete-ontology", "modify-foreign-table-column-semantics", "update-semantic-concept" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "09147c48-273b-450b-8b11-7ef9b9245244", + "name" : "export-table-data", + "description" : "${export-table-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d14af590-60a8-4d75-b864-40ee0165bd7f", + "name" : "delete-database-access", + "description" : "${delete-database-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "be051d45-cd74-4b13-8a45-f2d3351bd995", + "name" : "table-semantic-analyse", + "description" : "${table-semantic-analyse}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "272a79a7-e282-4261-8f7d-5d5d1364243a", + "name" : "update-maintenance-message", + "description" : "${update-maintenance-message}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "64c16bfb-2015-48ad-a23f-637ff24419cb", + "name" : "default-query-handling", + "description" : "${default-query-handling}", + "composite" : true, + "composites" : { + "realm" : [ "delete-database-view", "export-query-data", "execute-query", "delete-table-data", "export-table-data", "list-queries", "find-query", "list-database-views", "persist-query", "view-table-data", "re-execute-query", "view-table-history", "create-database-view", "find-database-view", "insert-table-data" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "c047d521-cec3-4444-86c4-aef098489b7b", + "name" : "delete-maintenance-message", + "description" : "${delete-maintenance-message}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "88f82262-be80-4d18-9fb4-5529da031f33", + "name" : "system", + "description" : "${system}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e14ab76b-1c24-484d-ae2d-478b8457edea", + "name" : "list-licenses", + "description" : "${list-licenses}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d4f29937-3ca0-41e9-9786-2b7b921b6cdd", + "name" : "modify-foreign-table-column-semantics", + "description" : "${modify-foreign-table-column-semantics}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "8eda9f5c-938c-4915-bed5-6a81a1de15a8", + "name" : "list-database-views", + "description" : "${list-database-views}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "b372f8f7-d203-4293-b991-ad93fb505917", + "name" : "escalated-database-handling", + "description" : "${escalated-database-handling}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a", + "name" : "default-roles-dbrepo", + "description" : "${role_default-roles}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "3293799a-82b9-4f47-8f25-1aad2e0222fd", + "name" : "find-identifier", + "description" : "${find-identifier}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "aaa3f804-38a0-4474-b8e9-f1020c4b3f62", + "name" : "list-queries", + "description" : "${list-queries}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "76e38f7b-99bf-4d12-8d74-1c7d8812f443", + "name" : "update-ontology", + "description" : "${update-ontology}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "11f7973e-d1eb-42cb-a35d-c59dfc122775", + "name" : "modify-user-theme", + "description" : "${modify-user-theme}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "f392bfcb-0be5-4fad-9ce4-8ac6396f176d", + "name" : "export-query-data", + "description" : "${export-query-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "da493b7e-fb9b-43ca-82a5-e274ad2e6b39", + "name" : "find-query", + "description" : "${find-query}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a4d4a788-ebcf-4d32-baed-4a85616ca037", + "name" : "escalated-identifier-handling", + "description" : "${escalated-identifier-handling}", + "composite" : true, + "composites" : { + "realm" : [ "create-foreign-identifier", "modify-identifier-metadata" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "ea38d69d-17b8-4c65-95e8-1c3501b83618", + "name" : "default-container-handling", + "description" : "${default-container-handling}", + "composite" : true, + "composites" : { + "realm" : [ "find-container", "list-containers" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "8b8813e0-af07-4d04-a8c1-e3f37192bace", + "name" : "publish-identifier", + "description" : "${publish-identifier}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "47f5eee7-9821-4bf8-b434-0da1f81c3e5a", + "name" : "default-broker-handling", + "description" : "${default-broker-handling}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "71874bde-64a5-4a69-8685-d8998303a80c", + "name" : "delete-table-data", + "description" : "${delete-table-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "7c0306fc-3b03-4c64-87d1-9a34f2073977", + "name" : "modify-table-column-semantics", + "description" : "${modify-table-column-semantics}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "cd0ee04c-4a5e-4035-a11b-f6a1165f7829", + "name" : "delete-container", + "description" : "${delete-container}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "67ee39c0-d601-4a67-a0fe-c4f0021d557e", + "name" : "list-containers", + "description" : "${list-containers}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "795c7bb8-3502-414a-a97b-2ba1cfd6a79c", + "name" : "persist-query", + "description" : "${persist-query}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d05e7698-ddf5-4f20-9027-771afb2cc3c7", + "name" : "list-identifiers", + "description" : "${list-identifiers}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e4bfaf36-9a5d-43e0-9fa3-0f4ea7bad8d0", + "name" : "default-developer-roles", + "description" : "${default-developer-roles}", + "composite" : true, + "composites" : { + "realm" : [ "escalated-query-handling", "escalated-broker-handling", "default-table-handling", "escalated-database-handling", "default-container-handling", "default-query-handling", "default-user-handling", "default-database-handling", "default-maintenance-handling", "escalated-container-handling", "escalated-table-handling", "default-identifier-handling" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "e2cb054e-ea41-4ab0-881b-e6f576f7424e", + "name" : "create-semantic-concept", + "description" : "${create-semantic-concept}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "feb612cc-96a6-4ed2-aaa5-01f39b25beb5", + "name" : "insert-table-data", + "description" : "${insert-table-data}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a0942e33-441b-4343-9f02-4353d03f7bbb", + "name" : "find-database", + "description" : "${find-database}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "6a0bb740-4448-49be-aee8-6dd183325be5", + "name" : "delete-foreign-table", + "description" : "${delete-foreign-table}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "7f3652c7-3073-4566-ab63-25385495ebc3", + "name" : "modify-database-visibility", + "description" : "${modify-database-visibility}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "4a5df51d-f14d-41a2-ad70-6521df5a5b4f", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "fd41c4c3-d2f8-4f49-84c7-dba84e9a5575", + "name" : "execute-query", + "description" : "${execute-query}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "2963c2bb-b129-4224-b98f-c8eeab8e72d1", + "name" : "create-table", + "description" : "${create-table}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "0c487c93-448f-4a82-8b9f-ebd8a0904bf8", + "name" : "find-foreign-user", + "description" : "${find-foreign-user}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "cf9735a9-fb70-4cc5-b5f4-75afc4e5654b", + "name" : "modify-identifier-metadata", + "description" : "${modify-identifier-metadata}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "64c2b8f2-1527-4928-81ea-b2651512d028", + "name" : "delete-ontology", + "description" : "${delete-ontology}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d6e38368-b40f-423b-82e4-e8aa595237c9", + "name" : "find-maintenance-message", + "description" : "${find-maintenance-message}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "fd1cc463-3e67-49d9-81b8-2cd90c1daa9c", + "name" : "check-database-access", + "description" : "${check-database-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "74013867-e426-46cc-ab98-2f4a9225ad1e", + "name" : "find-table", + "description" : "${find-table}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a2cc60df-d280-46c5-a539-92e2aa249b4a", + "name" : "modify-user-information", + "description" : "${modify-user-information}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "c367241f-b5b5-491f-84d5-07fe1bef3877", + "name" : "default-identifier-handling", + "description" : "${default-identifier-handling}", + "composite" : true, + "composites" : { + "realm" : [ "delete-identifier", "list-identifiers", "create-identifier", "find-identifier", "publish-identifier" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "ba1ad8f2-39aa-487d-987f-645e8a459559", + "name" : "delete-table", + "description" : "${delete-table}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "09f7bdb0-296f-46c8-a3a3-8f9254fb17e4", + "name" : "list-maintenance-messages", + "description" : "${list-maintenance-messages}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "fe3bc45c-61c2-4ece-bcaf-d410dc7de501", + "name" : "update-database-access", + "description" : "${update-database-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "f43e86ed-76de-4ca8-9b5e-c292c9359bfe", + "name" : "escalated-broker-handling", + "description" : "${escalated-broker-handling}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "916b1e65-f60c-42cd-96e4-5c98ffc1ba3c", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "d1afa3ed-bf4f-469a-a061-ad7325fb8d9e", + "name" : "delete-database-view", + "description" : "${delete-database-view}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "6f044bad-6651-4408-bffa-20c2d8f92eee", + "name" : "create-identifier", + "description" : "${create-identifier}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "be91195a-e30a-4d15-a8da-0aca0a68782f", + "name" : "escalated-table-handling", + "description" : "${escalated-table-handling}", + "composite" : true, + "composites" : { + "realm" : [ "delete-foreign-table" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "98bee7d6-d78c-4e7f-b6a3-3705968b248c", + "name" : "list-ontologies", + "description" : "${list-ontologies}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "15720c6b-027d-4d53-a0ff-0124bfab7c4c", + "name" : "re-execute-query", + "description" : "${re-execute-query}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "a9b5181a-8135-41d3-9862-ef80af42211d", + "name" : "delete-identifier", + "description" : "${delete-identifier}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + }, { + "id" : "469c2e63-cda6-48d4-ab8f-eb59a2c69798", + "name" : "find-database-view", + "description" : "${find-database-view}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "4628f654-f8f3-483b-8f92-2a7fc5930b14", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "95c2cc47-12f5-4d73-8b74-67e270c45ade", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "824791f3-c345-42f8-b103-b7e6d7e40114", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "1f840202-b7e2-4195-bac9-64e64dad2037", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "3c32c096-bb13-44c9-a080-d756a48a9ea3", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "e4b85a68-7f31-4fcf-89a2-f10d7df358e9", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "7d317752-ae56-46f2-a2ce-67c64d1b35f6", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "28824208-976e-4622-b4d7-3d18efbb46fa", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-realms", "view-identity-providers", "manage-identity-providers", "manage-authorization", "query-clients", "view-authorization", "view-users", "manage-users", "view-realm", "query-users", "view-clients", "query-groups", "create-client", "manage-clients", "manage-events", "impersonation", "view-events", "manage-realm" ] + } + }, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "57e846a2-930d-4621-819d-c35086507146", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "7fad9cde-bf96-475a-9174-14a87da51f95", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "bbcac294-d78a-4ea1-a4bf-0384266d2fe1", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "480e1437-ab9e-47de-b47a-edc6b6e285de", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "b9a9a8f5-f91e-4e73-9e88-1cdf42bd49f9", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "4d1397fb-247c-436f-b26f-124cd89afb08", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "e31f522b-b283-4ae1-b875-52afcd98b1d2", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "51822d02-fa28-4a49-89da-bc534719d8a8", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "b2743ce5-0ce8-4157-ae00-f693560f0b39", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "7ea3d7e0-9bf4-438a-b773-243daf622aaa", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + }, { + "id" : "fb73f6f5-0ed5-41d0-852c-0eb3b195b15a", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "dbrepo-client" : [ ], + "admin-cli" : [ ], + "rabbitmq-client" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "de0cfd5e-c2fe-4082-ac39-e3b092139a0f", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "88694c91-753d-4c44-9740-ec9ac06bba45", + "attributes" : { } + } ], + "account" : [ { + "id" : "acd78c04-eefc-4344-a5b4-3fc83d848936", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "939be844-8c49-45b3-9ca1-4b10a454b346", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "e52fdf00-3e73-4c17-bc1c-643493710a6b", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "b02a822e-a708-420a-bddc-1a315033fd7c", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "c590e5f5-2cbf-4151-b1dc-96c454f1f654", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "15974151-6c13-426b-8cc3-7683dd1311e1", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "c12d8d94-c2df-498e-bbe4-2f934a83ae92", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + }, { + "id" : "55f85811-bded-4d6b-8f7b-45844b963875", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "attributes" : { } + } ] + } + }, + "groups" : [ { + "id" : "f2ce17fe-7b15-47a4-bbf8-86f415298fa9", + "name" : "data-stewards", + "path" : "/data-stewards", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "default-data-steward-roles" ], + "clientRoles" : { } + }, { + "id" : "124d9888-0b6e-46aa-8225-077dcedaf16e", + "name" : "developers", + "path" : "/developers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "default-developer-roles" ], + "clientRoles" : { } + }, { + "id" : "f467c38e-9041-4faa-ae0b-39cec65ff4db", + "name" : "researchers", + "path" : "/researchers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "default-researcher-roles" ], + "clientRoles" : { } + }, { + "id" : "2b9f94b4-d434-4a98-8eab-25678cfee983", + "name" : "system", + "path" : "/system", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "default-system-roles" ], + "clientRoles" : { } + } ], + "defaultRole" : { + "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a", + "name" : "default-roles-dbrepo", + "description" : "${role_default-roles}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0" + }, + "defaultGroups" : [ "/researchers" ], + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "rabbitmq.tag:administrator", + "roles" : [ "escalated-broker-handling" ] + }, { + "clientScope" : "rabbitmq.tag:management", + "roles" : [ "default-broker-handling" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "e767a4a6-79e9-4e08-82b7-1076e1a09142", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/dbrepo/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/dbrepo/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "d3c4a04e-39ce-4549-a34a-11e25774cd96", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/dbrepo/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/dbrepo/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "22d90d9c-9881-474c-8dfd-a62c808a9f1c", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "81ef0f59-a5ca-4be4-a1d1-0c32edf1cfd6", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "88694c91-753d-4c44-9740-ec9ac06bba45", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "6b7ef364-4132-4831-b4e2-b6e9e9dc63ee", + "clientId" : "dbrepo-client", + "name" : "${dbrepo-client}", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG", + "redirectUris" : [ "*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1680085365", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "*", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "da0b27c1-ae2e-4baa-bf78-db233e15c78d", + "name" : "preferred_username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "userinfo.token.claim" : "true" + } + }, { + "id" : "7c94de93-f60f-487b-b4b7-1891c67f74cc", + "name" : "aud", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-hardcoded-claim-mapper", + "consentRequired" : false, + "config" : { + "claim.value" : "dbrepo", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "aud", + "access.tokenResponse.claim" : "false" + } + }, { + "id" : "0b4c644f-0cf0-4794-8395-d5d83009dabe", + "name" : "uid", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "CUSTOM_ID", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "uid", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "roles", "attributes" ], + "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] + }, { + "id" : "25741f6b-4867-4138-8238-6345c6ba8702", + "clientId" : "rabbitmq-client", + "name" : "${rabbitmq-client}", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "JEC2FexxrX4N65fLeDGukAl6R3Lc9y0u", + "redirectUris" : [ "*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1680000860", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "*", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "01a937ed-f0e8-4137-80f3-3be3c447f7fb", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "false", + "user.attribute" : "username", + "id.token.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "f1afc22d-f595-403b-ba2e-6ab19d98205e", + "name" : "Audience", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-hardcoded-claim-mapper", + "consentRequired" : false, + "config" : { + "claim.value" : "rabbitmq", + "userinfo.token.claim" : "false", + "id.token.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "aud", + "access.tokenResponse.claim" : "false" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "rabbitmq.tag:management" ], + "optionalClientScopes" : [ "rabbitmq.read:*/*", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "roles", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] + }, { + "id" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "f205c451-9524-4380-acc3-947f7ecb6b7c", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/dbrepo/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/dbrepo/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "c4d54410-3f22-4259-9571-94da2c43b752", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "69f4ecf0-4165-49ab-bf0d-38409b15b706", + "name" : "rabbitmq.tag:administrator", + "description" : "administrator", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "" + } + }, { + "id" : "7f6e9b44-e2eb-417d-b0fe-db820c9a6564", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "782819fe-ba5d-4ddb-9f95-cabb69d79c8d", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "ca613fc8-bbf2-4240-8b33-a1874f1559f3", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "b9da268f-6745-49dc-a764-3c54e385accc", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "84f0487a-1d7d-470c-9b8e-5835294ae235", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "bbdcdb36-3ec0-443d-b1af-9993d40f0567", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "9faa870b-5491-4ce9-b27d-c9ce07d6a95e", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "f0e3c012-9523-4076-83ae-e466e2d08220", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "f757d8ec-e181-429c-9287-9ad0600b061f", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "18cfbf4b-0a8e-45c7-a832-c0f72c92f3f3", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "841ea785-26ab-429a-a420-09ce3948924d", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "bfba13ff-f952-4e89-bbb1-a693fdebfae8", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "475f071d-5149-4379-b928-76482f5f519c", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "b8bebfed-b5e9-4604-a0ee-9817f7d439ac", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "445232c8-6830-476c-a6f1-8bbef167595a", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "65f2e474-6ede-4872-86e4-e49504dd0f2a", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "16cd5a27-ccf3-453c-ae1e-8621813ab73c", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "f9efedfc-3388-457c-b10a-1dff4525ff9b", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "627fa054-08eb-4206-af71-9e838e984b8b", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "e6cc53e5-5d7e-468e-88c8-0737dd3dc759", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "83b4444c-10fc-44e8-a0c0-0c1da1f9bba3", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "4122ff9e-ad3c-4142-afc6-9aefdecfc86d", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "bb0747fa-c008-4af3-93be-e7739650ebd5", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "2e76447d-fbe7-4fa7-a16c-54a381b960ae", + "name" : "rabbitmq.configure:*/*", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false", + "gui.order" : "", + "consent.screen.text" : "" + } + }, { + "id" : "52aad832-c6c4-49df-8a04-6ad4a406fdfa", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "dae802fb-9138-408a-b80e-a40eb0f56814", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "feb06a8d-b0eb-4911-8464-368d93f566fa", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "f64d64e8-57ce-4eb2-b99e-9f02fdbd99f9", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "c6411e3b-6478-453d-b530-5fe175a4d786", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "55341d34-0086-4173-ae61-d9b175b179d8", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "58ea3217-0fff-4207-9d08-919f5493b629", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "a02c2c38-923c-46ec-9899-321412b388e5", + "name" : "attributes", + "description" : "User Attributes", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "gui.order" : "", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "78c461c1-f3f9-4d10-8835-097f13bdcd60", + "name" : "Theme", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "false", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "theme_dark", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "attributes.theme_dark" + } + } ] + }, { + "id" : "06062e22-89c0-4e1d-a25b-2483903b02d5", + "name" : "rabbitmq.write:*/*", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false", + "gui.order" : "", + "consent.screen.text" : "" + } + }, { + "id" : "db63e03b-7918-492f-997b-f2dda98f3b39", + "name" : "rabbitmq.tag:management", + "description" : "management", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "" + } + }, { + "id" : "210cc792-6c07-45a6-a77e-827cdf3b41ba", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "425abf4a-2ee2-431d-aa92-e373a36fe556", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "8d4ffe4d-1d01-4ca1-8ff4-44eacca61b30", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "c96f0b73-ea79-4b46-93ef-d1092297f855", + "name" : "rabbitmq.read:*/*", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false", + "gui.order" : "", + "consent.screen.text" : "" + } + }, { + "id" : "37f61543-dad7-4a82-8e10-77acdd1eefdc", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "3b6b6914-8ad1-4a71-88ec-444f754aaacb", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "2defedf5-9af3-4531-822c-a879dedcd29d", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "a7bd6723-e58e-47f7-95c0-2925ce99283d", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "rabbitmq.tag:administrator", "rabbitmq.tag:management" ], + "defaultOptionalClientScopes" : [ "rabbitmq.write:*/*", "offline_access", "rabbitmq.configure:*/*", "roles", "role_list", "address", "phone", "acr", "microprofile-jwt", "email", "attributes", "profile", "rabbitmq.read:*/*", "web-origins" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "VERIFY_PROFILE_ERROR", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "OAUTH2_DEVICE_VERIFY_USER_CODE", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "TOKEN_EXCHANGE", "AUTHREQID_TO_TOKEN", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "OAUTH2_DEVICE_AUTH", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "OAUTH2_DEVICE_CODE_TO_TOKEN", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "AUTHREQID_TO_TOKEN_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "OAUTH2_DEVICE_AUTH_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "VERIFY_PROFILE", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "4d3f9f14-f5d2-4b0c-8ea7-e6d078aa2191", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "f35bce67-1e75-408b-b065-52183368d4fd", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "0efa669d-1017-4b4a-82e1-c2eaf72de2c9", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "528fb423-d66e-472e-9120-1f03ba9e0f18", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "f565cb47-3bcf-4078-8f94-eb4179c375b8", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "104ec5a9-025b-4c44-8ac0-82d22887ca3e", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "fb763636-e1ea-49c7-adca-ea105cdec4ad", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "2f53ccf3-37b0-4d34-83e7-ed497499ee51", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEA3b1tNLfcjFLUw9UShVDNf+ZD8sQqb4YBaIXcSJTX/zDQUPiCp176BBGI3s4VplDArnOW+LumozmKogeoHEnGEIDW8ovgK5uMU9tSA2p0qqGBUMOdR8YATTIfCJe7qGiiuGa3WZy3sQLM70SuRzx02YU8gvUcvl2Js4KyqAziOUX/w3Wa59H9jjGNUXYyqaPWJp73eHzbVYWySzyLG22mVlcUtBx5siL5T2/Xu0p9z4l7/bapwwmOVi1ZrcHjbEAwdGEiSMGI/uWqAF+r1BRpmJLR7HNXcL3eK4/56VYLaiwSejfyYeRFMITEn/UxGYhcXZ5xMUUCG0TxjBhLYpTBuwIDAQABAoIBAA4dwebcxkrH99Poa8+WkiE7JgaS9sahx9OBI2xwJANoIU2TpzGuNLQZ76uLgB+rPWZTD9Xm5a1iJjwOyQ9/937TzPCk91D0tpgcusRikb8jx/6TGB9acL4kBjYUVCCHr3BA2G75MKKGtJ2OMvAbCQSosZj+r2VDwYFEPUkV2jheE5JHSBkwyIRrus3JCwu8gu5fyCg9z8ljcxJxI5HIsi4v8Z21aCw/cLj7h5cMt44wCjQz4rOfYNBEFeHDtlfR1QtWKgjm4ZHHJbKrzf9b2kQXclziceEbSM0tYbROEXKi+s0Zc+z3HEG89vv0vfN400clmzzIAijKY6gg3pPRWdECgYEA+lnWYbSlXDMNYx6RBXm1RnlMUYIm4oy4/9ljgnoGJ6WCn3SjFkgaDtiKfGIG1BSB85r04pAPANgcWHf5tWDnq0ARvBVG0BX2bKd++7B3D4d3CRYKCwm88SslJXv9dfHVhq4+zViFPiUWwT20A72jCuUCvL88y5fh/YBecfdh+jECgYEA4r5RD0NB9dMaeg5/jk/GEHIo4Z9KLc6FrSoOFo2xFkPOy1sgDpDOiNtypuWvniO7k7Ose3DS3hlfTMsKzIW/CgQJ20+Y4cvBWDaOsRxfjj7w3d+jH5OSJdKKSzTrgLKc9ZhlRzVXy0J0hipIA6HG5kdVdLXmh85ITmf1CbJhE6sCgYBjPVeBNbXTHZ2x6/z62aslO5IoQVqetb/kE82hfDOSZcao5Ph9Lam+ttH2ynkAevykj4mBgi+gWwqpey2uW7KaLPSaxShj9kDQA3mP1fzsV/u0y1rB02Nlin/YIxVvOqU1FT9p8SwoXVVu1sHUNck62VtDbN9xqUx5S/ikXrclEQKBgQCoTssOwEcK+Vty9KYcdfy4onTUHZBLdjxl8Iyqkxy7QTQUYRznkvesQPDXEDGO+kk3dyx2KKZt9Hl4IFNww2quPZcvcuMx4DQxjbXXpA8OIIxcta95NepLJwA+mRai3nKCH1A2TlNP7pFeMa5o+8IPly3Ix2lKr4Wepa4PN5i1pwKBgCZ1QP6XAOERl9NznNmU0rXVcvYNP4PIIfQWfvGsldZ4QKkmjjAGiS0/oYqdWs+UDRZyCRChaVjDXO9fk0PEG5OGKAj9nyiYCT/M8xtJ3UeP5ffZZvJ/vnye3QdDIo1e38ZzsWwJHmLYw7fRqY9W5Vxo0Vsy22U3CJY70KTxVdTy" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICmzCCAYMCBgGG3GWycDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZkYnJlcG8wHhcNMjMwMzEzMTkxMzE3WhcNMzMwMzEzMTkxNDU3WjARMQ8wDQYDVQQDDAZkYnJlcG8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdvW00t9yMUtTD1RKFUM1/5kPyxCpvhgFohdxIlNf/MNBQ+IKnXvoEEYjezhWmUMCuc5b4u6ajOYqiB6gcScYQgNbyi+Arm4xT21IDanSqoYFQw51HxgBNMh8Il7uoaKK4ZrdZnLexAszvRK5HPHTZhTyC9Ry+XYmzgrKoDOI5Rf/DdZrn0f2OMY1RdjKpo9Ymnvd4fNtVhbJLPIsbbaZWVxS0HHmyIvlPb9e7Sn3PiXv9tqnDCY5WLVmtweNsQDB0YSJIwYj+5aoAX6vUFGmYktHsc1dwvd4rj/npVgtqLBJ6N/Jh5EUwhMSf9TEZiFxdnnExRQIbRPGMGEtilMG7AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK3kQ1VkQrzvSWvmXmazmNoA1ZiPzRDs1XhGUWxgsxzgPylr3dGBuqQbKvgnLUBQLSqlJHpI4fZflHswu1qrvVZYtekPcGef4WhcKAu2i1RwxrKa6RJQ1tRbrLuVYCzPv5p/DWgltWVn88aoLnqQn0SK/0PB/o4a4Cm7Kq2ZzCr1dACBr06LvOHsc7249OySmbG4HH+pLK6jVURhZ9VaObqAHe2FJBVVoIzURbdiRRURqumrIvbnpeaU1aFyg6ED5wTnXvmMPmVPt9F79mcB33bASO5wyu00X8t1hyN2Show2l2vxLACGUzVkTQt15s7uDLKE7qLmKSR3EuSGXWv3wA=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "230cb681-9ceb-4b1b-8a4c-929a11b08de0", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "8a489935-9a95-459b-9059-59b438ef0fa8" ], + "secret" : [ "xSCVgBlrLPWoF54gKQdR7BqXlfNaCD43xtS_ZgQRC0tGNAbqhy2Q9y8LdD2IR7K__8VGaDGYtyZayopgTebhDBb4gHDjDOBX7flhFYRrm0G3aTIuCIyFG-bPULwmyP_oHeC6tjwdQhqx5G0tE2mQQqPC9dDZuUA5I7QREIGK8cI" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "28ca0b6d-b2e2-4785-b04b-2391e6344e30", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "6dc4834f-a1de-4cfe-a29d-e84ac8e9b1a8" ], + "secret" : [ "HpuzG_jWYKwypLeoPEMC4A" ], + "priority" : [ "100" ] + } + }, { + "id" : "bd7945cf-6d35-4e03-9c3a-197f2dc76973", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "5034d264-cb50-4006-a59e-2ce636eb5f38" ], + "secret" : [ "ToVIw-a4IE-Yp9JpP8ztb8NAICYO8CT3tUiDPT6DdiBcgzKJ9Ym9vspxGVdmPceX3mAgbnGLAcTx1PkInSVrbZs-tX9QXFwdlyGbewhKiNpH8wEg32Wk4GuUDpTv8JCsymgWyQBY681jvIMv05eCoK2QWpqCzcgP828KM5peCzo" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "2293ff99-3c6d-46d1-8635-5e679d5b134a", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQABAoIBADNcMt6hAHub4JTAYS6Mra0EPRBO2XhWmACBrv3+8ETClXd5475KPLDewgRVtlmtbwU8G8awUXESQgPS9lfiqvQhPreA3cHlm6oP2WMKOEtakr2s8I+frsTBLCo0Ini9RaSzjoVVgS0zofyhASKi+T970MafSj5P3XNb8YBFdXgoYDiA7FXLH6a/+m7LScL+wGcFMAAeYESxZbMQLfH3v8L+4EcTraiwjLG17ZdlF3dpybMyUSse6ZQ/PdlyvBuzzLXhN6Ce2gd9ATfS+YWTzo7Yf+GU+ex5bIpVOfHqtuM/hyq7YGKENClsXwNZIAoFnvGCbvECAfgyapVrD30IfykCgYEA0rgsSZ82pxT40NxwgBD1g9lbNVBKXphRB/3S078qusUzJjT7AldEj4imGPhAbI7bI8gAeWJsp1XJWkjM8ktaVrh+NQl7p8e9OPh0pQF/5Bdg8ajbjXESpjnaU66pVYRQy/d+jNli/YRAHX5RUfsBl+6W4+WSVMGmKBiqJsur+ecCgYEAz1YVXClcmUnyZem5B+2E9noIzjF6ROE+jIb6rawM85P3Xd0lXtECQavtxw+Qk7I32qOwrxl1UpK2foVel3pazi+4OpMfmqtYGenRP1Zk1cZwrDo0cIemTDGjj3kJ8tYn12CGolFQpJZgK6OHzvG0tOxI5VZgjIViWNPe1PGWXtUCgYEAxXGNDe8BZs1f11S2lUlOw5yGug3hoYFXbAWJ5p7Ziuf8ZXB/QlJDC7se54a11wKEk6Jzz0lKRgE8CjzszJuOqnN0zn10QGIIC7nCklo1W6QMUmPGVWH994N976tZP6gbjQL6sT+AYcvpx7j0ubxYYeRNvnz+ACzzY964kGGHY0ECgYEAumlwPPNnMN7+VEjGNm2D7UMdJZ3wi3tkjF5ThdA5uMohTsAk+FG80KSu3RmOaGyEsUwY7+VYyYvlDm4E9PZqLBVVczyR3rMNPAcwPd0EPfvzk7WlLkOX7ct3fehaXH3VRlyfz9KCSeh1wOZ/lT1VtpD2nVOC7PSDzs92+kfXZZ0CgYAnrD1y4skgXkdwolZ3unn3EFyGm2d+X5aMTHwQPdWxqoNIAl/9wdghlzihwnPhhsxq1WzlxuC3V2IMrNPtRx70Mi+FbSmR5m4Xx5RptgMtMlwno+L40PzNJgMjHGjt0wcx3Vel8wuohDtnqMyS7P5nG1/TQx0Cyzwn7QOXlNpgbQ==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICmzCCAYMCBgGG3GWyBTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZkYnJlcG8wHhcNMjMwMzEzMTkxMzE3WhcNMzMwMzEzMTkxNDU3WjARMQ8wDQYDVQQDDAZkYnJlcG8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqqcdDYFZZb28M0tEJzEP77FmD/Xqioyj9zWX6VwUSOMAgmMmn8eqs9hT9T0a+q4YTo9tUW1PNbUpwprA5b4Uk04DcIajxDVMUR/PjcHytmkqwVskq9AZW/Vngdoo+8tSbuIybwe/3Vwt266hbHpDcM97a+DXcYooRl7tQWCEX7RP27wQrMD9epDQ6IgKayZg9vC9/03dsIqwH9jXQRiZlFvwiEKhX2aY7lPGBaCK414JO00K/Z49iov9TRa/IYVbSt5qwgrx6DcqsBSPwOnI6A85UGfeUEZ/7coVJiL7RvBlsllapsL9eWTbQajVh94k9Ei3sibEPbtH+U2OAM78zAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAASnN1Cuif1sdfEK2kWAURSXGJCohCROLWdKFjaeHPRaEfpbFJsgxW0Yj3nwX5O3bUlOWoTyENwnXSsXMQsqnNi+At32CKaKO8+AkhAbgQL9F0B+KeJwmYv3cUj5N/LYkJjBvZBzUZ4Ugu5dcxH0k7AktLAIwimkyEnxTNolOA3UyrGGpREr8MCKWVr10RFuOpF/0CsJNNwbHXzalO9D756EUcRWZ9VSg6QVNso0YYRKTnILWDn9hcTRnqGy3SHo3anFTqQZ+BB57YbgFWy6udC0LYRB3zdp6zNti87eu/VEymiDY/mmo1AB8Tm0b6vxFz4AKcL3ax5qS6YnZ9efSzk=" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "88e5d526-2298-413c-a904-133ad839d47f", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "a690c715-fbae-4c20-b680-bd4010718761", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "ad6d407e-c73e-4439-baf3-d7c99c6cb6ad", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "e5d03405-e10a-408a-adb2-41dbb4f24515", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "96b93843-62d0-44f1-84dd-21cc5f95f523", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "088f4051-36ab-4952-a4f2-4ba53c408083", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "05f37bb2-779d-4e3f-ad1b-f6eb33bb3de4", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "300a5647-7d2c-4348-9f1f-51504bfda1c4", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "26afc672-314b-4ad9-9711-7aaeafd7c00c", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "9b301f6c-eda7-4da0-ba09-1a6454ff910d", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6e54f1be-dbad-4b6d-8eee-8e048d413c63", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "31da4b94-03c4-4d79-9ac3-5df1445c0781", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "2e16651d-681f-4d9b-9dd4-9acdb465cd43", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "da109a26-fefa-48a4-ae8e-1d49627c2db8", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "4c983c77-241f-41c5-8b8a-e2cd6fc08914", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "d62c8dd6-633c-408a-aa99-43071510efb4", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "c8ca5be7-e76d-4e16-b5ca-3ced99d92dbb", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "389c1c37-e8af-4610-a507-e1257f55b954", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "d66ca9d0-1645-4c84-abfe-c0a696f17de4", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "061cc6b8-90be-4423-9bf9-974ead709b5d", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : false, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : false, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan-execute-actions" : "", + "actionTokenGeneratedByUserLifespan-verify-email" : "", + "clientOfflineSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan-reset-credentials" : "", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email" : "", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "shortVerificationUri" : "" + }, + "keycloakVersion" : "24.0.5", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/dbrepo-auth-service/listeners/target/create-event-listener.jar b/dbrepo-auth-service/listeners/target/create-event-listener.jar new file mode 100644 index 0000000000000000000000000000000000000000..26cb91c37666c966f6382d35feaaf9f5da5c7f8c Binary files /dev/null and b/dbrepo-auth-service/listeners/target/create-event-listener.jar differ diff --git a/dbrepo-auth-service/master-realm.json b/dbrepo-auth-service/master-realm.json index ef06561e687af0d808781b6c71a1a2b2ea664275..1cf53fe49cffabe7e5833675db95ebff6eec7034 100644 --- a/dbrepo-auth-service/master-realm.json +++ b/dbrepo-auth-service/master-realm.json @@ -40,6 +40,7 @@ "bruteForceProtected" : false, "permanentLockout" : false, "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", "maxFailureWaitSeconds" : 900, "minimumQuickLoginWaitSeconds" : 60, "waitIncrementSeconds" : 60, @@ -664,8 +665,8 @@ "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "client.use.lightweight.access.token.enabled" : "true" + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : true, @@ -783,8 +784,8 @@ "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, "authenticationFlowBindingOverrides" : { }, @@ -816,8 +817,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "4aed5e41-0d8d-4c24-80a0-cd9822072756", @@ -845,8 +846,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${organizationScopeConsentText}" + "consent.screen.text" : "${organizationScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "5e80a7d2-c9d0-48e1-aadc-d8848ff90f92", @@ -864,6 +865,61 @@ "jsonType.label" : "String" } } ] + }, { + "id" : "1be1e284-2749-4bbb-890a-2d519cc1531c", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "c913a673-cf66-4493-a2ed-14556c07617c", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "5c244d68-5c63-4356-ac71-5a586f40c77e", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "600285d4-ae51-4b39-a7be-bb83cf5870db", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + } ] }, { "id" : "0411ea86-a074-4781-850d-ea3ca94590a2", "name" : "offline_access", @@ -915,8 +971,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "2d1400be-4053-4393-ba87-91b64f699054", @@ -1217,8 +1273,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" + "consent.screen.text" : "", + "display.on.consent.screen" : "false" }, "protocolMappers" : [ { "id" : "635cbac1-7cab-43bd-99fc-f7084aca2fa2", @@ -1254,8 +1310,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "2b5a3df4-1adb-402d-bc28-2bd43224e682", @@ -1264,12 +1320,12 @@ "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", "user.attribute" : "foo", + "introspection.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "realm_access.roles", - "jsonType.label" : "String" + "jsonType.label" : "String", + "multivalued" : "true" } }, { "id" : "f3b60071-ef26-48a7-9554-67f62f84d543", @@ -1278,12 +1334,12 @@ "protocolMapper" : "oidc-usermodel-client-role-mapper", "consentRequired" : false, "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", "user.attribute" : "foo", + "introspection.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String" + "jsonType.label" : "String", + "multivalued" : "true" } }, { "id" : "b757200e-494a-4585-857e-e4c18aef7a0c", @@ -1303,8 +1359,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "e18769b3-778b-47d8-be52-dd2769deebd1", @@ -1344,8 +1400,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "98cc724c-3f53-47f7-bf9f-baf2f7e08026", @@ -1431,7 +1487,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper" ] } }, { "id" : "4b976576-c880-48a0-9b4d-2956cfd19b4a", @@ -1440,7 +1496,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "e1861ec9-2761-46fb-8048-149492269ff0", @@ -1470,6 +1526,14 @@ "allow-default-scopes" : [ "true" ] } } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "34049725-5a66-456c-b895-87ca7c11bb6b", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], "org.keycloak.storage.UserStorageProvider" : [ { "id" : "3a6f24e8-128b-4ac1-b3ab-694836db82fd", "name" : "Identity Service", @@ -1483,8 +1547,8 @@ "config" : { "ldap.attribute" : [ "mail" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "email" ] } }, { @@ -1495,8 +1559,8 @@ "config" : { "ldap.attribute" : [ "sn" ], "is.mandatory.in.ldap" : [ "true" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "lastName" ] } }, { @@ -1507,8 +1571,8 @@ "config" : { "ldap.attribute" : [ "modifyTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "modifyTimestamp" ] } }, { @@ -1541,17 +1605,17 @@ "providerId" : "group-ldap-mapper", "subComponents" : { }, "config" : { + "mode" : [ "LDAP_ONLY" ], "membership.attribute.type" : [ "DN" ], + "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "group.name.ldap.attribute" : [ "cn" ], + "ignore.missing.groups" : [ "false" ], "membership.user.ldap.attribute" : [ "uid" ], "preserve.group.inheritance" : [ "false" ], - "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], - "mode" : [ "LDAP_ONLY" ], - "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "membership.ldap.attribute" : [ "member" ], - "ignore.missing.groups" : [ "false" ], - "group.object.classes" : [ "groupOfNames" ], + "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], "memberof.ldap.attribute" : [ "memberOf" ], + "group.object.classes" : [ "groupOfNames" ], "drop.non.existing.groups.during.sync" : [ "false" ], "groups.path" : [ "/" ] } @@ -1563,8 +1627,8 @@ "config" : { "ldap.attribute" : [ "createTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "createTimestamp" ] } } ] @@ -1580,9 +1644,9 @@ "importEnabled" : [ "true" ], "enabled" : [ "true" ], "changedSyncPeriod" : [ "-1" ], + "usernameLDAPAttribute" : [ "uid" ], "bindCredential" : [ "admin" ], "bindDn" : [ "cn=admin,dc=dbrepo,dc=at" ], - "usernameLDAPAttribute" : [ "uid" ], "vendor" : [ "other" ], "uuidLDAPAttribute" : [ "entryUUID" ], "allowKerberosAuthentication" : [ "false" ], @@ -1600,14 +1664,6 @@ "validatePasswordPolicy" : [ "false" ] } } ], - "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "34049725-5a66-456c-b895-87ca7c11bb6b", - "providerId" : "declarative-user-profile", - "subComponents" : { }, - "config" : { - "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] - } - } ], "org.keycloak.keys.KeyProvider" : [ { "id" : "5b1052d2-fb71-47d2-86f9-908c869c8d1b", "name" : "hmac-generated-hs512", @@ -2219,10 +2275,12 @@ "parRequestUriLifespan" : "60", "clientSessionMaxLifespan" : "0", "frontendUrl" : "", + "organizationsEnabled" : "false", "acr.loa.map" : "{}" }, - "keycloakVersion" : "24.0.5", + "keycloakVersion" : "26.0.4", "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, "clientProfiles" : { "profiles" : [ ] }, diff --git a/dbrepo-data-service/Dockerfile b/dbrepo-data-service/Dockerfile index 4b45e9429050c08bcf4c3fd42b3d0ab259801657..9edf1375fb6c47f63dbd45f26bba0b3a6fe15255 100644 --- a/dbrepo-data-service/Dockerfile +++ b/dbrepo-data-service/Dockerfile @@ -8,7 +8,7 @@ LABEL org.opencontainers.image.authors="martin.weise@tuwien.ac.at" COPY ./pom.xml ./ -RUN mvn -fn -B -q dependency:go-offline +RUN mvn -fn dependency:go-offline COPY --from=dependency /root/.m2/repository/at/tuwien /root/.m2/repository/at/tuwien @@ -18,7 +18,7 @@ COPY ./rest-service ./rest-service COPY ./services ./services # Make sure it compiles -RUN mvn -fn -B -q clean package -DskipTests +RUN mvn -fn clean package -DskipTests ###### THIRD STAGE ###### FROM amazoncorretto:17-alpine3.19 AS runtime diff --git a/dbrepo-data-service/pom.xml b/dbrepo-data-service/pom.xml index 7d5e6941e4fc89f481e9724ac27d246f2b3fa979..e2bfa739618b3a518aeabd623ea49718b9a06d5b 100644 --- a/dbrepo-data-service/pom.xml +++ b/dbrepo-data-service/pom.xml @@ -11,7 +11,7 @@ <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> <name>dbrepo-data-service</name> - <version>1.6.2</version> + <version>1.6.3</version> <description>Service that manages the data</description> diff --git a/dbrepo-data-service/querystore/pom.xml b/dbrepo-data-service/querystore/pom.xml index cb712233ce0743cf684af6a7e5efaf2d7937cea5..2905b5a935407cab65086def3f9c21d980ee329a 100644 --- a/dbrepo-data-service/querystore/pom.xml +++ b/dbrepo-data-service/querystore/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-data-service-querystore</artifactId> <name>dbrepo-data-service-querystore</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies/> diff --git a/dbrepo-data-service/report/pom.xml b/dbrepo-data-service/report/pom.xml index 8de452bbf069bad2795233a79019222eabdcb6b3..4b230d55ebfbf084cfd00eea7cd7c568ac620d71 100644 --- a/dbrepo-data-service/report/pom.xml +++ b/dbrepo-data-service/report/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>report</artifactId> <name>dbrepo-data-service-report</name> - <version>1.6.2</version> + <version>1.6.3</version> <description> This module is only intended for the pipeline coverage report. See the detailed report in the respective modules diff --git a/dbrepo-data-service/rest-service/pom.xml b/dbrepo-data-service/rest-service/pom.xml index 8ff195ea791a9da6e2ceddd20ab2da5ddc9fdafc..721cf1a254c8fff0927bffbd7d6ff27b3dcead8d 100644 --- a/dbrepo-data-service/rest-service/pom.xml +++ b/dbrepo-data-service/rest-service/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>rest-service</artifactId> <name>dbrepo-data-service-rest-service</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>services</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </dependency> </dependencies> @@ -38,6 +38,14 @@ </execution> </executions> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>9</source> + <target>9</target> + </configuration> + </plugin> </plugins> </build> diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java index d4eccd0772eba12e007c6e99ae42a60b1304fca5..25dfd50dd434df444a45c4f7850fcdd803f0e9de 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java @@ -214,7 +214,7 @@ public class ViewEndpoint extends RestEndpoint { @RequestMapping(value = "/{viewId}/data", method = {RequestMethod.GET, RequestMethod.HEAD}) @Observed(name = "dbrepo_view_data") @Operation(summary = "Get view data", - description = "Gets data from a view of a database. For private databases, the user needs at least *READ* access to the associated database. Requires role `view-database-view-data`.", + description = "Gets data from a view of a database. For private databases, the user needs at least *READ* access to the associated database.", security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")}) @ApiResponses(value = { @ApiResponse(responseCode = "200", diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java index 5dce4988561b2bf653c77358701ccfba674d6269..b003ebfb4fd427c0b6bd92aef0595915f5c2bdbd 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java @@ -330,17 +330,17 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { NotAllowedException, MetadataServiceException { /* mock */ - when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) + .thenReturn(VIEW_1_PRIVILEGED_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); doThrow(NotAllowedException.class) .when(credentialService) - .getAccess(DATABASE_1_ID, USER_1_ID); + .getAccess(DATABASE_1_ID, USER_4_ID); /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_4_PRINCIPAL); }); } @@ -366,15 +366,15 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { NotAllowedException, MetadataServiceException { /* mock */ - when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) + .thenReturn(VIEW_1_PRIVILEGED_DTO); doThrow(NotAllowedException.class) .when(credentialService) - .getAccess(DATABASE_1_ID, USER_3_ID); + .getAccess(DATABASE_1_ID, USER_4_ID); /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, USER_3_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_4_PRINCIPAL); }); } @@ -384,15 +384,15 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { NotAllowedException, MetadataServiceException { /* mock */ - when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) + .thenReturn(VIEW_1_PRIVILEGED_DTO); doThrow(NotAllowedException.class) .when(credentialService) - .getAccess(DATABASE_1_ID, USER_3_ID); + .getAccess(DATABASE_1_ID, USER_4_ID); /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, null, USER_3_PRINCIPAL); + viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); }); } @@ -418,15 +418,15 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { NotAllowedException, MetadataServiceException { /* mock */ - when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) + .thenReturn(VIEW_1_PRIVILEGED_DTO); doThrow(NotAllowedException.class) .when(credentialService) - .getAccess(DATABASE_1_ID, USER_1_ID); + .getAccess(DATABASE_1_ID, USER_4_ID); /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, null, USER_1_PRINCIPAL); + viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); }); } diff --git a/dbrepo-data-service/services/pom.xml b/dbrepo-data-service/services/pom.xml index d0fe72cbeba104d992c2325ddf7813eb00c3c663..04ddee3c59536698ac1fb63cd094f93410941328 100644 --- a/dbrepo-data-service/services/pom.xml +++ b/dbrepo-data-service/services/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>services</artifactId> <name>dbrepo-data-service-services</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service-querystore</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </dependency> </dependencies> diff --git a/dbrepo-gateway-service/dbrepo.conf b/dbrepo-gateway-service/dbrepo.conf index fd66cf805ef4bc7d5e7bb5e93ee54381015e88f6..68778de757bf331e183e7258755c6f3ef210d6ec 100644 --- a/dbrepo-gateway-service/dbrepo.conf +++ b/dbrepo-gateway-service/dbrepo.conf @@ -40,6 +40,10 @@ upstream dashboard-service { server dashboard-service:3000; } +upstream auth-service { + server auth-service:8080; +} + server { listen 8080 default_server; server_name _; @@ -67,6 +71,26 @@ server { proxy_read_timeout 90; } + # Proxy Keycloak OIDC connections, c.f. https://www.keycloak.org/server/reverseproxy#_exposed_path_recommendations + location /realms { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://auth-service; + proxy_read_timeout 90; + } + + # Proxy Keycloak assets, c.f. https://www.keycloak.org/server/reverseproxy#_exposed_path_recommendations + location /resources { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://auth-service; + proxy_read_timeout 90; + } + location /api/search { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/dbrepo-metadata-db/1_setup-schema.sql b/dbrepo-metadata-db/1_setup-schema.sql index c9ce89d1be71f4791c5e55dbb7c24f46e979355a..e2bde25ed6d64f69c4f8d6e897a49a672e3f9a71 100644 --- a/dbrepo-metadata-db/1_setup-schema.sql +++ b/dbrepo-metadata-db/1_setup-schema.sql @@ -3,10 +3,10 @@ BEGIN; CREATE TABLE IF NOT EXISTS `mdb_users` ( id character varying(36) NOT NULL, + keycloak_id character varying(36) NOT NULL, username character varying(255) NOT NULL, firstname character varying(255), lastname character varying(255), - email character varying(255) NOT NULL, orcid character varying(255), affiliation character varying(255), is_internal BOOLEAN NOT NULL DEFAULT FALSE, @@ -14,8 +14,8 @@ CREATE TABLE IF NOT EXISTS `mdb_users` theme character varying(255) NOT NULL default ('light'), language character varying(3) NOT NULL default ('en'), PRIMARY KEY (id), - UNIQUE (username), - UNIQUE (email) + UNIQUE (keycloak_id), + UNIQUE (username) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_images` diff --git a/dbrepo-metadata-service/Dockerfile b/dbrepo-metadata-service/Dockerfile index ddc20cb420764196c3a4b423294200e26eaa5bce..fa92b799eeaac75f9daea7b5a1eec11560b04647 100644 --- a/dbrepo-metadata-service/Dockerfile +++ b/dbrepo-metadata-service/Dockerfile @@ -12,7 +12,7 @@ COPY ./rest-service/pom.xml ./rest-service/ COPY ./services/pom.xml ./services/ COPY ./test/pom.xml ./test/ -RUN mvn -fn -B dependency:go-offline +RUN mvn dependency:go-offline COPY ./api ./api COPY ./entities ./entities @@ -24,7 +24,7 @@ COPY ./services ./services COPY ./test ./test # Make sure it compiles -RUN mvn -fn -B clean install -DskipTests +RUN mvn clean install -DskipTests ###### SECOND STAGE ###### FROM amazoncorretto:17-alpine3.19 AS runtime diff --git a/dbrepo-metadata-service/api/pom.xml b/dbrepo-metadata-service/api/pom.xml index c1e74c5ae9a6f747d8180fc512aaf0f23b03e650..0d534105f8a12d52201e2437d31360903292f657 100644 --- a/dbrepo-metadata-service/api/pom.xml +++ b/dbrepo-metadata-service/api/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-api</artifactId> <name>dbrepo-metadata-service-api</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service-entities</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> <scope>compile</scope> </dependency> </dependencies> diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/auth/CreateUserDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/auth/CreateUserDto.java index a30208bad0c577af2e80c9d629b087bdf5b3e7e7..16f45aec4d625639f1188e0e853b3a81bd71811f 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/auth/CreateUserDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/auth/CreateUserDto.java @@ -1,13 +1,14 @@ package at.tuwien.api.auth; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.util.UUID; + @Getter @Setter @Builder @@ -18,18 +19,28 @@ import lombok.extern.jackson.Jacksonized; @ToString public class CreateUserDto { + @NotNull + @Schema(example = "3b91bc36-3eae-4662-a4be-8993624ab0cb", description = "The user id generated by Keycloak") + private UUID id; + + @NotNull + @JsonProperty("ldap_id") + @Schema(example = "ea022d6d-b4a4-42f3-836f-ff4e596a527a", description = "The user id generated by OpenLDAP") + private UUID ldapId; + @NotBlank - @Pattern(regexp = "^[a-z0-9]{3,}$") @Schema(example = "user") private String username; - @NotBlank - @Email - @Schema(example = "user@example.com") - private String email; + @JsonProperty("given_name") + @Schema(example = "foo") + private String givenName; - @NotNull - @ToString.Exclude - private String password; + @JsonProperty("family_name") + @Schema(example = "bar") + private String familyName; + + @Schema(example = "foo.bar@example.com") + private String email; } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java index 97f35026747aaf86f33b714dc83b1b3f173dda2f..f94edc2cf75421855a8bcd761252e6aba249bb27 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java @@ -50,6 +50,9 @@ public class IdentifierBriefDto { @NotNull private List<IdentifierTitleDto> titles; + @NotNull + private List<IdentifierDescriptionDto> descriptions; + @Schema(example = "10.1038/nphys1170") private String doi; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/ModifyUserDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/ModifyUserDto.java new file mode 100644 index 0000000000000000000000000000000000000000..26d700e798e1517ae484dc60e9359bd8b5646965 --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/ModifyUserDto.java @@ -0,0 +1,26 @@ +package at.tuwien.api.keycloak; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Jacksonized +@ToString +public class ModifyUserDto { + + @JsonProperty("firstName") + private String firstname; + + @JsonProperty("lastName") + private String lastname; + + @NotNull + private UserAttributesDto attributes; + +} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserAttributesDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserAttributesDto.java index 027955ba77b69fd708c1c18463c1a92d09c93c95..50718bc8034d500c2d707abacac58acf2bb95012 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserAttributesDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserAttributesDto.java @@ -16,12 +16,20 @@ import java.util.UUID; @ToString public class UserAttributesDto { - @Schema(example = "s3cr3t") - @JsonProperty("LDAP_ENTRY_DN") - private String[] ldapEntryDn; + @Schema(example = "dark") + @JsonProperty("THEME") + private String[] theme; - @Schema(example = "false") - @JsonProperty("LDAP_ID") - private UUID[] ldapId; + @Schema(example = "en") + @JsonProperty("LANGUAGE") + private String[] language; + + @Schema(example = "https://ror.org/04d836q62") + @JsonProperty("AFFILIATION") + private String[] affiliation; + + @Schema(example = "https://orcid.org/0000-0003-4216-302X") + @JsonProperty("ORCID") + private String[] orcid; } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserDto.java deleted file mode 100644 index a2d7811ab0aa334a4f3a5d49916428b58166317b..0000000000000000000000000000000000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserDto.java +++ /dev/null @@ -1,52 +0,0 @@ -package at.tuwien.api.keycloak; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.util.UUID; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class UserDto { - - @NotNull - private UUID id; - - @NotNull - @Schema(example = "jcarberry", description = "Only contains lowercase characters") - private String username; - - @NotNull - @Schema(example = "true") - private Boolean enabled; - - @NotNull - @Schema(example = "false") - private Boolean totp; - - @NotNull - @JsonProperty("emailVerified") - @Schema(example = "false") - private Boolean emailVerified; - - @NotNull - @Schema(example = "jcarberry@brown.edu") - private String email; - - @NotNull - @JsonProperty("notBefore") - @Schema(example = "0") - private Long notBefore; - - @NotNull - private UserAttributesDto attributes; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserIdAttributesDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserIdAttributesDto.java new file mode 100644 index 0000000000000000000000000000000000000000..3155d75f027d2864f2a06ecf19376c13c9be7d35 --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/keycloak/UserIdAttributesDto.java @@ -0,0 +1,27 @@ +package at.tuwien.api.keycloak; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import lombok.extern.jackson.Jacksonized; + +import java.util.UUID; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Jacksonized +@ToString +public class UserIdAttributesDto { + + @Schema(example = "s3cr3t") + @JsonProperty("LDAP_ENTRY_DN") + private String[] ldapEntryDn; + + @Schema(example = "false") + @JsonProperty("LDAP_ID") + private UUID[] ldapId; + +} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/orcid/activities/employments/affiliation/group/summary/organization/disambiguated/OrcidDisambiguatedSourceTypeDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/orcid/activities/employments/affiliation/group/summary/organization/disambiguated/OrcidDisambiguatedSourceTypeDto.java index 78b87e33212ab48ba2e37a583100eea780c459e7..f883a034f55bd5f04d3059f8eb50ef50a3867c79 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/orcid/activities/employments/affiliation/group/summary/organization/disambiguated/OrcidDisambiguatedSourceTypeDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/orcid/activities/employments/affiliation/group/summary/organization/disambiguated/OrcidDisambiguatedSourceTypeDto.java @@ -1,5 +1,6 @@ package at.tuwien.api.orcid.activities.employments.affiliation.group.summary.organization.disambiguated; public enum OrcidDisambiguatedSourceTypeDto { - RINGGOLD + RINGGOLD, + ROR } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDetailsDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDetailsDto.java index cd5e8fd3e0cefe2f016261470b1236e9a3442b16..2ab170d616c75c40d4be36af42854d518d3ec7c5 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDetailsDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDetailsDto.java @@ -1,6 +1,5 @@ package at.tuwien.api.user; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; @@ -30,10 +29,6 @@ public class UserDetailsDto implements UserDetails { @ToString.Exclude private String password; - @NotNull - @Email - private String email; - @Override public boolean isAccountNonExpired() { return true; diff --git a/dbrepo-metadata-service/entities/pom.xml b/dbrepo-metadata-service/entities/pom.xml index 9252dd2caa13505becc99e347759de5923cd7fc8..1967b24868d61e29351b34c8608c6f428bc316f9 100644 --- a/dbrepo-metadata-service/entities/pom.xml +++ b/dbrepo-metadata-service/entities/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-entities</artifactId> <name>dbrepo-metadata-service-entity</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies/> diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/user/User.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/user/User.java index fd87852c6ecce0e2e614a9c7dd05a5e41cfe2e16..ba86e3d29c6913d45d51ae0498fdac8d3092b657 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/user/User.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/user/User.java @@ -30,6 +30,10 @@ public class User { @Column(name = "ID", nullable = false, columnDefinition = "VARCHAR(36)") private UUID id; + @JdbcTypeCode(java.sql.Types.VARCHAR) + @Column(name = "keycloak_id", nullable = false, columnDefinition = "VARCHAR(36)") + private UUID keycloakId; + @Column(nullable = false) private String username; @@ -39,9 +43,6 @@ public class User { @Column private String lastname; - @Column(nullable = false) - private String email; - @Column private String orcid; diff --git a/dbrepo-metadata-service/metrics.md b/dbrepo-metadata-service/metrics.md index 56a69c68f6913ab2e3ea192cde66f329ea336e10..f3e0a3130f23e149a54f1eca8247c559443809da 100644 --- a/dbrepo-metadata-service/metrics.md +++ b/dbrepo-metadata-service/metrics.md @@ -59,8 +59,6 @@ | `dbrepo_user_find` | Get user | | `dbrepo_user_modify` | Update user | | `dbrepo_user_password_modify` | Update user password | -| `dbrepo_user_refresh_token` | Refresh token | -| `dbrepo_user_token` | Create token | | `dbrepo_users_list` | List users | | `dbrepo_view_create` | Create view | | `dbrepo_view_delete` | Delete view | diff --git a/dbrepo-metadata-service/oai/pom.xml b/dbrepo-metadata-service/oai/pom.xml index 87da814d4152dc1a3398ee67cf4662d1d188519b..b6db9e69674d52ae557644b94b7eb5302563c86f 100644 --- a/dbrepo-metadata-service/oai/pom.xml +++ b/dbrepo-metadata-service/oai/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-oai</artifactId> <name>dbrepo-metadata-service-oai</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies/> diff --git a/dbrepo-metadata-service/pom.xml b/dbrepo-metadata-service/pom.xml index 04af8a795f25072c5756489d3b7c42a1828c6b59..5dd6ce3318f7cb88a16c9c6ecc60c8d4c5ff5aa2 100644 --- a/dbrepo-metadata-service/pom.xml +++ b/dbrepo-metadata-service/pom.xml @@ -11,7 +11,7 @@ <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> <name>dbrepo-metadata-service</name> - <version>1.6.2</version> + <version>1.6.3</version> <description>Service that manages the metadata</description> @@ -27,7 +27,7 @@ <module>report</module> </modules> - <url>https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/</url> + <url>https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/</url> <developers> <developer> <name>Martin Weise</name> @@ -52,7 +52,7 @@ <apache-jena.version>4.10.0</apache-jena.version> <opencsv.version>5.7.1</opencsv.version> <super-csv.version>2.4.0</super-csv.version> - <keycloak.version>21.0.2</keycloak.version> + <keycloak.version>26.0.4</keycloak.version> <springdoc-openapi.version>2.3.0</springdoc-openapi.version> <testcontainers.version>1.19.1</testcontainers.version> <jackson.version>2.15.2</jackson.version> @@ -187,6 +187,11 @@ <artifactId>keycloak-common</artifactId> <version>${keycloak.version}</version> </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-admin-client</artifactId> + <version>${keycloak.version}</version> + </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> diff --git a/dbrepo-metadata-service/report/pom.xml b/dbrepo-metadata-service/report/pom.xml index 756681f202dde19b48c70187003ff8c7270066be..8d4d32c15d3e50cb65ba2300c58d25dcbb87eebd 100644 --- a/dbrepo-metadata-service/report/pom.xml +++ b/dbrepo-metadata-service/report/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-report</artifactId> <name>dbrepo-metadata-service-report</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/repositories/pom.xml b/dbrepo-metadata-service/repositories/pom.xml index 39e971b901c27abdd87fda9a25dbb41bdc999c0c..57f89e7ed46587a564e57536d05b79fc1e5cb121 100644 --- a/dbrepo-metadata-service/repositories/pom.xml +++ b/dbrepo-metadata-service/repositories/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-repositories</artifactId> <name>dbrepo-metadata-service-repositories</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index ac6cacf64f007440e26df82d86eb6d1302f27390..cfbf858000ae4b2e58be5999c3c98123cc70582c 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -1,6 +1,5 @@ package at.tuwien.mapper; -import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerDto; import at.tuwien.api.container.CreateContainerDto; @@ -13,14 +12,14 @@ import at.tuwien.api.database.*; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.ColumnBriefDto; -import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.database.table.columns.ColumnDto; +import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.database.table.columns.concepts.ConceptDto; import at.tuwien.api.database.table.columns.concepts.ConceptSaveDto; import at.tuwien.api.database.table.columns.concepts.UnitDto; import at.tuwien.api.database.table.columns.concepts.UnitSaveDto; -import at.tuwien.api.database.table.constraints.CreateTableConstraintsDto; import at.tuwien.api.database.table.constraints.ConstraintsDto; +import at.tuwien.api.database.table.constraints.CreateTableConstraintsDto; import at.tuwien.api.database.table.constraints.foreign.ForeignKeyBriefDto; import at.tuwien.api.database.table.constraints.foreign.ForeignKeyDto; import at.tuwien.api.database.table.constraints.foreign.ForeignKeyReferenceDto; @@ -31,9 +30,7 @@ import at.tuwien.api.datacite.doi.*; import at.tuwien.api.identifier.*; import at.tuwien.api.identifier.ld.LdCreatorDto; import at.tuwien.api.identifier.ld.LdDatasetDto; -import at.tuwien.api.keycloak.CredentialDto; -import at.tuwien.api.keycloak.CredentialTypeDto; -import at.tuwien.api.keycloak.UpdateCredentialsDto; +import at.tuwien.api.keycloak.TokenDto; import at.tuwien.api.keycloak.UserCreateDto; import at.tuwien.api.maintenance.BannerMessageBriefDto; import at.tuwien.api.maintenance.BannerMessageCreateDto; @@ -50,8 +47,8 @@ import at.tuwien.api.semantics.OntologyBriefDto; import at.tuwien.api.semantics.OntologyCreateDto; import at.tuwien.api.semantics.OntologyDto; import at.tuwien.api.user.UserBriefDto; -import at.tuwien.api.user.UserDetailsDto; import at.tuwien.api.user.UserDto; +import at.tuwien.api.user.UserUpdateDto; import at.tuwien.api.user.external.ExternalMetadataDto; import at.tuwien.api.user.external.ExternalResultType; import at.tuwien.api.user.external.affiliation.ExternalAffiliationDto; @@ -74,6 +71,8 @@ import at.tuwien.entities.maintenance.BannerMessage; import at.tuwien.entities.maintenance.BannerMessageType; import at.tuwien.entities.semantics.Ontology; import at.tuwien.entities.user.User; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.UserRepresentation; import org.mapstruct.*; import java.text.Normalizer; @@ -97,6 +96,16 @@ public interface MetadataMapper { }) DataTypeDto dataTypeToDataTypeDto(DataType data); + @Mappings({ + @Mapping(target = "attributes", ignore = true) + }) + UserRepresentation userCreateDtoToUserRepresentation(UserCreateDto data); + + @Mappings({ + @Mapping(target = "accessToken", source = "token") + }) + TokenDto accessTokenResponseToTokenDto(AccessTokenResponse data); + BannerMessageDto bannerMessageToBannerMessageDto(BannerMessage data); BannerMessageBriefDto bannerMessageToBannerMessageBriefDto(BannerMessage data); @@ -112,6 +121,16 @@ public interface MetadataMapper { }) Container containerCreateRequestDtoToContainer(CreateContainerDto data); + UserUpdateDto userToUserUpdateDto(User data); + + default List<String> optionalValueToMap(String value) { + final List<String> attr = new LinkedList<>(); + if (value != null) { + attr.add(value); + } + return attr; + } + ContainerDto containerToContainerDto(Container data); @Mappings({ @@ -395,6 +414,8 @@ public interface MetadataMapper { IdentifierType identifierTypeDtoToIdentifierType(IdentifierTypeDto data); + IdentifierStatusType identifierStatusTypeDtoToIdentifierStatusType(IdentifierStatusTypeDto data); + default String identifierToLocationUrl(String baseUrl, Identifier data) { if (data.getType().equals(IdentifierType.SUBSET)) { return baseUrl + "/database/" + data.getDatabase().getId() + "/subset/" + data.getQueryId() + "/info?pid=" + data.getId(); @@ -536,8 +557,6 @@ public interface MetadataMapper { } default TableDto tableToTableDto(Table data) { - data.getDatabase() - .setTables(null); final TableDto table = TableDto.builder() .id(data.getId()) .name(data.getName()) @@ -550,7 +569,10 @@ public interface MetadataMapper { .description(data.getDescription()) .identifiers(new LinkedList<>()) .columns(new LinkedList<>()) - .database(databaseToDatabaseDto(data.getDatabase())) + .database(databaseToDatabaseDto(data.getDatabase() + .toBuilder() + .tables(null) + .build())) .constraints(constraintsToConstraintsDto(data.getConstraints())) .build(); if (data.getIdentifiers() != null) { @@ -746,40 +768,6 @@ public interface MetadataMapper { }) TableColumn columnCreateDtoToTableColumn(CreateTableColumnDto data, ContainerImage image); - default UpdateCredentialsDto passwordToUpdateCredentialsDto(String password) { - return UpdateCredentialsDto.builder() - .credentials(List.of(CredentialDto.builder() - .temporary(false) - .type(CredentialTypeDto.PASSWORD) - .value(password) - .build())) - .build(); - } - - default UserCreateDto signupRequestDtoToUserCreateDto(CreateUserDto data) { - return UserCreateDto.builder() - .username(data.getUsername()) - .email(data.getEmail()) - .credentials(List.of(CredentialDto.builder() - .type(CredentialTypeDto.PASSWORD) - .temporary(false) - .value(data.getPassword()) - .build())) - .enabled(true) - .build(); - } - - /* keep */ - UserBriefDto keycloakUserDtoToUserBriefDto(at.tuwien.api.keycloak.UserDto data); - - /* keep */ - @Mappings({ - @Mapping(target = "id", expression = "java(data.getId().toString())") - }) - UserDetailsDto userDtoToUserDetailsDto(UserDto data); - - User userDtoToUser(at.tuwien.api.keycloak.UserDto data); - /* keep */ @Mappings({ @Mapping(target = "name", expression = "java(userToFullName(data))"), @@ -787,8 +775,6 @@ public interface MetadataMapper { }) UserBriefDto userToUserBriefDto(User data); - UserBriefDto userDtoToUserBriefDto(UserDto data); - /* keep */ @Mappings({ @Mapping(target = "attributes.language", source = "language"), @@ -801,9 +787,6 @@ public interface MetadataMapper { }) UserDto userToUserDto(User data); - /* keep */ - User userDtoToUserDto(UserDto data); - /* keep */ @Named("userToFullName") default String userToFullName(User data) { @@ -837,7 +820,8 @@ public interface MetadataMapper { } @Mappings({ - @Mapping(target = "database.views", ignore = true) + @Mapping(target = "database.views", ignore = true), + @Mapping(target = "database.tables", ignore = true) }) ViewDto viewToViewDto(View data); diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/repository/UserRepository.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/repository/UserRepository.java index 7415fb422c03882f7f71786b0edf756c7bd58159..30f2f20c1670f550f7463265f1d2d6afde967777 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/repository/UserRepository.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/repository/UserRepository.java @@ -17,7 +17,5 @@ public interface UserRepository extends JpaRepository<User, UUID> { boolean existsByUsername(String username); - boolean existsByEmail(String email); - } diff --git a/dbrepo-metadata-service/rest-service/pom.xml b/dbrepo-metadata-service/rest-service/pom.xml index 9f8055a149d1b6f1ca03bf03d69daba0cbce82a2..ff5966688771f30a93b2ce4d95bc99ab6aa0a83f 100644 --- a/dbrepo-metadata-service/rest-service/pom.xml +++ b/dbrepo-metadata-service/rest-service/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-rest-service</artifactId> <name>dbrepo-metadata-service-rest</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6bdb80973176bc7ae5722b932e7f7b1a2a183d45 --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java @@ -0,0 +1,16 @@ +package at.tuwien.config; + +import at.tuwien.converters.IdentifierStatusTypeDtoConverter; +import at.tuwien.converters.IdentifierTypeDtoConverter; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new IdentifierStatusTypeDtoConverter()); + registry.addConverter(new IdentifierTypeDtoConverter()); + } +} \ No newline at end of file diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..96e67f63d2bb85bb0aaebd02237a2d8aaa6ecdfa --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java @@ -0,0 +1,14 @@ +package at.tuwien.converters; + +import at.tuwien.api.identifier.IdentifierStatusTypeDto; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class IdentifierStatusTypeDtoConverter implements Converter<String, IdentifierStatusTypeDto> { + + @Override + public IdentifierStatusTypeDto convert(String source) { + return IdentifierStatusTypeDto.valueOf(source.toUpperCase()); + } +} diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java similarity index 79% rename from dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java rename to dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java index b3f52c4377989a1380c6be42b46ce96f1d902163..61e169604fa2cf1e10c464fdb1d1bc310ea7f125 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java @@ -5,7 +5,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @Component -public class IdentifierTypeConverter implements Converter<String, IdentifierTypeDto> { +public class IdentifierTypeDtoConverter implements Converter<String, IdentifierTypeDto> { @Override public IdentifierTypeDto convert(String source) { diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index 6ee9fa6c9e4a3e7b7d9882197e8a835eca8e6f9c..50016103d83ccfffab65ddc153e6b88074e34e2c 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -194,7 +194,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), @ApiResponse(responseCode = "404", - description = "Failed to fin user/database in metadata database", + description = "Failed to find database in metadata database", content = {@Content( mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), @@ -211,7 +211,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { }) public ResponseEntity<DatabaseBriefDto> refreshTableMetadata(@NotNull @PathVariable("databaseId") Long databaseId, @NotNull Principal principal) throws DataServiceException, - DataServiceConnectionException, DatabaseNotFoundException, SearchServiceException, UserNotFoundException, + DataServiceConnectionException, DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException, NotAllowedException, QueryNotFoundException, MalformedException, TableNotFoundException { log.debug("endpoint refresh database metadata, databaseId={}", databaseId); @@ -513,7 +513,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { .findFirst(); if (!database.getIsPublic() && !database.getIsSchemaPublic() && optional.isEmpty() && !isSystem(principal)) { log.error("Failed to find database: not public and no access found"); - throw new DatabaseNotFoundException("Failed to find database: not public and no access found"); + throw new NotAllowedException("Failed to find database: not public and no access found"); } /* reduce metadata */ database.setTables(database.getTables() @@ -534,14 +534,16 @@ public class DatabaseEndpoint extends AbstractEndpoint { throw new NotAllowedException("Failed to find database: not public and not authenticated"); } /* reduce metadata */ - database.setTables(database.getTables() - .stream() - .filter(t -> t.getIsPublic() || t.getIsSchemaPublic()) - .toList()); - database.setViews(database.getViews() - .stream() - .filter(v -> v.getIsPublic() || v.getIsSchemaPublic()) - .toList()); + database.getTables() + .removeAll(database.getTables() + .stream() + .filter(t -> !t.getIsPublic() && !t.getIsSchemaPublic()) + .toList()); + database.getViews() + .removeAll(database.getViews() + .stream() + .filter(v -> !v.getIsPublic() && !v.getIsSchemaPublic()) + .toList()); database.setAccesses(List.of()); } final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(database); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java index b70516fa66e8476fa65cb9d50ab750976394da9f..b3d699086ee93b943801a3139a2e2488dc9dcc8c 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java @@ -73,7 +73,7 @@ public class IdentifierEndpoint extends AbstractEndpoint { this.identifierService = identifierService; } - @GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, "application/ld+json"}) + @GetMapping @Transactional(readOnly = true) @Observed(name = "dbrepo_identifier_list") @Operation(summary = "List identifiers", @@ -87,48 +87,41 @@ public class IdentifierEndpoint extends AbstractEndpoint { @Content(mediaType = "application/ld+json", array = @ArraySchema(schema = @Schema(implementation = LdDatasetDto.class))) }), - @ApiResponse(responseCode = "406", - description = "Identifier could not be exported, the requested style is not known", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<?> findAll(@Valid @RequestParam(value = "dbid", required = false) Long dbid, + public ResponseEntity<?> findAll(@Valid @RequestParam(value = "type", required = false) IdentifierTypeDto type, + @Valid @RequestParam(value = "status", required = false) IdentifierStatusTypeDto status, + @Valid @RequestParam(value = "dbid", required = false) Long dbid, @Valid @RequestParam(value = "qid", required = false) Long qid, @Valid @RequestParam(value = "vid", required = false) Long vid, @Valid @RequestParam(value = "tid", required = false) Long tid, - @RequestHeader(HttpHeaders.ACCEPT) String accept) - throws FormatNotAvailableException { - log.debug("endpoint find identifiers, dbid={}, qid={}, vid={}, tid={}, accept={}", dbid, qid, vid, tid, accept); + @RequestHeader(HttpHeaders.ACCEPT) String accept, + Principal principal) { + log.debug("endpoint find identifiers, type={}, status={}, dbid={}, qid={}, vid={}, tid={}, accept={}", type, + status, dbid, qid, vid, tid, accept); final List<Identifier> identifiers = identifierService.findAll() .stream() + .filter(i -> !Objects.nonNull(type) || metadataMapper.identifierTypeDtoToIdentifierType(type).equals(i.getType())) + .filter(i -> !Objects.nonNull(status) || metadataMapper.identifierStatusTypeDtoToIdentifierStatusType(status).equals(i.getStatus())) .filter(i -> !Objects.nonNull(dbid) || dbid.equals(i.getDatabase().getId())) .filter(i -> !Objects.nonNull(qid) || qid.equals(i.getQueryId())) .filter(i -> !Objects.nonNull(vid) || vid.equals(i.getViewId())) .filter(i -> !Objects.nonNull(tid) || tid.equals(i.getTableId())) + .filter(i -> principal != null && i.getStatus().equals(IdentifierStatusType.DRAFT) ? i.getOwnedBy().equals(getId(principal)) : i.getStatus().equals(IdentifierStatusType.PUBLISHED)) .toList(); if (identifiers.isEmpty()) { return ResponseEntity.ok(List.of()); } log.trace("found persistent identifiers {}", identifiers); - return switch (accept) { - case "application/json" -> { - log.trace("accept header matches json"); - yield ResponseEntity.ok(identifiers.stream() - .map(metadataMapper::identifierToIdentifierBriefDto) - .toList()); - } - case "application/ld+json" -> { - log.trace("accept header matches json-ld"); - yield ResponseEntity.ok(identifiers.stream() - .map(i -> metadataMapper.identifierToLdDatasetDto(i, endpointConfig.getWebsiteUrl())) - .toList()); - } - default -> { - log.error("accept header {} is not supported", accept); - throw new FormatNotAvailableException("Must provide either application/json or application/ld+json headers"); - } - }; + if (accept.equals("application/ld+json")) { + log.trace("accept header matches json-ld"); + return ResponseEntity.ok(identifiers.stream() + .map(i -> metadataMapper.identifierToLdDatasetDto(i, endpointConfig.getWebsiteUrl())) + .toList()); + } + log.trace("default to json"); + return ResponseEntity.ok(identifiers.stream() + .map(metadataMapper::identifierToIdentifierBriefDto) + .toList()); } @GetMapping(value = "/{identifierId}", produces = {MediaType.APPLICATION_JSON_VALUE, "application/ld+json", @@ -156,6 +149,11 @@ public class IdentifierEndpoint extends AbstractEndpoint { content = {@Content( mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), + @ApiResponse(responseCode = "403", + description = "Not allowed to view identifier", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = ApiErrorDto.class))}), @ApiResponse(responseCode = "404", description = "Identifier could not be found", content = {@Content( @@ -188,14 +186,23 @@ public class IdentifierEndpoint extends AbstractEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<?> find(@Valid @PathVariable("identifierId") Long identifierId, - @RequestHeader(HttpHeaders.ACCEPT) String accept) throws IdentifierNotFoundException, + @RequestHeader(HttpHeaders.ACCEPT) String accept, + Principal principal) throws IdentifierNotFoundException, DataServiceException, DataServiceConnectionException, MalformedException, FormatNotAvailableException, - QueryNotFoundException { + QueryNotFoundException, NotAllowedException { log.debug("endpoint find identifier, identifierId={}, accept={}", identifierId, accept); if (accept == null) { accept = ""; } final Identifier identifier = identifierService.find(identifierId); + if (identifier.getStatus().equals(IdentifierStatusType.DRAFT)) { + if (principal == null) { + throw new NotAllowedException("Draft identifier: authentication required"); + } + if (!identifier.getOwnedBy().equals(getId(principal))) { + throw new NotAllowedException("Draft identifier: not authorized"); + } + } log.info("Found persistent identifier with id: {}", identifier.getId()); switch (accept) { case "application/json": diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java index ade963c2559062b0ce225e0857ead02de351774c..51f323c30f1581314df88dab86ec2900775c215e 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java @@ -1,10 +1,7 @@ package at.tuwien.endpoints; -import at.tuwien.api.auth.LoginRequestDto; -import at.tuwien.api.auth.RefreshTokenRequestDto; import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.error.ApiErrorDto; -import at.tuwien.api.keycloak.TokenDto; import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import at.tuwien.api.user.UserPasswordDto; @@ -95,10 +92,11 @@ public class UserEndpoint extends AbstractEndpoint { @PostMapping @Transactional(rollbackFor = {Exception.class}) - @PreAuthorize("!isAuthenticated()") + @PreAuthorize("hasAuthority('system')") @Observed(name = "dbrepo_user_create") @Operation(summary = "Create user", - description = "Creates a user in the auth service and metadata database. Requires that no credentials are sent in the request.") + description = "This webhook is called from the auth service to add a user to the metadata database. Requires role `system`.", + hidden = true) @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Created user", @@ -142,114 +140,10 @@ public class UserEndpoint extends AbstractEndpoint { public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody CreateUserDto data) throws UserExistsException, EmailExistsException, AuthServiceException, AuthServiceConnectionException, UserNotFoundException, CredentialsInvalidException { - log.debug("endpoint create user, data.username={}", data.getUsername()); - userService.validateUsernameNotExists(data.getUsername()); - userService.validateEmailNotExists(data.getEmail()); + log.debug("endpoint create user, data.id={}, data.username={}", data.getId(), data.getUsername()); return ResponseEntity.status(HttpStatus.CREATED) .body(userMapper.userToUserBriefDto( - userService.create(data, authenticationService.create(data).getAttributes().getLdapId()[0]))); - } - - @PostMapping("/token") - @Observed(name = "dbrepo_user_token") - @Operation(summary = "Create token", - description = "Creates a user token via the Auth Service.") - @ApiResponses(value = { - @ApiResponse(responseCode = "202", - description = "Obtained user token", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = TokenDto.class))}), - @ApiResponse(responseCode = "400", - description = "Invalid login request", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "403", - description = "Not allowed to get token", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "404", - description = "Failed to find user in auth database", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "428", - description = "Account is not fully setup in auth service (requires password change?)", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "502", - description = "Connection to auth service failed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "503", - description = "Failed to get user in auth service", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - }) - public ResponseEntity<TokenDto> getToken(@NotNull @Valid @RequestBody LoginRequestDto data) - throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, CredentialsInvalidException, - AccountNotSetupException { - log.debug("endpoint get token, data.username={}", data.getUsername()); - /* check */ - try { - userService.findByUsername(data.getUsername()); - } catch (UserNotFoundException e) { - /* need to sync */ - log.warn("User with username {} does not exist in metadata database yet", data.getUsername()); - final CreateUserDto request = CreateUserDto.builder() - .username(data.getUsername()) - .email("noreply@example.com") - .password(data.getPassword()) - .build(); - final at.tuwien.api.keycloak.UserDto user = authenticationService.findByUsername(data.getUsername()); - if (user.getAttributes().getLdapId() == null || user.getAttributes().getLdapId().length != 1) { - log.error("Failed to map ldap id for user with username: {}", data.getUsername()); - throw new UserNotFoundException("Failed to map ldap id"); - } - userService.create(request, user.getAttributes().getLdapId()[0]); - log.info("Patched missing user information for user with username: {}", data.getUsername()); - } - return ResponseEntity.accepted() - .body(authenticationService.obtainToken(data)); - } - - @PutMapping("/token") - @Observed(name = "dbrepo_user_refresh_token") - @Operation(summary = "Refresh token", - description = "Refreshes user token by refresh token.") - @ApiResponses(value = { - @ApiResponse(responseCode = "202", - description = "Refreshed user token", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = TokenDto.class))}), - @ApiResponse(responseCode = "400", - description = "Invalid refresh token", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "403", - description = "Not allowed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "502", - description = "Connection to auth service failed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - }) - public ResponseEntity<TokenDto> refreshToken(@NotNull @Valid @RequestBody RefreshTokenRequestDto data) - throws AuthServiceConnectionException, CredentialsInvalidException { - log.debug("endpoint refresh token"); - /* check */ - return ResponseEntity.accepted() - .body(authenticationService.refreshToken(data.getRefreshToken())); + userService.create(data))); } @GetMapping("/{userId}") @@ -327,11 +221,16 @@ public class UserEndpoint extends AbstractEndpoint { content = {@Content( mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), + @ApiResponse(responseCode = "503", + description = "Failed to modify user at auth service", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<UserBriefDto> modify(@NotNull @PathVariable("userId") UUID userId, - @NotNull @Valid @RequestBody UserUpdateDto data, - @NotNull Principal principal) throws NotAllowedException, - UserNotFoundException, DatabaseNotFoundException { + @NotNull @Valid @RequestBody UserUpdateDto data, + @NotNull Principal principal) throws NotAllowedException, + UserNotFoundException, AuthServiceException { log.debug("endpoint modify a user, userId={}, data={}", userId, data); final User user = userService.findById(userId); if (!user.getId().equals(getId(principal))) { @@ -381,9 +280,9 @@ public class UserEndpoint extends AbstractEndpoint { }) public ResponseEntity<Void> password(@NotNull @PathVariable("userId") UUID userId, @NotNull @Valid @RequestBody UserPasswordDto data, - @NotNull Principal principal) throws NotAllowedException, AuthServiceException, - AuthServiceConnectionException, UserNotFoundException, DatabaseNotFoundException, DataServiceException, - DataServiceConnectionException, CredentialsInvalidException { + @NotNull Principal principal) throws NotAllowedException, + UserNotFoundException, DatabaseNotFoundException, DataServiceException, + DataServiceConnectionException { log.debug("endpoint modify a user password, userId={}, principal.name={}", userId, principal.getName()); final User user = userService.findById(userId); if (!user.getUsername().equals(principal.getName())) { diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java index 3fc9c7216e5ad6f84fa530556708a8453eac2bac..a54f616b01e61edad50d85d4b5f0d494c9e429d6 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java @@ -2,8 +2,8 @@ package at.tuwien.validation; import at.tuwien.SortType; import at.tuwien.api.database.table.CreateTableDto; -import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; +import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.identifier.IdentifierSaveDto; import at.tuwien.endpoints.AbstractEndpoint; import at.tuwien.entities.database.AccessType; @@ -43,15 +43,6 @@ public class EndpointValidator extends AbstractEndpoint { this.accessService = accessService; } - public void validateOnlyPrivateDataAccess(Database database, Principal principal, boolean writeAccessOnly) - throws NotAllowedException, UserNotFoundException, AccessNotFoundException { - if (database.getIsPublic()) { - log.trace("database with id {} is public: no access needed", database.getId()); - return; - } - validateOnlyAccess(database, principal, writeAccessOnly); - } - public void validateOnlyPrivateSchemaAccess(Database database, Principal principal, boolean writeAccessOnly) throws NotAllowedException, UserNotFoundException, AccessNotFoundException { if (database.getIsSchemaPublic()) { @@ -61,11 +52,6 @@ public class EndpointValidator extends AbstractEndpoint { validateOnlyAccess(database, principal, writeAccessOnly); } - public void validateOnlyPrivateDataAccess(Database database, Principal principal) throws NotAllowedException, - UserNotFoundException, AccessNotFoundException { - validateOnlyPrivateDataAccess(database, principal, false); - } - public void validateOnlyPrivateSchemaAccess(Database database, Principal principal) throws NotAllowedException, UserNotFoundException, AccessNotFoundException { validateOnlyPrivateSchemaAccess(database, principal, false); @@ -169,17 +155,6 @@ public class EndpointValidator extends AbstractEndpoint { log.error("Validation failed: column {} type serial demands non-null", optional4a.get().getName()); throw new MalformedException("Validation failed: column " + optional4a.get().getName() + " type serial demands non-null"); } - final Optional<CreateTableColumnDto> optional4b = data.getColumns() - .stream() - .filter(c -> c.getType().equals(ColumnTypeDto.SERIAL) && data.getConstraints() - .getUniques() - .stream() - .noneMatch(uk -> uk.size() == 1 && uk.contains(c.getName()))) - .findFirst(); - if (optional4b.isPresent()) { - log.error("Validation failed: column {} type serial demands a unique constraint", optional4b.get().getName()); - throw new MalformedException("Validation failed: column " + optional4b.get().getName() + " type serial demands a unique constraint"); - } } public boolean validateOnlyMineOrWriteAccessOrHasRole(User owner, Principal principal, DatabaseAccess access, String role) { @@ -204,18 +179,6 @@ public class EndpointValidator extends AbstractEndpoint { return false; } - public boolean validateOnlyMineOrReadAccessOrHasRole(User owner, Principal principal, DatabaseAccess access, String role) { - if (validateOnlyMineOrWriteAccessOrHasRole(owner, principal, access, role)) { - return true; - } - if (access.getType().equals(AccessType.READ)) { - log.debug("validation passed: user {} has read access", principal.getName()); - return true; - } - log.debug("validation failed: user {} has insufficient access {} or role", principal.getName(), access.getType()); - return false; - } - @Transactional(readOnly = true) public void validateOnlyOwnerOrWriteAll(Table table, User user) throws NotAllowedException, AccessNotFoundException { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierStatusTypeDtoConverterUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierStatusTypeDtoConverterUnitTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8c7316b3d15590979652ea4a04498271f068d2fc --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierStatusTypeDtoConverterUnitTest.java @@ -0,0 +1,42 @@ +package at.tuwien.converters; + +import at.tuwien.api.identifier.IdentifierStatusTypeDto; +import at.tuwien.test.AbstractUnitTest; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Log4j2 +@SpringBootTest +public class IdentifierStatusTypeDtoConverterUnitTest extends AbstractUnitTest { + + @Autowired + private IdentifierStatusTypeDtoConverter identifierStatusTypeDtoConverter; + + @BeforeEach + public void beforeEach() { + genesis(); + } + + @Test + public void identifierStatusTypeDtoConverter_succeeds() { + + /* test */ + final IdentifierStatusTypeDto response = identifierStatusTypeDtoConverter.convert(IdentifierStatusTypeDto.DRAFT.getName()); + assertEquals(IdentifierStatusTypeDto.DRAFT, response); + } + + @Test + public void identifierStatusTypeDtoConverter_fails() { + + /* test */ + assertThrows(IllegalArgumentException.class, () -> { + identifierStatusTypeDtoConverter.convert("i_do_not_exist"); + }); + } +} diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeDtoConverterUnitTest.java similarity index 55% rename from dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java rename to dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeDtoConverterUnitTest.java index 7215e5db918f226e6a826fce66e4e129ca4f0c5f..98abd668d8ec0e30a0f420682a0c7b7fd3e5704d 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeDtoConverterUnitTest.java @@ -8,14 +8,15 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; @Log4j2 @SpringBootTest -public class IdentifierTypeConverterUnitTest extends AbstractUnitTest { +public class IdentifierTypeDtoConverterUnitTest extends AbstractUnitTest { @Autowired - private IdentifierTypeConverter identifierTypeConverter; + private IdentifierTypeDtoConverter identifierTypeDtoConverter; @BeforeEach public void beforeEach() { @@ -23,19 +24,19 @@ public class IdentifierTypeConverterUnitTest extends AbstractUnitTest { } @Test - public void identifierTypeConverter_succeeds() { + public void IdentifierTypeDtoConverter_succeeds() { /* test */ - final IdentifierTypeDto response = identifierTypeConverter.convert(IdentifierTypeDto.DATABASE.getName()); + final IdentifierTypeDto response = identifierTypeDtoConverter.convert(IdentifierTypeDto.DATABASE.getName()); assertEquals(IdentifierTypeDto.DATABASE, response); } @Test - public void identifierTypeConverter_fails() { + public void IdentifierTypeDtoConverter_fails() { /* test */ assertThrows(IllegalArgumentException.class, () -> { - identifierTypeConverter.convert("i_do_not_exist"); + identifierTypeDtoConverter.convert("i_do_not_exist"); }); } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AbstractEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AbstractEndpointUnitTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dfa4924957b9c300aeda92ad2ed305a4dd29b444 --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AbstractEndpointUnitTest.java @@ -0,0 +1,94 @@ +package at.tuwien.endpoints; + +import at.tuwien.api.database.AccessTypeDto; +import at.tuwien.api.database.DatabaseAccessDto; +import at.tuwien.api.user.UserDetailsDto; +import at.tuwien.entities.database.Database; +import at.tuwien.entities.database.DatabaseAccess; +import at.tuwien.entities.user.User; +import at.tuwien.exception.*; +import at.tuwien.mapper.MetadataMapper; +import at.tuwien.service.AccessService; +import at.tuwien.service.DatabaseService; +import at.tuwien.service.UserService; +import at.tuwien.test.AbstractUnitTest; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.security.Principal; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@Log4j2 +@SpringBootTest +@ExtendWith(SpringExtension.class) +public class AbstractEndpointUnitTest extends AbstractUnitTest { + + @Autowired + private AccessEndpoint accessEndpoint; + + @BeforeEach + public void beforeEach() { + genesis(); + } + + @Test + public void hasRole_noPrincipal_fails() { + + /* test */ + assertFalse(accessEndpoint.hasRole(null, "some-role")); + } + + @Test + public void hasRole_noRole_fails() { + + /* test */ + assertFalse(accessEndpoint.hasRole(USER_1_PRINCIPAL, null)); + } + + @Test + public void getId_fails() { + + /* test */ + assertNull(accessEndpoint.getId(null)); + } + + @Test + public void getId_noId_fails() { + final Principal principal = new UsernamePasswordAuthenticationToken(UserDetailsDto.builder() + .id(null) // <<< + .build(), null); + + /* test */ + assertThrows(IllegalArgumentException.class, () -> { + accessEndpoint.getId(principal); + }); + } + + @Test + public void getId_incompatible_fails() { + final Principal principal = new UsernamePasswordAuthenticationToken("", null); + + /* test */ + assertThrows(IllegalArgumentException.class, () -> { + accessEndpoint.getId(principal); + }); + } + +} diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java index f4a700e859e68f5705475a63dbc66f2afeaeb5c4..376769e3c318eaabc93e2a65def2673d85d3e7d2 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java @@ -219,6 +219,26 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { generic_update(USER_1_PRINCIPAL, USER_1, USER_2_ID, USER_2, DATABASE_1_USER_2_WRITE_OWN_ACCESS); } + @Test + @WithMockUser(username = USER_1_USERNAME, authorities = {"update-database-access"}) + public void update_ownerNoAccess_fails() { + + /* test */ + assertThrows(NotAllowedException.class, () -> { + generic_update(USER_1_PRINCIPAL, USER_1, USER_1_ID, null, null); + }); + } + + @Test + @WithMockUser(username = USER_1_USERNAME, authorities = {"update-database-access"}) + public void update_ownerNoWriteAllAccess_fails() { + + /* test */ + assertThrows(NotAllowedException.class, () -> { + generic_update(USER_1_PRINCIPAL, USER_1, USER_LOCAL_ADMIN_ID, USER_LOCAL, null); + }); + } + @Test @WithAnonymousUser public void revoke_anonymous_fails() { @@ -249,6 +269,26 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { }); } + @Test + @WithMockUser(username = USER_1_USERNAME, authorities = {"delete-database-access"}) + public void revoke_ownerNoAccess_fails() { + + /* test */ + assertThrows(NotAllowedException.class, () -> { + generic_revoke(USER_1_PRINCIPAL, USER_1, USER_1_ID, null); + }); + } + + @Test + @WithMockUser(username = USER_1_USERNAME, authorities = {"delete-database-access"}) + public void revoke_ownerNoWriteAllAccess_fails() { + + /* test */ + assertThrows(NotAllowedException.class, () -> { + generic_revoke(USER_1_PRINCIPAL, USER_1, USER_LOCAL_ADMIN_ID, USER_LOCAL); + }); + } + @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"delete-database-access"}) public void revoke_succeeds() throws NotAllowedException, DataServiceException, DataServiceConnectionException, diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ContainerEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ContainerEndpointUnitTest.java index 98ece39e1e78b5a2c2719974daf0a9443cd9c2ac..00185d9ea134c33eba73ba7c24a02ff2d606ab1d 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ContainerEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ContainerEndpointUnitTest.java @@ -1,12 +1,14 @@ package at.tuwien.endpoints; -import at.tuwien.api.container.CreateContainerDto; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.container.CreateContainerDto; import at.tuwien.entities.container.Container; -import at.tuwien.exception.*; +import at.tuwien.exception.ContainerAlreadyExistsException; +import at.tuwien.exception.ContainerNotFoundException; +import at.tuwien.exception.ImageNotFoundException; import at.tuwien.service.impl.ContainerServiceImpl; +import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,6 +16,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.test.context.support.WithAnonymousUser; @@ -52,19 +55,23 @@ public class ContainerEndpointUnitTest extends AbstractUnitTest { } @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"find-container"}) - public void findById_hasRole_succeeds() throws ContainerNotFoundException { + @WithMockUser(username = USER_1_USERNAME) + public void findById_succeeds() throws ContainerNotFoundException { /* test */ findById_generic(CONTAINER_1_ID, CONTAINER_1, USER_1_PRINCIPAL); } @Test - @WithMockUser(username = USER_4_USERNAME) - public void findById_noRole_succeeds() throws ContainerNotFoundException { + @WithMockUser(username = USER_LOCAL_ADMIN_USERNAME) + public void findById_system_succeeds() throws ContainerNotFoundException { /* test */ - findById_generic(CONTAINER_1_ID, CONTAINER_1, USER_4_PRINCIPAL); + final ResponseEntity<ContainerDto> response = findById_generic(CONTAINER_1_ID, CONTAINER_1, USER_LOCAL_ADMIN_PRINCIPAL); + final HttpHeaders headers = response.getHeaders() ; + assertEquals(List.of(CONTAINER_1_PRIVILEGED_USERNAME), headers.get("X-Username")); + assertEquals(List.of(CONTAINER_1_PRIVILEGED_PASSWORD), headers.get("X-Password")); + assertEquals(List.of("X-Username X-Password"), headers.get("Access-Control-Expose-Headers")); } @Test @@ -171,7 +178,7 @@ public class ContainerEndpointUnitTest extends AbstractUnitTest { /* ## GENERIC TEST CASES ## */ /* ################################################################################################### */ - public void findById_generic(Long containerId, Container container, Principal principal) + public ResponseEntity<ContainerDto> findById_generic(Long containerId, Container container, Principal principal) throws ContainerNotFoundException { /* mock */ @@ -182,6 +189,7 @@ public class ContainerEndpointUnitTest extends AbstractUnitTest { final ResponseEntity<ContainerDto> response = containerEndpoint.findById(containerId, principal); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); + return response; } public void delete_generic(Long containerId, Container container) throws ContainerNotFoundException { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java index 5118aec776b4f5956f8a82a23803f4ce2bfc6cdf..fd91fb5655ad563ee0b4f8503dbdb5b60db6fea9 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java @@ -4,7 +4,6 @@ import at.tuwien.api.database.*; import at.tuwien.entities.database.Database; import at.tuwien.entities.user.User; import at.tuwien.exception.*; -import at.tuwien.gateway.KeycloakGateway; import at.tuwien.service.*; import at.tuwien.service.impl.DatabaseServiceImpl; import at.tuwien.test.AbstractUnitTest; @@ -42,9 +41,6 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @MockBean private AccessService accessService; - @MockBean - private KeycloakGateway keycloakGateway; - @MockBean private ContainerService containerService; @@ -144,7 +140,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void refreshTableMetadata_anonymous_succeeds() { + public void refreshTableMetadata_anonymous_fails() { /* test */ assertThrows(AccessDeniedException.class, () -> { @@ -154,7 +150,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_1_USERNAME) - public void refreshTableMetadata_noRole_succeeds() { + public void refreshTableMetadata_noRole_fails() { /* test */ assertThrows(AccessDeniedException.class, () -> { @@ -353,15 +349,12 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-database-visibility"}) public void visibility_hasRole_succeeds() throws NotAllowedException, UserNotFoundException, - DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException, AuthServiceException, - AuthServiceConnectionException { + DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException { final DatabaseModifyVisibilityDto request = DatabaseModifyVisibilityDto.builder() .isPublic(true) .build(); /* mock */ - when(keycloakGateway.findByUsername(USER_1_USERNAME)) - .thenReturn(USER_1_KEYCLOAK_DTO); when(userService.findById(USER_1_ID)) .thenReturn(USER_1); @@ -509,7 +502,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-database-owner"}) public void transfer_hasRole_succeeds() throws DataServiceConnectionException, DataServiceException, NotAllowedException, UserNotFoundException, DatabaseNotFoundException, SearchServiceException, - SearchServiceConnectionException, AuthServiceException, AuthServiceConnectionException { + SearchServiceConnectionException { final DatabaseTransferDto request = DatabaseTransferDto.builder() .id(USER_4_ID) .build(); @@ -517,8 +510,6 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(databaseService.findById(DATABASE_1_ID)) .thenReturn(DATABASE_1); - when(keycloakGateway.findByUsername(USER_1_USERNAME)) - .thenReturn(USER_1_KEYCLOAK_DTO); when(userService.findById(USER_1_ID)) .thenReturn(USER_1); when(userService.findById(USER_4_ID)) @@ -550,7 +541,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findById_anonymous_fails() { + public void findById_anonymousPrivateSchemaNoAccess_fails() { /* test */ assertThrows(NotAllowedException.class, () -> { @@ -560,40 +551,51 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findById_anonymousNotFound_fails() { + public void findById_anonymousPublicSchemaNoAccess_succeeds() throws UserNotFoundException, NotAllowedException, + DataServiceException, DatabaseNotFoundException, ExchangeNotFoundException, DataServiceConnectionException { /* test */ - assertThrows(DatabaseNotFoundException.class, () -> { - findById_generic(DATABASE_1_ID, null, null); - }); + final DatabaseDto database = findById_generic(DATABASE_2_ID, DATABASE_2, null); + assertEquals(3, database.getTables().size()); + assertEquals(1, database.getViews().size()); + assertEquals(0, database.getAccesses().size()); } @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"find-database"}) - public void findById_hasRole_succeeds() throws DataServiceException, DataServiceConnectionException, - DatabaseNotFoundException, ExchangeNotFoundException, UserNotFoundException, NotAllowedException { - - /* pre-condition */ - assertTrue(DATABASE_3_PUBLIC); + @WithAnonymousUser + public void findById_anonymousPrivateSchemaNoAccessSystem_succeeds() throws UserNotFoundException, + NotAllowedException, DataServiceException, DatabaseNotFoundException, ExchangeNotFoundException, + DataServiceConnectionException { /* test */ - findById_generic(DATABASE_3_ID, DATABASE_3, USER_1_PRINCIPAL); + final DatabaseDto database = findById_generic(DATABASE_1_ID, DATABASE_1, USER_LOCAL_ADMIN_PRINCIPAL); + assertEquals(4, database.getTables().size()); + assertEquals(2, database.getViews().size()); + assertNotEquals(0, database.getAccesses().size()); } @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"find-database"}) - public void findById_hasRoleForeign_succeeds() throws DataServiceException, DataServiceConnectionException, - DatabaseNotFoundException, ExchangeNotFoundException, UserNotFoundException, NotAllowedException { + @WithAnonymousUser + public void findById_privateSchema_fails() { - /* pre-condition */ - assertTrue(DATABASE_3_PUBLIC); + /* test */ + assertThrows(NotAllowedException.class, () -> { + findById_generic(DATABASE_1_ID, DATABASE_1, null); + }); + } + + @Test + @WithAnonymousUser + public void findById_anonymousNotFound_fails() { /* test */ - findById_generic(DATABASE_3_ID, DATABASE_3, USER_1_PRINCIPAL); + assertThrows(DatabaseNotFoundException.class, () -> { + findById_generic(DATABASE_1_ID, null, null); + }); } @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"find-database"}) + @WithMockUser(username = USER_1_USERNAME) public void findById_ownerSeesAccessRights_succeeds() throws DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, ExchangeNotFoundException, UserNotFoundException, NotAllowedException { @@ -602,10 +604,10 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { .thenReturn(List.of(DATABASE_1_USER_1_WRITE_ALL_ACCESS, DATABASE_1_USER_2_READ_ACCESS)); /* test */ - final DatabaseDto response = findById_generic(DATABASE_1_ID, DATABASE_1, USER_1_PRINCIPAL); - final List<DatabaseAccessDto> accessList = response.getAccesses(); - assertNotNull(accessList); - assertEquals(3, accessList.size()); + final DatabaseDto database = findById_generic(DATABASE_1_ID, DATABASE_1, USER_1_PRINCIPAL); + assertEquals(4, database.getTables().size()); + assertEquals(3, database.getViews().size()); + assertEquals(3, database.getAccesses().size()); } @Test diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java index 74a252c5a63572e569a1e7b8379637e2d34dc03d..419393b485096b84f6495bfcd6ce2910e0c9ae46 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java @@ -122,12 +122,58 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { ); } + public static Stream<Arguments> findAll_anonymousFilterDatabase_parameters() { + return Stream.of( + Arguments.arguments("dbid", DATABASE_1_ID, null, null, null, null, 1), + Arguments.arguments("qid", DATABASE_1_ID, QUERY_1_ID, null, null, null, 0), + Arguments.arguments("vid", DATABASE_1_ID, null, VIEW_1_ID, null, null, 0), + Arguments.arguments("tid", DATABASE_1_ID, null, null, TABLE_1_ID, null, 0), + Arguments.arguments("status_published", DATABASE_1_ID, null, null, null, "PUBLISHED", 1), + Arguments.arguments("status_draft", DATABASE_1_ID, null, null, null, "DRAFT", 0) + ); + } + + public static Stream<Arguments> findAll_filterSubset_parameters() { + return Stream.of( + Arguments.arguments("status_published", DATABASE_2_ID, null, null, null, "PUBLISHED", 0), + Arguments.arguments("status_draft", DATABASE_2_ID, null, null, null, "DRAFT", 1) + ); + } + public static Stream<Arguments> findAll_filterDatabase_parameters() { return Stream.of( - Arguments.arguments("dbid", DATABASE_1_ID, null, null, null, 4), - Arguments.arguments("qid", DATABASE_1_ID, QUERY_1_ID, null, null, 1), - Arguments.arguments("vid", DATABASE_1_ID, null, VIEW_1_ID, null, 1), - Arguments.arguments("tid", DATABASE_1_ID, null, null, TABLE_1_ID, 1) + Arguments.arguments("database_dbid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("database_qid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("database_vid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("database_tid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL), + Arguments.arguments("subset_dbid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("subset_qid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, QUERY_1_ID, null, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("subset_vid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("subset_tid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL), + Arguments.arguments("view_dbid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("view_qid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("view_vid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, VIEW_1_ID, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("view_tid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL), + Arguments.arguments("table_dbid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL), + Arguments.arguments("table_qid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("table_vid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL), + Arguments.arguments("table_tid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 1, USER_1_PRINCIPAL), + Arguments.arguments("anon_database_dbid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, 1, null), + Arguments.arguments("anon_database_qid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null), + Arguments.arguments("anon_database_vid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null), + Arguments.arguments("anon_database_tid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null), + Arguments.arguments("anon_subset_dbid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, null, 1, null), + Arguments.arguments("anon_subset_qid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, QUERY_1_ID, null, null, 1, null), + Arguments.arguments("anon_subset_vid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null), + Arguments.arguments("anon_subset_tid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null), + Arguments.arguments("anon_view_dbid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, null, 1, null), + Arguments.arguments("anon_view_qid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null), + Arguments.arguments("anon_view_vid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, VIEW_1_ID, null, 1, null), + Arguments.arguments("anon_view_tid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null), + Arguments.arguments("anon_table_dbid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, null, 1, null), + Arguments.arguments("anon_table_qid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null), + Arguments.arguments("anon_table_vid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null), + Arguments.arguments("anon_table_tid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 1, null) ); } @@ -146,14 +192,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findAll_empty_succeeds() throws FormatNotAvailableException { + public void findAll_empty_succeeds() { /* mock */ when(identifierService.findAll()) .thenReturn(List.of()); /* test */ - final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/json"); + final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/json", null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); @@ -161,12 +207,90 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { assertEquals(0, identifiers.size()); } + @ParameterizedTest + @MethodSource("findAll_anonymousFilterDatabase_parameters") + @WithAnonymousUser + public void findAll_anonymousFilterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId, + Long tableId, IdentifierStatusTypeDto status, + Integer expectedSize) throws ViewNotFoundException, + TableNotFoundException, DatabaseNotFoundException { + + /* mock */ + when(identifierService.findAll()) + .thenReturn(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4, IDENTIFIER_5, IDENTIFIER_6, IDENTIFIER_7)); + if (viewId != null) { + when(viewService.findById(DATABASE_1, VIEW_1_ID)) + .thenReturn(VIEW_1); + } + if (tableId != null) { + when(tableService.findById(DATABASE_1, TABLE_1_ID)) + .thenReturn(TABLE_1); + } + + /* test */ + final ResponseEntity<?> response = identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, status, databaseId, queryId, viewId, tableId, "application/json", null); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); + assertNotNull(identifiers); + assertEquals(expectedSize, identifiers.size()); + } + + @ParameterizedTest + @MethodSource("findAll_filterSubset_parameters") + @WithMockUser(username = USER_2_USERNAME) + public void findAll_filterSubset_succeeds(String name, Long databaseId, Long queryId, Long viewId, Long tableId, + IdentifierStatusTypeDto status, Integer expectedSize) { + + /* mock */ + when(identifierService.findAll()) + .thenReturn(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4, IDENTIFIER_5, IDENTIFIER_6, IDENTIFIER_7)); + + /* test */ + final ResponseEntity<?> response = identifierEndpoint.findAll(IdentifierTypeDto.SUBSET, status, databaseId, queryId, viewId, tableId, "application/json", USER_2_PRINCIPAL); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); + assertNotNull(identifiers); + assertEquals(expectedSize, identifiers.size()); + } + + @ParameterizedTest + @MethodSource("findAll_anonymousFilterDatabase_parameters") + @WithAnonymousUser + public void findAll_wrongPrincipalFilterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId, + Long tableId, IdentifierStatusTypeDto status, + Integer expectedSize) + throws ViewNotFoundException, TableNotFoundException, DatabaseNotFoundException { + + /* mock */ + when(identifierService.findAll()) + .thenReturn(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4, IDENTIFIER_5, IDENTIFIER_6, IDENTIFIER_7)); + if (viewId != null) { + when(viewService.findById(DATABASE_1, VIEW_1_ID)) + .thenReturn(VIEW_1); + } + if (tableId != null) { + when(tableService.findById(DATABASE_1, TABLE_1_ID)) + .thenReturn(TABLE_1); + } + + /* test */ + final ResponseEntity<?> response = identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, status, databaseId, queryId, viewId, tableId, "application/json", USER_2_PRINCIPAL); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); + assertNotNull(identifiers); + assertEquals(expectedSize, identifiers.size()); + } + @ParameterizedTest @MethodSource("findAll_filterDatabase_parameters") @WithAnonymousUser - public void findAll_filterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId, Long tableId, - Integer expectedSize) throws FormatNotAvailableException, - ViewNotFoundException, TableNotFoundException, DatabaseNotFoundException { + public void findAll_filterDatabase_succeeds(String name, IdentifierTypeDto type, IdentifierStatusTypeDto status, + Long databaseId, Long queryId, Long viewId, Long tableId, + Integer expectedSize, Principal principal) throws ViewNotFoundException, + TableNotFoundException, DatabaseNotFoundException { /* mock */ when(identifierService.findAll()) @@ -181,7 +305,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } /* test */ - final ResponseEntity<?> response = identifierEndpoint.findAll(databaseId, queryId, viewId, tableId, "application/json"); + final ResponseEntity<?> response = identifierEndpoint.findAll(type, status, databaseId, queryId, viewId, tableId, "application/json", principal); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); @@ -191,14 +315,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findAll_json_succeeds() throws FormatNotAvailableException { + public void findAll_json_succeeds() { /* mock */ when(identifierService.findAll()) .thenReturn(List.of(IDENTIFIER_1)); /* test */ - final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/json"); + final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/json", null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); @@ -208,14 +332,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findAll_jsonLd_succeeds() throws FormatNotAvailableException { + public void findAll_jsonLd_succeeds() { /* mock */ when(identifierService.findAll()) .thenReturn(List.of(IDENTIFIER_1)); /* test */ - final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/ld+json"); + final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/ld+json", null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); final List<LdDatasetDto> identifiers = (List<LdDatasetDto>) response.getBody(); @@ -225,23 +349,95 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void findAll_format_fails() { + public void findAll_format_succeeds() { /* mock */ when(identifierService.findAll()) .thenReturn(List.of(IDENTIFIER_1)); + /* test */ + final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "text/html", null); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody(); + assertNotNull(identifiers); + assertEquals(1, identifiers.size()); + } + + @Test + @WithAnonymousUser + public void find_textCsvDatabase_fails() throws IdentifierNotFoundException { + + /* mock */ + when(identifierService.find(IDENTIFIER_1_ID)) + .thenReturn(IDENTIFIER_1); + /* test */ assertThrows(FormatNotAvailableException.class, () -> { - identifierEndpoint.findAll(null, null, null, null, "text/csv"); + identifierEndpoint.find(IDENTIFIER_1_ID, "text/csv", null); + }); + } + + @Test + @WithAnonymousUser + public void find_draft_fails() throws IdentifierNotFoundException { + + /* mock */ + when(identifierService.find(IDENTIFIER_5_ID)) + .thenReturn(IDENTIFIER_5); + + /* test */ + assertThrows(NotAllowedException.class, () -> { + identifierEndpoint.find(IDENTIFIER_5_ID, "application/json", null); + }); + } + + @Test + @WithMockUser(username = USER_1_USERNAME) + public void find_draftNotOwner_fails() throws IdentifierNotFoundException { + + /* mock */ + when(identifierService.find(IDENTIFIER_5_ID)) + .thenReturn(IDENTIFIER_5); + + /* test */ + assertThrows(NotAllowedException.class, () -> { + identifierEndpoint.find(IDENTIFIER_5_ID, "application/json", USER_1_PRINCIPAL); }); } + @Test + @WithMockUser(username = USER_2_USERNAME) + public void find_draft_succeeds() throws IdentifierNotFoundException, MalformedException, NotAllowedException, + DataServiceException, QueryNotFoundException, DataServiceConnectionException, FormatNotAvailableException { + + /* mock */ + when(identifierService.find(IDENTIFIER_5_ID)) + .thenReturn(IDENTIFIER_5); + + /* test */ + identifierEndpoint.find(IDENTIFIER_5_ID, "application/json", USER_2_PRINCIPAL); + } + @Test @WithAnonymousUser + public void find_defaultHtmlRespondsJson_succeeds() throws IdentifierNotFoundException, MalformedException, + NotAllowedException, DataServiceException, QueryNotFoundException, DataServiceConnectionException, + FormatNotAvailableException { + + /* mock */ + when(identifierService.find(IDENTIFIER_1_ID)) + .thenReturn(IDENTIFIER_1); + + /* test */ + identifierEndpoint.find(IDENTIFIER_1_ID, "text/html", null); + } + + @Test + @WithMockUser(username = USER_4_USERNAME) public void find_json0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "application/json"; final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata0.json"), StandardCharsets.UTF_8), IdentifierDto.class); @@ -250,7 +446,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_7); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final IdentifierDto body = (IdentifierDto) response.getBody(); assertNotNull(body); @@ -271,7 +467,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_json1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "application/json"; final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata1.json"), StandardCharsets.UTF_8), IdentifierDto.class); @@ -280,7 +476,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final IdentifierDto body = (IdentifierDto) response.getBody(); assertNotNull(body); @@ -321,7 +517,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_csv_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/csv"; final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv"))); final InputStreamResource mock = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv"))); @@ -333,7 +529,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(mock); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_2_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_2_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final InputStreamResource body = (InputStreamResource) response.getBody(); assertNotNull(body); @@ -344,7 +540,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliography_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"), StandardCharsets.UTF_8); @@ -356,7 +552,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -365,9 +561,29 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser + public void find_anonymousBibliographyApa0_fails() throws IOException, MalformedException, + IdentifierNotFoundException { + final String accept = "text/bibliography; style=apa"; + final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa0.txt"), + StandardCharsets.UTF_8); + + /* mock */ + when(identifierService.exportBibliography(IDENTIFIER_7, BibliographyTypeDto.APA)) + .thenReturn(compare); + when(identifierService.find(IDENTIFIER_7_ID)) + .thenReturn(IDENTIFIER_7); + + /* test */ + assertThrows(NotAllowedException.class, () -> { + identifierEndpoint.find(IDENTIFIER_7_ID, accept, null); + }); + } + + @Test + @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyApa0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa0.txt"), StandardCharsets.UTF_8); @@ -379,7 +595,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_7); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -390,7 +606,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"), StandardCharsets.UTF_8); @@ -402,7 +618,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -410,10 +626,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser - public void find_bibliographyApa2_succeeds() throws IOException, MalformedException, DataServiceException, + @WithMockUser(username = USER_2_USERNAME) + public void find_draftBibliographyApa2_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa2.txt"), StandardCharsets.UTF_8); @@ -425,7 +641,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_5); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -436,7 +652,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa3_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa3.txt"), StandardCharsets.UTF_8); @@ -448,7 +664,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_6); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_6_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_6_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -459,7 +675,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa4_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa4.txt"), StandardCharsets.UTF_8); @@ -471,7 +687,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1_WITH_DOI); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -479,10 +695,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser + @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyIeee0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee0.txt"), StandardCharsets.UTF_8); @@ -494,7 +710,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_7); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -505,7 +721,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyIeee1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee1.txt"), StandardCharsets.UTF_8); @@ -517,7 +733,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -525,10 +741,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser + @WithMockUser(username = USER_2_USERNAME) public void find_bibliographyIeee2_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee2.txt"), StandardCharsets.UTF_8); @@ -540,7 +756,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_5); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -551,7 +767,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyIeee3_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee3.txt"), StandardCharsets.UTF_8); @@ -563,7 +779,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1_WITH_DOI); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -571,10 +787,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser + @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyBibtex0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex0.txt"), StandardCharsets.UTF_8); @@ -586,7 +802,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_7); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -597,7 +813,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyBibtex1_succeeds() throws MalformedException, IOException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex1.txt"), StandardCharsets.UTF_8); @@ -609,7 +825,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -617,10 +833,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser + @WithMockUser(username = USER_2_USERNAME) public void find_bibliographyBibtex2_succeeds() throws MalformedException, DataServiceException, IOException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex2.txt"), StandardCharsets.UTF_8); @@ -632,7 +848,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_5); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -643,7 +859,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyBibtex3_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, IOException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException { + FormatNotAvailableException, NotAllowedException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex3.txt"), StandardCharsets.UTF_8); @@ -655,7 +871,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1_WITH_DOI); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final String body = (String) response.getBody(); assertNotNull(body); @@ -665,7 +881,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_jsonLd_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException { + QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException { final String accept = "application/ld+json"; /* mock */ @@ -673,7 +889,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final LdDatasetDto body = (LdDatasetDto) response.getBody(); assertNotNull(body); @@ -689,22 +905,22 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_7); /* test */ - assertThrows(FormatNotAvailableException.class, () -> { - identifierEndpoint.find(IDENTIFIER_7_ID, accept); + assertThrows(NotAllowedException.class, () -> { + identifierEndpoint.find(IDENTIFIER_7_ID, accept, null); }); } @Test @WithAnonymousUser public void find_move_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException { + QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException { /* mock */ when(identifierService.find(IDENTIFIER_1_ID)) .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, null); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, null, null); assertEquals(HttpStatus.MOVED_PERMANENTLY, response.getStatusCode()); } @@ -848,7 +1064,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_json_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException { + FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException, NotAllowedException { final String accept = "application/json"; /* mock */ @@ -856,7 +1072,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { .thenReturn(IDENTIFIER_1); /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept); + final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); assertEquals(HttpStatus.OK, response.getStatusCode()); final IdentifierDto body = (IdentifierDto) response.getBody(); assertNotNull(body); @@ -875,7 +1091,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_xml_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - IOException, QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException { + IOException, QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, + NotAllowedException { final InputStreamResource resource = new InputStreamResource(FileUtils.openInputStream( new File("src/test/resources/xml/datacite-example-dataset-v4.xml"))); @@ -892,7 +1109,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_httpRedirect_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, FormatNotAvailableException, QueryNotFoundException, - IdentifierNotFoundException { + IdentifierNotFoundException, NotAllowedException { /* test */ final ResponseEntity<?> response = generic_find(null, null); @@ -1291,7 +1508,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { protected ResponseEntity<?> generic_find(String accept, InputStreamResource resource) throws MalformedException, DataServiceException, DataServiceConnectionException, FormatNotAvailableException, - QueryNotFoundException, IdentifierNotFoundException { + QueryNotFoundException, IdentifierNotFoundException, NotAllowedException { /* mock */ when(identifierService.find(IDENTIFIER_1_ID)) @@ -1304,7 +1521,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { } /* test */ - return identifierEndpoint.find(IDENTIFIER_1_ID, accept); + return identifierEndpoint.find(IDENTIFIER_1_ID, accept, null); } protected static String inputStreamToString(InputStream inputStream) throws IOException { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java index 152c17c4616a9637c08c50b9ba944dcef41c92f8..b1a65fc0cdce05ee9d1bcdfa0ad63e03f47933ba 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java @@ -1,8 +1,6 @@ package at.tuwien.endpoints; -import at.tuwien.api.auth.LoginRequestDto; import at.tuwien.api.auth.CreateUserDto; -import at.tuwien.api.keycloak.UserAttributesDto; import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import at.tuwien.api.user.UserPasswordDto; @@ -17,9 +15,6 @@ import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -34,7 +29,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.security.Principal; import java.util.List; import java.util.UUID; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -56,13 +50,6 @@ public class UserEndpointUnitTest extends AbstractUnitTest { @Autowired private UserEndpoint userEndpoint; - public static Stream<Arguments> getToken_parameters() { - return Stream.of( - Arguments.arguments("null", null), - Arguments.arguments("empty", new UUID[]{}) - ); - } - @BeforeEach public void beforeEach() { genesis(); @@ -104,31 +91,21 @@ public class UserEndpointUnitTest extends AbstractUnitTest { } @Test - @WithAnonymousUser - public void create_anonymous_succeeds() throws UserExistsException, EmailExistsException, UserNotFoundException, + @WithMockUser(username = USER_LOCAL_ADMIN_USERNAME, authorities = {"system"}) + public void create_succeeds() throws UserExistsException, EmailExistsException, UserNotFoundException, AuthServiceException, AuthServiceConnectionException, CredentialsInvalidException { - final CreateUserDto request = CreateUserDto.builder() - .email(USER_1_EMAIL) - .username(USER_1_USERNAME) - .password(USER_1_PASSWORD) - .build(); /* test */ - create_generic(request, USER_1, USER_1_KEYCLOAK_DTO, USER_1_ID); + create_generic(USER_1_SIGNUP_REQUEST_DTO, USER_1); } @Test @WithMockUser(username = USER_1_USERNAME) - public void create_isAuthenticated_fails() { - final CreateUserDto request = CreateUserDto.builder() - .email(USER_2_EMAIL) - .username(USER_2_USERNAME) - .password(USER_2_PASSWORD) - .build(); + public void create_noRole_fails() { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - create_generic(request, null, null, null); + create_generic(USER_1_SIGNUP_REQUEST_DTO, null); }); } @@ -235,7 +212,8 @@ public class UserEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-user-information"}) - public void modify_succeeds() throws NotAllowedException, UserNotFoundException, DatabaseNotFoundException { + public void modify_succeeds() throws NotAllowedException, UserNotFoundException, DatabaseNotFoundException, + AuthServiceException, AuthServiceConnectionException { final UserUpdateDto request = UserUpdateDto.builder() .firstname(USER_1_FIRSTNAME) .lastname(USER_1_LASTNAME) @@ -286,136 +264,6 @@ public class UserEndpointUnitTest extends AbstractUnitTest { password_generic(USER_1_PRINCIPAL, request); } - @Test - @WithAnonymousUser - public void getToken_anonymous_succeeds() throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, AccountNotSetupException, CredentialsInvalidException { - - /* test */ - getToken_generic(USER_1_LOGIN_REQUEST_DTO, USER_1_PRINCIPAL, USER_1); - } - - @Test - @WithMockUser(username = USER_1_USERNAME) - public void getToken_loggedIn_succeeds() throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, AccountNotSetupException, CredentialsInvalidException { - - /* test */ - getToken_generic(USER_1_LOGIN_REQUEST_DTO, USER_1_PRINCIPAL, USER_1); - } - - @Test - @WithAnonymousUser - public void getToken_notExists_succeeds() throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, AccountNotSetupException, CredentialsInvalidException { - - /* mock */ - when(authenticationService.findByUsername(USER_1_USERNAME)) - .thenReturn(USER_1_KEYCLOAK_DTO); - when(userService.create(any(CreateUserDto.class), any(UUID.class))) - .thenReturn(USER_1); - - /* test */ - getToken_generic(USER_1_LOGIN_REQUEST_DTO, USER_1_PRINCIPAL, null); - } - - @Test - @WithAnonymousUser - public void getToken_notExists_fails() throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, CredentialsInvalidException { - - /* mock */ - doThrow(UserNotFoundException.class) - .when(authenticationService) - .findByUsername(USER_1_USERNAME); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - getToken_generic(USER_1_LOGIN_REQUEST_DTO, USER_1_PRINCIPAL, null); - }); - } - - @ParameterizedTest - @MethodSource("getToken_parameters") - @WithAnonymousUser - public void getToken_missingLdapId_fails(String name, UUID[] ldapId) throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, CredentialsInvalidException { - final at.tuwien.api.keycloak.UserDto mock = at.tuwien.api.keycloak.UserDto.builder() - .attributes(UserAttributesDto.builder() - .ldapId(ldapId) - .build()) - .build(); - - /* mock */ - when(authenticationService.findByUsername(USER_1_USERNAME)) - .thenReturn(mock); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - getToken_generic(USER_1_LOGIN_REQUEST_DTO, USER_1_PRINCIPAL, null); - }); - } - - @Test - @WithAnonymousUser - public void refreshToken_anonymous_succeeds() throws AuthServiceConnectionException, CredentialsInvalidException { - - /* mock */ - when(authenticationService.refreshToken(anyString())) - .thenReturn(TOKEN_DTO); - - /* test */ - final ResponseEntity<?> response = userEndpoint.refreshToken(REFRESH_TOKEN_REQUEST_DTO); - assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); - assertNotNull(response.getBody()); - } - - @Test - @WithMockUser(username = USER_1_USERNAME) - public void refreshToken_loggedIn_succeeds() throws AuthServiceConnectionException, CredentialsInvalidException { - - /* mock */ - when(authenticationService.refreshToken(anyString())) - .thenReturn(TOKEN_DTO); - - /* test */ - final ResponseEntity<?> response = userEndpoint.refreshToken(REFRESH_TOKEN_REQUEST_DTO); - assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); - assertNotNull(response.getBody()); - } - - @Test - @WithMockUser(username = USER_1_USERNAME) - public void refreshToken_authServiceConnection_fails() throws AuthServiceConnectionException, - CredentialsInvalidException { - - /* mock */ - doThrow(AuthServiceConnectionException.class) - .when(authenticationService) - .refreshToken(anyString()); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - userEndpoint.refreshToken(REFRESH_TOKEN_REQUEST_DTO); - }); - } - - @Test - @WithMockUser(username = USER_1_USERNAME) - public void refreshToken_invalidCredentials_fails() throws AuthServiceConnectionException, - CredentialsInvalidException { - - /* mock */ - doThrow(CredentialsInvalidException.class) - .when(authenticationService) - .refreshToken(anyString()); - - /* test */ - assertThrows(CredentialsInvalidException.class, () -> { - userEndpoint.refreshToken(REFRESH_TOKEN_REQUEST_DTO); - }); - } - /* ################################################################################################### */ /* ## GENERIC TEST CASES ## */ /* ################################################################################################### */ @@ -445,17 +293,12 @@ public class UserEndpointUnitTest extends AbstractUnitTest { return response.getBody(); } - protected void create_generic(CreateUserDto data, User user, at.tuwien.api.keycloak.UserDto userDto, UUID id) - throws UserExistsException, EmailExistsException, UserNotFoundException, AuthServiceException, - AuthServiceConnectionException, CredentialsInvalidException { + protected void create_generic(CreateUserDto data, User user) throws UserExistsException, EmailExistsException, + UserNotFoundException, AuthServiceException, AuthServiceConnectionException, CredentialsInvalidException { /* mock */ - when(userService.create(eq(data), any(UUID.class))) + when(userService.create(any(CreateUserDto.class))) .thenReturn(user); - when(authenticationService.findByUsername(data.getUsername())) - .thenReturn(userDto); - when(authenticationService.create(data)) - .thenReturn(userDto); /* test */ final ResponseEntity<UserBriefDto> response = userEndpoint.create(data); @@ -486,7 +329,8 @@ public class UserEndpointUnitTest extends AbstractUnitTest { } protected void modify_generic(UUID userId, User user, Principal principal, UserUpdateDto data) - throws NotAllowedException, UserNotFoundException, DatabaseNotFoundException { + throws NotAllowedException, UserNotFoundException, DatabaseNotFoundException, AuthServiceException, + AuthServiceConnectionException { /* mock */ if (user != null) { when(userService.findById(userId)) @@ -522,26 +366,4 @@ public class UserEndpointUnitTest extends AbstractUnitTest { final ResponseEntity<?> response = userEndpoint.password(USER_1_ID, data, principal); assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); } - - protected void getToken_generic(LoginRequestDto request, Principal principal, User user) - throws UserNotFoundException, AuthServiceConnectionException, AccountNotSetupException, - CredentialsInvalidException, AuthServiceException { - - /* mock */ - when(authenticationService.obtainToken(any(LoginRequestDto.class))) - .thenReturn(TOKEN_DTO); - if (user != null) { - when(userService.findByUsername(principal.getName())) - .thenReturn(user); - } else { - doThrow(UserNotFoundException.class) - .when(userService) - .findByUsername(principal.getName()); - } - - /* test */ - final ResponseEntity<?> response = userEndpoint.getToken(request); - assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); - assertNotNull(response.getBody()); - } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java index 4f1a3da45c87922aabd73ee6e75b1cca3160dd62..d1434ef9e4035cd13d81a4f12ec68bad71683bf1 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java @@ -1,7 +1,7 @@ package at.tuwien.endpoints; -import at.tuwien.api.database.ViewBriefDto; import at.tuwien.api.database.CreateViewDto; +import at.tuwien.api.database.ViewBriefDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.ViewUpdateDto; import at.tuwien.entities.database.Database; @@ -21,6 +21,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -173,6 +174,25 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { find_generic(DATABASE_3_ID, DATABASE_3, USER_2_PRINCIPAL, USER_2_ID, USER_2, DATABASE_2_USER_1_READ_ACCESS); } + @Test + @WithMockUser(username = USER_2_USERNAME) + public void find_publicSystem_succeeds() throws UserNotFoundException, DatabaseNotFoundException, + AccessNotFoundException, ViewNotFoundException { + + /* test */ + final ResponseEntity<ViewDto> response = find_generic(DATABASE_3_ID, DATABASE_3, USER_LOCAL_ADMIN_PRINCIPAL, + USER_LOCAL_ADMIN_ID, null, null); + final HttpHeaders headers = response.getHeaders(); + assertEquals(List.of(CONTAINER_1_PRIVILEGED_USERNAME), headers.get("X-Username")); + assertEquals(List.of(CONTAINER_1_PRIVILEGED_PASSWORD), headers.get("X-Password")); + assertEquals(List.of(CONTAINER_1_HOST), headers.get("X-Host")); + assertEquals(List.of("" + CONTAINER_1_PORT), headers.get("X-Port")); + assertEquals(List.of(IMAGE_1_JDBC), headers.get("X-Type")); + assertEquals(List.of(DATABASE_3_INTERNALNAME), headers.get("X-Database")); + assertEquals(List.of(VIEW_5_INTERNAL_NAME), headers.get("X-View")); + assertEquals(List.of("X-Username X-Password X-Host X-Port X-Type X-Database X-View"), headers.get("Access-Control-Expose-Headers")); + } + @Test @WithMockUser(username = USER_2_USERNAME) public void find_publicHasRoleHasAccess_succeeds() throws UserNotFoundException, DatabaseNotFoundException, @@ -494,9 +514,9 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { assertEquals(VIEW_1_NAME, response.getBody().getName()); } - protected void find_generic(Long databaseId, Database database, Principal principal, UUID userId, User user, - DatabaseAccess access) throws DatabaseNotFoundException, UserNotFoundException, - AccessNotFoundException, ViewNotFoundException { + protected ResponseEntity<ViewDto> find_generic(Long databaseId, Database database, Principal principal, + UUID userId, User user, DatabaseAccess access) + throws DatabaseNotFoundException, UserNotFoundException, AccessNotFoundException, ViewNotFoundException { /* mock */ when(databaseService.findById(databaseId)) @@ -514,18 +534,18 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { when(userService.findById(userId)) .thenReturn(user); when(viewService.findById(any(Database.class), anyLong())) - .thenReturn(VIEW_1); + .thenReturn(VIEW_5); } else { when(viewService.findById(any(Database.class), anyLong())) - .thenReturn(VIEW_1); + .thenReturn(VIEW_5); } /* test */ - final ResponseEntity<ViewDto> response = viewEndpoint.find(databaseId, VIEW_1_ID, USER_1_PRINCIPAL); + final ResponseEntity<ViewDto> response = viewEndpoint.find(databaseId, VIEW_5_ID, principal); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); - assertEquals(VIEW_1_ID, response.getBody().getId()); - assertEquals(VIEW_1_NAME, response.getBody().getName()); + assertEquals(VIEW_5_ID, response.getBody().getId()); + return response; } protected void delete_generic(Long databaseId, Database database, Long viewId, View view, Principal principal, diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e72cd7fa7591a7e641c74df6eab07845b0a193ea --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayIntegrationTest.java @@ -0,0 +1,114 @@ +package at.tuwien.gateway; + +import at.tuwien.exception.UserNotFoundException; +import at.tuwien.gateway.impl.KeycloakGatewayImpl; +import at.tuwien.test.AbstractUnitTest; +import at.tuwien.utils.KeycloakUtils; +import dasniko.testcontainers.keycloak.KeycloakContainer; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.images.PullPolicy; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Log4j2 +@SpringBootTest +@Testcontainers +@ExtendWith(SpringExtension.class) +public class KeycloakGatewayIntegrationTest extends AbstractUnitTest { + + @Autowired + private KeycloakGatewayImpl keycloakGateway; + + @Autowired + private KeycloakUtils keycloakUtils; + + @BeforeEach + public void beforeEach() { + genesis(); + /* auth service */ + keycloakUtils.deleteUser(USER_1_USERNAME); + } + + @Container + private static KeycloakContainer keycloakContainer = new KeycloakContainer(KEYCLOAK_IMAGE) + .withImagePullPolicy(PullPolicy.alwaysPull()) + .withAdminUsername("admin") + .withAdminPassword("admin") + .withRealmImportFile("./init/dbrepo-realm.json") + .withEnv("KC_HOSTNAME_STRICT_HTTPS", "false"); + + @DynamicPropertySource + static void keycloakProperties(DynamicPropertyRegistry registry) { + final String authServiceEndpoint = "http://localhost:" + keycloakContainer.getMappedPort(8080); + log.trace("set auth endpoint: {}", authServiceEndpoint); + registry.add("dbrepo.endpoints.authService", () -> authServiceEndpoint); + } + + @Test + public void deleteUser_succeeds() throws UserNotFoundException { + + /* mock */ + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); + + /* test */ + keycloakGateway.deleteUser(keycloakUtils.getUserId(USER_1_USERNAME)); + } + + @Test + public void deleteUser_notFound_fails() { + + /* test */ + assertThrows(UserNotFoundException.class, () -> { + keycloakGateway.deleteUser(USER_1_ID); + }); + } + + @Test + public void findByUsername_succeeds() throws UserNotFoundException { + + /* mock */ + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); + + /* test */ + keycloakGateway.findByUsername(USER_1_USERNAME); + } + + @Test + public void findByUsername_notFound_fails() { + + /* test */ + assertThrows(UserNotFoundException.class, () -> { + keycloakGateway.findByUsername(USER_1_USERNAME); + }); + } + + @Test + public void updateUserCredentials_succeeds() throws UserNotFoundException { + + /* mock */ + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); + + /* test */ + keycloakGateway.updateUserCredentials(keycloakUtils.getUserId(USER_1_USERNAME), USER_1_PASSWORD_DTO); + } + + @Test + public void updateUserCredentials_notFound_fails() { + + /* test */ + assertThrows(UserNotFoundException.class, () -> { + keycloakGateway.updateUserCredentials(keycloakUtils.getUserId(USER_1_USERNAME), USER_1_PASSWORD_DTO); + }); + } + +} diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayUnitTest.java deleted file mode 100644 index bb3bcbb094ad1e9a2510abe20b9649ee73e6e975..0000000000000000000000000000000000000000 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/KeycloakGatewayUnitTest.java +++ /dev/null @@ -1,489 +0,0 @@ -package at.tuwien.gateway; - -import at.tuwien.test.AbstractUnitTest; -import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.api.keycloak.UserDto; -import at.tuwien.exception.*; -import at.tuwien.gateway.impl.KeycloakGatewayImpl; -import lombok.extern.log4j.Log4j2; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.*; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestTemplate; - -import java.nio.charset.Charset; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@Log4j2 -@SpringBootTest -@ExtendWith(SpringExtension.class) -public class KeycloakGatewayUnitTest extends AbstractUnitTest { - - @MockBean - @Qualifier("keycloakRestTemplate") - private RestTemplate keycloakRestTemplate; - - @MockBean - @Qualifier("restTemplate") - private RestTemplate restTemplate; - - @Autowired - private KeycloakGatewayImpl keycloakGateway; - - @Test - public void createUser_succeeds() throws UserExistsException, EmailExistsException, AuthServiceException, - AuthServiceConnectionException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.CREATED) - .build()); - - /* test */ - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); - } - - @Test - public void createUser_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .build()); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); - }); - } - - @Test - public void createUser_sameUsername_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.Conflict.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(UserExistsException.class, () -> { - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); - }); - } - - @Test - public void createUser_connection_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpServerErrorException.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); - }); - } - - @Test - public void deleteUser_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .build()); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.deleteUser(USER_1_ID); - }); - } - - @Test - public void deleteUser_succeeds() throws UserNotFoundException, AuthServiceException, - AuthServiceConnectionException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .build()); - - /* test */ - keycloakGateway.deleteUser(USER_1_ID); - } - - @Test - public void deleteUser_notFound_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.NotFound.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - keycloakGateway.deleteUser(USER_1_ID); - }); - } - - @Test - public void deleteUser_unexpected_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.Conflict.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.deleteUser(USER_1_ID); - }); - } - - @Test - public void deleteUser_connection_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpServerErrorException.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.deleteUser(USER_1_ID); - }); - } - - @Test - public void updateUserCredentials_succeeds() throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .build()); - - /* test */ - keycloakGateway.updateUserCredentials(USER_1_ID, USER_1_PASSWORD_DTO); - } - - @Test - public void updateUserCredentials_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .build()); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.updateUserCredentials(USER_1_ID, USER_1_PASSWORD_DTO); - }); - } - - @Test - public void updateUserCredentials_connection_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpServerErrorException.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.updateUserCredentials(USER_1_ID, USER_1_PASSWORD_DTO); - }); - } - - @Test - public void updateUserCredentials_unexpected_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.Conflict.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class)); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.updateUserCredentials(USER_1_ID, USER_1_PASSWORD_DTO); - }); - } - - @Test - public void findByUsername_notFound_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto[].class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(new UserDto[]{})); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - keycloakGateway.findByUsername(USER_1_USERNAME); - }); - } - - @Test - public void findByUsername_connection_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpServerErrorException.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto[].class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.findByUsername(USER_1_USERNAME); - }); - } - - @Test - public void findByUsername_unexpected_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.Conflict.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto[].class)); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.findByUsername(USER_1_USERNAME); - }); - } - - @Test - public void findById_succeeds() throws UserNotFoundException, AuthServiceException, AuthServiceConnectionException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - when(keycloakRestTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(USER_1_KEYCLOAK_DTO)); - - /* test */ - final UserDto response = keycloakGateway.findById(USER_1_ID); - assertEquals(USER_1_ID, response.getId()); - } - - @Test - public void findById_notFound_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.NotFound.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto.class)); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - keycloakGateway.findById(USER_1_ID); - }); - } - - @Test - public void findById_connection_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpServerErrorException.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.findById(USER_1_ID); - }); - } - - @Test - public void findById_unexpected_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - doThrow(HttpClientErrorException.Conflict.class) - .when(keycloakRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(UserDto.class)); - - /* test */ - assertThrows(AuthServiceException.class, () -> { - keycloakGateway.findById(USER_1_ID); - }); - } - - @Test - public void refreshUserToken_succeeds() throws AuthServiceConnectionException, CredentialsInvalidException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - - /* test */ - final TokenDto response = keycloakGateway.refreshUserToken(TOKEN_DTO.getRefreshToken()); - assertNotNull(response.getAccessToken()); - } - - @Test - public void refreshUserToken_connection_fails() { - - /* mock */ - doThrow(HttpServerErrorException.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.refreshUserToken(TOKEN_DTO.getRefreshToken()); - }); - } - - @Test - public void refreshUserToken_unauthorized_fails() { - - /* mock */ - doThrow(HttpClientErrorException.Unauthorized.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(CredentialsInvalidException.class, () -> { - keycloakGateway.refreshUserToken(TOKEN_DTO.getRefreshToken()); - }); - } - - @Test - public void refreshUserToken_badRequest_fails() { - - /* mock */ - doThrow(HttpClientErrorException.BadRequest.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(CredentialsInvalidException.class, () -> { - keycloakGateway.refreshUserToken(TOKEN_DTO.getRefreshToken()); - }); - } - - @Test - public void refreshUserToken_badRequestInactiveSession_fails() { - - /* mock */ - doThrow(HttpClientErrorException.BadRequest.create(HttpStatus.BAD_REQUEST, "Session not active", new HttpHeaders(), new byte[]{}, Charset.defaultCharset())) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(CredentialsInvalidException.class, () -> { - keycloakGateway.refreshUserToken(TOKEN_DTO.getRefreshToken()); - }); - } - - @Test - public void obtainUserToken_succeeds() throws AuthServiceConnectionException, - AccountNotSetupException, CredentialsInvalidException { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .body(TOKEN_DTO)); - - /* test */ - final TokenDto response = keycloakGateway.obtainUserToken(USER_1_USERNAME, USER_1_PASSWORD); - assertNotNull(response.getAccessToken()); - } - - @Test - public void obtainUserToken_connection_fails() { - - /* mock */ - doThrow(HttpServerErrorException.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(AuthServiceConnectionException.class, () -> { - keycloakGateway.obtainUserToken(USER_1_USERNAME, USER_1_PASSWORD); - }); - } - - @Test - public void obtainUserToken_unauthorized_fails() { - - /* mock */ - doThrow(HttpClientErrorException.Unauthorized.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(TokenDto.class)); - - /* test */ - assertThrows(CredentialsInvalidException.class, () -> { - keycloakGateway.obtainUserToken(USER_1_USERNAME, USER_1_PASSWORD); - }); - } - -} diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java index fa0b6f64f76712b82ce1386ad38ff621d5e561dc..0365db6c4a09dfaedb742528b3bb08d4784d53f1 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java @@ -9,7 +9,6 @@ import at.tuwien.repository.ContainerRepository; import at.tuwien.repository.DatabaseRepository; import at.tuwien.repository.LicenseRepository; import at.tuwien.repository.UserRepository; -import at.tuwien.service.AuthenticationService; import at.tuwien.test.AbstractUnitTest; import at.tuwien.utils.KeycloakUtils; import dasniko.testcontainers.keycloak.KeycloakContainer; @@ -49,9 +48,6 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest @Autowired private KeycloakUtils keycloakUtils; - @Autowired - private KeycloakGateway keycloakGateway; - @Autowired private UserRepository userRepository; @@ -65,7 +61,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest private DatabaseRepository databaseRepository; @Autowired - private AuthenticationService authenticationService; + private KeycloakGateway keycloakGateway; @Container private static KeycloakContainer keycloakContainer = new KeycloakContainer(KEYCLOAK_IMAGE) @@ -97,7 +93,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_database_basicUser_succeeds() throws Exception { /* mock */ - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/database/1").with(httpBasic(USER_1_USERNAME, USER_1_PASSWORD))) @@ -112,7 +108,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_database_basicAdmin_succeeds() throws Exception { /* pre condition */ - keycloakGateway.createUser(USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_LOCAL_ADMIN_ID, USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/database/1").with(httpBasic(USER_LOCAL_ADMIN_USERNAME, USER_LOCAL_ADMIN_PASSWORD))) @@ -127,8 +123,8 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_database_bearerAdmin_succeeds() throws Exception { /* pre condition */ - keycloakGateway.createUser(USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); - final TokenDto jwt = authenticationService.obtainToken(USER_LOCAL_ADMIN_LOGIN_REQUEST_DTO); + keycloakUtils.createUser(USER_LOCAL_ADMIN_ID, USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); + final TokenDto jwt = keycloakGateway.obtainUserToken(USER_LOCAL_ADMIN_USERNAME, USER_LOCAL_ADMIN_PASSWORD); /* test */ this.mockMvc.perform(get("/api/database/1").header("Authorization", "Bearer " + jwt.getAccessToken())) @@ -143,8 +139,8 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_table_bearerAdmin_succeeds() throws Exception { /* pre condition */ - keycloakGateway.createUser(USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); - final TokenDto jwt = authenticationService.obtainToken(USER_LOCAL_ADMIN_LOGIN_REQUEST_DTO); + keycloakUtils.createUser(USER_LOCAL_ADMIN_ID, USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); + final TokenDto jwt = keycloakGateway.obtainUserToken(USER_LOCAL_ADMIN_USERNAME, USER_LOCAL_ADMIN_PASSWORD); /* test */ @@ -160,7 +156,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_table_basicUser_succeeds() throws Exception { /* mock */ - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/database/1/table/1").with(httpBasic(USER_1_USERNAME, USER_1_PASSWORD))) @@ -175,7 +171,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_table_basicAdmin_succeeds() throws Exception { /* mock */ - keycloakGateway.createUser(USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_LOCAL_ADMIN_ID, USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/database/1/table/1").with(httpBasic(USER_LOCAL_ADMIN_USERNAME, USER_LOCAL_ADMIN_PASSWORD))) @@ -190,7 +186,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_view_basicUser_succeeds() throws Exception { /* mock */ - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/database/1/view/1").with(httpBasic(USER_1_USERNAME, USER_1_PASSWORD))) @@ -205,7 +201,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest public void findById_container_basicUser_succeeds() throws Exception { /* mock */ - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); /* test */ this.mockMvc.perform(get("/api/container/1").with(httpBasic(USER_1_USERNAME, USER_1_PASSWORD))) diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java index dc41121b90133b3c759ad556a71c081d0ed9aeae..790262c7399d36fb0d9a3cf6103f7899300daffb 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -1,12 +1,14 @@ package at.tuwien.mvc; -import at.tuwien.api.auth.RefreshTokenRequestDto; import at.tuwien.api.container.CreateContainerDto; -import at.tuwien.test.AbstractUnitTest; -import at.tuwien.api.database.*; +import at.tuwien.api.database.DatabaseModifyImageDto; +import at.tuwien.api.database.DatabaseModifyVisibilityDto; +import at.tuwien.api.database.DatabaseTransferDto; import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto; +import at.tuwien.api.identifier.IdentifierTypeDto; import at.tuwien.config.MetricsConfig; import at.tuwien.endpoints.*; +import at.tuwien.test.AbstractUnitTest; import io.micrometer.observation.annotation.Observed; import io.micrometer.observation.tck.TestObservationRegistry; import io.swagger.v3.oas.annotations.Operation; @@ -24,7 +26,6 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; @@ -32,7 +33,10 @@ import org.springframework.test.web.servlet.MockMvc; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -273,7 +277,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - identifierEndpoint.findAll(DATABASE_1_ID, null, null, null, MediaType.APPLICATION_JSON_VALUE); + identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, MediaType.APPLICATION_JSON_VALUE, null); } catch (Exception e) { /* ignore */ } @@ -587,45 +591,16 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { } catch (Exception e) { /* ignore */ } - try { - userEndpoint.refreshToken(RefreshTokenRequestDto.builder().build()); - } catch (Exception e) { - /* ignore */ - } /* test */ - for (String metric : List.of("dbrepo_user_refresh_token", "dbrepo_users_list", - "dbrepo_user_find", "dbrepo_user_modify", "dbrepo_user_password_modify")) { + for (String metric : List.of("dbrepo_users_list", "dbrepo_user_find", "dbrepo_user_modify", + "dbrepo_user_password_modify")) { assertThat(registry) .hasObservationWithNameEqualTo(metric); } generic_openApiDocs(UserEndpoint.class); } - @Test - @WithAnonymousUser - public void prometheusUserEndpoint2_succeeds() { - - /* mock */ - try { - userEndpoint.create(USER_1_SIGNUP_REQUEST_DTO); - } catch (Exception e) { - /* ignore */ - } - try { - userEndpoint.getToken(USER_1_LOGIN_REQUEST_DTO); - } catch (Exception e) { - /* ignore */ - } - - /* test */ - for (String metric : List.of("dbrepo_user_create", "dbrepo_user_token")) { - assertThat(registry) - .hasObservationWithNameEqualTo(metric); - } - // already done above - } - @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"create-database-view", "delete-database-view"}) public void prometheusViewEndpoint_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java index fa1cd5d4beeb1fe8fb40d8f59fa974b5d2501dba..d655a25cf1f599b2e92a9fd426cfa343747a3fe1 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java @@ -1,9 +1,10 @@ package at.tuwien.service; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.gateway.KeycloakGateway; +import at.tuwien.test.AbstractUnitTest; +import at.tuwien.utils.KeycloakUtils; import dasniko.testcontainers.keycloak.KeycloakContainer; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; @@ -19,6 +20,8 @@ import org.testcontainers.images.PullPolicy; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import java.util.UUID; + @Log4j2 @Testcontainers @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -32,13 +35,16 @@ public class AuthenticationServiceIntegrationTest extends AbstractUnitTest { @Autowired private KeycloakGateway keycloakGateway; + @Autowired + private KeycloakUtils keycloakUtils; + @BeforeEach public void beforeEach() { genesis(); } @Container - private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") + private static KeycloakContainer keycloakContainer = new KeycloakContainer(KEYCLOAK_IMAGE) .withImagePullPolicy(PullPolicy.alwaysPull()) .withAdminUsername("admin") .withAdminPassword("admin") @@ -47,7 +53,9 @@ public class AuthenticationServiceIntegrationTest extends AbstractUnitTest { @DynamicPropertySource static void keycloakProperties(DynamicPropertyRegistry registry) { - registry.add("dbrepo.endpoints.authService", () -> "http://localhost:" + keycloakContainer.getMappedPort(8080)); + final String authServiceEndpoint = "http://localhost:" + keycloakContainer.getMappedPort(8080); + log.trace("set auth endpoint: {}", authServiceEndpoint); + registry.add("dbrepo.endpoints.authService", () -> authServiceEndpoint); } @Test @@ -55,14 +63,10 @@ public class AuthenticationServiceIntegrationTest extends AbstractUnitTest { AuthServiceException, AuthServiceConnectionException, CredentialsInvalidException { /* mock */ - try { - keycloakGateway.deleteUser(keycloakGateway.findByUsername(USER_1_USERNAME).getId()); - } catch (Exception e) { - /* ignore */ - } - keycloakGateway.createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); + keycloakUtils.deleteUser(USER_1_USERNAME); + keycloakUtils.createUser(USER_1_ID, USER_1_KEYCLOAK_SIGNUP_REQUEST); final User request = User.builder() - .id(keycloakGateway.findByUsername(USER_1_USERNAME).getId()) + .keycloakId(UUID.fromString(keycloakGateway.findByUsername(USER_1_USERNAME).getId())) .username(USER_1_USERNAME) .build(); @@ -70,20 +74,4 @@ public class AuthenticationServiceIntegrationTest extends AbstractUnitTest { authenticationService.delete(request); } - @Test - public void create_succeeds() throws EmailExistsException, UserExistsException, - DataServiceConnectionException, AuthServiceException, AuthServiceConnectionException, - CredentialsInvalidException { - - /* mock */ - try { - keycloakGateway.deleteUser(keycloakGateway.findByUsername(USER_1_USERNAME).getId()); - } catch (Exception e) { - /* ignore */ - } - - /* test */ - authenticationService.create(USER_1_SIGNUP_REQUEST_DTO); - } - } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServicePersistenceTest.java index 8724e08be1c3312b74f487f413d035b7bfa1dfd2..e9d6b158ce097fc469694ee1d09e068357120d39 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServicePersistenceTest.java @@ -1,12 +1,10 @@ package at.tuwien.service; -import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.user.UserPasswordDto; import at.tuwien.api.user.UserUpdateDto; import at.tuwien.entities.user.User; -import at.tuwien.exception.EmailExistsException; -import at.tuwien.exception.UserExistsException; -import at.tuwien.exception.UserNotFoundException; +import at.tuwien.exception.*; +import at.tuwien.gateway.KeycloakGateway; import at.tuwien.repository.UserRepository; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; @@ -15,12 +13,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.List; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doNothing; @Log4j2 @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -34,6 +34,9 @@ public class UserServicePersistenceTest extends AbstractUnitTest { @Autowired private UserService userService; + @MockBean + private KeycloakGateway keycloakGateway; + @BeforeEach public void beforeEach() { genesis(); @@ -68,20 +71,16 @@ public class UserServicePersistenceTest extends AbstractUnitTest { } @Test - public void create_succeeds() throws UserExistsException, UserNotFoundException, EmailExistsException { - final CreateUserDto request = CreateUserDto.builder() - .username(USER_2_USERNAME) - .password(USER_2_PASSWORD) - .email(USER_2_EMAIL) - .build(); + public void create_succeeds() throws UserExistsException, UserNotFoundException, EmailExistsException, + AuthServiceException, AuthServiceConnectionException { /* test */ - final User response = userService.create(request, USER_2_ID); + final User response = userService.create(USER_2_SIGNUP_REQUEST_DTO); assertEquals(USER_2_USERNAME, response.getUsername()); } @Test - public void modify_succeeds() { + public void modify_succeeds() throws UserNotFoundException, AuthServiceException { final UserUpdateDto request = UserUpdateDto.builder() .firstname(USER_1_FIRSTNAME) .lastname(USER_1_LASTNAME) @@ -91,6 +90,11 @@ public class UserServicePersistenceTest extends AbstractUnitTest { .language("de") .build(); + /* mock */ + doNothing() + .when(keycloakGateway) + .updateUser(USER_1_ID, request); + /* test */ final User response = userService.modify(USER_1, request); assertEquals(USER_1_ID, response.getId()); @@ -103,17 +107,14 @@ public class UserServicePersistenceTest extends AbstractUnitTest { } @Test - public void updatePassword_succeeds() { + public void updatePassword_succeeds() throws UserNotFoundException, AuthServiceException, + AuthServiceConnectionException { final UserPasswordDto request = UserPasswordDto.builder() .password(USER_3_PASSWORD) .build(); /* mock */ - final User user = userService.create(CreateUserDto.builder() - .username(USER_3_USERNAME) - .password(USER_3_PASSWORD) - .email(USER_3_EMAIL) - .build(), USER_3_ID); + final User user = userService.create(USER_3_SIGNUP_REQUEST_DTO); /* test */ userService.updatePassword(user, request); @@ -151,20 +152,4 @@ public class UserServicePersistenceTest extends AbstractUnitTest { userService.validateUsernameNotExists(USER_1_USERNAME); }); } - - @Test - public void validateEmailNotExists_succeeds() throws EmailExistsException { - - /* test */ - userService.validateEmailNotExists(USER_2_EMAIL); - } - - @Test - public void validateEmailNotExists_fails() { - - /* test */ - assertThrows(EmailExistsException.class, () -> { - userService.validateEmailNotExists(USER_1_EMAIL); - }); - } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java index a9fe4694cc69d8eb347e9fefd12498704b8797fd..4c423aa25b6fd36f82485d836c190c1b2779e12d 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java @@ -1,10 +1,10 @@ package at.tuwien.service; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.gateway.KeycloakGateway; import at.tuwien.repository.UserRepository; +import at.tuwien.test.AbstractUnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -86,20 +86,15 @@ public class UserServiceUnitTest extends AbstractUnitTest { .thenReturn(Optional.of(USER_1)); when(userRepository.save(any(User.class))) .thenReturn(USER_1); - doNothing() - .when(keycloakGateway) - .createUser(USER_1_KEYCLOAK_SIGNUP_REQUEST); - when(keycloakGateway.findByUsername(USER_1_USERNAME)) - .thenReturn(USER_1_KEYCLOAK_DTO); /* test */ - final User response = userService.create(USER_1_SIGNUP_REQUEST_DTO, USER_1_ID); + final User response = userService.create(USER_1_SIGNUP_REQUEST_DTO); assertEquals(USER_1_ID, response.getId()); assertEquals(USER_1_USERNAME, response.getUsername()); } @Test - public void modify_succeeds() { + public void modify_succeeds() throws UserNotFoundException, AuthServiceException { /* mock */ when(userRepository.findById(USER_1_ID)) @@ -114,8 +109,7 @@ public class UserServiceUnitTest extends AbstractUnitTest { } @Test - public void updatePassword_succeeds() throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException { + public void updatePassword_succeeds() throws UserNotFoundException { /* mock */ doNothing() diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/utils/KeycloakUtils.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/utils/KeycloakUtils.java index f5ad18b694081ed50b879d6690e2a85748b3bece..b3612fcc0fc306892db006bd12aa6ef483cf45a9 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/utils/KeycloakUtils.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/utils/KeycloakUtils.java @@ -1,34 +1,71 @@ package at.tuwien.utils; -import at.tuwien.exception.AuthServiceConnectionException; -import at.tuwien.exception.AuthServiceException; +import at.tuwien.api.keycloak.UserCreateDto; +import at.tuwien.config.KeycloakConfig; import at.tuwien.exception.UserNotFoundException; -import at.tuwien.gateway.KeycloakGateway; +import at.tuwien.mapper.MetadataMapper; +import jakarta.ws.rs.core.Response; import lombok.extern.log4j.Log4j2; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.List; import java.util.UUID; @Log4j2 @Component public class KeycloakUtils { - final static UUID realmId = UUID.fromString("82c39861-d877-4667-a0f3-4daa2ee230e0"); - - private final KeycloakGateway keycloakGateway; + private final Keycloak keycloak; + private final KeycloakConfig keycloakConfig; + private final MetadataMapper metadataMapper; @Autowired - public KeycloakUtils(KeycloakGateway keycloakGateway) { - this.keycloakGateway = keycloakGateway; + public KeycloakUtils(Keycloak keycloak, KeycloakConfig keycloakConfig, MetadataMapper metadataMapper) { + this.keycloak = keycloak; + this.keycloakConfig = keycloakConfig; + this.metadataMapper = metadataMapper; + } + + public void createUser(UUID ldapId, UserCreateDto data) { + final UserRepresentation user = metadataMapper.userCreateDtoToUserRepresentation(data); + user.singleAttribute("CUSTOM_ID", ldapId.toString()); + try (Response response = keycloak.realm(keycloakConfig.getRealm()) + .users() + .create(user)) { + if (response.getStatus() != 201) { + log.warn("Failed to create user: {}", response.getStatus()); + } + } + log.debug("Created user {} at auth service", data.getUsername()); } - public void deleteUser(String username) throws AuthServiceException, AuthServiceConnectionException { - try { - final UUID userId = keycloakGateway.findByUsername(username).getId(); - keycloakGateway.deleteUser(userId); - } catch (UserNotFoundException e) { - /* ignore */ + public UUID getUserId(String username) throws UserNotFoundException { + final List<UserRepresentation> users = keycloak.realm(keycloakConfig.getRealm()) + .users() + .search(username); + if (users.isEmpty()) { + throw new UserNotFoundException("Failed to find user: " + username); + } + return UUID.fromString(users.get(0).getId()); + } + + public void deleteUser(String username) { + final List<UserRepresentation> users = keycloak.realm(keycloakConfig.getRealm()) + .users() + .search(username); + if (users.isEmpty()) { + log.warn("Failed to find user"); + return; + } + try (Response response = keycloak.realm(keycloakConfig.getRealm()) + .users() + .delete(users.get(0).getId())) { + if (response.getStatus() != 200) { + log.error("Failed to delete user: {}", response.getStatus()); + } } } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java index fe650589addb6ae15cb3d1d4d4b48f801054eca3..486db28e5945737e776da4b8f0aaf6d6d977715e 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java @@ -2,8 +2,8 @@ package at.tuwien.validator; import at.tuwien.SortType; import at.tuwien.api.database.table.CreateTableDto; -import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; +import at.tuwien.api.database.table.columns.CreateTableColumnDto; import at.tuwien.api.identifier.IdentifierSaveDto; import at.tuwien.entities.database.Database; import at.tuwien.entities.user.User; @@ -70,6 +70,14 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { endpointValidator.validateDataParams(0L, 1L); } + @Test + public void validateOnlyAccess_system_succeeds() throws UserNotFoundException, NotAllowedException, + AccessNotFoundException { + + /* test */ + endpointValidator.validateOnlyAccess(DATABASE_1, USER_LOCAL_ADMIN_PRINCIPAL, false); + } + @Test public void validateDataParams_bothNull_succeeds() throws PaginationException { @@ -222,6 +230,20 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { endpointValidator.validateOnlyAccessOrPublic(DATABASE_1, USER_1_PRINCIPAL); } + @Test + public void validateOnlyWriteOwnOrWriteAllAccess_succeeds() throws DatabaseNotFoundException, + TableNotFoundException, AccessNotFoundException, NotAllowedException { + + /* mock */ + when(tableService.findById(DATABASE_1, TABLE_1_ID)) + .thenReturn(TABLE_1); + when(accessService.find(eq(DATABASE_1), any(User.class))) + .thenReturn(DATABASE_1_USER_1_WRITE_ALL_ACCESS); + + /* test */ + endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(TABLE_1, USER_1); + } + @Test public void validateOnlyWriteOwnOrWriteAllAccess_privateHasReadAccess_fails() throws DatabaseNotFoundException, TableNotFoundException, AccessNotFoundException { @@ -323,6 +345,20 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { }); } + @Test + public void validateOnlyPrivateDataHasRole_publicDatabase_succeeds() throws NotAllowedException { + + /* test */ + endpointValidator.validateOnlyPrivateDataHasRole(DATABASE_4, null, "nobody-role"); + } + + @Test + public void validateOnlyPrivateDataHasRole_privateDatabaseHasRole_succeeds() throws NotAllowedException { + + /* test */ + endpointValidator.validateOnlyPrivateDataHasRole(DATABASE_1, USER_1_PRINCIPAL, "find-database"); + } + @Test public void validateOnlyPrivateDataHasRole_privatePrincipalMissing_fails() { @@ -491,6 +527,13 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { assertTrue(endpointValidator.validatePublicationDate(request)); } + @Test + public void validateOnlyMineOrWriteAccessOrHasRole_succeeds() { + + /* test */ + assertTrue(endpointValidator.validateOnlyMineOrWriteAccessOrHasRole(USER_1, USER_1_PRINCIPAL, null, "find-database")); + } + @Test public void validateOnlyMineOrWriteAccessOrHasRole_noAccess_fails() { diff --git a/dbrepo-metadata-service/rest-service/src/test/resources/init/dbrepo-realm.json b/dbrepo-metadata-service/rest-service/src/test/resources/init/dbrepo-realm.json index 7ee28b34a4f2d662bb43979930732dec74da8a63..fb6df2007f2c5d50ab9ad744b622e248ea158cde 100644 --- a/dbrepo-metadata-service/rest-service/src/test/resources/init/dbrepo-realm.json +++ b/dbrepo-metadata-service/rest-service/src/test/resources/init/dbrepo-realm.json @@ -27,7 +27,7 @@ "oauth2DevicePollingInterval" : 5, "enabled" : true, "sslRequired" : "none", - "registrationAllowed" : false, + "registrationAllowed" : true, "registrationEmailAsUsername" : false, "rememberMe" : false, "verifyEmail" : true, @@ -38,6 +38,7 @@ "bruteForceProtected" : false, "permanentLockout" : false, "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", "maxFailureWaitSeconds" : 900, "minimumQuickLoginWaitSeconds" : 60, "waitIncrementSeconds" : 60, @@ -73,7 +74,7 @@ "description" : "${default-system-roles}", "composite" : true, "composites" : { - "realm" : [ "delete-database-view", "update-semantic-unit", "export-query-data", "default-data-steward-roles", "execute-query", "default-user-handling", "delete-table-data", "find-query", "list-database-views", "persist-query", "update-search-index", "delete-database-access", "view-table-history", "create-ontology", "update-ontology", "modify-user-theme", "default-system-roles", "create-semantic-concept", "default-container-handling", "create-container", "create-table", "default-broker-handling", "default-maintenance-handling", "execute-semantic-query", "uma_authorization", "table-semantic-analyse", "list-containers", "check-database-access", "escalated-query-handling", "delete-identifier", "modify-database-owner", "list-tables", "export-table-data", "create-database-access", "delete-container", "re-execute-query", "create-semantic-unit", "escalated-identifier-handling", "system", "update-table-statistic", "escalated-semantics-handling", "default-database-handling", "delete-ontology", "find-database", "find-database-view", "update-semantic-concept", "find-user", "import-database-data", "publish-identifier", "default-roles-dbrepo", "find-foreign-user", "create-database", "create-maintenance-message", "find-maintenance-message", "escalated-container-handling", "default-researcher-roles", "default-identifier-handling", "escalated-user-handling", "modify-user-information", "create-database-view", "update-maintenance-message", "delete-foreign-table", "offline_access", "modify-foreign-table-column-semantics", "delete-maintenance-message", "find-container", "insert-table-data", "modify-identifier-metadata", "modify-database-image", "escalated-broker-handling", "modify-table-column-semantics", "escalated-database-handling", "default-semantics-handling", "update-database-access", "default-query-handling", "find-table", "list-queries", "default-developer-roles", "create-identifier", "escalated-table-handling", "find-identifier", "view-database-view-data", "view-table-data", "list-licenses", "default-table-handling", "list-identifiers", "create-foreign-identifier", "list-databases", "list-ontologies", "modify-database-visibility", "list-maintenance-messages", "delete-table" ] + "realm" : [ "delete-database-view", "update-semantic-unit", "export-query-data", "check-foreign-database-access", "default-data-steward-roles", "execute-query", "default-user-handling", "delete-table-data", "find-query", "list-database-views", "persist-query", "update-search-index", "delete-database-access", "view-table-history", "create-ontology", "update-ontology", "modify-user-theme", "default-system-roles", "create-semantic-concept", "default-container-handling", "create-container", "create-table", "default-broker-handling", "default-maintenance-handling", "execute-semantic-query", "uma_authorization", "table-semantic-analyse", "list-containers", "check-database-access", "escalated-query-handling", "delete-identifier", "modify-database-owner", "list-tables", "export-table-data", "create-database-access", "delete-container", "re-execute-query", "create-semantic-unit", "escalated-identifier-handling", "system", "update-table-statistic", "escalated-semantics-handling", "default-database-handling", "delete-ontology", "find-database", "find-database-view", "update-semantic-concept", "find-user", "import-database-data", "publish-identifier", "default-roles-dbrepo", "find-foreign-user", "create-database", "create-maintenance-message", "find-maintenance-message", "escalated-container-handling", "default-researcher-roles", "default-identifier-handling", "escalated-user-handling", "modify-user-information", "create-database-view", "update-maintenance-message", "delete-foreign-table", "offline_access", "modify-foreign-table-column-semantics", "delete-maintenance-message", "find-container", "insert-table-data", "modify-identifier-metadata", "modify-database-image", "escalated-broker-handling", "modify-table-column-semantics", "escalated-database-handling", "default-semantics-handling", "update-database-access", "default-query-handling", "find-table", "list-queries", "default-developer-roles", "create-identifier", "escalated-table-handling", "find-identifier", "view-table-data", "list-licenses", "default-table-handling", "list-identifiers", "create-foreign-identifier", "list-databases", "list-ontologies", "modify-database-visibility", "list-maintenance-messages", "delete-table" ] }, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", @@ -146,19 +147,11 @@ "description" : "${default-table-handling}", "composite" : true, "composites" : { - "realm" : [ "modify-table-column-semantics", "list-tables", "update-table-statistic", "find-table", "create-table", "delete-table" ] + "realm" : [ "modify-table-column-semantics", "list-tables", "update-table-statistic", "find-table", "create-table", "delete-table", "update-table" ] }, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } - }, { - "id" : "b0d66d3d-59b4-4aae-aa66-e3d5a49f28e3", - "name" : "view-database-view-data", - "description" : "${view-database-view-data}", - "composite" : false, - "clientRole" : false, - "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", - "attributes" : { } }, { "id" : "f5ea431a-9b2c-4195-bcb4-9511f38e4b44", "name" : "create-database-view", @@ -219,7 +212,7 @@ "description" : "${default-researcher-roles}", "composite" : true, "composites" : { - "realm" : [ "default-table-handling", "default-semantics-handling", "default-container-handling", "default-query-handling", "default-user-handling", "default-database-handling", "default-broker-handling", "default-identifier-handling" ] + "realm" : [ "default-table-handling", "default-semantics-handling", "default-container-handling", "default-query-handling", "default-user-handling", "default-database-handling", "default-broker-handling", "default-identifier-handling", "default-view-handling" ] }, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", @@ -264,6 +257,14 @@ "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } + }, { + "id" : "22449528-00c9-4e86-9400-4b8ae6fd8f4d", + "name" : "modify-view-visibility", + "description" : "${modify-view-visibility}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } }, { "id" : "c12c1f4e-186f-4153-a795-26e79fb623d6", "name" : "create-ontology", @@ -296,6 +297,17 @@ "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } + }, { + "id" : "d75e7938-9d5e-4cb3-8c57-18a446867d3a", + "name" : "default-view-handling", + "description" : "${default-view-handling}", + "composite" : true, + "composites" : { + "realm" : [ "delete-database-view", "update-database-view", "create-database-view", "modify-view-visibility", "find-database-view", "list-database-views" ] + }, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } }, { "id" : "535f1484-4514-4d24-8d97-e3f6c11a426b", "name" : "create-container", @@ -390,17 +402,33 @@ "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } + }, { + "id" : "6ae766b0-b8b4-4067-a95d-c8576bc4ac77", + "name" : "update-table", + "description" : "${update-table}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } }, { "id" : "64c16bfb-2015-48ad-a23f-637ff24419cb", "name" : "default-query-handling", "description" : "${default-query-handling}", "composite" : true, "composites" : { - "realm" : [ "delete-database-view", "export-query-data", "execute-query", "delete-table-data", "export-table-data", "list-queries", "find-query", "list-database-views", "persist-query", "view-database-view-data", "view-table-data", "re-execute-query", "view-table-history", "create-database-view", "find-database-view", "insert-table-data" ] + "realm" : [ "delete-database-view", "export-query-data", "execute-query", "delete-table-data", "export-table-data", "list-queries", "find-query", "list-database-views", "persist-query", "view-table-data", "re-execute-query", "view-table-history", "create-database-view", "find-database-view", "insert-table-data" ] }, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } + }, { + "id" : "b05e9b2b-748d-490b-949b-e78655bf7805", + "name" : "check-foreign-database-access", + "description" : "${check-foreign-database-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } }, { "id" : "c047d521-cec3-4444-86c4-aef098489b7b", "name" : "delete-maintenance-message", @@ -409,6 +437,14 @@ "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } + }, { + "id" : "df20b7d1-8d30-4a99-80eb-e8195fab0e76", + "name" : "update-database-view", + "description" : "${update-database-view}", + "composite" : false, + "clientRole" : false, + "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", + "attributes" : { } }, { "id" : "88f82262-be80-4d18-9fb4-5529da031f33", "name" : "system", @@ -522,7 +558,7 @@ "description" : "${default-container-handling}", "composite" : true, "composites" : { - "realm" : [ "find-container", "list-containers" ] + "realm" : [ "find-container" ] }, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", @@ -902,7 +938,7 @@ "composite" : true, "composites" : { "client" : { - "realm-management" : [ "query-realms", "view-identity-providers", "manage-identity-providers", "manage-authorization", "query-clients", "view-authorization", "view-users", "manage-users", "view-realm", "query-users", "view-clients", "query-groups", "create-client", "manage-clients", "manage-events", "impersonation", "view-events", "manage-realm" ] + "realm-management" : [ "query-realms", "manage-authorization", "manage-identity-providers", "view-identity-providers", "query-clients", "view-authorization", "view-users", "manage-users", "view-realm", "query-users", "view-clients", "create-client", "query-groups", "impersonation", "manage-clients", "manage-events", "view-events", "manage-realm" ] } }, "clientRole" : true, @@ -1203,12 +1239,13 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { "id" : "d3c4a04e-39ce-4549-a34a-11e25774cd96", @@ -1233,6 +1270,7 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, @@ -1247,7 +1285,7 @@ "consentRequired" : false, "config" : { } } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { "id" : "81ef0f59-a5ca-4be4-a1d1-0c32edf1cfd6", @@ -1270,12 +1308,14 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, + "fullScopeAllowed" : true, "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { "id" : "88694c91-753d-4c44-9740-ec9ac06bba45", @@ -1298,6 +1338,7 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "true", "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, @@ -1331,6 +1372,7 @@ "frontchannelLogout" : true, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", "oidc.ciba.grant.enabled" : "false", "client.secret.creation.time" : "1680085365", "backchannel.logout.session.required" : "true", @@ -1342,6 +1384,38 @@ "fullScopeAllowed" : true, "nodeReRegistrationTimeout" : -1, "protocolMappers" : [ { + "id" : "266edf62-a19a-483b-b594-81428e4af792", + "name" : "orcid", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "ORCID", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "orcid", + "jsonType.label" : "String" + } + }, { + "id" : "1a21798a-38b6-4df5-89f0-86942415246f", + "name" : "theme", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "THEME", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "theme", + "jsonType.label" : "String" + } + }, { "id" : "da0b27c1-ae2e-4baa-bf78-db233e15c78d", "name" : "preferred_username", "protocol" : "openid-connect", @@ -1355,27 +1429,77 @@ "userinfo.token.claim" : "true" } }, { - "id" : "7c94de93-f60f-487b-b4b7-1891c67f74cc", - "name" : "aud", + "id" : "1bc6a1f4-4be2-439c-8c7f-b3fb0bb9956a", + "name" : "affiliation", "protocol" : "openid-connect", - "protocolMapper" : "oidc-hardcoded-claim-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "claim.value" : "dbrepo", + "introspection.token.claim" : "true", "userinfo.token.claim" : "true", + "user.attribute" : "AFFILIATION", "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", - "claim.name" : "aud", - "access.tokenResponse.claim" : "false" + "claim.name" : "affiliation", + "jsonType.label" : "String" } }, { - "id" : "0b4c644f-0cf0-4794-8395-d5d83009dabe", + "id" : "7cbf6dc6-653e-40a9-9974-0e5bf7a363c3", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "70bbd779-d085-4204-ac4b-3a40abab9d88", + "name" : "language", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "LANGUAGE", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "language", + "jsonType.label" : "String" + } + }, { + "id" : "b817424d-7f91-43d8-b7d0-6a32582377fb", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "030a1cd9-53d1-4a62-a375-94d50a2dc6fc", "name" : "uid", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { + "aggregate.attrs" : "false", "introspection.token.claim" : "true", + "multivalued" : "false", "userinfo.token.claim" : "true", "user.attribute" : "CUSTOM_ID", "id.token.claim" : "true", @@ -1384,9 +1508,26 @@ "claim.name" : "uid", "jsonType.label" : "String" } + }, { + "id" : "c304ed2f-5952-4772-838d-91998a45f154", + "name" : "aud", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-hardcoded-claim-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "claim.value" : "account", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "aud", + "jsonType.label" : "String", + "access.tokenResponse.claim" : "false" + } } ], - "defaultClientScopes" : [ "roles", "attributes" ], - "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] + "defaultClientScopes" : [ "roles", "basic" ], + "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "attributes", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] }, { "id" : "25741f6b-4867-4138-8238-6345c6ba8702", "clientId" : "rabbitmq-client", @@ -1413,6 +1554,7 @@ "frontchannelLogout" : true, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", "oidc.ciba.grant.enabled" : "false", "client.secret.creation.time" : "1680000860", "backchannel.logout.session.required" : "true", @@ -1430,12 +1572,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "false", "user.attribute" : "username", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "client_id", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "false" } }, { "id" : "f1afc22d-f595-403b-ba2e-6ab19d98205e", @@ -1444,15 +1586,15 @@ "protocolMapper" : "oidc-hardcoded-claim-mapper", "consentRequired" : false, "config" : { - "claim.value" : "rabbitmq", - "userinfo.token.claim" : "false", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "aud", + "claim.value" : "rabbitmq", + "userinfo.token.claim" : "false", "access.tokenResponse.claim" : "false" } } ], - "defaultClientScopes" : [ "web-origins", "acr", "rabbitmq.tag:management" ], + "defaultClientScopes" : [ "web-origins", "acr", "rabbitmq.tag:management", "basic" ], "optionalClientScopes" : [ "rabbitmq.read:*/*", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "roles", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] }, { "id" : "cfffd5d0-aa19-4057-8ca0-f2c51ca0e930", @@ -1475,6 +1617,7 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "true", "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, @@ -1505,11 +1648,13 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, + "fullScopeAllowed" : true, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { "id" : "c4d54410-3f22-4259-9571-94da2c43b752", @@ -1518,15 +1663,15 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] } ], "clientScopes" : [ { @@ -1547,8 +1692,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "782819fe-ba5d-4ddb-9f95-cabb69d79c8d", @@ -1557,12 +1702,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "emailVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } }, { "id" : "ca613fc8-bbf2-4240-8b33-a1874f1559f3", @@ -1571,12 +1716,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "email", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1586,8 +1731,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "84f0487a-1d7d-470c-9b8e-5835294ae235", @@ -1596,12 +1741,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "preferred_username", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bbdcdb36-3ec0-443d-b1af-9993d40f0567", @@ -1610,12 +1755,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "gender", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "gender", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "9faa870b-5491-4ce9-b27d-c9ce07d6a95e", @@ -1624,12 +1769,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "birthdate", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "birthdate", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f0e3c012-9523-4076-83ae-e466e2d08220", @@ -1649,12 +1794,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "profile", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "profile", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "18cfbf4b-0a8e-45c7-a832-c0f72c92f3f3", @@ -1663,12 +1808,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "updatedAt", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "updated_at", - "jsonType.label" : "long" + "jsonType.label" : "long", + "userinfo.token.claim" : "true" } }, { "id" : "841ea785-26ab-429a-a420-09ce3948924d", @@ -1677,12 +1822,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "lastName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "family_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bfba13ff-f952-4e89-bbb1-a693fdebfae8", @@ -1691,12 +1836,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "website", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "website", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "475f071d-5149-4379-b928-76482f5f519c", @@ -1705,12 +1850,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "zoneinfo", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "zoneinfo", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "b8bebfed-b5e9-4604-a0ee-9817f7d439ac", @@ -1719,12 +1864,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "middleName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "middle_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "445232c8-6830-476c-a6f1-8bbef167595a", @@ -1733,12 +1878,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "picture", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "picture", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "65f2e474-6ede-4872-86e4-e49504dd0f2a", @@ -1747,12 +1892,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "16cd5a27-ccf3-453c-ae1e-8621813ab73c", @@ -1761,12 +1906,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "firstName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "given_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f9efedfc-3388-457c-b10a-1dff4525ff9b", @@ -1775,12 +1920,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "nickname", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "nickname", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1814,12 +1959,12 @@ "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "upn", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1861,8 +2006,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "dae802fb-9138-408a-b80e-a40eb0f56814", @@ -1871,12 +2016,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumber", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "feb06a8d-b0eb-4911-8464-368d93f566fa", @@ -1885,12 +2030,12 @@ "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumberVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } } ] }, { @@ -1900,8 +2045,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" + "consent.screen.text" : "", + "display.on.consent.screen" : "false" }, "protocolMappers" : [ { "id" : "c6411e3b-6478-453d-b530-5fe175a4d786", @@ -1981,6 +2126,61 @@ "gui.order" : "", "consent.screen.text" : "" } + }, { + "id" : "aa5c6ca7-812d-4fff-80b9-f5095ca82ce6", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "bb359b0f-97dc-4d6a-9a2f-89458b53c512", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "7aa3a4d2-3dd1-48dd-8886-562906eadb2a", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "c4882d39-e815-49f5-8a73-eb8b83572eae", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ] }, { "id" : "210cc792-6c07-45a6-a77e-827cdf3b41ba", "name" : "offline_access", @@ -1997,8 +2197,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "8d4ffe4d-1d01-4ca1-8ff4-44eacca61b30", @@ -2029,6 +2229,41 @@ "gui.order" : "", "consent.screen.text" : "" } + }, { + "id" : "ba11267a-478b-4b32-872f-4eb2d125d116", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1445e14f-49b0-4666-8ddc-691493c24ad9", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "846f1ef0-2b86-4e07-9d25-691d25af5fce", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + } ] }, { "id" : "37f61543-dad7-4a82-8e10-77acdd1eefdc", "name" : "roles", @@ -2036,8 +2271,8 @@ "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "3b6b6914-8ad1-4a71-88ec-444f754aaacb", @@ -2053,11 +2288,15 @@ "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "false", + "multivalued" : "true", "user.attribute" : "foo", + "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" + "jsonType.label" : "String" } }, { "id" : "a7bd6723-e58e-47f7-95c0-2925ce99283d", @@ -2074,7 +2313,7 @@ } } ] } ], - "defaultDefaultClientScopes" : [ "rabbitmq.tag:administrator", "rabbitmq.tag:management" ], + "defaultDefaultClientScopes" : [ "rabbitmq.tag:administrator", "rabbitmq.tag:management", "basic" ], "defaultOptionalClientScopes" : [ "rabbitmq.write:*/*", "offline_access", "rabbitmq.configure:*/*", "roles", "role_list", "address", "phone", "acr", "microprofile-jwt", "email", "attributes", "profile", "rabbitmq.read:*/*", "web-origins" ], "browserSecurityHeaders" : { "contentSecurityPolicyReportOnly" : "", @@ -2087,6 +2326,10 @@ "strictTransportSecurity" : "max-age=31536000; includeSubDomains" }, "smtpServer" : { }, + "loginTheme" : "keycloak", + "accountTheme" : "", + "adminTheme" : "", + "emailTheme" : "", "eventsEnabled" : false, "eventsListeners" : [ "jboss-logging" ], "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "VERIFY_PROFILE_ERROR", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "OAUTH2_DEVICE_VERIFY_USER_CODE", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "TOKEN_EXCHANGE", "AUTHREQID_TO_TOKEN", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "OAUTH2_DEVICE_AUTH", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "OAUTH2_DEVICE_CODE_TO_TOKEN", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "AUTHREQID_TO_TOKEN_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "OAUTH2_DEVICE_AUTH_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "VERIFY_PROFILE", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], @@ -2136,7 +2379,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "saml-role-list-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper" ] } }, { "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", @@ -2162,11 +2405,11 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ] } } ], "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "fb763636-e1ea-49c7-adca-ea105cdec4ad", + "id" : "a407a1d6-a7f6-4a72-ba3a-149de03d5a43", "providerId" : "declarative-user-profile", "subComponents" : { }, "config" : { @@ -2185,17 +2428,6 @@ "priority" : [ "100" ], "algorithm" : [ "RSA-OAEP" ] } - }, { - "id" : "230cb681-9ceb-4b1b-8a4c-929a11b08de0", - "name" : "hmac-generated-hs512", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "8a489935-9a95-459b-9059-59b438ef0fa8" ], - "secret" : [ "xSCVgBlrLPWoF54gKQdR7BqXlfNaCD43xtS_ZgQRC0tGNAbqhy2Q9y8LdD2IR7K__8VGaDGYtyZayopgTebhDBb4gHDjDOBX7flhFYRrm0G3aTIuCIyFG-bPULwmyP_oHeC6tjwdQhqx5G0tE2mQQqPC9dDZuUA5I7QREIGK8cI" ], - "priority" : [ "100" ], - "algorithm" : [ "HS512" ] - } }, { "id" : "28ca0b6d-b2e2-4785-b04b-2391e6344e30", "name" : "aes-generated", @@ -2212,8 +2444,8 @@ "providerId" : "hmac-generated", "subComponents" : { }, "config" : { - "kid" : [ "5034d264-cb50-4006-a59e-2ce636eb5f38" ], - "secret" : [ "ToVIw-a4IE-Yp9JpP8ztb8NAICYO8CT3tUiDPT6DdiBcgzKJ9Ym9vspxGVdmPceX3mAgbnGLAcTx1PkInSVrbZs-tX9QXFwdlyGbewhKiNpH8wEg32Wk4GuUDpTv8JCsymgWyQBY681jvIMv05eCoK2QWpqCzcgP828KM5peCzo" ], + "kid" : [ "7f9f9054-5697-4f60-bdc8-67e3bd0f4db6" ], + "secret" : [ "1SCIY20z3AbAHCL28LuJfBU-7zfsZv5dacgliUeGdRW_WK3vH9fJUpPu1f7iDrdlhF7YQmHxLXsWjxhQId4ShI7QBdgKCArHWqi0GeH37oNXfZFg_uv-K_3JSfxfGBRu5jpRQhhSBxESZWsFVkskhxWUvNe6b5l9dFbMIif72rI" ], "priority" : [ "100" ], "algorithm" : [ "HS256" ] } @@ -2228,12 +2460,23 @@ "certificate" : [ "MIICmzCCAYMCBgGG3GWyBTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZkYnJlcG8wHhcNMjMwMzEzMTkxMzE3WhcNMzMwMzEzMTkxNDU3WjARMQ8wDQYDVQQDDAZkYnJlcG8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqqcdDYFZZb28M0tEJzEP77FmD/Xqioyj9zWX6VwUSOMAgmMmn8eqs9hT9T0a+q4YTo9tUW1PNbUpwprA5b4Uk04DcIajxDVMUR/PjcHytmkqwVskq9AZW/Vngdoo+8tSbuIybwe/3Vwt266hbHpDcM97a+DXcYooRl7tQWCEX7RP27wQrMD9epDQ6IgKayZg9vC9/03dsIqwH9jXQRiZlFvwiEKhX2aY7lPGBaCK414JO00K/Z49iov9TRa/IYVbSt5qwgrx6DcqsBSPwOnI6A85UGfeUEZ/7coVJiL7RvBlsllapsL9eWTbQajVh94k9Ei3sibEPbtH+U2OAM78zAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAASnN1Cuif1sdfEK2kWAURSXGJCohCROLWdKFjaeHPRaEfpbFJsgxW0Yj3nwX5O3bUlOWoTyENwnXSsXMQsqnNi+At32CKaKO8+AkhAbgQL9F0B+KeJwmYv3cUj5N/LYkJjBvZBzUZ4Ugu5dcxH0k7AktLAIwimkyEnxTNolOA3UyrGGpREr8MCKWVr10RFuOpF/0CsJNNwbHXzalO9D756EUcRWZ9VSg6QVNso0YYRKTnILWDn9hcTRnqGy3SHo3anFTqQZ+BB57YbgFWy6udC0LYRB3zdp6zNti87eu/VEymiDY/mmo1AB8Tm0b6vxFz4AKcL3ax5qS6YnZ9efSzk=" ], "priority" : [ "100" ] } + }, { + "id" : "addbae10-c6ae-4735-851f-7a5ea035ce25", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "352d0ea1-8218-42b5-ab78-e2ca56cf6a95" ], + "secret" : [ "_kr6EZOZ8IKqPWgJltHAAsQ34wCIGPs8oOQLYWwJrSIH7Qie3CEVKZnICyBP1goR-QgUtg25tR8Qu5MkvYkb8assJ8Iok5x_8iYCR4Txkf_mS-emrlAtQajlIjmOfNBtx704dTnZlP9rWzqpW6mrpeiOaiCw1K0XCpY5C_ZjXKw" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } } ] }, "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "88e5d526-2298-413c-a904-133ad839d47f", + "id" : "259dd7b6-01b7-433a-bda4-028857151ecd", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -2255,7 +2498,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a690c715-fbae-4c20-b680-bd4010718761", + "id" : "542ca1d7-9627-4102-b843-98837ce433fb", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2277,7 +2520,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "ad6d407e-c73e-4439-baf3-d7c99c6cb6ad", + "id" : "4f153b98-6851-440b-a022-0a14e67a9b2f", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2299,7 +2542,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "e5d03405-e10a-408a-adb2-41dbb4f24515", + "id" : "3d791b35-d35c-40b2-bb3e-e806d72b27ee", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2321,7 +2564,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "96b93843-62d0-44f1-84dd-21cc5f95f523", + "id" : "9b746104-9371-4c3f-b69f-9322cead1b08", "alias" : "Handle Existing Account", "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", @@ -2343,7 +2586,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "088f4051-36ab-4952-a4f2-4ba53c408083", + "id" : "7a164efe-c97b-4fbb-950d-7745359ba9a4", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -2365,7 +2608,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "05f37bb2-779d-4e3f-ad1b-f6eb33bb3de4", + "id" : "4fdc5e1b-1b55-4662-8360-67d75fa22677", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -2388,7 +2631,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "300a5647-7d2c-4348-9f1f-51504bfda1c4", + "id" : "75893341-c338-44d8-ae27-a3fc7bfe8f2d", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -2410,7 +2653,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "26afc672-314b-4ad9-9711-7aaeafd7c00c", + "id" : "89626b76-f4cf-4c46-934c-4408c225a44b", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -2446,7 +2689,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "9b301f6c-eda7-4da0-ba09-1a6454ff910d", + "id" : "4112115a-e7a7-44c2-9af5-65d538e4ba0d", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -2482,7 +2725,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6e54f1be-dbad-4b6d-8eee-8e048d413c63", + "id" : "f82a9b0a-2c0a-4cb1-96b2-6c78b0b1f14f", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -2511,7 +2754,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "31da4b94-03c4-4d79-9ac3-5df1445c0781", + "id" : "3614e155-e8ce-4958-98fb-a27e4706cc70", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -2526,7 +2769,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "2e16651d-681f-4d9b-9dd4-9acdb465cd43", + "id" : "506f9b96-5002-47c0-96e3-3830a0fcfa26", "alias" : "first broker login", "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId" : "basic-flow", @@ -2549,7 +2792,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "da109a26-fefa-48a4-ae8e-1d49627c2db8", + "id" : "4b7a7e91-36db-4b27-8e2d-01a04a822980", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -2571,7 +2814,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "4c983c77-241f-41c5-8b8a-e2cd6fc08914", + "id" : "04c2fe01-5076-4aa4-9596-4efb4004195f", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -2587,7 +2830,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "d62c8dd6-633c-408a-aa99-43071510efb4", + "id" : "d12f77e1-7733-44a2-98ff-fd75c784d721", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -2616,7 +2859,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "c8ca5be7-e76d-4e16-b5ca-3ced99d92dbb", + "id" : "91f6048c-a376-4809-8f37-c8d7a517830c", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -2652,7 +2895,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "389c1c37-e8af-4610-a507-e1257f55b954", + "id" : "7b8fb487-53b8-4533-a696-76bc05256cb1", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -2668,13 +2911,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "d66ca9d0-1645-4c84-abfe-c0a696f17de4", + "id" : "48372696-0579-45e5-b074-5e8dbdbbe7d6", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "061cc6b8-90be-4423-9bf9-974ead709b5d", + "id" : "08df3b83-e522-42a7-9e24-9028b960bf39", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" @@ -2785,10 +3028,12 @@ "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email" : "", "parRequestUriLifespan" : "60", "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false", "shortVerificationUri" : "" }, - "keycloakVersion" : "24.0.5", + "keycloakVersion" : "26.0.4", "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, "clientProfiles" : { "profiles" : [ ] }, diff --git a/dbrepo-metadata-service/services/pom.xml b/dbrepo-metadata-service/services/pom.xml index 0ec2d62d1dfa663ce71b468e16e74bc1fb015c40..d1b8f9a7026e77f2235c580f0149f4dac17df779 100644 --- a/dbrepo-metadata-service/services/pom.xml +++ b/dbrepo-metadata-service/services/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-services</artifactId> <name>dbrepo-metadata-service-services</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/BasicAuthenticationProvider.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/BasicAuthenticationProvider.java index b9fea2b54b1c242f58dbc0cd578e9dff328ba49c..d6535bad491b18744afecbe4fdc4792dfd3e7f33 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/BasicAuthenticationProvider.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/BasicAuthenticationProvider.java @@ -1,7 +1,6 @@ package at.tuwien.auth; import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.exception.*; import at.tuwien.gateway.KeycloakGateway; import jakarta.servlet.ServletException; import lombok.extern.log4j.Log4j2; @@ -34,8 +33,7 @@ public class BasicAuthenticationProvider implements AuthenticationManager { final UserDetails userDetails = authTokenFilter.verifyJwt(tokenDto.getAccessToken()); log.debug("set basic auth principal: {}", userDetails); return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - } catch (ServletException | CredentialsInvalidException | AccountNotSetupException | - AuthServiceConnectionException e) { + } catch (ServletException e) { throw new BadCredentialsException("Failed to authenticate with authentication service", e); } } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java index 835b7245d1ef2ca017375990dd77c20693f3ef6f..b0edc929ed0097ce94e65153707dc47406f0f3c9 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java @@ -2,9 +2,6 @@ package at.tuwien.auth; import at.tuwien.api.keycloak.TokenDto; import at.tuwien.config.GatewayConfig; -import at.tuwien.exception.AccountNotSetupException; -import at.tuwien.exception.AuthServiceConnectionException; -import at.tuwien.exception.CredentialsInvalidException; import at.tuwien.gateway.KeycloakGateway; import lombok.extern.log4j.Log4j2; import org.springframework.http.HttpHeaders; @@ -33,15 +30,10 @@ public class InternalRequestInterceptor implements ClientHttpRequestInterceptor throws IOException { final HttpHeaders headers = request.getHeaders(); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - try { - final TokenDto token = keycloakGateway.obtainUserToken(gatewayConfig.getSystemUsername(), - gatewayConfig.getSystemPassword()); - headers.setBearerAuth(token.getAccessToken()); - log.trace("set bearer token for internal user: {}", gatewayConfig.getSystemUsername()); - return execution.execute(request, body); - } catch (AuthServiceConnectionException | CredentialsInvalidException | AccountNotSetupException e) { - log.error("Failed to obtain token for internal user: {}", gatewayConfig.getSystemUsername()); - throw new IOException("Failed to obtain token for internal user", e); - } + final TokenDto token = keycloakGateway.obtainUserToken(gatewayConfig.getSystemUsername(), + gatewayConfig.getSystemPassword()); + headers.setBearerAuth(token.getAccessToken()); + log.trace("set bearer token for internal user: {}", gatewayConfig.getSystemUsername()); + return execution.execute(request, body); } } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/KeycloakConfig.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/KeycloakConfig.java index a24bbf41b8168b6e5c9ffef70c1b7bbbd6f5c674..4b62b61dcba2e06f9847d9eb20042b1f535bbc8d 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/KeycloakConfig.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/KeycloakConfig.java @@ -1,12 +1,11 @@ package at.tuwien.config; -import at.tuwien.interceptor.KeycloakInterceptor; import lombok.Getter; +import org.keycloak.admin.client.Keycloak; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.DefaultUriBuilderFactory; @Getter @Configuration @@ -27,17 +26,15 @@ public class KeycloakConfig { @Value("${dbrepo.keycloak.clientSecret}") private String keycloakClientSecret; + private final String realm = "dbrepo"; + @Bean public RestTemplate restTemplate() { return new RestTemplate(); } - @Bean("keycloakRestTemplate") - public RestTemplate brokerRestTemplate() { - final RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(keycloakEndpoint)); - restTemplate.getInterceptors() - .add(new KeycloakInterceptor(restTemplate(), keycloakUsername, keycloakPassword, keycloakEndpoint)); - return restTemplate; + @Bean + public Keycloak keycloak() { + return Keycloak.getInstance(keycloakEndpoint, "master", keycloakUsername, keycloakPassword, "admin-cli"); } } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/KeycloakGateway.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/KeycloakGateway.java index 94ea986f78727a6fdc927b4e7ebb25ca6f0616bd..cd5fd08a7ef64e88134a1fc19aa0840ad1e98020 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/KeycloakGateway.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/KeycloakGateway.java @@ -1,37 +1,26 @@ package at.tuwien.gateway; import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.api.keycloak.UserCreateDto; -import at.tuwien.api.keycloak.UserDto; import at.tuwien.api.user.UserPasswordDto; -import at.tuwien.exception.*; +import at.tuwien.api.user.UserUpdateDto; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.exception.UserNotFoundException; +import org.keycloak.representations.idm.UserRepresentation; import java.util.UUID; public interface KeycloakGateway { - TokenDto obtainUserToken(String username, String password) throws AuthServiceConnectionException, - CredentialsInvalidException, AccountNotSetupException; + TokenDto obtainUserToken(String username, String password); - TokenDto refreshUserToken(String refreshToken) throws AuthServiceConnectionException, - CredentialsInvalidException; - - /** - * Creates a user at the Authentication Service with given credentials. - * - * @param data The user credentials. - * @throws UserExistsException The user already exists at the Authentication Service. - * @throws EmailExistsException The user email already exists in the metadata database. - */ - void createUser(UserCreateDto data) throws AuthServiceException, AuthServiceConnectionException, - EmailExistsException, UserExistsException; + UserRepresentation findByUsername(String username) throws UserNotFoundException; /** * Deletes a user at the Authentication Service with given user id. * * @param id The user id. */ - void deleteUser(UUID id) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException; + void deleteUser(UUID id) throws UserNotFoundException; /** * Update the credentials for a given user. @@ -39,17 +28,7 @@ public interface KeycloakGateway { * @param id The user id. * @param password The user credential. */ - void updateUserCredentials(UUID id, UserPasswordDto password) throws AuthServiceException, - AuthServiceConnectionException, UserNotFoundException; - - /** - * Finds a user in the metadata database by given username. - * - * @param username The user username. - * @return The updated user. - */ - UserDto findByUsername(String username) throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException; + void updateUserCredentials(UUID id, UserPasswordDto password) throws UserNotFoundException; - UserDto findById(UUID id) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException; + void updateUser(UUID id, UserUpdateDto data) throws AuthServiceException, UserNotFoundException; } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java index bce9d6e264b5283864c4e0ce4d2a157bd3d7dab4..af54651d6c36197e136134003291ea88f5df2a49 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java @@ -1,232 +1,128 @@ package at.tuwien.gateway.impl; -import at.tuwien.api.auth.KeycloakErrorDto; -import at.tuwien.api.keycloak.*; +import at.tuwien.api.keycloak.TokenDto; import at.tuwien.api.user.UserPasswordDto; +import at.tuwien.api.user.UserUpdateDto; import at.tuwien.config.KeycloakConfig; -import at.tuwien.exception.*; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.exception.UserNotFoundException; import at.tuwien.gateway.KeycloakGateway; import at.tuwien.mapper.MetadataMapper; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.*; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.DefaultUriBuilderFactory; +import java.util.List; import java.util.UUID; @Log4j2 @Service public class KeycloakGatewayImpl implements KeycloakGateway { - private final RestTemplate restTemplate; - private final RestTemplate keycloakRestTemplate; + private final Keycloak keycloak; private final KeycloakConfig keycloakConfig; private final MetadataMapper metadataMapper; - public KeycloakGatewayImpl(@Qualifier("restTemplate") RestTemplate restTemplate, - @Qualifier("keycloakRestTemplate") RestTemplate keycloakRestTemplate, - KeycloakConfig keycloakConfig, MetadataMapper metadataMapper) { - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(keycloakConfig.getKeycloakEndpoint())); - this.restTemplate = restTemplate; - this.keycloakRestTemplate = keycloakRestTemplate; + public KeycloakGatewayImpl(Keycloak keycloak, KeycloakConfig keycloakConfig, MetadataMapper metadataMapper) { + this.keycloak = keycloak; this.keycloakConfig = keycloakConfig; this.metadataMapper = metadataMapper; } @Override - public TokenDto obtainUserToken(String username, String password) throws AuthServiceConnectionException, - CredentialsInvalidException, AccountNotSetupException { - final HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - final MultiValueMap<String, String> payload = new LinkedMultiValueMap<>(); - payload.add("username", username); - payload.add("password", password); - payload.add("grant_type", "password"); - payload.add("scope", "openid roles"); - payload.add("client_id", keycloakConfig.getKeycloakClient()); - payload.add("client_secret", keycloakConfig.getKeycloakClientSecret()); - final String path = "/realms/dbrepo/protocol/openid-connect/token"; - log.trace("obtain user token at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<TokenDto> response; - try { - response = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(payload, headers), TokenDto.class); - } catch (HttpServerErrorException e) { - log.error("Failed to obtain user token: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.BadRequest e) { - final KeycloakErrorDto error = e.getResponseBodyAs(KeycloakErrorDto.class); - if (error != null && error.getError().equals("invalid_grant")) { - log.error("Failed to obtain user token: {}", error.getErrorDescription()); - throw new AccountNotSetupException(error.getErrorDescription()); - } - log.error("Failed to obtain user token: bad request"); - throw new CredentialsInvalidException("Bad request", e); - } catch (HttpClientErrorException.Unauthorized e) { - log.error("Failed to obtain user token: invalid credentials"); - throw new CredentialsInvalidException("Invalid credentials", e); + public TokenDto obtainUserToken(String username, String password) { + try (Keycloak userKeycloak = KeycloakBuilder.builder() + .serverUrl(keycloakConfig.getKeycloakEndpoint()) + .realm(keycloakConfig.getRealm()) + .grantType(OAuth2Constants.PASSWORD) + .clientId(keycloakConfig.getKeycloakClient()) + .clientSecret(keycloakConfig.getKeycloakClientSecret()) + .username(username) + .password(password) + .build()) { + return metadataMapper.accessTokenResponseToTokenDto(userKeycloak.tokenManager() + .getAccessToken()); } - return response.getBody(); } @Override - public TokenDto refreshUserToken(String refreshToken) throws AuthServiceConnectionException, - CredentialsInvalidException { - final HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - final MultiValueMap<String, String> payload = new LinkedMultiValueMap<>(); - payload.add("refresh_token", refreshToken); - payload.add("grant_type", "refresh_token"); - payload.add("client_id", keycloakConfig.getKeycloakClient()); - payload.add("client_secret", keycloakConfig.getKeycloakClientSecret()); - final String path = "/realms/dbrepo/protocol/openid-connect/token"; - log.trace("refresh user token at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<TokenDto> response; - try { - response = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(payload, headers), TokenDto.class); - } catch (HttpServerErrorException e) { - log.error("Failed to refresh user token: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.Unauthorized e) { - log.error("Failed to refresh user token: invalid credentials"); - throw new CredentialsInvalidException("Invalid credentials", e); - } catch (HttpClientErrorException.BadRequest e) { - if (e.getMessage() != null && e.getMessage().contains("Session not active")) { - log.error("Failed to refresh user token: inactive session", e); - throw new CredentialsInvalidException("Inactive session", e); - } - log.error("Failed to refresh user token: unexpected response: {}", e.getMessage(), e); - throw new CredentialsInvalidException("Unexpected response: " + e.getMessage(), e); + public UserRepresentation findByUsername(String username) throws UserNotFoundException { + final List<UserRepresentation> users = keycloak.realm(keycloakConfig.getRealm()) + .users() + .search(username); + if (users.isEmpty()) { + log.error("Failed to find user with username {}", username); + throw new UserNotFoundException("Failed to find user"); } - return response.getBody(); + return users.get(0); } @Override - public void createUser(UserCreateDto data) throws AuthServiceException, AuthServiceConnectionException, - EmailExistsException, UserExistsException { - final String path = "/admin/realms/dbrepo/users"; - log.trace("create user at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<Void> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(data), Void.class); - } catch (HttpServerErrorException e) { - log.error("Failed to create user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.Conflict e) { - if (e.getResponseBodyAsByteArray() != null && e.getResponseBodyAsByteArray().length > 0) { - final KeycloakErrorDto error = e.getResponseBodyAs(KeycloakErrorDto.class); - if (error != null && error.getErrorMessage().contains("same email")) { - log.error("Failed to create user: email exists: {}", e.getMessage()); - throw new EmailExistsException("E-Mail exists", e); - } + public void deleteUser(UUID id) throws UserNotFoundException { + try (Response response = keycloak.realm(keycloakConfig.getRealm()) + .users() + .delete(String.valueOf(id))) { + if (response.getStatus() == 404) { + log.error("Failed to delete user: not found"); + throw new UserNotFoundException("Failed to delete user: not found"); } - log.error("Failed to create user: user exists: {}", e.getMessage()); - throw new UserExistsException("User exists", e); - } - if (!response.getStatusCode().equals(HttpStatus.CREATED)) { - log.error("Failed to create user: unexpected status: {}", response.getStatusCode().value()); - throw new AuthServiceException("Unexpected status: " + response.getStatusCode().value()); - } - log.debug("Created user {} at auth service", data.getUsername()); - } - - @Override - public void deleteUser(UUID id) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException { - final String path = "/admin/realms/dbrepo/users/" + id; - log.trace("delete user at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<Void> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.DELETE, HttpEntity.EMPTY, Void.class); - } catch (HttpServerErrorException e) { - log.error("Failed to delete user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to delete user: user not found: {}", e.getMessage()); - throw new UserNotFoundException("User not found", e); - } catch (Exception e) { - log.error("Failed to delete user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); - } - if (!response.getStatusCode().equals(HttpStatus.NO_CONTENT)) { - log.error("Failed to delete user: unexpected response"); - throw new AuthServiceException("Unexpected result"); } log.info("Deleted user {} at auth service", id); } @Override - public void updateUserCredentials(UUID id, UserPasswordDto data) throws AuthServiceException, - AuthServiceConnectionException, UserNotFoundException { - final UpdateCredentialsDto payload = metadataMapper.passwordToUpdateCredentialsDto(data.getPassword()); - final String path = "/admin/realms/dbrepo/users/" + id; - log.trace("update user credentials at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<Void> response; + public void updateUserCredentials(UUID id, UserPasswordDto data) throws UserNotFoundException { + final CredentialRepresentation credential = new CredentialRepresentation(); + credential.setTemporary(false); + credential.setValue(data.getPassword()); + credential.setType(CredentialRepresentation.PASSWORD); try { - response = keycloakRestTemplate.exchange(path, HttpMethod.PUT, new HttpEntity<>(payload), Void.class); - } catch (HttpServerErrorException e) { - log.error("Failed to update user credentials: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to update user credentials: user not found: {}", e.getMessage()); - throw new UserNotFoundException("User not found", e); - } catch (Exception e) { - log.error("Failed to update user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); - } - if (!response.getStatusCode().equals(HttpStatus.NO_CONTENT)) { - log.error("Failed to update user: unexpected status: {}", response.getStatusCode().value()); - throw new AuthServiceException("Unexpected status: " + response.getStatusCode().value()); + keycloak.realm(keycloakConfig.getRealm()) + .users() + .get(String.valueOf(id)) + .resetPassword(credential); + } catch (NotFoundException e) { + log.error("Failed to update user password: not found"); + throw new UserNotFoundException("Failed to update user password: not found", e); } log.info("Updated user {} password at auth service", id); } @Override - public UserDto findByUsername(String username) throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException { - final String path = "/admin/realms/dbrepo/users/?username=" + username; - log.trace("find user by username at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<UserDto[]> response; + public void updateUser(UUID id, UserUpdateDto data) throws AuthServiceException, UserNotFoundException { + final UserResource resource = keycloak.realm(keycloakConfig.getRealm()) + .users() + .get(String.valueOf(id)); + UserRepresentation user; try { - response = keycloakRestTemplate.exchange(path, HttpMethod.GET, HttpEntity.EMPTY, UserDto[].class); - } catch (HttpServerErrorException e) { - log.error("Failed to find user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (Exception e) { - log.error("Failed to find user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); + user = resource.toRepresentation(); + } catch (NotFoundException e) { + log.error("Failed to update user: not found: {}", e.getMessage()); + throw new UserNotFoundException("Failed to update user: not found", e); } - final UserDto[] body = response.getBody(); - if (body == null || body.length != 1) { - log.error("Failed to find user with username {}", username); - throw new UserNotFoundException("Failed to find user with username " + username); - } - return body[0]; - } - - @Override - public UserDto findById(UUID id) throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException { - final String path = "/admin/realms/dbrepo/users/" + id; - log.trace("find user by id at endpoint {} with path {}", keycloakConfig.getKeycloakEndpoint(), path); - final ResponseEntity<UserDto> response; + user.setFirstName(data.getFirstname()); + user.setLastName(data.getLastname()); + user.singleAttribute("THEME", data.getTheme()); + user.singleAttribute("ORCID", data.getOrcid()); + user.singleAttribute("LANGUAGE", data.getLanguage()); + user.singleAttribute("AFFILIATION", data.getAffiliation()); + log.trace("update user: {}", data); try { - response = keycloakRestTemplate.exchange(path, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class); - } catch (HttpServerErrorException e) { - log.error("Failed to find user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to find user: not found: {}", e.getMessage()); - throw new UserNotFoundException("User not found"); - } catch (Exception e) { - log.error("Failed to find user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); + resource.update(user); + } catch (ForbiddenException e) { + log.error("Failed to update user: forbidden: {}", e.getMessage()); + throw new AuthServiceException("Failed to update user: forbidden", e); } - return response.getBody(); + log.info("Updated user {} attributes at auth service", id); } } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/AuthenticationService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/AuthenticationService.java index a288d1d6e08488de5935e6d0af776fb157c8531e..75b647bf954fd638dfde8ffa8b7f650d03ce7c49 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/AuthenticationService.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/AuthenticationService.java @@ -1,30 +1,14 @@ package at.tuwien.service; -import at.tuwien.api.auth.LoginRequestDto; -import at.tuwien.api.auth.CreateUserDto; -import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.api.keycloak.UserDto; import at.tuwien.api.user.UserPasswordDto; import at.tuwien.entities.user.User; -import at.tuwien.exception.*; - -import java.util.UUID; +import at.tuwien.exception.AuthServiceConnectionException; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.exception.CredentialsInvalidException; +import at.tuwien.exception.UserNotFoundException; public interface AuthenticationService { - /** - * Create a user at the Authentication Service with given credentials. - * - * @param data The credentials. - * @return The user, if successful. - * @throws UserExistsException The user already exists at the auth database. - * @throws AuthServiceException The auth service responded with unexpected behavior. - * @throws AuthServiceConnectionException The connection with the auth service could not be established. - * @throws EmailExistsException The user email already exists in the metadata database. - */ - UserDto create(CreateUserDto data) throws UserExistsException, AuthServiceException, AuthServiceConnectionException, - EmailExistsException, CredentialsInvalidException; - /** * Deletes a user at the Authentication Service with given user id. * @@ -35,31 +19,12 @@ public interface AuthenticationService { */ void delete(User user) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, CredentialsInvalidException; - /** - * Finds a user with given username. - * - * @param username The username. - * @return The user, if successful. - * @throws AuthServiceException The auth service responded with unexpected behavior. - * @throws AuthServiceConnectionException The connection with the auth service could not be established. - * @throws UserNotFoundException The user was not found in the auth database. - */ - UserDto findByUsername(String username) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, CredentialsInvalidException; - - UserDto findById(UUID id) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, CredentialsInvalidException; - - TokenDto obtainToken(LoginRequestDto data) throws AuthServiceConnectionException, CredentialsInvalidException, AccountNotSetupException; - - TokenDto refreshToken(String refreshToken) throws AuthServiceConnectionException, CredentialsInvalidException; - /** * Updates the password of a user with given id. * * @param user The user. * @param data The new password. - * @throws AuthServiceException The auth service responded with unexpected behavior. - * @throws AuthServiceConnectionException The connection with the auth service could not be established. + * @throws UserNotFoundException The user was not found after creation in the auth database. */ - void updatePassword(User user, UserPasswordDto data) throws AuthServiceException, AuthServiceConnectionException, - CredentialsInvalidException, UserNotFoundException; + void updatePassword(User user, UserPasswordDto data) throws UserNotFoundException; } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/UserService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/UserService.java index c2f57c4e53af97573e7710b978f82b399ec54240..581641a93ab95927d63c5b4a5f6b069eb6f270b7 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/UserService.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/UserService.java @@ -4,7 +4,8 @@ import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.user.UserPasswordDto; import at.tuwien.api.user.UserUpdateDto; import at.tuwien.entities.user.User; -import at.tuwien.exception.EmailExistsException; +import at.tuwien.exception.AuthServiceConnectionException; +import at.tuwien.exception.AuthServiceException; import at.tuwien.exception.UserExistsException; import at.tuwien.exception.UserNotFoundException; @@ -44,10 +45,9 @@ public interface UserService { * Creates a user in the metadata database managed by Keycloak in the given realm. * * @param data The user data. - * @param id The user id. * @return The user, if successful. */ - User create(CreateUserDto data, UUID id); + User create(CreateUserDto data) throws UserNotFoundException, AuthServiceException; /** * Updates the user information for a user with given id in the metadata database. @@ -56,7 +56,7 @@ public interface UserService { * @param data The user information. * @return The user if successful. False otherwise. */ - User modify(User user, UserUpdateDto data); + User modify(User user, UserUpdateDto data) throws UserNotFoundException, AuthServiceException; /** * Updates the user password for a user with given id in the metadata database. @@ -74,13 +74,5 @@ public interface UserService { */ void validateUsernameNotExists(String username) throws UserExistsException; - /** - * Validates if a user with the given email already exists in the metadata database. - * - * @param email The email. - * @throws EmailExistsException The user with this email already exists. - */ - void validateEmailNotExists(String email) throws EmailExistsException; - String getMariaDbPassword(String password); } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AuthenticationServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AuthenticationServiceImpl.java index 24ebeb1665a77a874fd311f9e4fc4151ac80689f..1159913039ef59227758006f28d678db99329386 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AuthenticationServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AuthenticationServiceImpl.java @@ -1,81 +1,36 @@ package at.tuwien.service.impl; -import at.tuwien.api.auth.LoginRequestDto; -import at.tuwien.api.auth.CreateUserDto; -import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.api.keycloak.UserDto; import at.tuwien.api.user.UserPasswordDto; import at.tuwien.entities.user.User; -import at.tuwien.exception.*; +import at.tuwien.exception.AuthServiceConnectionException; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.exception.CredentialsInvalidException; +import at.tuwien.exception.UserNotFoundException; import at.tuwien.gateway.KeycloakGateway; -import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.AuthenticationService; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.UUID; - @Log4j2 @Service public class AuthenticationServiceImpl implements AuthenticationService { - private final MetadataMapper metadataMapper; private final KeycloakGateway keycloakGateway; @Autowired - public AuthenticationServiceImpl(MetadataMapper metadataMapper, KeycloakGateway keycloakGateway) { - this.metadataMapper = metadataMapper; + public AuthenticationServiceImpl(KeycloakGateway keycloakGateway) { this.keycloakGateway = keycloakGateway; } @Override - public UserDto create(CreateUserDto data) throws UserExistsException, AuthServiceException, - AuthServiceConnectionException, EmailExistsException, CredentialsInvalidException { - keycloakGateway.createUser(metadataMapper.signupRequestDtoToUserCreateDto(data)); - try { - return findByUsername(data.getUsername()); - } catch (UserNotFoundException e) { - throw new AuthServiceException("Failed to find user in auth service", e); - } - } - - @Override - public void delete(User user) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, - CredentialsInvalidException { - final UserDto keycloakUser = findByUsername(user.getUsername()); - keycloakGateway.deleteUser(keycloakUser.getId()); - } - - @Override - public UserDto findByUsername(String username) throws AuthServiceException, AuthServiceConnectionException, - UserNotFoundException, CredentialsInvalidException { - return keycloakGateway.findByUsername(username); - } - - @Override - public UserDto findById(UUID id) throws AuthServiceException, AuthServiceConnectionException, UserNotFoundException, - CredentialsInvalidException { - return keycloakGateway.findById(id); - } - - @Override - public TokenDto obtainToken(LoginRequestDto data) throws AuthServiceConnectionException, - CredentialsInvalidException, AccountNotSetupException { - return keycloakGateway.obtainUserToken(data.getUsername(), data.getPassword()); - } - - @Override - public TokenDto refreshToken(String refreshToken) throws AuthServiceConnectionException, - CredentialsInvalidException { - return keycloakGateway.refreshUserToken(refreshToken); + public void delete(User user) throws AuthServiceException, UserNotFoundException { + keycloakGateway.deleteUser(user.getKeycloakId()); } @Override - public void updatePassword(User user, UserPasswordDto data) throws AuthServiceException, - AuthServiceConnectionException, CredentialsInvalidException, UserNotFoundException { - final UserDto keycloakUser = findByUsername(user.getUsername()); - keycloakGateway.updateUserCredentials(keycloakUser.getId(), data); + public void updatePassword(User user, UserPasswordDto data) throws UserNotFoundException { + keycloakGateway.updateUserCredentials(user.getKeycloakId(), data); } } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java index 17ba0bd60beba0281c605bfbf32e7439fc0a0fbc..8b4c73fb2f063cfc779fa001b7982675d74cf447 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java @@ -216,6 +216,8 @@ public class DatabaseServiceImpl implements DatabaseService { } log.debug("fetched unknown table from data service: {}.{}", database.getInternalName(), table.getInternalName()); final Table tableEntity = metadataMapper.tableDtoToTable(table); + tableEntity.setIsPublic(database.getIsPublic()); + tableEntity.setIsSchemaPublic(database.getIsSchemaPublic()); tableEntity.setDatabase(database); tableEntity.getColumns() .forEach(column -> { diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java index 932a1a598c49f5f2a70f95b8c8264f5433a9cc13..96b8bd83963cca91e4731812ee0812a54a4630dc 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java @@ -287,7 +287,7 @@ public class IdentifierServiceImpl implements IdentifierService { /* save identifier */ switch (identifier.getType()) { case SUBSET -> { - log.debug("identifier type: subset with id {} and database with id {}", identifier.getQueryId(), identifier.getDatabase().getId()); + log.debug("identifier type: subset with id {}", identifier.getQueryId()); final QueryDto query = dataServiceGateway.findQuery(identifier.getDatabase().getId(), identifier.getQueryId()); identifier.setQuery(query.getQuery()); identifier.setQueryId(query.getId()); @@ -298,14 +298,14 @@ public class IdentifierServiceImpl implements IdentifierService { identifier.setResultHash(query.getResultHash()); } case VIEW -> { - log.debug("identifier type: view with id {} and database with id {}", identifier.getViewId(), identifier.getDatabase().getId()); + log.debug("identifier type: view with id {}", identifier.getViewId()); final View view = viewService.findById(identifier.getDatabase(), identifier.getViewId()); identifier.setViewId(view.getId()); identifier.setQuery(view.getQuery()); identifier.setQueryNormalized(view.getQuery()); identifier.setQueryHash(view.getQueryHash()); } - case DATABASE -> log.debug("identifier type: database with id {}", identifier.getDatabase()); + case DATABASE -> log.debug("identifier type: database with id {}", identifier.getDatabase().getId()); case TABLE -> log.debug("identifier type: table with id {}", identifier.getTableId()); } /* save identifier in metadata database */ diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java index 042684f8c9e398aaf60ea8a15b6adb1b45b56df5..cb550be1a5d408139b656f0d0619467f8f59fa26 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java @@ -3,15 +3,18 @@ package at.tuwien.service.impl; import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.user.UserPasswordDto; import at.tuwien.api.user.UserUpdateDto; -import at.tuwien.config.KeycloakConfig; import at.tuwien.entities.user.User; -import at.tuwien.exception.EmailExistsException; +import at.tuwien.exception.AuthServiceConnectionException; +import at.tuwien.exception.AuthServiceException; import at.tuwien.exception.UserExistsException; import at.tuwien.exception.UserNotFoundException; +import at.tuwien.gateway.KeycloakGateway; +import at.tuwien.mapper.MetadataMapper; import at.tuwien.repository.UserRepository; import at.tuwien.service.UserService; import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -24,13 +27,16 @@ import java.util.UUID; @Service public class UserServiceImpl implements UserService { - private final KeycloakConfig keycloakConfig; + private final MetadataMapper metadataMapper; private final UserRepository userRepository; + private final KeycloakGateway keycloakGateway; @Autowired - public UserServiceImpl(KeycloakConfig keycloakConfig, UserRepository userRepository) { - this.keycloakConfig = keycloakConfig; + public UserServiceImpl(MetadataMapper metadataMapper, UserRepository userRepository, + KeycloakGateway keycloakGateway) { + this.metadataMapper = metadataMapper; this.userRepository = userRepository; + this.keycloakGateway = keycloakGateway; } @Override @@ -64,32 +70,36 @@ public class UserServiceImpl implements UserService { } @Override - public User create(CreateUserDto data, UUID id) { + public User create(CreateUserDto data) throws UserNotFoundException, AuthServiceException { /* create at authentication service */ final User entity = User.builder() - .id(id) + .id(data.getLdapId()) + .keycloakId(data.getId()) .username(data.getUsername()) - .email(data.getEmail()) .theme("light") - .mariadbPassword(getMariaDbPassword(data.getPassword())) + .mariadbPassword(getMariaDbPassword(RandomStringUtils.randomAlphabetic(10))) /* user needs to set it later to access */ .language("en") + .firstname(data.getGivenName()) + .lastname(data.getFamilyName()) .isInternal(false) .build(); - /* create at metadata database */ + /* save in metadata database */ final User user = userRepository.save(entity); log.info("Created user with id: {}", user.getId()); return user; } @Override - public User modify(User user, UserUpdateDto data) { + public User modify(User user, UserUpdateDto data) throws UserNotFoundException, AuthServiceException { user.setFirstname(data.getFirstname()); user.setLastname(data.getLastname()); user.setAffiliation(data.getAffiliation()); user.setOrcid(data.getOrcid()); user.setTheme(data.getTheme()); user.setLanguage(data.getLanguage()); - /* create at metadata database */ + /* save in auth service */ + keycloakGateway.updateUser(user.getKeycloakId(), data); + /* save in metadata database */ user = userRepository.save(user); log.info("Modified user with id: {}", user.getId()); return user; @@ -110,13 +120,6 @@ public class UserServiceImpl implements UserService { } } - @Override - public void validateEmailNotExists(String email) throws EmailExistsException { - if (userRepository.existsByEmail(email)) { - throw new EmailExistsException("User with email " + email + " already exists"); - } - } - @Override public String getMariaDbPassword(String password) { final byte[] utf8 = password.getBytes(StandardCharsets.UTF_8); diff --git a/dbrepo-metadata-service/test/pom.xml b/dbrepo-metadata-service/test/pom.xml index 97768ad4a7d4fa6d467be813af71d9e6ee82ed5d..d51ed22d4d84941f982296b18b4fcb9005db7dce 100644 --- a/dbrepo-metadata-service/test/pom.xml +++ b/dbrepo-metadata-service/test/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.6.2</version> + <version>1.6.3</version> </parent> <artifactId>dbrepo-metadata-service-test</artifactId> <name>dbrepo-metadata-service-test</name> - <version>1.6.2</version> + <version>1.6.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index f4b89de5852dc4008d365835a5e29ccd1db7231c..0f07af9af968eae4e16f1f54a099312a20cabbb0 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -46,7 +46,6 @@ import at.tuwien.api.orcid.person.name.OrcidNameDto; import at.tuwien.api.orcid.person.name.OrcidValueDto; import at.tuwien.api.semantics.*; import at.tuwien.api.user.UserAttributesDto; -import at.tuwien.api.user.UserDto; import at.tuwien.api.user.*; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.entities.container.Container; @@ -99,12 +98,12 @@ import static java.time.temporal.ChronoUnit.MINUTES; * <ul> * <li>Table 1 (Private Data, Private Schema)</li> * <li>Table 2 (Private Data, Public Schema)</li> - * <li>Table 3</li> - * <li>Table 4</li> + * <li>Table 3 (Private Data, Private Schema)</li> + * <li>Table 4 (Public Data, Private Schema)</li> * <li>Query 1</li> - * <li>View 1</li> - * <li>View 2</li> - * <li>View 3</li> + * <li>View 1 (Private Data, Private Schema)</li> + * <li>View 2 (Public Data, Public Schema)</li> + * <li>View 3 (Public Data, Private Schema)</li> * <li>Identifier 1 (Title=en, Description=en, type=database)</li> * <li>Identifier 2 (Title=en, Description=en, type=subset, queryId=1)</li> * <li>Identifier 3 (Title=en, Description=en, type=view, viewId=1)</li> @@ -113,22 +112,22 @@ import static java.time.temporal.ChronoUnit.MINUTES; * <p> * Database 2 (Private Data, Public Schema, User 2) -> Container 1 * <ul> - * <li>Table 5</li> - * <li>Table 6</li> - * <li>Table 7</li> + * <li>Table 5 (Public Data, Public Schema)</li> + * <li>Table 6 (Public Data, Private Schema)</li> + * <li>Table 7 (Public Data, Public Schema)</li> * <li>Query 2</li> * <li>Query 6</li> - * <li>View 4</li> + * <li>View 4 (Public Data, Private Schema)</li> * <li>Identifier 5 (Title=de, Description=de)</li> * </ul> * <p> * Database 3 (Public Data, Private Schema, User 3) -> Container 1 * <ul> - * <li>Table 8</li> + * <li>Table 8 (Private Data, Private Schema)</li> * <li>Query 3</li> * <li>Query 4</li> * <li>Query 5</li> - * <li>View 5</li> + * <li>View 5 (Public Data, Public Schema)</li> * <li>Identifier 6 (Title=en, Description=en, Query=3)</li> * </ul> * <p> @@ -153,7 +152,7 @@ public abstract class BaseTest { public final static String RABBITMQ_IMAGE = "rabbitmq:3.13.7"; - public final static String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:24.0"; + public final static String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:26.0"; public final static String[] DEFAULT_SEMANTICS_HANDLING = new String[]{"default-semantics-handling", "create-semantic-unit", "execute-semantic-query", "table-semantic-analyse", "create-semantic-concept"}; @@ -166,7 +165,7 @@ public abstract class BaseTest { "update-semantic-unit", "create-ontology", "update-ontology"}; public final static String[] DEFAULT_CONTAINER_HANDLING = new String[]{"default-container-handling", - "create-container", "list-containers", "modify-container-state", "find-container"}; + "create-container", "list-containers", "modify-container-state"}; public final static String[] ESCALATED_CONTAINER_HANDLING = new String[]{"escalated-container-handling", "modify-foreign-container-state", "delete-container"}; @@ -186,9 +185,9 @@ public abstract class BaseTest { "modify-identifier-metadata", "update-foreign-identifier", "create-foreign-identifier"}; public final static String[] DEFAULT_QUERY_HANDLING = new String[]{"default-query-handling", "view-table-data", - "execute-query", "view-table-history", "list-database-views", "view-database-view-data", - "export-query-data", "create-database-view", "delete-database-view", "delete-table-data", - "export-table-data", "persist-query", "re-execute-query", "insert-table-data", "find-database-view"}; + "execute-query", "view-table-history", "list-database-views", "export-query-data", "create-database-view", + "delete-database-view", "delete-table-data", "export-table-data", "persist-query", "re-execute-query", + "insert-table-data", "find-database-view"}; public final static String[] ESCALATED_QUERY_HANDLING = new String[]{"escalated-query-handling"}; @@ -217,7 +216,7 @@ public abstract class BaseTest { public final static String[] DEFAULT_DATA_STEWARD_ROLES = ArrayUtils.merge(List.of(new String[]{"default-data-steward-roles"}, ESCALATED_IDENTIFIER_HANDLING, DEFAULT_SEMANTICS_HANDLING, ESCALATED_SEMANTICS_HANDLING, DEFAULT_VIEW_HANDLING)); - public final static String[] DEFAULT_LOCAL_ADMIN_ROLES = new String[]{"admin"}; + public final static String[] DEFAULT_LOCAL_ADMIN_ROLES = new String[]{"system"}; public final static List<GrantedAuthorityDto> AUTHORITY_LOCAL_ADMIN_ROLES = Arrays.stream(DEFAULT_LOCAL_ADMIN_ROLES) .map(GrantedAuthorityDto::new) @@ -440,13 +439,13 @@ public abstract class BaseTest { public final static String USER_BROKER_PASSWORD = "guest"; public final static UUID USER_LOCAL_ADMIN_ID = UUID.fromString("a54dcb2e-a644-4e82-87e7-05a96413983d"); + public final static UUID USER_LOCAL_ADMIN_KEYCLOAK_ID = UUID.fromString("703c2ca0-8fc3-4c03-9bc5-4dae6b211e78"); public final static String USER_LOCAL_ADMIN_USERNAME = "admin"; @SuppressWarnings("java:S2068") public final static String USER_LOCAL_ADMIN_PASSWORD = "admin"; public final static String USER_LOCAL_ADMIN_THEME = "dark"; public final static Boolean USER_LOCAL_ADMIN_IS_INTERNAL = true; public final static Boolean USER_LOCAL_ADMIN_ENABLED = true; - public final static String USER_LOCAL_ADMIN_EMAIL = "admin@local"; @SuppressWarnings("java:S2068") public final static String USER_LOCAL_ADMIN_MARIADB_PASSWORD = "*440BA4FD1A87A0999647DB67C0EE258198B247BA"; @@ -456,7 +455,7 @@ public abstract class BaseTest { .build(); public final static UserDetails USER_LOCAL_ADMIN_DETAILS = UserDetailsDto.builder() - .id(String.valueOf(USER_LOCAL_ADMIN_ID)) + .id(USER_LOCAL_ADMIN_ID.toString()) .username(USER_LOCAL_ADMIN_USERNAME) .password(USER_LOCAL_ADMIN_PASSWORD) .authorities(AUTHORITY_DEFAULT_LOCAL_ADMIN_AUTHORITIES) @@ -464,8 +463,8 @@ public abstract class BaseTest { public final static User USER_LOCAL = User.builder() .id(USER_LOCAL_ADMIN_ID) + .keycloakId(USER_LOCAL_ADMIN_KEYCLOAK_ID) .username(USER_LOCAL_ADMIN_USERNAME) - .email(USER_LOCAL_ADMIN_EMAIL) .mariadbPassword(USER_LOCAL_ADMIN_MARIADB_PASSWORD) .theme(USER_LOCAL_ADMIN_THEME) .isInternal(USER_LOCAL_ADMIN_IS_INTERNAL) @@ -475,8 +474,7 @@ public abstract class BaseTest { USER_LOCAL_ADMIN_PASSWORD, USER_LOCAL_ADMIN_DETAILS.getAuthorities()); public final static UUID USER_1_ID = UUID.fromString("cd5bab0d-7799-4069-85fb-c5d738572a0b"); - public final static UUID USER_1_LDAP_ID = UUID.fromString("cd5bab0d-7799-4069-85fb-c5d738572a0b"); - public final static String USER_1_EMAIL = "john.doe@example.com"; + public final static UUID USER_1_KEYCLOAK_ID = UUID.fromString("cd5bab0d-7799-4069-85fb-c5d738572a0b"); public final static String USER_1_USERNAME = "junit1"; @SuppressWarnings("java:S2068") public final static String USER_1_PASSWORD = "junit1"; @@ -530,7 +528,6 @@ public abstract class BaseTest { public final static UserCreateDto USER_1_KEYCLOAK_SIGNUP_REQUEST = UserCreateDto.builder() .username(USER_1_USERNAME) - .email(USER_1_EMAIL) .enabled(USER_1_ENABLED) .credentials(new LinkedList<>(List.of(USER_1_KEYCLOAK_CREDENTIAL_1))) .attributes(UserCreateAttributesDto.builder() @@ -540,7 +537,6 @@ public abstract class BaseTest { public final static UserCreateDto USER_LOCAL_KEYCLOAK_SIGNUP_REQUEST = UserCreateDto.builder() .username(USER_LOCAL_ADMIN_USERNAME) - .email(USER_LOCAL_ADMIN_EMAIL) .enabled(USER_LOCAL_ADMIN_ENABLED) .credentials(new LinkedList<>(List.of(USER_LOCAL_KEYCLOAK_CREDENTIAL_1))) .groups(new LinkedList<>(List.of("system"))) @@ -551,8 +547,8 @@ public abstract class BaseTest { public final static User USER_1 = User.builder() .id(USER_1_ID) + .keycloakId(USER_1_KEYCLOAK_ID) .username(USER_1_USERNAME) - .email(USER_1_EMAIL) .firstname(USER_1_FIRSTNAME) .lastname(USER_1_LASTNAME) .affiliation(USER_1_AFFILIATION) @@ -586,19 +582,6 @@ public abstract class BaseTest { .password(USER_1_PASSWORD) .build(); - public final static at.tuwien.api.keycloak.UserDto USER_1_KEYCLOAK_DTO = at.tuwien.api.keycloak.UserDto.builder() - .id(USER_1_ID) - .username(USER_1_USERNAME) - .email(USER_1_EMAIL) - .emailVerified(USER_1_VERIFIED) - .notBefore(USER_1_NOT_BEFORE) - .totp(USER_1_TOTP) - .attributes(at.tuwien.api.keycloak.UserAttributesDto.builder() - .ldapEntryDn(new String[]{"cn=" + USER_1_USERNAME + ",dn=dbrepo,dn=at"}) - .ldapId(new UUID[]{USER_1_LDAP_ID}) - .build()) - .build(); - public final static UserBriefDto USER_1_BRIEF_DTO = UserBriefDto.builder() .id(USER_1_ID) .username(USER_1_USERNAME) @@ -612,7 +595,6 @@ public abstract class BaseTest { public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder() .id(USER_1_ID.toString()) .username(USER_1_USERNAME) - .email(USER_1_EMAIL) .password(USER_1_PASSWORD) .authorities(AUTHORITY_DEFAULT_RESEARCHER_AUTHORITIES) .build(); @@ -621,9 +603,9 @@ public abstract class BaseTest { USER_1_PASSWORD, USER_1_DETAILS.getAuthorities()); public final static CreateUserDto USER_1_SIGNUP_REQUEST_DTO = CreateUserDto.builder() + .id(USER_1_KEYCLOAK_ID) + .ldapId(USER_1_ID) .username(USER_1_USERNAME) - .password(USER_1_PASSWORD) - .email(USER_1_EMAIL) .build(); public final static LoginRequestDto USER_1_LOGIN_REQUEST_DTO = LoginRequestDto.builder() @@ -632,7 +614,7 @@ public abstract class BaseTest { .build(); public final static UUID USER_2_ID = UUID.fromString("eeb9a51b-4cd8-4039-90bf-e24f17372f7c"); - public final static UUID USER_2_LDAP_ID = UUID.fromString("eeb9a51b-4cd8-4039-90bf-e24f17372f7c"); + public final static UUID USER_2_KEYCLOAK_ID = UUID.fromString("eeb9a51b-4cd8-4039-90bf-e24f17372f7c"); public final static String USER_2_EMAIL = "jane.doe@example.com"; public final static String USER_2_USERNAME = "junit2"; public final static String USER_2_FIRSTNAME = "Jane"; @@ -666,8 +648,8 @@ public abstract class BaseTest { public final static User USER_2 = User.builder() .id(USER_2_ID) + .keycloakId(USER_2_KEYCLOAK_ID) .username(USER_2_USERNAME) - .email(USER_2_EMAIL) .firstname(USER_2_FIRSTNAME) .lastname(USER_2_LASTNAME) .affiliation(USER_2_AFFILIATION) @@ -699,28 +681,21 @@ public abstract class BaseTest { .build(); public final static CreateUserDto USER_2_SIGNUP_REQUEST_DTO = CreateUserDto.builder() + .id(USER_2_KEYCLOAK_ID) + .ldapId(USER_2_ID) .username(USER_2_USERNAME) - .password(USER_2_PASSWORD) .email(USER_2_EMAIL) + .givenName(USER_2_FIRSTNAME) + .familyName(USER_2_LASTNAME) .build(); public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder() .id(USER_2_ID.toString()) .username(USER_2_USERNAME) - .email(USER_2_EMAIL) .password(USER_2_PASSWORD) .authorities(AUTHORITY_DEFAULT_RESEARCHER_AUTHORITIES) .build(); - public final static at.tuwien.api.keycloak.UserDto USER_2_KEYCLOAK_DTO = at.tuwien.api.keycloak.UserDto.builder() - .id(USER_2_ID) - .username(USER_2_USERNAME) - .email(USER_2_EMAIL) - .emailVerified(USER_2_VERIFIED) - .notBefore(USER_2_NOT_BEFORE) - .totp(USER_2_TOTP) - .build(); - public final static at.tuwien.api.amqp.UserDetailsDto USER_2_DETAILS_DTO = at.tuwien.api.amqp.UserDetailsDto.builder() .name(USER_2_USERNAME) .tags(new String[]{}) @@ -730,7 +705,7 @@ public abstract class BaseTest { USER_2_PASSWORD, USER_2_DETAILS.getAuthorities()); public final static UUID USER_3_ID = UUID.fromString("7b080e33-d8db-4276-9d53-47208e657006"); - public final static UUID USER_3_LDAP_ID = UUID.fromString("7b080e33-d8db-4276-9d53-47208e657006"); + public final static UUID USER_3_KEYCLOAK_ID = UUID.fromString("b0108bc3-95aa-4a3f-8868-dc301286aeca"); public final static String USER_3_USERNAME = "junit3"; public final static String USER_3_FIRSTNAME = "System"; public final static String USER_3_LASTNAME = "System"; @@ -762,8 +737,8 @@ public abstract class BaseTest { public final static User USER_3 = User.builder() .id(USER_3_ID) + .keycloakId(USER_3_KEYCLOAK_ID) .username(USER_3_USERNAME) - .email(USER_3_EMAIL) .firstname(USER_3_FIRSTNAME) .lastname(USER_3_LASTNAME) .affiliation(USER_3_AFFILIATION) @@ -795,18 +770,14 @@ public abstract class BaseTest { public final static UserDetails USER_3_DETAILS = UserDetailsDto.builder() .id(USER_3_ID.toString()) .username(USER_3_USERNAME) - .email(USER_3_EMAIL) .password(USER_3_PASSWORD) .authorities(AUTHORITY_DEFAULT_RESEARCHER_AUTHORITIES) .build(); - public final static at.tuwien.api.keycloak.UserDto USER_3_KEYCLOAK_DTO = at.tuwien.api.keycloak.UserDto.builder() - .id(USER_3_ID) + public final static CreateUserDto USER_3_SIGNUP_REQUEST_DTO = CreateUserDto.builder() + .id(USER_3_KEYCLOAK_ID) + .ldapId(USER_3_ID) .username(USER_3_USERNAME) - .email(USER_3_EMAIL) - .emailVerified(USER_3_VERIFIED) - .notBefore(USER_3_NOT_BEFORE) - .totp(USER_3_TOTP) .build(); public final static Principal USER_3_PRINCIPAL = new UsernamePasswordAuthenticationToken(USER_3_DETAILS, @@ -818,7 +789,7 @@ public abstract class BaseTest { .build(); public final static UUID USER_4_ID = UUID.fromString("791d58c5-bfab-4520-b4fc-b44d4ab9feb0"); - public final static UUID USER_4_LDAP_ID = UUID.fromString("791d58c5-bfab-4520-b4fc-b44d4ab9feb0"); + public final static UUID USER_4_KEYCLOAK_ID = UUID.fromString("25040ad3-6d57-4052-b357-6b4c8a6e7f4d"); public final static String USER_4_USERNAME = "junit4"; public final static String USER_4_FIRSTNAME = "JUnit"; public final static String USER_4_LASTNAME = "4"; @@ -830,7 +801,6 @@ public abstract class BaseTest { @SuppressWarnings("java:S2068") public final static String USER_4_DATABASE_PASSWORD = "*C20EF5C6875857DEFA9BE6E9B62DD76AAAE51882" /* junit4 */; public final static String USER_4_QUALIFIED_NAME = USER_4_FIRSTNAME + " " + USER_4_LASTNAME + " — @" + USER_4_USERNAME; - public final static String USER_4_EMAIL = "junit4@ossdip.at"; public final static Boolean USER_4_VERIFIED = true; public final static Boolean USER_4_ENABLED = true; public final static Boolean USER_4_IS_INTERNAL = false; @@ -847,8 +817,8 @@ public abstract class BaseTest { public final static User USER_4 = User.builder() .id(USER_4_ID) + .keycloakId(USER_4_KEYCLOAK_ID) .username(USER_4_USERNAME) - .email(USER_4_EMAIL) .firstname(USER_4_FIRSTNAME) .lastname(USER_4_LASTNAME) .affiliation(USER_4_AFFILIATION) @@ -880,7 +850,6 @@ public abstract class BaseTest { public final static UserDetails USER_4_DETAILS = UserDetailsDto.builder() .id(USER_4_ID.toString()) .username(USER_4_USERNAME) - .email(USER_4_EMAIL) .password(USER_4_PASSWORD) .authorities(new LinkedList<>()) .build(); @@ -889,7 +858,7 @@ public abstract class BaseTest { USER_4_PASSWORD, USER_4_DETAILS.getAuthorities()); public final static UUID USER_5_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); - public final static UUID USER_5_LDAP_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); + public final static UUID USER_5_KEYCLOAK_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); public final static String USER_5_USERNAME = "nobody"; public final static String USER_5_FIRSTNAME = "No"; public final static String USER_5_LASTNAME = "Body"; @@ -901,7 +870,6 @@ public abstract class BaseTest { @SuppressWarnings("java:S2068") public final static String USER_5_DATABASE_PASSWORD = "*C20EF5C6875857DEFA9BE6E9B62DD76AAAE51882" /* junit5 */; public final static String USER_5_QUALIFIED_NAME = USER_5_FIRSTNAME + " " + USER_5_LASTNAME + " — @" + USER_5_USERNAME; - public final static String USER_5_EMAIL = "system@ossdip.at"; public final static Boolean USER_5_VERIFIED = true; public final static Boolean USER_5_ENABLED = true; public final static Boolean USER_5_IS_INTERNAL = false; @@ -936,7 +904,6 @@ public abstract class BaseTest { public final static UserDetails USER_5_DETAILS = UserDetailsDto.builder() .id(USER_5_ID.toString()) .username(USER_5_USERNAME) - .email(USER_5_EMAIL) .password(USER_5_PASSWORD) .authorities(AUTHORITY_DEFAULT_DEVELOPER_AUTHORITIES) .build(); @@ -946,8 +913,8 @@ public abstract class BaseTest { public final static User USER_5 = User.builder() .id(USER_5_ID) + .keycloakId(USER_5_KEYCLOAK_ID) .username(USER_5_USERNAME) - .email(USER_5_EMAIL) .firstname(USER_5_FIRSTNAME) .lastname(USER_5_LASTNAME) .affiliation(USER_5_AFFILIATION) @@ -967,7 +934,6 @@ public abstract class BaseTest { public final static String USER_6_PASSWORD = "junit5"; @SuppressWarnings("java:S2068") public final static String USER_6_DATABASE_PASSWORD = "*C20EF5C6875857DEFA9BE6E9B62DD76AAAE51882" /* junit5 */; - public final static String USER_6_EMAIL = "system@ossdip.at"; public final static Boolean USER_6_VERIFIED = true; public final static Boolean USER_6_ENABLED = true; public final static Boolean USER_6_IS_INTERNAL = false; @@ -985,7 +951,6 @@ public abstract class BaseTest { public final static UserDetails USER_6_DETAILS = UserDetailsDto.builder() .id(USER_6_ID.toString()) .username(USER_6_USERNAME) - .email(USER_6_EMAIL) .password(USER_6_PASSWORD) .authorities(AUTHORITY_DEFAULT_RESEARCHER_AUTHORITIES) .build(); @@ -2067,7 +2032,7 @@ public abstract class BaseTest { public final static String TABLE_6_INTERNALNAME = "names"; public final static Boolean TABLE_6_VERSIONED = true; public final static Boolean TABLE_6_IS_PUBLIC = true; - public final static Boolean TABLE_6_SCHEMA_PUBLIC = true; + public final static Boolean TABLE_6_SCHEMA_PUBLIC = false; public final static Boolean TABLE_6_PROCESSED_CONSTRAINTS = true; public final static String TABLE_6_DESCRIPTION = "Some names dataset"; public final static String TABLE_6_QUEUE_NAME = TABLE_6_INTERNALNAME; @@ -2186,9 +2151,8 @@ public abstract class BaseTest { public final static String TABLE_4_NAME = "Sensor 2"; public final static String TABLE_4_INTERNALNAME = "sensor_2"; public final static Boolean TABLE_4_VERSIONED = true; - public final static Boolean TABLE_4_IS_PUBLIC = false; + public final static Boolean TABLE_4_IS_PUBLIC = true; public final static Boolean TABLE_4_SCHEMA_PUBLIC = false; - public final static Boolean TABLE_4_PROCESSED_CONSTRAINTS = true; public final static String TABLE_4_DESCRIPTION = "Hello sensor"; public final static String TABLE_4_QUEUE_NAME = TABLE_4_INTERNALNAME; public final static String TABLE_4_ROUTING_KEY = "dbrepo\\." + DATABASE_1_ID + "\\." + TABLE_4_ID; @@ -2366,7 +2330,6 @@ public abstract class BaseTest { public final static Boolean TABLE_8_VERSIONED = true; public final static Boolean TABLE_8_IS_PUBLIC = false; public final static Boolean TABLE_8_SCHEMA_PUBLIC = false; - public final static Boolean TABLE_8_PROCESSED_CONSTRAINTS = true; public final static String TABLE_8_DESCRIPTION = "Hello mfcc"; public final static String TABLE_8_QUEUE_NAME = TABLE_8_INTERNAL_NAME; public final static String TABLE_8_ROUTING_KEY = "dbrepo\\." + DATABASE_3_ID + "\\." + TABLE_8_ID; @@ -5071,8 +5034,8 @@ public abstract class BaseTest { public final static String VIEW_1_INTERNAL_NAME = "junit"; public final static Long VIEW_1_CONTAINER_ID = CONTAINER_1_ID; public final static Long VIEW_1_DATABASE_ID = DATABASE_1_ID; - public final static Boolean VIEW_1_PUBLIC = true; - public final static Boolean VIEW_1_SCHEMA_PUBLIC = true; + public final static Boolean VIEW_1_PUBLIC = false; + public final static Boolean VIEW_1_SCHEMA_PUBLIC = false; public final static String VIEW_1_QUERY = "select `location`, `lat`, `lng` from `weather_location`"; public final static String VIEW_1_QUERY_HASH = "dc81a6877c7c51a6a6f406e1fc2a255e44a0d49a20548596e0d583c3eb849c23"; @@ -5393,7 +5356,7 @@ public abstract class BaseTest { public final static String VIEW_3_INTERNAL_NAME = "junit3"; public final static Long VIEW_3_CONTAINER_ID = CONTAINER_1_ID; public final static Long VIEW_3_DATABASE_ID = DATABASE_1_ID; - public final static Boolean VIEW_3_PUBLIC = false; + public final static Boolean VIEW_3_PUBLIC = true; public final static Boolean VIEW_3_SCHEMA_PUBLIC = false; public final static String VIEW_3_QUERY = "select w.`mintemp`, w.`rainfall`, w.`location`, m.`date` from `weather_aus` w join `junit2` m on m.`location` = w.`location` and m.`date` = w.`date`"; public final static String VIEW_3_QUERY_HASH = "bbbaa56a5206b3dc3e6cf9301b0db9344eb6f19b100c7b88550ffb597a0bd255"; @@ -5555,7 +5518,7 @@ public abstract class BaseTest { public final static Long VIEW_4_TABLE_ID = TABLE_5_ID; public final static Table VIEW_4_TABLE = TABLE_5; public final static Boolean VIEW_4_PUBLIC = true; - public final static Boolean VIEW_4_SCHEMA_PUBLIC = true; + public final static Boolean VIEW_4_SCHEMA_PUBLIC = false; public final static String VIEW_4_QUERY = "SELECT `animal_name`, `hair`, `feathers`, `eggs`, `milk`, `airborne`, `aquatic`, `predator`, `backbone`, `breathes`, `venomous`, `fins`, `legs`, `tail`, `domestic`, `catsize`, `class_type` FROM `zoo` WHERE `class_type` = 1"; public final static String VIEW_4_QUERY_HASH = "3561cd0bb0b0e94d6f15ae602134252a5760d09d660a71a4fb015b6991c8ba0b"; diff --git a/dbrepo-search-service/Pipfile b/dbrepo-search-service/Pipfile index f7161287a02cd23f2d18444f04d896b3351c3068..3ae299480180796fb640802d582aaf2745f876ca 100644 --- a/dbrepo-search-service/Pipfile +++ b/dbrepo-search-service/Pipfile @@ -18,7 +18,7 @@ jwt = "~=1.3" testcontainers-opensearch = "*" pytest = "*" rdflib = "*" -dbrepo = {path = "./lib/dbrepo-1.6.2.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.6.3.tar.gz"} gunicorn = "*" [dev-packages] diff --git a/dbrepo-search-service/Pipfile.lock b/dbrepo-search-service/Pipfile.lock index d75a0069a1a39cd64ff953624e9bbb8adb61c78a..6c62e03b928bcd62b443395c39dcdef902fe488d 100644 --- a/dbrepo-search-service/Pipfile.lock +++ b/dbrepo-search-service/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "729017f537f9f8fb6dcc15703392c7fd79aec494feba4c107e7a1888e8ea955d" + "sha256": "2ff9fc673f1fb1e5dc272aa711f4e730088fa0188b44449db042abf99b6c4db7" }, "pipfile-spec": 6, "requires": { @@ -124,11 +124,11 @@ }, "attrs": { "hashes": [ - "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", - "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" + "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", + "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a" ], "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "version": "==25.1.0" }, "blinker": { "hashes": [ @@ -360,9 +360,9 @@ }, "dbrepo": { "hashes": [ - "sha256:a41ca60353510cbecf8fb647cf2483acb100258743794a16bc8ad6f8e9ea4481" + "sha256:ac99f4bd19961f08665abd513e4d9452fcea5554f122457840e95f90698bab4d" ], - "path": "./lib/dbrepo-1.6.2.tar.gz" + "path": "./lib/dbrepo-1.6.3.tar.gz" }, "docker": { "hashes": [ @@ -739,11 +739,11 @@ }, "mistune": { "hashes": [ - "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", - "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667" + "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", + "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c" ], "markers": "python_version >= '3.8'", - "version": "==3.1.0" + "version": "==3.1.1" }, "multidict": { "hashes": [ @@ -1099,11 +1099,11 @@ }, "pydantic": { "hashes": [ - "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff", - "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53" + "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", + "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236" ], "markers": "python_version >= '3.8'", - "version": "==2.10.5" + "version": "==2.10.6" }, "pydantic-core": { "hashes": [ @@ -1330,11 +1330,11 @@ }, "referencing": { "hashes": [ - "sha256:363d9c65f080d0d70bc41c721dce3c7f3e77fc09f269cd5c8813da18069a6794", - "sha256:ca2e6492769e3602957e9b831b94211599d2aade9477f5d44110d2530cf9aade" + "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", + "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0" ], "markers": "python_version >= '3.9'", - "version": "==0.36.1" + "version": "==0.36.2" }, "requests": { "hashes": [ diff --git a/dbrepo-search-service/init/Pipfile b/dbrepo-search-service/init/Pipfile index 8ded635ab78a63a71e36787a2a91f48f61932b4c..b74ed7bc40da1da1c51c401b53a1da2676fb739e 100644 --- a/dbrepo-search-service/init/Pipfile +++ b/dbrepo-search-service/init/Pipfile @@ -9,7 +9,7 @@ opensearch-py = "~=2.2" python-dotenv = "~=1.0" testcontainers-opensearch = "*" pytest = "*" -dbrepo = {path = "./lib/dbrepo-1.6.2.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.6.3.tar.gz"} rdflib = "*" [dev-packages] diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock index e4a2e7d71877f6d44a929b2e344390bbf1117db0..039873e7c5ffd4b2e5c6352027f7f501254ce5c6 100644 --- a/dbrepo-search-service/init/Pipfile.lock +++ b/dbrepo-search-service/init/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "39898ff53a7a701c750b2fc2bfe2d7f72704100e41a183feceb1d8bd09c71a00" + "sha256": "dac534d1eb6a0942c0e296c8a58491847c65d3ca23315039a3725591c86f694f" }, "pipfile-spec": 6, "requires": { @@ -124,11 +124,11 @@ }, "attrs": { "hashes": [ - "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", - "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" + "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", + "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a" ], "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "version": "==25.1.0" }, "blinker": { "hashes": [ @@ -254,9 +254,9 @@ }, "dbrepo": { "hashes": [ - "sha256:a41ca60353510cbecf8fb647cf2483acb100258743794a16bc8ad6f8e9ea4481" + "sha256:ac99f4bd19961f08665abd513e4d9452fcea5554f122457840e95f90698bab4d" ], - "path": "./lib/dbrepo-1.6.2.tar.gz" + "path": "./lib/dbrepo-1.6.3.tar.gz" }, "docker": { "hashes": [ @@ -808,11 +808,11 @@ }, "pydantic": { "hashes": [ - "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff", - "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53" + "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", + "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236" ], "markers": "python_version >= '3.8'", - "version": "==2.10.5" + "version": "==2.10.6" }, "pydantic-core": { "hashes": [ diff --git a/dbrepo-search-service/init/lib/dbrepo-1.6.3-py3-none-any.whl b/dbrepo-search-service/init/lib/dbrepo-1.6.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b7f45eecc067d496a9d39d189e619ac7524c66b1 Binary files /dev/null and b/dbrepo-search-service/init/lib/dbrepo-1.6.3-py3-none-any.whl differ diff --git a/dbrepo-search-service/init/lib/dbrepo-1.6.3.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.6.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2aa4f75ed8dd08245bd29d34c151dbe9b7eb2253 Binary files /dev/null and b/dbrepo-search-service/init/lib/dbrepo-1.6.3.tar.gz differ diff --git a/dbrepo-search-service/lib/dbrepo-1.6.3-py3-none-any.whl b/dbrepo-search-service/lib/dbrepo-1.6.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b7f45eecc067d496a9d39d189e619ac7524c66b1 Binary files /dev/null and b/dbrepo-search-service/lib/dbrepo-1.6.3-py3-none-any.whl differ diff --git a/dbrepo-search-service/lib/dbrepo-1.6.3.tar.gz b/dbrepo-search-service/lib/dbrepo-1.6.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2aa4f75ed8dd08245bd29d34c151dbe9b7eb2253 Binary files /dev/null and b/dbrepo-search-service/lib/dbrepo-1.6.3.tar.gz differ diff --git a/dbrepo-search-service/test/test_opensearch_client.py b/dbrepo-search-service/test/test_opensearch_client.py index 9da77adfde53e155dddc36f364bea9d974964125..edbdff683dff0f66319d87fb064fdeab6931bca0 100644 --- a/dbrepo-search-service/test/test_opensearch_client.py +++ b/dbrepo-search-service/test/test_opensearch_client.py @@ -2,7 +2,7 @@ import unittest import opensearchpy from dbrepo.api.dto import Database, Table, Column, ColumnType, Constraints, PrimaryKey, \ - TableMinimal, ColumnMinimal, ConceptBrief, UnitBrief, UserBrief, ContainerBrief, ImageBrief + ConceptBrief, UnitBrief, UserBrief, ContainerBrief, ImageBrief, TableBrief, ColumnBrief from opensearchpy import NotFoundError from app import app @@ -57,10 +57,6 @@ class OpenSearchClientTest(unittest.TestCase): def test_update_database_succeeds(self): with app.app_context(): - # mock - OpenSearchClient().update_database(database_id=req.id, data=req) - - # test req.tables = [Table(id=1, name="Test Table", internal_name="test_table", @@ -71,10 +67,20 @@ class OpenSearchClientTest(unittest.TestCase): database_id=req.id, constraints=Constraints(uniques=[], foreign_keys=[], checks=[], primary_key=[PrimaryKey(id=1, - table=TableMinimal(id=1, - database_id=req.id), - column=ColumnMinimal(id=1, table_id=1, - database_id=req.id))]), + table=TableBrief(id=1, + database_id=req.id, + name="Test Table", + internal_name="test_table", + is_public=True, + is_schema_public=True, + is_versioned=True, + owned_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502"), + column=ColumnBrief(id=1, + name="ID", + database_id=req.id, + table_id=1, + internal_name="id", + type=ColumnType.BIGINT))]), is_versioned=True, owner=UserBrief(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", username="foo"), columns=[Column(id=1, @@ -85,6 +91,10 @@ class OpenSearchClientTest(unittest.TestCase): internal_name="id", type=ColumnType.BIGINT, is_null_allowed=False)])] + # mock + OpenSearchClient().update_database(database_id=req.id, data=req) + + # test database = OpenSearchClient().update_database(database_id=req.id, data=req) self.assertEqual(1, database.id) self.assertEqual("Test", database.name) diff --git a/dbrepo-ui/bun.lockb b/dbrepo-ui/bun.lockb index 45a8c51c621dab127458d68a4493b23d2df9f88d..7ee1d3897245ee94212d8bfb6a6b97556b64f1d2 100755 Binary files a/dbrepo-ui/bun.lockb and b/dbrepo-ui/bun.lockb differ diff --git a/dbrepo-ui/components/JumboBox.vue b/dbrepo-ui/components/JumboBox.vue index d2b804f819f22782ba4895c601c8b2ea118ce4e3..5a26ec6139bff1a1b816c2acce1dc3fb08cd7207 100644 --- a/dbrepo-ui/components/JumboBox.vue +++ b/dbrepo-ui/components/JumboBox.vue @@ -21,6 +21,7 @@ </v-row> </div> </template> + <script> export default { props: { diff --git a/dbrepo-ui/components/Loading.vue b/dbrepo-ui/components/Loading.vue index 743701ab6724dba40d6baa0cb0cfafcb26061556..84094bfef8773aa306d977e6f0f7cbb4cca1dde9 100644 --- a/dbrepo-ui/components/Loading.vue +++ b/dbrepo-ui/components/Loading.vue @@ -7,6 +7,7 @@ indeterminate /> </v-list-item-title> </template> + <script> export default { props: { diff --git a/dbrepo-ui/components/OntologiesList.vue b/dbrepo-ui/components/OntologiesList.vue deleted file mode 100644 index c7120cac4a1e58e4a1f22c651f77aebcaf002584..0000000000000000000000000000000000000000 --- a/dbrepo-ui/components/OntologiesList.vue +++ /dev/null @@ -1,73 +0,0 @@ -<template> - <div> - <v-card - v-for="(ontology, idx) in ontologies" - :key="idx" - :to="`/semantic/ontology/${ontology.id}`" - variant="flat" - rounded="0"> - <v-divider - class="mx-4" /> - <v-card-title> - {{ ontology.prefix }} - </v-card-title> - <v-card-subtitle> - {{ ontology.uri }} - </v-card-subtitle> - <v-card-text> - <div - class="db-tags"> - <v-chip - v-if="ontology.sparql" - size="small" - color="success" - text="SPARQL" - variant="outlined" /> - <v-chip - v-if="ontology.rdf" - size="small" - text="RDF" - variant="outlined" /> - </div> - </v-card-text> - </v-card> - </div> -</template> - -<script> -import { useUserStore } from '@/stores/user.js' -import { useCacheStore } from '@/stores/cache.js' - -export default { - data () { - return { - userStore: useUserStore(), - cacheStore: useCacheStore() - } - }, - computed: { - token () { - return this.userStore.getToken - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, - ontologies () { - return this.cacheStore.getOntologies - } - }, - mounted () { - }, - methods: { - } -} -</script> - -<style> -.db-tags .v-chip:not(:first-child) { - margin-left: 4px; -} -</style> diff --git a/dbrepo-ui/components/ResourceStatus.vue b/dbrepo-ui/components/ResourceStatus.vue index 5167d899ea6e73c150db478f8fc3b063a3ba1e12..6db6d25385359ee82086b510537107fb3d0d0f31 100644 --- a/dbrepo-ui/components/ResourceStatus.vue +++ b/dbrepo-ui/components/ResourceStatus.vue @@ -5,7 +5,7 @@ v-if="!inline" :size="size" :color="color" - variant="outlined"> + :variant="chipVariant"> {{ status }} </v-chip> <span @@ -14,6 +14,7 @@ </span> </span> </template> + <script> export default { props: { @@ -38,6 +39,9 @@ export default { if (!this.resource) { return null } + if (this.hasIdentifier) { + return 'pid' + } if (!this.resource.is_public && !this.resource.is_schema_public) { return 'draft' } else if(!this.resource.is_public && this.resource.is_schema_public) { @@ -53,7 +57,19 @@ export default { } return this.$t(`pages.database.status.${this.mode}`) }, + hasIdentifier () { + return this.resource.identifiers?.length > 0 + }, + chipVariant () { + if (this.hasIdentifier) { + return 'tonal' + } + return 'outlined' + }, color () { + if (this.hasIdentifier) { + return 'info' + } switch (this.mode) { case 'schema': case 'data': diff --git a/dbrepo-ui/components/TimeDrift.vue b/dbrepo-ui/components/TimeDrift.vue deleted file mode 100644 index 2f2555f9f84f107dff1ffd12ad75533d388c1f62..0000000000000000000000000000000000000000 --- a/dbrepo-ui/components/TimeDrift.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> - <v-alert - v-cloak - v-if="timestamp && offSeconds > 3" - class="banner" - border="start" - type="warning"> - {{ $t('error.data.drift') + ' ' + offSeconds + 's' }} - </v-alert> -</template> - -<script> -import { formatTimestamp, timestampsToHumanDifference } from '@/utils' - -export default { - data () { - return { - timestamp: null - } - }, - computed: { - drift () { - return this.timestampsToHumanDifference(Date.now(), this.timestamp) - }, - offSeconds () { - if (!this.timestamp) { - return null - } - return (Date.now().valueOf() - Date.parse(this.timestamp)) / 1000 - } - }, - mounted() { - const databaseService = useDatabaseService() - databaseService.getServerTime() - .then((timestamp) => { - this.timestamp = timestamp - }) - }, - methods: { - formatTimestamp, - timestampsToHumanDifference - } -} -</script> diff --git a/dbrepo-ui/components/database/DatabaseToolbar.vue b/dbrepo-ui/components/database/DatabaseToolbar.vue index 4177ae592cbae97090d0b109c576137b0ee74cbc..3e661cd754b98ca94519bda6a7f4ad49d4ff0378 100644 --- a/dbrepo-ui/components/database/DatabaseToolbar.vue +++ b/dbrepo-ui/components/database/DatabaseToolbar.vue @@ -83,7 +83,6 @@ <script> import { useCacheStore } from '@/stores/cache.js' -import { useUserStore } from '@/stores/user.js' import ResourceStatus from '@/components/ResourceStatus.vue' export default { @@ -94,8 +93,7 @@ export default { return { tab: null, error: false, - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { @@ -103,13 +101,13 @@ export default { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess - }, - user () { - return this.userStore.getUser + return this.cacheStore.getAccess }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser }, isContrastTheme () { return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') @@ -141,12 +139,6 @@ export default { } return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' }, - canImportCsv () { - if (!this.user || !this.hasWriteAccess) { - return false - } - return this.roles.includes('insert-table-data') - }, canCreateSubset () { if (!this.database) { return false @@ -157,22 +149,22 @@ export default { return this.hasReadAccess }, canCreateView () { - if (!this.user || !this.isOwner) { + if (!this.cacheUser || !this.isOwner || !this.roles) { return false } return this.roles.includes('create-database-view') }, canCreateTable () { - if (!this.user || !this.hasWriteAccess) { + if (!this.cacheUser || !this.hasWriteAccess || !this.roles) { return false } return this.roles.includes('create-table') }, isOwner () { - if (!this.database || !this.user) { + if (!this.database || !this.cacheUser) { return false } - return this.database.owner.username === this.user.username + return this.database.owner.id === this.cacheUser.uid }, buttonVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/components/identifier/Banner.vue b/dbrepo-ui/components/identifier/Banner.vue index 1450347c412727830e07f4fc1e11b857ab22c346..63c2a7153a03d4dd6742920e2e32d0813bbd9036 100644 --- a/dbrepo-ui/components/identifier/Banner.vue +++ b/dbrepo-ui/components/identifier/Banner.vue @@ -3,6 +3,7 @@ {{ prefix }}: <a :href="href">{{ displayName }}</a> </div> </template> + <script> export default { props: { @@ -23,7 +24,7 @@ export default { return identifierService.identifierToDisplayName(this.identifier) }, href () { - if (!this.identifier || (this.identifier.status && this.identifier.status !== 'published')) { + if (!this.identifier) { return null } const identifierService = useIdentifierService() diff --git a/dbrepo-ui/components/identifier/Persist.vue b/dbrepo-ui/components/identifier/Persist.vue index 3dba450e634c6db374ccd28bd327ff2823756f3e..f37c5c6d7d9893562d94393fe6aa19ab3b448d79 100644 --- a/dbrepo-ui/components/identifier/Persist.vue +++ b/dbrepo-ui/components/identifier/Persist.vue @@ -12,6 +12,7 @@ <v-spacer /> <v-btn v-if="canSave" + class="mr-2" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null" color="secondary" variant="flat" @@ -22,7 +23,7 @@ @click="createOrSave"/> <v-btn v-if="canRemove" - class="ml-2" + class="mr-2" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-delete' : null" color="error" variant="flat" @@ -32,7 +33,7 @@ @click="remove" /> <v-btn v-if="canPublish" - class="ml-2" + class="mr-2" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null" color="primary" variant="flat" @@ -138,14 +139,6 @@ :color="canShiftUp(creator, i) ? 'tertiary' : ''" :variant="buttonVariant" @click="shiftDown(i)" /> - <v-btn - v-if="canInsertSelf" - class="mr-2" - size="small" - color="secondary" - variant="flat" - :text="$t('pages.identifier.subpages.create.creators.insert.text')" - @click="insertSelf(creator)" /> <v-btn v-if="i > 0" size="small" @@ -830,7 +823,6 @@ <script> import { formatYearUTC, formatMonthUTC, formatDayUTC, languages } from '@/utils' import { useCacheStore } from '@/stores/cache.js' -import { useUserStore } from '@/stores/user.js' import { MerkleJson } from 'merkle-json' export default { @@ -962,16 +954,15 @@ export default { { value: 'IsObsoletedBy' }, { value: 'Obsoletes' } ], - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser + cacheUser () { + return this.cacheStore.getUser }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, isSubset () { return this.type === 'subset' @@ -1045,23 +1036,14 @@ export default { } } }, - isUpdate () { - return 'id' in this.identifier && this.identifier.id - }, - canInsertSelf () { - if (!this.user) { - return false - } - return this.user.given_name || this.user.family_name || this.user.attributes.affiliation || this.user.attributes.orcid - }, isCreator () { - if (!this.user || !this.identifier) { + if (!this.cacheUser || !this.identifier) { return false } - if (!this.identifier.creator) { + if (!this.identifier.owner) { return true } - return this.identifier.creator.id === this.user.id + return this.identifier.owner.id === this.cacheUser.uid }, formValid () { /* somehow Vue3/Vuetify3 validation form is broken for arrays */ @@ -1123,10 +1105,10 @@ export default { return this.roles.includes('create-identifier') && !this.isPublished }, canRemove () { - if (!this.roles || !this.identifier || !this.identifier.creator || !this.user) { + if (!this.roles || !this.identifier || !this.identifier.owner || !this.cacheUser) { return false } - return this.roles.includes('delete-identifier') && this.identifier.creator.id === this.user.id && !this.isPublished + return this.roles.includes('delete-identifier') && this.identifier.owner.id === this.cacheUser.uid && !this.isPublished }, canPublish () { if (!this.roles || !this.identifier || !this.roles.includes('publish-identifier') || this.isPublished || !this.identifier.id) { @@ -1494,15 +1476,15 @@ export default { if (this.isPublished) { return false } - if (this.user.attributes.orcid) { - creator.name_identifier = this.user.attributes.orcid + if (this.cacheUser.attributes.orcid) { + creator.name_identifier = this.cacheUser.attributes.orcid this.retrieveCreator(creator) return } - creator.firstname = this.user.given_name - creator.lastname = this.user.family_name + creator.firstname = this.cacheUser.given_name + creator.lastname = this.cacheUser.family_name creator.creator_name = (creator.lastname ? creator.lastname + ', ' : '') + creator.firstname - creator.affiliation = this.user.attributes.affiliation + creator.affiliation = this.cacheUser.attributes.affiliation }, canShiftUp (creator, idx) { if (this.isPublished) { diff --git a/dbrepo-ui/components/identifier/Select.vue b/dbrepo-ui/components/identifier/Select.vue index 4404a09635c6833e609f74c4f685988550dc97c8..e5572866140858970c5141d40591af3dda5c389a 100644 --- a/dbrepo-ui/components/identifier/Select.vue +++ b/dbrepo-ui/components/identifier/Select.vue @@ -8,7 +8,7 @@ :color="color(identifier)" :variant="listVariant" :href="href(identifier)" - :title="formatTimestampUTCLabel(identifier.created)" + :title="title(identifier)" lines="two"> <v-list-item-subtitle> <Banner @@ -43,8 +43,6 @@ <script> import Banner from '@/components/identifier/Banner.vue' -import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -61,32 +59,31 @@ export default { identifier: { type: Object, default () { - return {} + return null } } }, data () { return { idx: null, - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles + cacheUser () { + return this.cacheStore.getUser }, displayIdentifiers () { - if (!this.identifiers) { - return [] + if (!this.identifiers || this.identifiers.length === 0) { + if (!this.identifier) { + return [] + } + return [this.identifier] } - if (!this.user) { + if (!this.cacheUser) { return this.identifiers.filter(i => i.status === 'published') } - return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.user.id) + return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.cacheUser.uid) }, listVariant () { const runtimeConfig = useRuntimeConfig() @@ -105,8 +102,10 @@ export default { this.init() }, methods: { - formatTimestampUTCLabel, href (identifier) { + if (!identifier) { + return null + } if (identifier.status === 'published') { return `/pid/${identifier.id}` } @@ -121,6 +120,13 @@ export default { return `/database/${identifier.database_id}/view/${identifier.view_id}/persist/${identifier.id}` } }, + title (identifier) { + if (!identifier) { + return null + } + const identifierService = useIdentifierService() + return identifierService.identifierPreferEnglishTitle(identifier) + }, isActive (identifier) { if (!identifier) { return false diff --git a/dbrepo-ui/components/identifier/Summary.vue b/dbrepo-ui/components/identifier/Summary.vue index 655a7bb907c9f8a6b868e280097033b157cd893c..6ef120599b515fece88870ad832ed23a508e4ebe 100644 --- a/dbrepo-ui/components/identifier/Summary.vue +++ b/dbrepo-ui/components/identifier/Summary.vue @@ -170,10 +170,10 @@ export default { }, computed: { access () { - return this.userStore.getAccess.value + return this.cacheStore.getAccess }, database () { - return this.cacheStore.getDatabase.value + return this.cacheStore.getDatabase }, pid () { return `/pid/${this.database.identifier.id}` diff --git a/dbrepo-ui/components/subset/Builder.vue b/dbrepo-ui/components/subset/Builder.vue index b45881479cb5243861ab8137a99999def787e551..f670700f2f23aa2a08a6ecf8004fecfb899c6c00 100644 --- a/dbrepo-ui/components/subset/Builder.vue +++ b/dbrepo-ui/components/subset/Builder.vue @@ -31,14 +31,12 @@ :text="$t('pages.subset.subpages.create.expert.text')" /> </v-tabs> </v-toolbar> - <TimeDrift /> <v-card rounded="0" variant="flat"> <v-card-text> <v-form ref="form" - v-model="valid" @submit.prevent> <v-row v-if="isView" @@ -74,7 +72,7 @@ required clearable :rules="[ - v => !!v || $t('validation.required') + v => v !== null || $t('validation.required') ]" :label="$t('pages.database.resource.data.label')" :hint="$t('pages.database.resource.data.hint')" /> @@ -89,7 +87,7 @@ required clearable :rules="[ - v => !!v || $t('validation.required') + v => v !== null || $t('validation.required') ]" :label="$t('pages.database.resource.schema.label')" :hint="$t('pages.database.resource.schema.hint', { resource: 'subset', schema: 'query' })" /> @@ -304,18 +302,15 @@ </template> <script> -import TimeDrift from '@/components/TimeDrift.vue' import Raw from '@/components/subset/Raw.vue' import Results from '@/components/subset/Results.vue' import { useCacheStore } from '@/stores/cache.js' -import { useUserStore } from '@/stores/user.js' import { format } from 'sql-formatter' export default { components: { Raw, Results, - TimeDrift }, props: { mode: { @@ -359,8 +354,7 @@ export default { tabs: 0, loadingQuery: false, loadingColumns: false, - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { @@ -388,9 +382,6 @@ export default { } return this.database.container.image.data_types }, - user () { - return this.userStore.getUser - }, viewNames () { if (!this.database) { return [] @@ -449,7 +440,7 @@ export default { if (this.isView) { return this.view.name !== null && this.view.is_public !== null && this.view.query !== null } - return this.sql !== null && !this.sql.includes(';') + return this.sql !== null && this.sql !== '' && !this.sql.includes(';') }, inputVariant () { const runtimeConfig = useRuntimeConfig() @@ -473,7 +464,7 @@ export default { if (!this.table) { return } - this.fetchTableColumns(this.table.id) + this.fetchTableColumns(this.table?.id) } }, mounted () { @@ -550,13 +541,24 @@ export default { this.view.query = this.sql const viewService = useViewService() viewService.create(this.$route.params.database_id, this.view) - .then(async (view) => { - this.resultId = view.id - this.cacheStore.reloadDatabase() - const toast = useToastInstance() - toast.success(this.$t('success.view.create')) - await this.$router.push(`/database/${this.$route.params.database_id}/view/${view.id}/data`) - this.loadingQuery = false + .then((simpleView) => { + this.resultId = simpleView.id + viewService.findOne(this.$route.params.database_id, simpleView.id) + .then(async (view) => { + this.cacheStore.setView(view) + const toast = useToastInstance() + toast.success(this.$t('success.view.create')) + await this.$router.push(`/database/${this.$route.params.database_id}/view/${view.id}/data`) + this.loadingQuery = false + }) + .catch(({code}) => { + this.loadingQuery = false + const toast = useToastInstance() + if (typeof code !== 'string') { + return + } + toast.error(this.$t(code)) + }) }) .catch(({code}) => { this.loadingQuery = false diff --git a/dbrepo-ui/components/subset/Results.vue b/dbrepo-ui/components/subset/Results.vue index 3948667518939fc6ae4d1a5d05502a81e3e14315..661c7d1a3d6e7502cf349a63cc695cf7d593f427 100644 --- a/dbrepo-ui/components/subset/Results.vue +++ b/dbrepo-ui/components/subset/Results.vue @@ -80,23 +80,6 @@ export default { } }, methods: { - executeFirstTime (parent, sql, timestamp) { - this.loading++ - const payload = { - statement: sql, - timestamp - } - const queryService = useQueryService() - queryService.execute(this.$route.params.database_id, payload, this.options.page - 1, this.options.itemsPerPage) - .then((result) => { - this.mapResults(result) - parent.resultId = result.id - this.id = result.id - }) - .finally(() => { - this.loading-- - }) - }, reExecute (id) { if (id === null) { return diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue index 6908b2b4381d88d7b5a7a19ce540b4c88c633e0e..df0948372eed1dbf60260b6f3bd835e51dd93d2a 100644 --- a/dbrepo-ui/components/subset/SubsetList.vue +++ b/dbrepo-ui/components/subset/SubsetList.vue @@ -14,28 +14,20 @@ <Loading /> </v-list-item> <div - v-for="(item, i) in subsets" + v-for="(subset, i) in subsets" :key="`q-${i}`"> <v-divider v-if="i !== 0" class="mx-4" /> <v-list> <v-list-item lines="two" - :title="title(item)" - :subtitle="subtitle(item)" - :class="clazz(item)" - :to="link(item)" - :href="link(item)"> + :title="title(subset)" + :subtitle="subtitle(subset)" + :class="clazz(subset)" + :to="link(subset)" + :href="link(subset)"> <template v-slot:append> - <v-tooltip - v-if="hasPublishedIdentifier(item)" - :text="$t('pages.identifier.pid.title')" - left> - <template v-slot:activator="{ props }"> - <v-icon - color="primary" - v-bind="props">mdi-identifier</v-icon> - </template> - </v-tooltip> + <ResourceStatus + :resource="subset" /> </template> </v-list-item> </v-list> @@ -45,8 +37,6 @@ </template> <script> -import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -55,14 +45,10 @@ export default { loadingSubsets: false, loadingIdentifiers: false, subsets: [], - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -130,7 +116,6 @@ export default { } } </script> - <style lang="scss" scoped> .pid-icon { flex: 0 !important; diff --git a/dbrepo-ui/components/subset/SubsetToolbar.vue b/dbrepo-ui/components/subset/SubsetToolbar.vue index d5f45e48e3a2d596977a37186a0076179bcd583f..e6026090978a3745af50b7e9a3e7a559ae5363a5 100644 --- a/dbrepo-ui/components/subset/SubsetToolbar.vue +++ b/dbrepo-ui/components/subset/SubsetToolbar.vue @@ -58,7 +58,6 @@ <script> import DownloadButton from '@/components/identifier/DownloadButton.vue' import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -71,7 +70,6 @@ export default { loading: false, loadingSave: false, downloadLoading: false, - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -86,17 +84,14 @@ export default { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles + return this.cacheStore.getAccess }, subset () { return this.cacheStore.getSubset }, + cacheUser () { + return this.cacheStore.getUser + }, identifiers () { if (!this.subset) { return [] @@ -117,9 +112,7 @@ export default { if (this.pid) { const filter = this.identifiers.filter(i => i.id === Number(this.pid)) if (filter.length > 0) { - const identifier = filter[0] - console.debug('identifier set according to route pid', identifier) - return identifier + return filter[0] } } return this.identifiers[0] @@ -154,10 +147,10 @@ export default { return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' }, canGetPid () { - if (!this.user || !this.subset || !this.database) { + if (!this.cacheUser || !this.subset || !this.database) { return false } - return this.database.owner.id === this.user.id || (this.subset.owner.id === this.user.id && this.hasReadAccess) + return this.database.owner.id === this.cacheUser.uid || (this.subset.owner.id === this.cacheUser.uid && this.hasReadAccess) }, title () { if (!this.identifier) { diff --git a/dbrepo-ui/components/table/TableList.vue b/dbrepo-ui/components/table/TableList.vue index b78e20ae3a6d423a90a636f119d96e4b406ea45d..5f87090b85855cdc7d6113a9de97bbea18106484 100644 --- a/dbrepo-ui/components/table/TableList.vue +++ b/dbrepo-ui/components/table/TableList.vue @@ -21,16 +21,6 @@ <template v-slot:append> <ResourceStatus :resource="table" /> - <v-tooltip - v-if="hasPublishedIdentifier(table)" - :text="$t('pages.identifier.pid.title')" - left> - <template v-slot:activator="{ props }"> - <v-icon - color="primary" - v-bind="props">mdi-identifier</v-icon> - </template> - </v-tooltip> </template> </v-list-item> </v-list> @@ -40,7 +30,6 @@ <script> import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -74,19 +63,15 @@ export default { { value: 'string', title: 'Character Varying' }, { value: 'text', title: 'Text' } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, tables () { if (!this.database) { diff --git a/dbrepo-ui/components/table/TableToolbar.vue b/dbrepo-ui/components/table/TableToolbar.vue index d6fd4868eae0e65a52d932339ba5bbab6e3ad56f..e09cd67cba31e8d3aaec7e9055078fa354bd4ded 100644 --- a/dbrepo-ui/components/table/TableToolbar.vue +++ b/dbrepo-ui/components/table/TableToolbar.vue @@ -80,7 +80,6 @@ <script> import EditTuple from '@/components/dialogs/EditTuple.vue' import { useCacheStore } from '@/stores/cache.js' -import { useUserStore } from '@/stores/user.js' export default { components: { @@ -93,8 +92,7 @@ export default { error: false, edit: false, dropTableDialog: false, - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { @@ -105,39 +103,42 @@ export default { return this.cacheStore.getTable }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess + }, + cacheUser () { + return this.cacheStore.getUser + }, + roles () { + return this.cacheStore.getRoles }, hasReadAccess () { if (!this.access) { return false } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles + const userService = useUserService() + return userService.hasReadAccess(this.access) }, canUpdateTable () { - if (!this.roles || !this.user || !this.table) { + if (!this.roles || !this.cacheUser || !this.table) { return false } - return this.roles.includes('update-table') && this.table.owner.id === this.user.id + return this.roles.includes('update-table') && this.table.owner.id === this.cacheUser.uid }, canExecuteQuery () { - if (!this.roles || !this.table || !this.user) { + if (!this.roles || !this.table || !this.cacheUser) { return false } - const userService = useUserService() - return userService.hasReadAccess(this.access) && this.roles.includes('execute-query') + return this.hasReadAccess && this.roles.includes('execute-query') + }, + isOwner () { + const databaseService = useDatabaseService() + return databaseService.isOwner(this.database, this.cacheUser) }, canCreateView () { - if (!this.roles || !this.table || !this.user) { + if (!this.roles || !this.table || !this.cacheUser) { return false } - const databaseService = useDatabaseService() - return databaseService.isOwner(this.database, this.user) && this.roles.includes('create-database-view') + return this.isOwner && this.roles.includes('create-database-view') }, canViewData () { if (!this.table) { @@ -146,10 +147,10 @@ export default { if (this.table.is_public) { return true } - if (!this.user) { + if (!this.cacheUser) { return false } - return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id + return this.hasReadAccess || this.table.owner.id === this.cacheUser.uid || this.database.owner.id === this.cacheUser.uid }, canViewSchema () { if (!this.table) { @@ -158,22 +159,22 @@ export default { if (this.table.is_schema_public) { return true } - if (!this.user) { + if (!this.cacheUser) { return false } - return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id + return this.hasReadAccess || this.table.owner.id === this.cacheUser.uid || this.database.owner.id === this.cacheUser.uid }, canImportCsv () { - if (!this.roles || !this.table || !this.user) { + if (!this.roles || !this.table || !this.cacheUser) { return false } return this.roles.includes('insert-table-data') }, canGetPid () { - if (!this.user || !this.table || !this.database) { + if (!this.cacheUser || !this.table || !this.database) { return false } - return this.database.owner.id === this.user.id || this.table.owner.id === this.user.id + return this.hasReadAccess && this.database.owner.id === this.cacheUser.uid || this.table.owner.id === this.cacheUser.uid }, buttonVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/components/user/UserToolbar.vue b/dbrepo-ui/components/user/UserToolbar.vue index e5a20c75a3230d80dc3e6f63c27c5bb6a743a463..54210130d32f9c1cbe73378138e6c0db28c8cc88 100644 --- a/dbrepo-ui/components/user/UserToolbar.vue +++ b/dbrepo-ui/components/user/UserToolbar.vue @@ -1,6 +1,9 @@ <template> - <div> - <v-toolbar title="Settings" flat> + <div + v-if="loggedIn"> + <v-toolbar + title="Settings" + flat> <template v-slot:extension> <v-tabs v-model="tab" @@ -11,31 +14,20 @@ <v-tab :text="$t('toolbars.user.authentication')" to="/user/authentication" /> - <v-tab - :text="$t('toolbars.user.developer')" - to="/user/developer" /> </v-tabs> </template> </v-toolbar> </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> -import { useUserStore } from '@/stores/user.js' - export default { data () { return { tab: null, - userStore: useUserStore() - } - }, - computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles } } } diff --git a/dbrepo-ui/components/view/ViewList.vue b/dbrepo-ui/components/view/ViewList.vue index d6539bd253cb7ef40c202633db53d564ec1e6269..afa3067921b10d1c1f2fe6d78cac4e9c7781bebe 100644 --- a/dbrepo-ui/components/view/ViewList.vue +++ b/dbrepo-ui/components/view/ViewList.vue @@ -16,16 +16,6 @@ <template v-slot:append> <ResourceStatus :resource="view" /> - <v-tooltip - v-if="hasPublishedIdentifier(view)" - :text="$t('pages.identifier.pid.title')" - left> - <template v-slot:activator="{ props }"> - <v-icon - color="primary" - v-bind="props">mdi-identifier</v-icon> - </template> - </v-tooltip> </template> </v-list-item> </v-list> @@ -34,7 +24,6 @@ </template> <script> -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -43,14 +32,10 @@ export default { loading: false, loadingDetails: false, error: false, - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, diff --git a/dbrepo-ui/components/view/ViewToolbar.vue b/dbrepo-ui/components/view/ViewToolbar.vue index 4ed83ff43fa777f9f8f663cf020d89d5d58456cd..d9e0bfb6bf6740c19aac53f6b3ac8cc374be7011 100644 --- a/dbrepo-ui/components/view/ViewToolbar.vue +++ b/dbrepo-ui/components/view/ViewToolbar.vue @@ -57,7 +57,6 @@ </template> <script> -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' import CreateOntology from '@/components/dialogs/CreateOntology.vue' import ViewVisibility from '@/components/dialogs/ViewVisibility.vue' @@ -73,7 +72,6 @@ export default { loading: false, loadingDelete: false, updateViewDialog: false, - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -91,6 +89,12 @@ export default { view () { return this.cacheStore.getView }, + cacheUser () { + return this.cacheStore.getUser + }, + roles () { + return this.cacheStore.getRoles + }, canViewData () { if (!this.view) { return false @@ -98,10 +102,10 @@ export default { if (this.view.is_public) { return true } - if (!this.user) { + if (!this.cacheUser) { return false } - return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id + return this.hasReadAccess || this.view.owner.id === this.cacheUser.uid || this.database.owner.id === this.cacheUser.uid }, canViewSchema () { if (!this.view) { @@ -110,32 +114,26 @@ export default { if (this.view.is_schema_public) { return true } - if (!this.user) { + if (!this.cacheUser) { return false } - return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id + return this.hasReadAccess || this.view.owner.id === this.cacheUser.uid || this.database.owner.id === this.cacheUser.uid }, canViewSettings () { - if (!this.user || !this.view) { + if (!this.cacheUser || !this.view) { return false } - return this.view.owner.id === this.user.id + return this.view.owner.id === this.cacheUser.uid }, canCreatePid () { - if (!this.roles || !this.user || !this.view) { + if (!this.roles || !this.cacheUser || !this.view) { return false } - const userService = useUserService() - return this.roles.includes('create-identifier') && userService.hasReadAccess(this.access) + const cacheUserService = useUserService() + return cacheUserService.hasReadAccess(this.access) && this.roles.includes('create-identifier') }, access () { - return this.userStore.getAccess - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles + return this.cacheStore.getAccess }, hasReadAccess () { if (!this.access) { diff --git a/dbrepo-ui/composables/axios-instance.ts b/dbrepo-ui/composables/axios-instance.ts index 95edea3c28d5c12f1f2422ccf8b457e409c5b459..ca7a7b111cf70cf92524f5459808277e4b14144e 100644 --- a/dbrepo-ui/composables/axios-instance.ts +++ b/dbrepo-ui/composables/axios-instance.ts @@ -1,11 +1,9 @@ import axios, {type AxiosInstance} from 'axios' -import {useUserStore} from '@/stores/user' let instance: AxiosInstance | null = null; export const useAxiosInstance = () => { const config = useRuntimeConfig() - const userStore = useUserStore() if (!instance) { instance = axios.create({ timeout: 90_000, @@ -18,38 +16,16 @@ export const useAxiosInstance = () => { baseURL: config.public.api.client }); instance.interceptors.request.use((config) => { - const token = userStore.getToken - const refreshToken = userStore.getRefreshToken - if (!token || !refreshToken) { + const { loggedIn, user, login, logout } = useOidcAuth() + if (!loggedIn) { return config } - const authenticationService = useAuthenticationService() - if (authenticationService.isExpiredToken(refreshToken)) { - console.warn('Refresh token is expired: trigger logout of user') - userStore.logout() + const { accessToken } = user.value + if (!accessToken) { return config } - if (!authenticationService.isExpiredToken(token)) { - config.headers.Authorization = `Bearer ${token}` - return config - } - console.warn('Access token expired: request a new one') - const userService = useUserService() - return userService.refreshToken(refreshToken) - .then((response: KeycloakOpenIdTokenDto) => { - userStore.setToken(response.access_token) - userStore.setRefreshToken(response.refresh_token) - console.debug('new access token expires:', authenticationService.tokenToExpiryDate(response.access_token)) - config.headers.Authorization = `Bearer ${response.access_token}` - return config - }) - .catch((error: ApiErrorDto) => { - if (error.code === 'error.user.credentials') { - console.warn('User session expired.') - userStore.logout() - } - return config - }); + config.headers.Authorization = `Bearer ${accessToken}` + return config }) } return instance; diff --git a/dbrepo-ui/composables/database-service.ts b/dbrepo-ui/composables/database-service.ts index 7956f7b4dff6cb748733fd381275dd6d0622b60c..f318e073054d6a4283b654bbae985b4f903aec00 100644 --- a/dbrepo-ui/composables/database-service.ts +++ b/dbrepo-ui/composables/database-service.ts @@ -66,23 +66,6 @@ export const useDatabaseService = (): any => { }); } - async function getServerTime(): Promise<Date> { - const axios = useAxiosInstance(); - console.debug('find server time'); - return new Promise<Date>((resolve, reject) => { - axios.head<Date>('/api/database') - .then((response) => { - const date: Date = new Date(response.headers['Date']) - console.info(`Found ${date} server time`); - resolve(date); - }) - .catch((error) => { - console.error('Failed to find server time', error); - reject(axiosErrorToApiError(error)); - }); - }); - } - async function findOne(id: number, rawError: boolean = false): Promise<DatabaseDto | null> { const axios = useAxiosInstance(); console.debug('find database with id', id); @@ -239,16 +222,12 @@ export const useDatabaseService = (): any => { refreshTablesMetadata, refreshViewsMetadata, findOne, - findPreviewImage, - findCount, - getServerTime, updateVisibility, updateImage, updateOwner, create, databaseToOwner, databaseToContact, - databaseToJsonLd, isOwner } } diff --git a/dbrepo-ui/composables/identifier-service.ts b/dbrepo-ui/composables/identifier-service.ts index 3ae194ff2fcaf85110e61eb710f7981ffc74e67a..6875a7cb7b2c168ecfb7357cc314219792930bf8 100644 --- a/dbrepo-ui/composables/identifier-service.ts +++ b/dbrepo-ui/composables/identifier-service.ts @@ -24,7 +24,7 @@ export const useIdentifierService = (): any => { } async function create(data: IdentifierSaveDto): Promise<IdentifierDto> { - const axios= useAxiosInstance() + const axios = useAxiosInstance() console.debug('create identifier') return new Promise<IdentifierDto>((resolve, reject) => { axios.post<IdentifierDto>('/api/identifier', data) @@ -40,7 +40,7 @@ export const useIdentifierService = (): any => { } async function save(data: IdentifierSaveDto): Promise<IdentifierDto> { - const axios= useAxiosInstance() + const axios = useAxiosInstance() console.debug('save identifier', data.id) return new Promise<IdentifierDto>((resolve, reject) => { axios.put<IdentifierDto>(`/api/identifier/${data.id}`, data) @@ -241,13 +241,28 @@ export const useIdentifierService = (): any => { if (!data || !data.titles || data.titles.length === 0) { return null } - const filtered = data.titles.filter(d => d.language && d.language === 'en') + const filtered = data.titles.filter((d) => d.language && d.language === 'en') if (filtered.length === 0) { - return data.titles[0].title + const title = data.titles[0] + return title.title } return filtered[0].title } + function identifierToResourceUrl(identifier: IdentifierDto): string | null { + const config = useRuntimeConfig() + switch (identifier.type) { + case 1: + return `${config.public.api.client}/api/database/${identifier.database_id}/subset/${identifier.subset_id}/data` + case 2: + return `${config.public.api.client}/api/database/${identifier.database_id}/table/${identifier.table_id}/data` + case 3: + return `${config.public.api.client}/api/database/${identifier.database_id}/view/${identifier.view_id}/data` + default: + return null + } + } + function identifierToUrl(data: IdentifierDto): string | null { if (!data) { return null @@ -315,244 +330,48 @@ export const useIdentifierService = (): any => { return jsonLd } - function identifierToHasPartJsonLd(identifier: IdentifierDto) { - return { - '@type': 'Dataset', - name: identifierPreferEnglishTitle(identifier), - description: identifierPreferEnglishDescription(identifier), - identifier: identifierToUrl(identifier), - citation: identifierToUrl(identifier), - temporalCoverage: identifier.publication_year, - version: identifier.created - } - } - - function databaseToServerHead(database: DatabaseDto) { - if (!database) { - return + function identifiersToServerHead(identifiers: IdentifierBriefDto[]): any { + if (!identifiers || !identifiers[0]) { + return null } - const config = useRuntimeConfig() + const identifier = identifiers[0] /* Google Rich Results */ const json: any = { '@context': 'https://schema.org/', '@type': 'Dataset', - url: `${config.public.api.client}/database/${database.id}/info`, - citation: `${config.public.api.client}/database/${database.id}/info`, + url: identifierToUrl(identifier), + citation: identifierToUrl(identifier), hasPart: [], - version: database.created + identifier: identifiers.map(i => identifierToUrl(i)), + creator: identifier.creators.map((c) => creatorToCreatorJsonLd(c)), + temporalCoverage: identifier.publication_year } - /* FAIR Signposting */ - const meta: any [] = [] - if (database.identifiers.length > 0) { - const identifier = database.identifiers[0] - const partIdentifiers: IdentifierDto[] = [] - if (database.subsets.length > 0) { - database.subsets.forEach((s) => { - partIdentifiers.push(s) - }) - } - if (database.tables.length > 0) { - database.tables.forEach((t) => { - if (t.identifiers.length > 0) { - t.identifiers.forEach(i => partIdentifiers.push(i)) - } - }) - } - if (database.views.length > 0) { - database.views.forEach((v) => { - if (v.identifiers.length > 0) { - v.identifiers.forEach(i => partIdentifiers.push(i)) - } - }) - } + if (identifier.titles.length > 0) { json['name'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['identifier'] = database.identifiers.map(i => identifierToUrl(i)) - json['license'] = identifierToPreferFirstLicenseUri(identifier) - json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c)) - json['citation'] = identifierToUrl(identifier) - json['hasPart'] = partIdentifiers.map(i => identifierToHasPartJsonLd(i)) - json['temporalCoverage'] = identifier.publication_year - meta.push({rel: 'cite-as', href: identifierToUrl(identifier)}) - identifier.creators.forEach((c: CreatorDto) => { - if (c.name_identifier) { - meta.push({rel: 'author', href: c.name_identifier}) - } - }) - meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)}) - meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)}) - if (identifier.licenses) { - identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri})) - } - } - return { - script: [ - { - type: 'application/ld+json', - innerHTML: json - } - ], - link: meta } - } - - function subsetToServerHead(subset: QueryDto) { - const config = useRuntimeConfig() - /* Google Rich Results */ - const json: any = { - '@context': 'https://schema.org/', - '@type': 'Dataset', - description: subset.query, - url: `${config.public.api.client}/database/${subset.database_id}/info`, - citation: `${config.public.api.client}/database/${subset.database_id}/info`, - hasPart: [], - version: subset.created - } - /* FAIR Signposting */ - const meta: any[] = [] - if (subset.identifiers.length > 0) { - const identifier = subset.identifiers[0] - json['name'] = identifierPreferEnglishTitle(identifier) + if (identifier.descriptions.length > 0) { json['description'] = identifierPreferEnglishDescription(identifier) - json['identifier'] = subset.identifiers.map(i => identifierToUrl(i)) - json['license'] = identifierToPreferFirstLicenseUri(identifier) - json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c)) - json['citation'] = identifierToUrl(identifier) - json['temporalCoverage'] = identifier.publication_year - meta.push({rel: 'cite-as', href: identifierToUrl(identifier)}) - identifier.creators.forEach((c: CreatorDto) => { - if (c.name_identifier) { - meta.push({rel: 'author', href: c.name_identifier}) - } - }) - meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)}) - meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)}) - if (identifier.licenses) { - identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri})) - } - meta.push({ - rel: 'item', - type: 'application/json', - href: `${config.public.api.client}/api/database/${subset.database_id}/subset/${subset.id}/data` - }) - meta.push({ - rel: 'item', - type: 'text/csv', - href: `${config.public.api.client}/api/database/${subset.database_id}/subset/${subset.id}/data` - }) - } - return { - script: [ - { - type: 'application/ld+json', - innerHTML: json - } - ], - link: meta - } - } - - function tableToServerHead(table: TableDto) { - const config = useRuntimeConfig() - /* Google Rich Results */ - const json: any = { - '@context': 'https://schema.org/', - '@type': 'Dataset', - description: table.description, - url: `${config.public.api.client}/database/${table.database_id}/table/${table.id}/info`, - citation: `${config.public.api.client}/database/${table.database_id}/table/${table.id}/info`, - hasPart: [], - version: table.created } /* FAIR Signposting */ const meta: any[] = [] - if (table.identifiers.length > 0) { - const identifier: IdentifierDto = table.identifiers[0] - json['name'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['identifier'] = table.identifiers.map((i: IdentifierDto) => identifierToUrl(i)) - json['license'] = identifierToPreferFirstLicenseUri(identifier) - json['creator'] = identifier.creators.map((c: CreatorDto) => creatorToCreatorJsonLd(c)) - json['citation'] = identifierToUrl(identifier) - json['temporalCoverage'] = identifier.publication_year - meta.push({rel: 'cite-as', href: identifierToUrl(identifier)}) - identifier.creators.forEach((c: CreatorDto): void => { - if (c.name_identifier) { - meta.push({rel: 'author', href: c.name_identifier}) - } - }) - meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)}) - meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)}) - if (identifier.licenses) { - identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri})) + meta.push({rel: 'cite-as', href: identifierToUrl(identifier)}) + identifier.creators.forEach((c: CreatorDto) => { + if (c.name_identifier) { + meta.push({rel: 'author', href: c.name_identifier}) } - meta.push({ - rel: 'item', - type: 'application/json', - href: `${config.public.api.client}/api/database/${table.database_id}/table/${table.id}/data` - }) - meta.push({ - rel: 'item', - type: 'text/csv', - href: `${config.public.api.client}/api/database/${table.database_id}/table/${table.id}/data` - }) - } - return { - script: [ - { - type: 'application/ld+json', - innerHTML: json - } - ], - link: meta - } - } - - function viewToServerHead(view: ViewDto) { - const config = useRuntimeConfig() - /* Google Rich Results */ - const json: any = { - '@context': 'https://schema.org/', - '@type': 'Dataset', - description: view.query, - url: `${config.public.api.client}/database/${view.database_id}/table/${view.id}/info`, - citation: `${config.public.api.client}/database/${view.database_id}/table/${view.id}/info`, - hasPart: [], - version: view.created - } - /* FAIR Signposting */ - const meta: any[] = [] - if (view.identifiers.length > 0) { - const identifier = view.identifiers[0] - json['name'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['identifier'] = view.identifiers.map(i => identifierToUrl(i)) - json['license'] = identifierToPreferFirstLicenseUri(identifier) - json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c)) - json['citation'] = identifierToUrl(identifier) - json['temporalCoverage'] = identifier.publication_year - meta.push({rel: 'cite-as', href: identifierToUrl(identifier)}) - identifier.creators.forEach((c: CreatorDto) => { - if (c.name_identifier) { - meta.push({rel: 'author', href: c.name_identifier}) - } - }) - meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)}) - meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)}) - if (identifier.licenses) { - identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri})) - } - meta.push({ - rel: 'item', - type: 'application/json', - href: `${config.public.api.client}/api/database/${view.database_id}/view/${view.id}/data` - }) - meta.push({ - rel: 'item', - type: 'text/csv', - href: `${config.public.api.client}/api/database/${view.database_id}/view/${view.id}/data` - }) - } + }) + meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)}) + meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)}) + meta.push({ + rel: 'item', + type: 'application/json', + href: identifierToResourceUrl(identifier) + }) + meta.push({ + rel: 'item', + type: 'text/csv', + href: identifierToResourceUrl(identifier) + }) return { script: [ { @@ -564,56 +383,15 @@ export const useIdentifierService = (): any => { } } - function databaseToServerSeoMeta(database: DatabaseDto) { - const json: any = { - ogTitle: database.name - } - if (database.identifiers.length > 0) { - const identifier = database.identifiers[0] - json['ogTitle'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['ogDescription'] = identifierPreferEnglishDescription(identifier) - } - return json - } - - function subsetToServerSeoMeta(subset: QueryDto) { - const json: any = { - description: subset.query - } - if (subset.identifiers.length > 0) { - const identifier = subset.identifiers[0] - json['ogTitle'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['ogDescription'] = identifierPreferEnglishDescription(identifier) - } - return json - } - - function tableToServerSeoMeta(table: TableDto) { - const json: any = { - ogTitle: table.name, - description: table.description - } - if (table.identifiers.length > 0) { - const identifier = table.identifiers[0] - json['ogTitle'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['ogDescription'] = identifierPreferEnglishDescription(identifier) + function identifiersToServerSeoMeta(identifiers: IdentifierBriefDto[]): any | null { + if (!identifiers|| !identifiers[0]) { + return null } - return json - } - - function viewToServerSeoMeta(view: ViewDto) { + const identifier = identifiers[0] const json: any = { - ogTitle: view.name, - description: view.query - } - if (view.identifiers.length > 0) { - const identifier = view.identifiers[0] - json['ogTitle'] = identifierPreferEnglishTitle(identifier) - json['description'] = identifierPreferEnglishDescription(identifier) - json['ogDescription'] = identifierPreferEnglishDescription(identifier) + ogTitle: identifierPreferEnglishTitle(identifier), + ogDescription: identifierPreferEnglishDescription(identifier), + description: identifierPreferEnglishDescription(identifier) } return json } @@ -633,13 +411,7 @@ export const useIdentifierService = (): any => { identifierToUrl, identifierToDisplayName, identifierToDisplayAcronym, - databaseToServerHead, - subsetToServerHead, - tableToServerHead, - viewToServerHead, - databaseToServerSeoMeta, - subsetToServerSeoMeta, - tableToServerSeoMeta, - viewToServerSeoMeta, + identifiersToServerHead, + identifiersToServerSeoMeta } } diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index 119915de2785763d549bdf3bf2d97379811f8fed..e314993ecd233c486198f7995dff4da5389eb06f 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -161,12 +161,12 @@ export const useQueryService = (): any => { } } sql += ` \`${clause.params[0]}\` ${clause.params[1]} ` - const filteredType = types.filter(t => t.value === filteredColumn[0].column_type) + const filteredType = types.filter(t => t.value === filteredColumn[0].type) if (filteredType.length === 0) { return { error: true, reason: 'exists', - column: filteredColumn[0].column_type, + column: filteredColumn[0].type, raw: null, formatted: null } @@ -175,7 +175,7 @@ export const useQueryService = (): any => { return { error: true, reason: 'build', - column: filteredColumn[0].column_type, + column: filteredColumn[0].type, raw: null, formatted: null } diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts index 45268d6295fc0ba55fdf268936a1614b350033e3..5f290745245b46a8ce395a489f62aa9171800d5a 100644 --- a/dbrepo-ui/composables/table-service.ts +++ b/dbrepo-ui/composables/table-service.ts @@ -255,7 +255,7 @@ export const useTableService = (): any => { if (!table || !user) { return false } - return table.owner.id === user.id + return table.owner.id === user.uid } function tableNameToInternalName(name: string) { diff --git a/dbrepo-ui/composables/upload-service.ts b/dbrepo-ui/composables/upload-service.ts index f7a6964d58cba007c3aa7e515f362349ca0a4f4a..ee0bdd5dc029a061ee102a9e526933ef7ca26413 100644 --- a/dbrepo-ui/composables/upload-service.ts +++ b/dbrepo-ui/composables/upload-service.ts @@ -1,11 +1,9 @@ import * as tus from 'tus-js-client' import {useCacheStore} from '@/stores/cache' -import {useUserStore} from '@/stores/user' export const useUploadService = (): any => { function create (data: File) { - const userStore = useUserStore() const config = useRuntimeConfig() const endpoint = config.public.upload.client return new Promise<string>((resolve, reject) => { @@ -13,10 +11,16 @@ export const useUploadService = (): any => { console.error('Your browser does not support uploads!') return } + const { loggedIn, user, login, logout } = useOidcAuth() + if (!loggedIn || !user.value?.accessToken) { + console.error('Please login to use the upload!') + return + } + const { accessToken } = user.value const uploadClient: tus.Upload = new tus.Upload(data, { endpoint, headers: { - 'Authorization': `Bearer ${userStore.getToken}` + 'Authorization': `Bearer ${accessToken}` }, retryDelays: [0, 3000, 5000, 10000, 20000], onError (error) { diff --git a/dbrepo-ui/composables/user-service.ts b/dbrepo-ui/composables/user-service.ts index e68b914e0e5bd5cfc410b82213e18f3eb0fa32ab..3425dbaa5c7d77d927798914b8a4fdac6123c166 100644 --- a/dbrepo-ui/composables/user-service.ts +++ b/dbrepo-ui/composables/user-service.ts @@ -80,32 +80,6 @@ export const useUserService = (): any => { }) } - async function obtainToken(username: string, password: string): Promise<KeycloakOpenIdTokenDto> { - console.debug('obtain user token for user with username', username) - return new Promise<KeycloakOpenIdTokenDto>((resolve, reject) => { - const config = useRuntimeConfig() - const userStore = useUserStore() - const instance = axios.create({ - timeout: 90_000, - params: {}, - baseURL: config.public.api.client - }) - instance.post<KeycloakOpenIdTokenDto>('/api/user/token', {username, password}) - .then((response) => { - console.info('Obtained user token') - // eslint-disable-next-line camelcase - const {access_token, refresh_token} = response.data - userStore.setToken(access_token) - userStore.setRefreshToken(refresh_token) - userStore.setRoles(tokenToRoles(access_token)) - resolve(response.data) - }).catch((error) => { - console.error('Failed to obtain user token', error) - reject(axiosErrorToApiError(error)) - }) - }) - } - async function refreshToken(refreshToken: string): Promise<KeycloakOpenIdTokenDto> { console.debug('refresh user token') return new Promise<KeycloakOpenIdTokenDto>((resolve, reject) => { @@ -136,21 +110,6 @@ export const useUserService = (): any => { return data.realm_access.roles || [] } - function tokenToUserId(token: string): string { - const data: Token = jwtDecode<Token>(token) - return data.uid - } - - function userInfoToUser(data: UserDto) { - const obj: UserDto = Object.assign({}, data) - obj.attributes = { - theme: data.attributes.theme, - orcid: data.attributes.orcid, - affiliation: data.attributes.affiliation - } - return obj - } - function nameIdentifierToNameIdentifierScheme(nameIdentifier: string) { if (nameIdentifier.includes('orcid.org')) { return 'ORCID' @@ -182,13 +141,13 @@ export const useUserService = (): any => { } function hasWriteAccess(table: TableDto, access: DatabaseAccessDto, user: UserDto): boolean { - if (!table || !access) { + if (!table || !access || !user) { return false } if (access.type === 'write_all') { return true } - return access.type === 'write_own' && table.owner.id === user.id + return access.type === 'write_own' && table.owner.id === user.uid } return { @@ -197,11 +156,6 @@ export const useUserService = (): any => { update, create, updatePassword, - obtainToken, - refreshToken, - tokenToRoles, - tokenToUserId, - userInfoToUser, nameIdentifierToNameIdentifierScheme, userToFullName, hasReadAccess, diff --git a/dbrepo-ui/dto/index.ts b/dbrepo-ui/dto/index.ts index 9171734aa350f242d2f38b0d0d0f795871f0306d..605a7c0db9984525632570c70e76dd542845f225 100644 --- a/dbrepo-ui/dto/index.ts +++ b/dbrepo-ui/dto/index.ts @@ -224,7 +224,11 @@ interface IdentifierFunderSaveDto { interface IdentifierDto { id: number; - type: string; + database_id: number | null; + query_id: number | null; + table_id: number | null; + view_id: number | null; + type: IdentifierTypeDto; titles: IdentifierTitleDto[] | []; descriptions: IdentifierDescriptionDto[] | []; funders: IdentifierFunderDto[] | []; @@ -236,23 +240,43 @@ interface IdentifierDto { licenses: LicenseDto[] | []; creators: CreatorDto[] | []; created: Date; - database_id: number | null; - query_id: number | null; - table_id: number | null; - view_id: number | null; query_normalized: string | null; related_identifiers: RelatedIdentifierDto[] | []; query_hash: string | null; result_hash: string | null; - /** - * @deprecated - */ result_number: number | null; publication_day: number | null; publication_month: number | null; - value: string | null; publication_year: number; - last_modified: Date; +} + +enum IdentifierTypeDto { + database, + subset, + table, + view +} + +enum IdentifierStatusTypeDto { + draft, + published +} + +interface IdentifierBriefDto { + id: number; + database_id: number | null; + query_id: number | null; + table_id: number | null; + view_id: number | null; + type: IdentifierTypeDto; + creators: CreatorBriefDto[] | []; + titles: IdentifierTitleDto[] | []; + description: IdentifierDescriptionDto[] | []; + doi: string | null; + publisher: string; + publication_year: number; + status: IdentifierStatusTypeDto; + owned_by: string; } interface IdentifierTitleDto { @@ -279,19 +303,35 @@ interface IdentifierFunderDto { award_title: string; } +enum NameTypeDto { + Personal, + Organizational +} + interface CreatorDto { id: number; firstname: string; lastname: string; affiliation: string; creator_name: string; - name_type: string; - name_identifier: string; - name_identifier_scheme: string; - name_identifier_scheme_uri: string; - affiliation_identifier: string; - affiliation_identifier_scheme: string; - affiliation_identifier_scheme_uri: string; + name_type: NameTypeDto | null; + name_identifier: string | null; + name_identifier_scheme: string | null; + name_identifier_scheme_uri: string | null; + affiliation_identifier: string | null; + affiliation_identifier_scheme: string | null; + affiliation_identifier_scheme_uri: string | null; +} + +interface CreatorBriefDto { + id: number; + affiliation: string; + creator_name: string; + name_type: NameTypeDto | null; + name_identifier: string | null; + name_identifier_scheme: string | null; + affiliation_identifier: string | null; + affiliation_identifier_scheme: string | null; } interface RelatedIdentifierDto { @@ -342,7 +382,6 @@ interface ColumnDto { database_id: number; table_id: number; internal_name: string; - date_format: ImageDateDto; is_primary_key: boolean; index_length: number; length: number; diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue index 0ad4cb818d353d283c15d0cc7c6240fec513a54b..3ac8e32f900e9b2b95cfb5ab3dc6929a5aae7684 100644 --- a/dbrepo-ui/layouts/default.vue +++ b/dbrepo-ui/layouts/default.vue @@ -96,29 +96,22 @@ @click:append-inner="retrieve" /> <v-spacer /> <v-btn - v-if="!user" + v-if="!loggedIn" class="mr-2" color="secondary" variant="flat" + :loading="loadingLogin" :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-login' : null" - to="/login"> + @click="loadingLogin=true;login()"> {{ $t('navigation.login') }} </v-btn> <v-btn - v-if="!user" - color="primary" - variant="flat" - :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-account-plus' : null" - to="/signup"> - {{ $t('navigation.signup') }} - </v-btn> - <v-btn - v-if="user" + v-if="cacheUser" to="/user" variant="plain" - :text="user.username" /> + :text="cacheUser.preferred_username" /> <v-menu - v-if="user" + v-if="loggedIn" location="bottom"> <template v-slot:activator="{ props }"> <v-btn @@ -127,20 +120,19 @@ </template> <v-list> <v-list-item - v-if="user" + v-if="cacheUser" exact - :to="`/search?type=database&owner.username=${user.username}`"> + :to="`/search?type=database&owner.username=${cacheUser.username}`"> {{ $t('navigation.databases') + ' ' + $t('navigation.mine')}} </v-list-item> <v-list-item - v-if="user" + v-if="cacheUser" exact - :to="`/search?type=identifier&identifiers.creator.username=${user.username}`"> + :to="`/search?type=identifier&identifiers.creator.username=${cacheUser.username}`"> {{ $t('navigation.identifiers') + ' ' + $t('navigation.mine') }} </v-list-item> <v-list-item - v-if="user" - @click="logout"> + @click="logout()"> {{ $t('navigation.logout') }} </v-list-item> </v-list> @@ -161,10 +153,13 @@ </template> <script setup> -import { ref } from 'vue' +import { useCacheStore } from '@/stores/cache.js' +const { loggedIn, user, login, logout } = useOidcAuth() +const cacheStore = useCacheStore() +cacheStore.setUser(loggedIn ? user.value?.userInfo : null) +cacheStore.setRoles(loggedIn ? user.value?.claims?.realm_access?.roles : []) const runtimeConfig = useRuntimeConfig() -const config = ref(runtimeConfig) useServerHead({ title: runtimeConfig.public.title, meta: [ @@ -175,9 +170,9 @@ useServerHead({ </script> <script> import JumboBox from '@/components/JumboBox.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' import { errorCodeKey, makeError } from '@/utils' +import {useNuxtApp} from "#app"; export default { components: { @@ -189,6 +184,7 @@ export default { model: null, query: null, loading: true, + loadingLogin: false, databaseError: null, accessError: null, searchResults: [], @@ -197,26 +193,13 @@ export default { loadingSearch: false, loadingDatabases: false, search: null, - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - token () { - return this.userStore.getToken - }, - user () { - return this.userStore.getUser - }, - locale () { - return this.userStore.getLocale - }, messages () { return this.cacheStore.getMessages }, - access () { - return this.userStore.getAccess - }, table () { return this.cacheStore.getTable }, @@ -229,6 +212,18 @@ export default { database () { return this.cacheStore.getDatabase }, + access () { + return this.cacheStore.getAccess + }, + roles () { + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser + }, + identifier () { + return this.cacheStore.getIdentifier + }, resource () { if (!this.$route.params.database_id) { return null @@ -244,9 +239,6 @@ export default { } return 'database' }, - roles () { - return this.userStore.getRoles - }, version () { return this.$config.public.version }, @@ -260,22 +252,25 @@ export default { return this.$config.public.commit.substr(0, 8) }, error () { + if (this.identifier) { + return null + } if (this.databaseError) { return this.databaseError } if (this.accessError) { return this.accessError } - if (!this.user) { + if (!this.cacheUser) { return null } - if (this.table && !this.table.is_public && !this.table.is_schema_public && this.table.owner.id !== this.user.id) { + if (this.table && !this.table.is_public && !this.table.is_schema_public && !this.access) { return makeError(403, null, null) } - if (this.view && !this.view.is_public && !this.view.is_schema_public && this.view.owner.id !== this.user.id) { + if (this.view && !this.view.is_public && !this.view.is_schema_public && !this.access) { return makeError(403, null, null) } - if (this.subset && !this.subset.is_public && !this.subset.is_schema_public && this.subset.owner.id !== this.user.id) { + if (this.subset && !this.subset.is_public && !this.subset.is_schema_public && !this.access) { return makeError(403, null, null) } return null @@ -287,41 +282,53 @@ export default { return this.roles.includes('list-ontologies') }, canListContainers () { - if (!this.roles) { - return false - } - return this.roles.includes('list-containers') + return this.cacheUser }, logo () { return this.$config.public.logo }, + locale () { + return this.cacheStore.getLocale + }, searchVariant () { const runtimeConfig = useRuntimeConfig() return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.input.contrast : 'solo-filled' - }, + } }, watch: { '$route.params': { handler (newObj, oldObj) { + if (import.meta.server) { + return + } if (!newObj.database_id) { this.databaseError = null this.accessError = null this.cacheStore.setTable(null) this.cacheStore.setView(null) this.cacheStore.setSubset(null) + this.cacheStore.setAccess(null) + this.cacheStore.setIdentifier(null) return } - if (import.meta.server) { - return + if (this.identifier) { + if (newObj.query_id && this.identifier.query_id !== Number(newObj.query_id)) { + this.cacheStore.setIdentifier(null) + } else if (newObj.table_id && this.identifier.table_id !== Number(newObj.table_id)) { + this.cacheStore.setIdentifier(null) + } else if (newObj.view_id && this.identifier.view_id !== Number(newObj.view_id)) { + this.cacheStore.setIdentifier(null) + } + if (this.$route.query.pid && this.identifier.id !== Number(this.$route.query.pid)) { + this.cacheStore.setIdentifier(null) + } } /* load database and optional access */ + this.cacheStore.setRouteAccess(newObj.database_id, this.cacheUser?.uid) this.cacheStore.setRouteDatabase(newObj.database_id) .catch((error) => { this.databaseError = error }) - if (this.user) { - this.userStore.setRouteAccess(newObj.database_id) - } /* load table */ if (newObj.table_id) { this.cacheStore.setRouteTable(newObj.database_id, newObj.table_id) @@ -346,43 +353,33 @@ export default { } }, mounted () { - this.initEnvironment() if (this.$route.query && this.$route.query.q) { this.search = this.$route.query.q } - if (!this.user) { + if (!this.cacheUser) { return } this.setTheme() + this.setLocale() this.cacheStore.reloadMessages() }, methods: { - errorCodeKey, - login () { - const redirect = ![undefined, '/', '/login'].includes(this.$router.currentRoute.path) - this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} }) - }, - logout () { - this.$vuetify.theme.global.name = 'tuwThemeLight' - this.userStore.logout() - this.$router.push('/database') - }, retrieve () { console.debug('performing fuzzy search') this.$router.push({ path: '/search', query: { q: this.search } }) }, - initEnvironment () { - if (this.token && !this.user) { - console.error('Something went wrong with loading the user: reset user cache') - this.userStore.logout() - } + setLocale () { if (!this.locale) { - this.userStore.setLocale('en') + this.cacheStore.setLocale('en') + return } this.$i18n.locale = this.locale }, setTheme () { - switch (this.user.attributes.theme) { + if (!this.cacheUser?.theme) { + return + } + switch (this.cacheUser.theme) { case 'dark': this.$vuetify.theme.global.name = 'tuwThemeDark' break @@ -396,10 +393,6 @@ export default { this.$vuetify.theme.global.name = 'tuwThemeDarkContrast' break } - }, - setLocale (code) { - this.userStore.setLocale(code) - this.$i18n.locale = this.locale } } } diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json index 2e94f572ec68f7f3c595254ed5c9a5e14099e623..07ac0163ef086dd1915d90daf5cf7aabc59695d4 100644 --- a/dbrepo-ui/locales/en-US.json +++ b/dbrepo-ui/locales/en-US.json @@ -606,10 +606,10 @@ }, "status": { "title": "Status", - "public": "Public", + "public": "Visible", "data": "Data-only", "schema": "Schema-only", - "draft": "Draft" + "draft": "Hidden" }, "resource": { "data": { diff --git a/dbrepo-ui/nuxt.config.ts b/dbrepo-ui/nuxt.config.ts index 4b1833d8162e7e1494f4164fa90bb76e107f7635..b3da7bd98c657513e85c64cd51fac4c118d433b7 100644 --- a/dbrepo-ui/nuxt.config.ts +++ b/dbrepo-ui/nuxt.config.ts @@ -3,19 +3,26 @@ import vuetify from 'vite-plugin-vuetify' const proxy: any = {} -// /* proxies the backend calls, >>NOT<< the frontend calls (clicking) */ -// if (process.env.NODE_ENV === 'development') { -// const api = 'http://localhost' -// proxy['/api'] = api -// proxy['/pid'] = { -// target: api + '/api', -// changeOrigin: true, -// pathRewrite: { -// '^/pid': '/pid' -// } -// } -// process.env.NUXT_PUBLIC_API_SERVER = api -// } +/* proxies the backend calls, >>NOT<< the frontend calls */ +if (process.env.NODE_ENV === 'development') { + const api = 'http://localhost' + proxy['/api'] = api + proxy['/pid'] = { + target: api + '/api', + changeOrigin: true, + pathRewrite: { + '^/pid': '/pid' + } + } + process.env.VERSION = 'bun-dev' + process.env.NUXT_PUBLIC_API_SERVER = api + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_AUTHORIZATION_URL = api + '/realms/dbrepo/protocol/openid-connect/auth' + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_REDIRECT_URI = api + ':3001' + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_URL = api + '/realms/dbrepo/protocol/openid-connect/logout' + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_REDIRECT_URI = api + ':3001/auth/keycloak/callback' + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_TOKEN_URL = api + '/realms/dbrepo/protocol/openid-connect/token' + process.env.NUXT_OIDC_PROVIDERS_KEYCLOAK_USER_INFO_URL = api + '/realms/dbrepo/protocol/openid-connect/userinfo' +} /** * https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering @@ -107,11 +114,37 @@ export default defineNuxtConfig({ port: 3001 }, + oidc: { + defaultProvider: 'keycloak', + providers: { + keycloak: { + audience: 'account', + authorizationUrl: '', + baseUrl: 'http://localhost/realms/dbrepo', + clientId: 'dbrepo-client', + clientSecret: 'MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG', + exposeAccessToken: true, + logoutRedirectUri: '', + logoutUrl: '', + optionalClaims: ['realm_access'], + redirectUri: 'http://localhost', + scope: ['openid', 'roles'], + tokenUrl: '', + userInfoUrl: '' + }, + }, + middleware: { + globalMiddlewareEnabled: false, + customLoginPage: false + }, + }, + modules: [ - '@artmizu/nuxt-prometheus', + ['@artmizu/nuxt-prometheus', {verbose: false}], '@nuxtjs/i18n', '@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt', + 'nuxt-oidc-auth', async (options, nuxt) => { nuxt.hooks.hook('vite:extendConfig', config => config.plugins.push( vuetify() @@ -160,6 +193,8 @@ export default defineNuxtConfig({ }, }, - devtools: {enabled: true}, - compatibilityDate: '2024-07-24' + devtools: { + enabled: false + }, + compatibilityDate: '2025-01-25' }) diff --git a/dbrepo-ui/package.json b/dbrepo-ui/package.json index a1354778200ebabb28a575e204e264e6bd155774..d7d4917e9f45f113156951746486755d91ae7bed 100644 --- a/dbrepo-ui/package.json +++ b/dbrepo-ui/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "build": "nuxt build", - "dev": "VERSION=bun-dev NODE_ENV=development nuxt dev", + "dev": "NODE_ENV=development nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare", @@ -28,6 +28,7 @@ "merkle-json": "^2.6.0", "moment": "^2.30.1", "nuxt": "^3.10.3", + "nuxt-oidc-auth": "^1.0.0-beta.5", "parse-md": "^3.0.3", "pinia": "^2.1.7", "qs": "^6.11.2", diff --git a/dbrepo-ui/pages/container/index.vue b/dbrepo-ui/pages/container/index.vue index 360ce1543fdf086d60d568ed066ccca1f7c04537..20fed36801c24184664744ccf8b06eecba358c33 100644 --- a/dbrepo-ui/pages/container/index.vue +++ b/dbrepo-ui/pages/container/index.vue @@ -1,5 +1,6 @@ <template> - <div> + <div + v-if="loggedIn"> <v-toolbar flat :title="$t('pages.container.title')"> @@ -11,6 +12,9 @@ </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> import ContainerList from '@/components/container/ContainerList.vue' @@ -25,19 +29,19 @@ export default { containers: [] } }, - computed: { - roles () { - return this.userStore.getRoles - }, - }, mounted () { - this.loading = true - const containerService = useContainerService(); - containerService.findAll() - .then((containers) => { - this.containers = containers - this.loading = false - }) + this.fetchContainers() + }, + methods: { + fetchContainers () { + this.loading = true + const containerService = useContainerService(); + containerService.findAll() + .then((containers) => { + this.containers = containers + this.loading = false + }) + } } } </script> diff --git a/dbrepo-ui/pages/database/[database_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/info.vue index 6e1e35aabb3b2c150f532edf8c8fea5af610088c..025cc9c4c438523c876c346f6ccb219933a5362c 100644 --- a/dbrepo-ui/pages/database/[database_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/info.vue @@ -1,26 +1,27 @@ <template> <div - v-if="canViewSchema"> + v-if="identifier || canViewInfo"> <DatabaseToolbar /> <v-window v-model="tab"> <v-window-item value="1"> <Summary - v-if="hasIdentifier" + v-if="identifier" :identifier="identifier" /> <v-card - v-if="hasIdentifier" + v-if="identifier" variant="flat" rounded="0"> <v-card-text> <Select - :identifiers="filteredIdentifiers" + :identifiers="identifiers" :identifier="identifier" /> </v-card-text> </v-card> <v-divider - v-if="hasIdentifier" /> + v-if="identifier" /> <v-card + v-if="canViewInfo" :title="$t('pages.database.title')" variant="flat" rounded="0"> @@ -94,7 +95,7 @@ <div> <UserBadge :user="database.owner" - :other-user="user" /> + :other-user="cacheUser" /> </div> </v-list-item> <v-list-item @@ -104,7 +105,7 @@ <div> <UserBadge :user="database.contact" - :other-user="user" /> + :other-user="cacheUser" /> </div> </v-list-item> </v-list> @@ -163,13 +164,30 @@ </div> </template> +<script setup> +import { ref } from 'vue' + +const config = useRuntimeConfig() +const { pid } = useRoute().query +const { database_id } = useRoute().params +const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&type=database&status=published`) + +if (data.value && data.value.length > 0) { + const identifierService = useIdentifierService() + useServerHead(identifierService.identifiersToServerHead(data.value)) + useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value)) +} +const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null) + +const cacheStore = useCacheStore() +cacheStore.setIdentifier(identifier) +</script> <script> import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue' import Summary from '@/components/identifier/Summary.vue' import Select from '@/components/identifier/Select.vue' import UserBadge from '@/components/user/UserBadge.vue' import { sizeToHumanLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -197,7 +215,6 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -206,7 +223,7 @@ export default { return 0 }, description () { - if (!this.hasIdentifier) { + if (!this.identifier) { return '' } return this.database.identifier.description @@ -218,46 +235,25 @@ export default { return this.$config.public.database.image.height }, publisher () { - if (!this.hasIdentifier) { + if (!this.identifier) { return '' } return this.database.identifier.publisher }, - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, - roles () { - return this.userStore.getRoles + cacheUser () { + return this.cacheStore.getUser }, - identifiers () { - if (!this.database) { - return [] - } - return this.database.identifiers + access () { + return this.cacheStore.getAccess }, - filteredIdentifiers () { - if (!this.identifiers) { + identifiers () { + if (!this.database || !this.database.identifiers) { return [] } - if (!this.user) { - return this.identifiers.filter(i => i.status === 'published') - } - return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.user.id) - }, - identifier () { - if (this.pid) { - const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid)) - if (filter.length > 0) { - return filter[0] - } - } - return this.filteredIdentifiers[0] - }, - access () { - return this.userStore.getAccess + return this.database.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id)) }, pid () { return this.$route.query.pid @@ -300,9 +296,6 @@ export default { const databaseService = useDatabaseService() return databaseService.databaseToOwner(this.database) }, - hasIdentifier () { - return this.identifier - }, accessDescription () { if (!this.access) { return @@ -335,12 +328,19 @@ export default { } return this.database.preview_image }, - canViewSchema () { - if (this.error) { + canViewInfo () { + if (!this.database) { return false } - return this.database - } + if (this.database.is_public || this.database.is_schema_public) { + return true + } + if (!this.access) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) + }, } } </script> diff --git a/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue index a57b439b216ae4743fd4bda292dc53e5f7e05a69..505a7651232b071a03dd2b7ea481ff306e8dc3b4 100644 --- a/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canCreateIdentifier || canUpdateIdentifier"> + v-if="canPersistIdentifier || canUpdateIdentifier"> <Persist type="database" :database="database" /> @@ -9,9 +9,8 @@ </template> <script> -import Persist from '~/components/identifier/Persist.vue' -import { useUserStore } from '~/stores/user.js' -import { useCacheStore } from '~/stores/cache.js' +import Persist from '@/components/identifier/Persist.vue' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -39,34 +38,53 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { + database () { + return this.cacheStore.getDatabase + }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - user () { - return this.userStore.getUser + cacheUser () { + return this.cacheStore.getUser }, - database () { - return this.cacheStore.getDatabase + access () { + return this.cacheStore.getAccess + }, + identifier () { + if (!this.database) { + return false + } + const filter = this.database.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id)) + return filter.length === 1 ? filter[0] : null }, - canCreateIdentifier () { - if (!this.roles) { + canPersistIdentifier () { + if (!this.database || !this.roles || !this.cacheUser || !this.access) { return false } if (this.roles.includes('create-foreign-identifier')) { return true } - return this.roles.includes('create-identifier') + if (!this.roles.includes('create-identifier')) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.database.owner.id === this.cacheUser.uid }, canUpdateIdentifier () { - if (!this.roles) { + if (!this.identifier || !this.roles) { + return false + } + if (this.roles.includes('modify-identifier-metadata')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } - return this.roles.includes('modify-identifier-metadata') + return this.identifier.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/persist/index.vue index df675d262e8e116b0c4252d74fe6878681388acb..b66e8d706de8f4d87dd44baff8e7ded81aa7fffa 100644 --- a/dbrepo-ui/pages/database/[database_id]/persist/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/persist/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canCreateIdentifier || canUpdateIdentifier"> + v-if="canPersistIdentifier"> <Persist type="database" :database="database" /> @@ -9,9 +9,8 @@ </template> <script> -import Persist from '~/components/identifier/Persist.vue' -import { useUserStore } from '~/stores/user.js' -import { useCacheStore } from '~/stores/cache.js' +import Persist from '@/components/identifier/Persist.vue' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -35,46 +34,40 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - roles () { - return this.userStore.getRoles - }, - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, - hasIdentifier () { - if (this.database && 'identifier' in this.database && this.database.identifier) { - return 'id' in this.database.identifier - } - return false + cacheUser () { + return this.cacheStore.getUser + }, + roles () { + return this.cacheStore.getRoles + }, + access () { + return this.cacheStore.getAccess }, isOwner () { - if (!this.database || !this.user) { + if (!this.database || !this.cacheUser) { return false } - return this.database.owner.username === this.user.username + return this.database.owner.id === this.cacheUser.uid }, - canCreateIdentifier () { - if (!this.roles || this.hasIdentifier) { + canPersistIdentifier () { + if (!this.database || !this.roles || !this.cacheUser || !this.access) { return false } if (this.roles.includes('create-foreign-identifier')) { return true } - return this.roles.includes('create-identifier') && this.isOwner - }, - canUpdateIdentifier () { - if (!this.roles) { + if (!this.roles.includes('create-identifier')) { return false } - return this.hasIdentifier && this.roles.includes('modify-identifier-metadata') + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.database.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/settings.vue index d0fb517daebc37d65c3d18f1faed9f631a0ecff0..8905ede1d2f88c8b2411f65655c4443827c23315 100644 --- a/dbrepo-ui/pages/database/[database_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/settings.vue @@ -1,14 +1,13 @@ <template> <div - v-if="canView"> + v-if="canViewSettings"> <DatabaseToolbar ref="toolbar" /> <v-window - v-if="user" v-model="tab"> <v-window-item> <v-card - v-if="isOwner && canModifyImage" + v-if="canModifyImage" variant="flat" rounded="0" :title="$t('pages.database.subpages.settings.title')" @@ -89,7 +88,6 @@ </v-card> <v-divider /> <v-card - v-if="isOwner" variant="flat" rounded="0" :title="$t('pages.database.subpages.access.title')" @@ -106,7 +104,7 @@ </template> <template v-slot:item.action="{ item }"> <v-btn - v-if="item && item.user && item.user.username !== user.username" + v-if="item && item.user && item.user.username !== cacheUser.username" size="x-small" variant="flat" color="warning" @@ -247,7 +245,6 @@ <script> import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue' import EditAccess from '@/components/dialogs/EditAccess.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -323,7 +320,6 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -335,34 +331,22 @@ export default { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess - }, - token () { - return this.userStore.getToken + return this.cacheStore.getAccess }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - user () { - return this.userStore.getUser + cacheUser () { + return this.cacheStore.getUser }, uploadProgress () { return this.cacheStore.getUploadProgress }, - isOwner () { - if (!this.database || !this.user) { - return false - } - if (this.database.owner.id === null || this.user.id === null) { - return false - } - return this.database.owner.id === this.user.id - }, isSameOwner () { - if (!this.modifyOwner || !this.user) { + if (!this.modifyOwner || !this.cacheUser) { return false } - return this.modifyOwner.id === this.user.id + return this.modifyOwner.id === this.cacheUser.uid }, isSameVisibility () { if (!this.modifyVisibility || !this.database) { @@ -371,46 +355,47 @@ export default { return this.modifyVisibility.is_public === this.database.is_public && this.modifyVisibility.is_schema_public === this.database.is_schema_public }, canModifyVisibility () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('modify-database-visibility') }, canModifyOwnership () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('modify-database-owner') }, canUpdateScheme () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('find-database') }, canModifyAccess () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('update-database-access') }, canCreateAccess () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('create-database-access') }, canModifyImage () { - if (!this.isOwner) { + if (!this.roles) { return false } return this.roles.includes('modify-database-image') }, - canView () { - if (this.error) { + canViewSettings () { + if (!this.database || !this.cacheUser || !this.access) { return false } - return this.database + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.database.owner.id === this.cacheUser.uid }, previewImage () { if (this.file) { @@ -477,7 +462,7 @@ export default { .then((database) => { const toast = useToastInstance() toast.success(this.$t('success.database.visibility')) - this.cacheStore.setDatabase(database) + this.cacheStore.reloadDatabase() }) .catch(() => { this.loading = false diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue index e9719cd0efe49369d3735ab7f9d5a0e2acd22070..682fc59b98fa61b0b3fb15af227960050e5d94cb 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue @@ -1,5 +1,6 @@ <template> - <div> + <div + v-if="canViewSubsetData"> <SubsetToolbar /> <v-toolbar color="secondary" @@ -17,7 +18,7 @@ </v-toolbar-title> <v-spacer /> <v-btn - v-if="canDownload" + v-if="canViewSubsetData" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-download' : null" variant="flat" :loading="downloadLoading" @@ -96,27 +97,28 @@ export default { subset () { return this.cacheStore.getSubset }, + access () { + return this.cacheStore.getAccess + }, executionUTC () { if (!this.subset) { return null } return formatTimestampUTCLabel(this.subset.created) }, - canDownload () { - if (!this.result_visibility || !this.subset.id) { - return false - } - return this.subset.id - }, - result_visibility () { + canViewSubsetData () { if (!this.database || !this.subset) { return false } if (this.database.is_public) { return true } - return this.subset.owner.username === this.username - }, + if (!this.access) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) + } }, mounted () { this.loadSubset() @@ -139,10 +141,6 @@ export default { this.loadingSubset = false }) }, - loadResult () { - if (this.subset) { - } - }, download () { this.downloadLoading = true const queryService = useQueryService() diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue index f067fbadb4a190aff0843e289281971997426772..db5d45b4610de195f2a16d213eea60674c8dcf86 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue @@ -1,36 +1,29 @@ <template> - <div> + <div + v-if="identifier || canViewInfo"> <SubsetToolbar /> <v-card variant="flat" rounded="0"> <Summary - v-if="hasIdentifier" + v-if="identifier" :identifier="identifier" /> <v-card-text - v-if="hasIdentifier"> + v-if="identifier"> <Select :identifiers="identifiers" :identifier="identifier" /> </v-card-text> </v-card> <v-divider - v-if="subset && identifier" /> + v-if="canViewInfo && identifier" /> <v-card + v-if="canViewInfo" variant="flat" rounded="0" :title="$t('pages.subset.title')"> <v-card-text> <v-list - v-if="!subset" - lines="two" - dense> - <v-skeleton-loader - type="list-item-three-line" - width="50%" /> - </v-list> - <v-list - v-else-if="subset" lines="two" dense> <v-list-item @@ -50,7 +43,9 @@ v-if="subset.creator" :title="$t('pages.subset.creator.title')" density="compact"> - <UserBadge :user="subset.creator" :other-user="user" /> + <UserBadge + :user="subset.creator" + :other-user="cacheUser" /> </v-list-item> <v-list-item :title="$t('pages.subset.query.title')" @@ -86,13 +81,30 @@ </div> </template> +<script setup> +import { ref } from 'vue' + +const config = useRuntimeConfig() +const { pid } = useRoute().query +const { database_id, subset_id } = useRoute().params +const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&qid=${subset_id}&type=subset&status=published`) + +if (data.value && data.value.length > 0) { + const identifierService = useIdentifierService() + useServerHead(identifierService.identifiersToServerHead(data.value)) + useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value)) +} +const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null) + +const cacheStore = useCacheStore() +cacheStore.setIdentifier(identifier) +</script> <script> import Summary from '@/components/identifier/Summary.vue' import SubsetToolbar from '@/components/subset/SubsetToolbar.vue' import Select from '@/components/identifier/Select.vue' import UserBadge from '@/components/user/UserBadge.vue' import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -134,7 +146,6 @@ export default { downloadLoading: false, error: false, promises: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -145,35 +156,33 @@ export default { database () { return this.cacheStore.getDatabase }, - access () { - return this.userStore.getAccess + cacheUser () { + return this.cacheStore.getUser }, subset () { return this.cacheStore.getSubset }, - user () { - return this.userStore.getUser - }, identifiers () { - if (!this.database || !this.database.subsets || this.database.subsets.length === 0) { + if (!this.database || !this.database.subsets) { return [] } - return this.database.subsets.filter(s => s.query_id === Number(this.$route.params.subset_id)) - }, - hasIdentifier () { - return this.identifiers.length > 0 + return this.database.subsets.filter(i => i.query_id === Number(this.$route.params.subset_id)) }, - identifier () { - if (this.pid) { - const filter = this.identifiers.filter(i => i.id === Number(this.pid)) - if (filter.length > 0) { - return filter[0] - } + canViewInfo () { + if (!this.database) { + return false + } + if (this.database.is_public || this.database.is_schema_public) { + return true + } + if (!this.access) { + return false } - return this.identifiers[0] + const userService = useUserService() + return userService.hasReadAccess(this.access) }, title () { - if (!this.hasIdentifier) { + if (!this.identifier) { return null } const enTitle = this.identifier.titles.filter(t => t.language).filter(t => t.language === 'en') diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue index b15ecb3292520979e550f68c7c16ef873638b124..78878a0015f61ea3dadf38258b747b20fa8e94d9 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canCreateIdentifier || canUpdateIdentifier"> + v-if="canPersistIdentifier || canUpdateIdentifier"> <Persist type="subset" :database="database" /> @@ -9,9 +9,8 @@ </template> <script> -import Persist from '~/components/identifier/Persist.vue' -import { useUserStore } from '~/stores/user.js' -import { useCacheStore } from '~/stores/cache.js' +import Persist from '@/components/identifier/Persist.vue' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -47,34 +46,56 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { + database () { + return this.cacheStore.getDatabase + }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - user () { - return this.userStore.getUser + subset () { + return this.cacheStore.getSubset }, - database () { - return this.cacheStore.getDatabase + access () { + return this.cacheStore.getAccess + }, + cacheUser () { + return this.cacheStore.getUser }, - canCreateIdentifier () { - if (!this.roles) { + identifier () { + if (!this.subset) { + return false + } + const filter = this.subset.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id)) + return filter.length === 1 ? filter[0] : null + }, + canPersistIdentifier () { + if (!this.subset || !this.roles || !this.cacheUser || !this.access) { return false } if (this.roles.includes('create-foreign-identifier')) { return true } - return this.roles.includes('create-identifier') + if (!this.roles.includes('create-identifier')) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.subset.owner.id === this.cacheUser.uid }, canUpdateIdentifier () { - if (!this.roles) { + if (!this.identifier || !this.roles) { + return false + } + if (this.roles.includes('modify-identifier-metadata')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } - return this.roles.includes('modify-identifier-metadata') + return this.identifier.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue index 07be66bc73911c9bd7580991ef2c71c6d36de5bc..88209f50188e473e306a826674051de883e83b3f 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue @@ -1,18 +1,17 @@ <template> <div - v-if="canPersistQuery"> + v-if="canPersistIdentifier"> <Persist type="subset" :database="database" - :query="query" /> + :query="subset" /> <v-breadcrumbs :items="items" class="pa-0 mt-2" /> </div> </template> <script> -import Persist from '~/components/identifier/Persist.vue' -import { useUserStore } from '~/stores/user.js' -import { useCacheStore } from '~/stores/cache.js' +import Persist from '@/components/identifier/Persist.vue' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -21,8 +20,6 @@ export default { data () { return { loading: false, - loadingQuery: false, - query: null, isAuthorizationError: false, items: [ { @@ -47,51 +44,37 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess + }, + subset () { + return this.cacheStore.getSubset + }, + roles () { + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser }, - canPersistQuery () { - if (this.loadingQuery || !this.query) { + canPersistIdentifier () { + if (!this.subset || !this.roles || !this.cacheUser || !this.access) { + return false + } + if (this.roles.includes('create-foreign-identifier')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } const userService = useUserService() - return userService.hasReadAccess(this.access) - } - }, - mounted () { - this.loadQuery() - }, - methods: { - loadQuery () { - this.loadingQuery = true - return new Promise((resolve, reject) => { - const queryService = useQueryService() - queryService.findOne(this.$route.params.database_id, this.$route.params.subset_id) - .then((query) => { - this.query = query - resolve(query) - }) - .catch((error) => { - if (error.response.status === 405) { - this.isAuthorizationError = true - } - reject(error) - }) - .finally(() => { - this.loadingQuery = false - }) - }) + return userService.hasReadAccess(this.access) && this.subset.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/subset/create.vue b/dbrepo-ui/pages/database/[database_id]/subset/create.vue index 0fb591f9f8307018da13539491089c68c2a9ba16..c3d07bba1570ef6e116ff0bb5e18145104526175 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/create.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/create.vue @@ -7,7 +7,6 @@ </template> <script> -import { useUserStore } from '@/stores/user.js' import Builder from '@/components/subset/Builder.vue' import {useCacheStore} from '@/stores/cache.js' @@ -36,28 +35,15 @@ export default { disabled: true } ], - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess - }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + return this.cacheStore.getAccess }, canCreateSubset () { if (!this.database) { @@ -66,7 +52,11 @@ export default { if (this.database.is_public) { return true } - return this.hasReadAccess + if (!this.access) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) } } } diff --git a/dbrepo-ui/pages/database/[database_id]/subset/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/index.vue index 2f68604881a0e222dbd280a5c54ff375ca8c0825..d98a8ba2afc6f26466c50e31acfc10ed0f32acd3 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/index.vue @@ -39,11 +39,21 @@ export default { database () { return this.cacheStore.getDatabase }, + access () { + return this.cacheStore.getAccess + }, canViewSchema () { - if (this.error) { + if (!this.database) { + return false + } + if (this.database.is_schema_public) { + return true + } + if (!this.access) { return false } - return this.database + const userService = useUserService() + return userService.hasReadAccess(this.access) } } } diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue index 07747ed0cb50cdd950ee24dece9ff3c5faf6de06..13ee3951c80fae3bb0a3f8391133b53aec57bf79 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue @@ -53,7 +53,6 @@ class="ml-2 mr-2" @click.stop="pick" /> </v-toolbar> - <TimeDrift /> <v-card v-if="error" variant="flat"> @@ -81,6 +80,7 @@ @close="pickVersion" /> </v-dialog> <v-dialog + v-if="loggedIn" v-model="addTupleDialog" persistent max-width="640"> @@ -91,6 +91,7 @@ @close="close" /> </v-dialog> <v-dialog + v-if="loggedIn" v-model="editTupleDialog" persistent max-width="640"> @@ -104,12 +105,13 @@ </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> import TableHistory from '@/components/table/TableHistory.vue' -import TimeDrift from '@/components/TimeDrift.vue' import TableToolbar from '@/components/table/TableToolbar.vue' import { formatTimestamp } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' import EditTuple from '@/components/dialogs/EditTuple.vue' import BlobDownload from '@/components/table/BlobDownload.vue' @@ -121,8 +123,7 @@ export default { BlobDownload, EditTuple, TableHistory, - TableToolbar, - TimeDrift + TableToolbar }, data () { return { @@ -179,31 +180,24 @@ export default { ], headers: [], rows: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, table () { return this.cacheStore.getTable }, - user () { - return this.userStore.getUser + roles () { + return this.cacheStore.getRoles }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + cacheUser () { + return this.cacheStore.getUser }, title () { return (this.version ? this.$t('toolbars.database.history') : this.$t('toolbars.database.current')) + ' ' + this.versionFormatted @@ -229,15 +223,6 @@ export default { } return this.version.substring(0, 10) + 'T' + this.version.substring(11, 19) + 'Z' }, - canModify () { - if (!this.user || !this.access || !this.table) { - return false - } - if (this.access.type === 'write_own' && this.table.owner.id === this.user.id) { - return true - } - return this.access.type === 'write_all' - }, primaryKeyColumns () { if (!this.table) { return [] @@ -245,47 +230,45 @@ export default { return this.table.constraints.primary_key.map(pk => pk.column) }, canViewTableData () { - if (this.error) { - return false - } if (!this.table) { return false } if (this.table.is_public) { return true } - if (!this.roles || !this.roles.includes('view-table-data')) { + if (!this.roles || !this.roles.includes('view-table-data') || !this.access) { return false } - return this.hasReadAccess + const userService = useUserService() + return userService.hasReadAccess(this.access) }, canAddTuple () { if (!this.roles) { return false } const userService = useUserService() - return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('insert-table-data') + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) && this.roles.includes('insert-table-data') }, canSelectTuples () { if (!this.roles) { return false } const userService = useUserService() - return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('insert-table-data') + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) && this.roles.includes('insert-table-data') }, canEditTuple () { if (!this.roles || this.selection === null || this.selection.length !== 1) { return false } const userService = useUserService() - return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('insert-table-data') + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) && this.roles.includes('insert-table-data') }, canDeleteTuple () { if (!this.roles || this.selection === null || this.selection.length < 1) { return false } const userService = useUserService() - return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('delete-table-data') + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) && this.roles.includes('delete-table-data') } }, watch: { diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue index 4cf0057ea429438ca08282a63ac27f182f80baf0..efbcd6accf7d3d896769241a7de0d1325a30b3a4 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue @@ -30,7 +30,6 @@ <script> import TableImport from '@/components/table/TableImport.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -69,19 +68,21 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser + table () { + return this.cacheStore.getTable }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - table () { - return this.cacheStore.getTable + cacheUser () { + return this.cacheStore.getUser + }, + access () { + return this.cacheStore.getAccess }, title () { if (!this.table) { @@ -90,10 +91,11 @@ export default { return this.$t('pages.table.import.title') + ' ' + this.table.name }, canInsertTableData () { - if (!this.roles) { + if (!this.table || !this.access || !this.cacheUser || !this.roles || !this.roles.includes('insert-table-data')) { return false } - return this.roles.includes('insert-table-data') + const userService = useUserService() + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) } } } diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue index 916c72b85052ce54c71d0bfa3cbe67708d606b0a..687358f0290177012dc89f36e23666d6dd6d48d0 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue @@ -1,15 +1,14 @@ <template> <div - v-if="canViewSchema"> + v-if="identifier || canViewInfo"> <TableToolbar :selection="selection" /> <v-card + v-if="identifier" variant="flat"> <Summary - v-if="hasIdentifier" :identifier="identifier" /> - <v-card-text - v-if="hasIdentifier"> + <v-card-text> <Select :identifiers="identifiers" :identifier="identifier" /> @@ -18,6 +17,7 @@ <v-divider v-if="identifier" /> <v-card + v-if="canViewInfo" variant="flat" rounded="0" :title="$t('pages.table.title')"> @@ -64,7 +64,7 @@ :title="$t('pages.table.owner.title')"> <UserBadge :user="table.owner" - :other-user="user" /> + :other-user="cacheUser" /> </v-list-item> </v-list> </v-card-text> @@ -118,12 +118,29 @@ </div> </template> +<script setup> +import { ref } from 'vue' + +const config = useRuntimeConfig() +const { pid } = useRoute().query +const { database_id, table_id } = useRoute().params +const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&tid=${table_id}&type=table&status=published`) + +if (data.value && data.value.length > 0) { + const identifierService = useIdentifierService() + useServerHead(identifierService.identifiersToServerHead(data.value)) + useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value)) +} +const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null) + +const cacheStore = useCacheStore() +cacheStore.setIdentifier(identifier) +</script> <script> import TableToolbar from '@/components/table/TableToolbar.vue' import Select from '@/components/identifier/Select.vue' import Summary from '@/components/identifier/Summary.vue' import UserBadge from '@/components/user/UserBadge.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -165,7 +182,6 @@ export default { loading: false, exchange: null, queue: null, - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -173,50 +189,47 @@ export default { pid () { return this.$route.query.pid }, - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, table () { return this.cacheStore.getTable }, + cacheUser () { + return this.cacheStore.getUser + }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, canRead () { if (this.database && this.database.is_public) { return true } - if (!this.user || !this.access) { + if (!this.access) { return false } - return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' + const userService = useUserService() + return userService.hasReadAccess(this.access) }, - canViewSchema () { - if (this.error) { - return false - } + canViewInfo () { if (!this.table) { return false } - if (this.table.is_schema_public || this.table.is_public) { + if (this.table.is_public || this.table.is_schema_public) { return true } - if (!this.user) { + if (!this.access) { return false } - return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id + const userService = useUserService() + return userService.hasReadAccess(this.access) }, canWrite () { - if (!this.table || !this.user || !this.access) { - return false - } - return (this.access.type === 'write_own' && this.table.owned_by === this.user.id) || this.access.type === 'write_all' + const userService = useUserService() + return userService.hasWriteAccess(this.table, this.access, this.cacheUser) }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, hasDescription () { return this.table && this.table.description @@ -228,31 +241,10 @@ export default { return this.roles.includes('insert-table-data') }, identifiers () { - if (!this.table || !this.table.identifiers || this.table.identifiers.length === 0) { - return [] - } - return this.table.identifiers - }, - filteredIdentifiers () { - if (!this.identifiers) { + if (!this.table || !this.table.identifiers) { return [] } - if (!this.user) { - return this.identifiers.filter(i => i.status === 'published') - } - return this.identifiers.filter(i => i.status === 'published' || i.owned_by === this.user.id) - }, - identifier () { - if (this.pid) { - const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid)) - if (filter.length > 0) { - return filter[0] - } - } - return this.filteredIdentifiers[0] - }, - hasIdentifier () { - return this.identifier + return this.table.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id)) }, brokerExtraInfo () { return this.$config.public.broker.extra diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue index 663b2a5550d7875e1da0c3ed86fab1f24f359350..e2d16e8db44efb0f558fa2c42e1225fc9e6b8355 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canCreateIdentifier || canUpdateIdentifier"> + v-if="canPersistIdentifier || canUpdateIdentifier"> <Persist type="table" :database="database" /> <v-breadcrumbs :items="items" class="pa-0 mt-2" /> </div> @@ -8,7 +8,6 @@ <script> import Persist from '@/components/identifier/Persist.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -45,34 +44,56 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { + database () { + return this.cacheStore.getDatabase + }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - user () { - return this.userStore.getUser + table () { + return this.cacheStore.getTable }, - database () { - return this.cacheStore.getDatabase + cacheUser () { + return this.cacheStore.getUser + }, + access () { + return this.cacheStore.getAccess }, - canCreateIdentifier () { - if (!this.roles) { + identifier () { + if (!this.table) { + return false + } + const filter = this.table.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id)) + return filter.length === 1 ? filter[0] : null + }, + canPersistIdentifier () { + if (!this.table || !this.roles || !this.cacheUser || !this.access) { return false } if (this.roles.includes('create-foreign-identifier')) { return true } - return this.roles.includes('create-identifier') + if (!this.roles.includes('create-identifier')) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.table.owner.id === this.cacheUser.uid }, canUpdateIdentifier () { - if (!this.roles) { + if (!this.identifier || !this.roles) { + return false + } + if (this.roles.includes('modify-identifier-metadata')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } - return this.roles.includes('modify-identifier-metadata') + return this.identifier.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue index 250fedaa5d4676dc6c304d3d71aa9b3a23b21ffc..6c26187fa23ff0669bad399b57e3ab48b15ec061 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canPersistTable"> + v-if="canPersistIdentifier"> <Persist type="table" :database="database" @@ -13,7 +13,6 @@ <script> import Persist from '@/components/identifier/Persist.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -48,29 +47,37 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, table () { return this.cacheStore.getTable }, - canPersistTable () { - if (!this.table) { + roles () { + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser + }, + canPersistIdentifier () { + if (!this.table || !this.roles || !this.cacheUser || !this.access) { + return false + } + if (this.roles.includes('create-foreign-identifier')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } const userService = useUserService() - return userService.hasReadAccess(this.access) + return userService.hasReadAccess(this.access) && this.table.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue index 0c6a60e15b9c052656a61e9d71ec3fcd3d944cb2..ac48b40644823b9393b26608a0cfa5e56790d03a 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue @@ -120,9 +120,11 @@ </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> import TableToolbar from '@/components/table/TableToolbar.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -168,14 +170,10 @@ export default { { value: 'description', title: this.$t('pages.table.subpages.schema.description.title') }, ], dateColumns: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -183,7 +181,13 @@ export default { return this.cacheStore.getTable }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess + }, + cacheUser () { + return this.cacheStore.getUser + }, + roles () { + return this.cacheStore.getRoles }, hasReadAccess () { if (!this.access) { @@ -191,29 +195,24 @@ export default { } return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' }, - roles () { - return this.userStore.getRoles - }, canViewSchema () { - if (this.error) { - return false - } if (!this.table) { return false } if (this.table.is_schema_public) { return true } - if (!this.user) { + if (!this.access) { return false } - return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id + const userService = useUserService() + return userService.hasReadAccess(this.access) }, primaryKeysColumns () { return this.table.constraints.primary_key.map(pk => pk.column.internal_name).join(', ') }, canAssignSemanticInformation () { - if (!this.user) { + if (!this.cacheUser || !this.roles) { return false } if (this.roles.includes('modify-foreign-table-column-semantics')) { @@ -222,7 +221,7 @@ export default { if (!this.access) { return false } - return this.roles.includes('modify-table-column-semantics') && (this.access.type === 'write_all' || this.table.owner.username === this.user.username) + return this.roles.includes('modify-table-column-semantics') && (this.access.type === 'write_all' || this.table.owner.id === this.cacheUser.uid) }, inputVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue index f9530711363fb38884ac05dcaba3d28e1b6ee82a..0f0a8feab62a7fb065c3ecb6af6b63b83a9884be 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue @@ -3,7 +3,6 @@ v-if="canUpdateTable"> <TableToolbar /> <v-window - v-if="user" v-model="tab"> <v-window-item> <v-form @@ -23,9 +22,6 @@ <v-textarea v-model="modify.description" rows="2" - :rules="[ - v => max(v, 180) || ($t('validation.max-length') + 180), - ]" clearable counter="180" persistent-counter @@ -116,7 +112,6 @@ <script> import TableToolbar from '@/components/table/TableToolbar.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' import { max } from '@/utils' @@ -127,7 +122,7 @@ export default { data () { return { tab: 0, - valid: false, + valid: true, loading: false, modify: { description: null, @@ -175,14 +170,10 @@ export default { { value: 'description', title: this.$t('pages.table.subpages.schema.description.title') }, ], dateColumns: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -190,47 +181,39 @@ export default { return this.cacheStore.getTable }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + cacheUser () { + return this.cacheStore.getUser }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, isChange () { if (!this.table) { return false } - if (this.table.is_public !== this.modify.is_public) { + if (this.table.is_public !== this.modify.is_public || this.table.is_schema_public !== this.modify.is_schema_public) { return true } - return this.table.is_schema_public !== this.modify.is_schema_public + return this.table.description !== this.modify.description }, canUpdateTable () { - if (!this.roles || !this.user || !this.table) { - return false - } - return this.roles.includes('update-table') && this.table.owner.id === this.user.id - }, - canModifyVisibility () { - if (!this.roles || !this.user || !this.table) { + if (!this.cacheUser || !this.table || !this.access || !this.roles || !this.roles.includes('update-table')) { return false } - return this.roles.includes('update-table') && this.table.owner.id === this.user.id + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.table.owner.id === this.cacheUser.uid }, canDropTable () { - if (!this.roles || !this.table || !this.user) { + if (!this.roles || !this.table || !this.cacheUser) { return false } if (this.roles.includes('delete-foreign-table')) { return true } const tableService = useTableService() - return tableService.isOwner(this.table, this.user) && this.roles.includes('delete-table') && this.table.identifiers.length === 0 + return tableService.isOwner(this.table, this.cacheUser) && this.roles.includes('delete-table') && this.table.identifiers.length === 0 }, inputVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue index df74cc6e7046e90f6e5e9844769a2e1e8f45e372..24aed7f2ffc6b77edfae4a90e1c4dc7607f97955 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue @@ -92,9 +92,6 @@ <v-textarea v-model="tableCreate.description" rows="2" - :rules="[ - v => (!!v || v.length <= 180) || ($t('validation.max-length') + 180), - ]" clearable counter="180" persistent-counter @@ -219,10 +216,9 @@ </div> </template> -<script> + <script> import TableSchema from '@/components/table/TableSchema.vue' import { notEmpty } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -304,7 +300,6 @@ export default { loading: false, url: null, columns: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -316,15 +311,12 @@ export default { this.tableCreate.is_schema_public = this.database.is_schema_public }, computed: { - user() { - return this.userStore.getUser - }, - roles() { - return this.userStore.getRoles - }, database() { return this.cacheStore.getDatabase }, + roles () { + return this.cacheStore.getRoles + }, generatedTableName() { if (!this.tableCreate.name) { return null diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue index c900ba31aa104dcc1ce5e3250dd0bb5a2b92f6fa..804ae03c15072719f7612fd46eeca80d45b1a2b8 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue @@ -70,9 +70,6 @@ <v-textarea v-model="tableCreate.description" rows="2" - :rules="[ - v => (!v || v.length <= 180) || $t('validation.max-length') + 180 - ]" clearable counter="180" persistent-counter @@ -188,7 +185,6 @@ <script> import TableSchema from '@/components/table/TableSchema.vue' import { notEmpty } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -247,7 +243,6 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, @@ -259,15 +254,12 @@ export default { const tableService = useTableService() return tableService.tableNameToInternalName(this.tableCreate.name) }, - roles () { - return this.userStore.getRoles - }, - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, + roles () { + return this.cacheStore.getRoles + }, canCreateTable () { if (!this.roles) { return false diff --git a/dbrepo-ui/pages/database/[database_id]/table/index.vue b/dbrepo-ui/pages/database/[database_id]/table/index.vue index ae3e3c13f1b173b62514e1ee8887fd648e3f65a1..c2a2b76206918a0882c7e041aa3f38fb19ccfe12 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/index.vue @@ -48,11 +48,18 @@ export default { database () { return this.cacheStore.getDatabase }, + access () { + return this.cacheStore.getAccess + }, canViewSchema () { - if (this.error) { + if (!this.database) { return false } - return this.database + if (this.database.is_schema_public) { + return true + } + const userService = useUserService() + return userService.hasReadAccess(this.access) } } } diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue index 54c54dad00d4764383c8d38e979870391680d563..f732661f369710b4637f01952f5fab930e9e4812 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue @@ -22,7 +22,6 @@ :loading="loadingData" @click="reload" /> </v-toolbar> - <TimeDrift /> <v-card tile> <QueryResults id="query-results" @@ -35,14 +34,11 @@ </template> <script> -import TimeDrift from '@/components/TimeDrift.vue' import QueryResults from '@/components/subset/Results.vue' -import { useUserStore } from '@/stores/user.js' export default { components: { - QueryResults, - TimeDrift + QueryResults }, data () { return { @@ -70,14 +66,10 @@ export default { disabled: true } ], - cacheStore: useCacheStore(), - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -85,13 +77,10 @@ export default { return this.cacheStore.getView }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' + cacheUser () { + return this.cacheStore.getUser }, canReadData () { if (!this.view) { @@ -100,10 +89,11 @@ export default { if (this.view.is_public) { return true } - if (!this.user) { + if (!this.access) { return false } - return this.view.owner.id === this.user.id || this.hasReadAccess + const userService = useUserService() + return userService.hasReadAccess(this.access) }, }, mounted () { diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue index c9865a1b4abbefd3bd07361b7c6a5a5ff86b0834..3c0c40e33ce7546e351ea3252a051a0ebb56049e 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue @@ -1,25 +1,25 @@ <template> <div - v-if="canViewView"> + v-if="identifier || canViewInfo"> <ViewToolbar /> <v-window v-model="tab"> - <v-window-item - v-if="view"> + <v-window-item> <v-card variant="flat"> <Summary - v-if="hasIdentifier" + v-if="identifier" :identifier="identifier" /> <v-card-text - v-if="hasIdentifier"> + v-if="identifier"> <Select :identifiers="identifiers" :identifier="identifier" /> </v-card-text> </v-card> <v-divider - v-if="hasIdentifier" /> + v-if="identifier" /> <v-card + v-if="canViewInfo" :title="$t('pages.view.title')" variant="flat"> <v-card-text> @@ -39,7 +39,7 @@ <UserBadge v-if="view" :user="view.owner" - :other-user="user" /> + :other-user="cacheUser" /> <v-skeleton-loader v-else type="subtitle" @@ -59,13 +59,30 @@ </div> </template> +<script setup> +import { ref } from 'vue' + +const config = useRuntimeConfig() +const { pid } = useRoute().query +const { database_id, view_id } = useRoute().params +const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&vid=${view_id}&type=view&status=published`) + +if (data.value && data.value.length > 0) { + const identifierService = useIdentifierService() + useServerHead(identifierService.identifiersToServerHead(data.value)) + useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value)) +} +const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null) + +const cacheStore = useCacheStore() +cacheStore.setIdentifier(identifier) +</script> <script> import ViewToolbar from '@/components/view/ViewToolbar.vue' import Summary from '@/components/identifier/Summary.vue' import Select from '@/components/identifier/Select.vue' import UserBadge from '@/components/user/UserBadge.vue' import { formatTimestampUTCLabel } from '@/utils' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -103,55 +120,30 @@ export default { } ], error: false, - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, view () { return this.cacheStore.getView }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + roles () { + return this.cacheStore.getRoles }, - identifiers () { - if (!this.view) { - return [] - } - return this.view.identifiers + cacheUser () { + return this.cacheStore.getUser }, - filteredIdentifiers () { - if (!this.identifiers) { + identifiers () { + if (!this.view || !this.view.identifiers) { return [] } - if (!this.user) { - return this.identifiers.filter(i => i.status === 'published') - } - return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.user.id) - }, - identifier () { - if (this.pid) { - const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid)) - if (filter.length > 0) { - return filter[0] - } - } - return this.filteredIdentifiers[0] + return this.view.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id)) }, views () { if (!this.database) { @@ -162,9 +154,6 @@ export default { pid () { return this.$route.query.pid }, - hasIdentifier () { - return this.identifier - }, creator () { if (!this.view) { return null @@ -172,17 +161,18 @@ export default { const userService = useUserService() return userService.userToFullName(this.view.creator) }, - canViewView () { + canViewInfo () { if (!this.view) { return false } if (this.view.is_public) { return true } - if (!this.user) { + if (!this.access) { return false } - return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id + const userService = useUserService() + return userService.hasReadAccess(this.access) } }, methods: { diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue index 8c1f24c2a166150bd1dc3903dfa984c435815f9b..540bbbdb5e454c725c6d5b9dc6a1da6498e5f8bc 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canCreateIdentifier || canUpdateIdentifier"> + v-if="canPersistIdentifier || canUpdateIdentifier"> <Persist type="view" :database="database" /> @@ -10,7 +10,6 @@ <script> import Persist from '@/components/identifier/Persist.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -47,34 +46,56 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { + database () { + return this.cacheStore.getDatabase + }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - user () { - return this.userStore.getUser + view () { + return this.cacheStore.getView }, - database () { - return this.cacheStore.getDatabase + access () { + return this.cacheStore.getAccess + }, + cacheUser () { + return this.cacheStore.getUser }, - canCreateIdentifier () { - if (!this.roles) { + identifier () { + if (!this.view) { + return false + } + const filter = this.view.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id)) + return filter.length === 1 ? filter[0] : null + }, + canPersistIdentifier () { + if (!this.view || !this.roles || !this.cacheUser || !this.access) { return false } if (this.roles.includes('create-foreign-identifier')) { return true } - return this.roles.includes('create-identifier') + if (!this.roles.includes('create-identifier')) { + return false + } + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.view.owner.id === this.cacheUser.uid }, canUpdateIdentifier () { - if (!this.roles) { + if (!this.identifier || !this.roles) { + return false + } + if (this.roles.includes('modify-identifier-metadata')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } - return this.roles.includes('modify-identifier-metadata') + return this.identifier.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue index b76d076645e05ef7123215c578d20b489924d523..ed8067d21327f2e0a069733fb9873b7912a5a32e 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue @@ -1,6 +1,6 @@ <template> <div - v-if="canPersistView"> + v-if="canPersistIdentifier"> <Persist type="view" :database="database" @@ -11,7 +11,6 @@ <script> import Persist from '@/components/identifier/Persist.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -45,32 +44,37 @@ export default { disabled: true } ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - roles () { - return this.userStore.getRoles - }, database () { return this.cacheStore.getDatabase }, + access () { + return this.cacheStore.getAccess + }, + cacheUser () { + return this.cacheStore.getUser + }, view () { - if (!this.database) { - return null - } - return this.database.views.filter(v => v.id === Number(this.$route.params.view_id))[0] + return this.cacheStore.getView }, - access () { - return this.userStore.getAccess + roles () { + return this.cacheStore.getRoles }, - canPersistView () { - if (!this.view) { + canPersistIdentifier () { + if (!this.view || !this.roles || !this.cacheUser || !this.access) { + return false + } + if (this.roles.includes('create-foreign-identifier')) { + return true + } + if (!this.roles.includes('create-identifier')) { return false } const userService = useUserService() - return userService.hasReadAccess(this.access) + return userService.hasReadAccess(this.access) && this.view.owner.id === this.cacheUser.uid } } } diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue index 477654f656c9dbc5361315d323b8cc638add6ca9..5b35faf3a5250c712a6e9b7f442a6e779863b42c 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue @@ -53,7 +53,6 @@ <script> import TableToolbar from '@/components/table/TableToolbar.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -95,14 +94,10 @@ export default { { value: 'is_null_allowed', title: this.$t('pages.table.subpages.schema.nullable.title') }, { value: 'description', title: this.$t('pages.table.subpages.schema.description.title') }, ], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -110,13 +105,13 @@ export default { return this.cacheStore.getView }, access () { - return this.userStore.getAccess + return this.cacheStore.getAccess }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + roles () { + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser }, canViewSchema () { if (!this.view) { @@ -125,13 +120,11 @@ export default { if (this.view.is_schema_public) { return true } - if (!this.user) { + if (!this.access) { return false } - return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id - }, - roles () { - return this.userStore.getRoles + const userService = useUserService() + return userService.hasReadAccess(this.access) }, inputVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue index 18d73d05768d1a45b36eed9a58ea7841235554b3..d027a4347b5300025f83d23adba52d78278a443f 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue @@ -92,7 +92,6 @@ <script> import ViewToolbar from '@/components/view/ViewToolbar.vue' -import { useUserStore } from '@/stores/user.js' import { useCacheStore } from '@/stores/cache.js' export default { @@ -148,14 +147,10 @@ export default { { value: 'description', title: this.$t('pages.table.subpages.schema.description.title') }, ], dateColumns: [], - userStore: useUserStore(), cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser - }, database () { return this.cacheStore.getDatabase }, @@ -163,16 +158,13 @@ export default { return this.cacheStore.getView }, access () { - return this.userStore.getAccess - }, - hasReadAccess () { - if (!this.access) { - return false - } - return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + return this.cacheStore.getAccess }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles + }, + cacheUser () { + return this.cacheStore.getUser }, isChange () { if (!this.view) { @@ -184,22 +176,23 @@ export default { return this.view.is_schema_public !== this.modify.is_schema_public }, canUpdateVisibility () { - if (!this.roles || !this.user || !this.view) { + if (!this.roles || !this.cacheUser || !this.view) { return false } - return this.roles.includes('modify-view-visibility') && this.view.owner.id === this.user.id + return this.roles.includes('modify-view-visibility') && this.view.owner.id === this.cacheUser.uid }, canDeleteView () { - if (!this.roles || !this.user || !this.view) { + if (!this.roles || !this.cacheUser || !this.view) { return false } - return this.roles.includes('delete-database-view') && this.view.owner.id === this.user.id + return this.roles.includes('delete-database-view') && this.view.owner.id === this.cacheUser.uid }, canViewSettings () { - if (!this.user || !this.view) { + if (!this.view || !this.access || !this.cacheUser) { return false } - return this.view.owner.id === this.user.id + const userService = useUserService() + return userService.hasReadAccess(this.access) && this.view.owner.id === this.cacheUser.uid }, inputVariant () { const runtimeConfig = useRuntimeConfig() diff --git a/dbrepo-ui/pages/database/[database_id]/view/create.vue b/dbrepo-ui/pages/database/[database_id]/view/create.vue index 47b56cfbad81968d056d028cc48758d250efc612..a834bdb5c90340d247dbb3df880acbec8dc4a08f 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/create.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/create.vue @@ -8,7 +8,7 @@ <script> import Builder from '@/components/subset/Builder.vue' -import { useUserStore } from '@/stores/user.js' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -35,21 +35,22 @@ export default { disabled: true } ], - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser + access () { + return this.cacheStore.getAccess }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, canCreateView () { - if (!this.roles) { + if (!this.roles || !this.roles.includes('create-database-view')) { return false } - return this.roles.includes('create-database-view') + const userService = useUserService() + return userService.hasReadAccess(this.access) } } } diff --git a/dbrepo-ui/pages/database/[database_id]/view/index.vue b/dbrepo-ui/pages/database/[database_id]/view/index.vue index 64f777dce3f905f19d2ff8f16e5e4b3de80fd277..b2a2c17a1afe0553777e21e593b8865bfb63efc9 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/index.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/index.vue @@ -48,11 +48,18 @@ export default { database () { return this.cacheStore.getDatabase }, + access () { + return this.cacheStore.getAccess + }, canViewSchema () { - if (this.error) { + if (!this.database) { return false } - return this.database + if (this.database.is_schema_public) { + return true + } + const userService = useUserService() + return userService.hasReadAccess(this.access) } } } diff --git a/dbrepo-ui/pages/index.vue b/dbrepo-ui/pages/index.vue index 1c30c25e5c7e9edfd52861ca568c492ce39f13a7..9e5e20186dcdd58ab66662b5aca827e2682df121 100644 --- a/dbrepo-ui/pages/index.vue +++ b/dbrepo-ui/pages/index.vue @@ -30,7 +30,7 @@ <script> import DatabaseList from '@/components/database/DatabaseList.vue' import DatabaseCreate from '@/components/database/DatabaseCreate.vue' -import { useUserStore } from '@/stores/user.js' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -42,12 +42,12 @@ export default { loading: true, dialog: null, databases: [], - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, canCreateDatabase () { if (!this.roles) { @@ -57,17 +57,20 @@ export default { } }, mounted () { - this.loading = true - const databaseService = useDatabaseService(); - databaseService.findAll() - .then((databases) => { - this.databases = databases - this.loading = false - }) + this.fetchDatabases() }, methods: { closed () { this.dialog = false + }, + fetchDatabases () { + this.loading = true + const databaseService = useDatabaseService() + databaseService.findAll() + .then((databases) => { + this.databases = databases + this.loading = false + }) } } } diff --git a/dbrepo-ui/pages/login.vue b/dbrepo-ui/pages/login.vue deleted file mode 100644 index 532a221c4823cfd8be39fd53e94c34426311260a..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/login.vue +++ /dev/null @@ -1,160 +0,0 @@ -<template> - <div> - <v-toolbar - v-if="!user" - variant="flat" - :title="$t('pages.login.name')"> - </v-toolbar> - <v-card - rounded="0" - variant="flat"> - <v-card-text> - <v-form - v-if="!user" - ref="form" - v-model="valid" - @submit.prevent="submit"> - <v-row - dense> - <v-col - md="8"> - <v-text-field - v-model="username" - autocomplete="off" - autofocus - required - name="username" - persistent-hint - :rules="[v => !!v || $t('validation.required')]" - :label="$t('pages.login.username.label')" - :hint="$t('pages.login.username.hint')"/> - </v-col> - </v-row> - <v-row - dense> - <v-col - md="8"> - <v-text-field - v-model="password" - autocomplete="off" - type="password" - required - name="password" - persistent-hint - :rules="[v => !!v || $t('validation.required')]" - :label="$t('pages.login.password.label')" - :hint="$t('pages.login.password.hint')"/> - </v-col> - </v-row> - <v-row> - <v-col - md="8"> - <v-btn - id="login" - class="mb-2" - :disabled="!valid" - color="primary" - variant="flat" - type="submit" - name="submit" - :loading="loading" - :text="$t('pages.login.submit.label')" - @click="login"/> - </v-col> - </v-row> - </v-form> - </v-card-text> - <v-card-actions> - <v-spacer/> - <v-btn - v-for="(link, i) in loginLinks" - :key="`li-${i}`" - variant="plain" - size="small" - :text="link.text" - :href="link.href"/> - </v-card-actions> - </v-card> - </div> -</template> - -<script> -import {useUserStore} from '@/stores/user.js' - -export default { - data() { - return { - loading: false, - valid: false, - username: null, - password: null, - userStore: useUserStore() - } - }, - computed: { - user() { - return this.userStore.getUser - }, - loginLinks() { - if (!this.$config.public.links) { - return [] - } - return Object.keys(this.$config.public.links).map(key => { - return this.$config.public.links[key] - }) - } - }, - methods: { - submit() { - this.$refs.form.validate() - }, - login() { - this.loading = true - const userService = useUserService() - userService.obtainToken(this.username, this.password) - .then((data) => { - const userId = userService.tokenToUserId(data.access_token) - userService.findOne(userId) - .then((user) => { - const toast = useToastInstance() - toast.success(this.$t('success.user.login', { username : user.username })) - switch (user.attributes.theme) { - case 'dark': - this.$vuetify.theme.global.name = 'tuwThemeDark' - break - case 'light': - this.$vuetify.theme.global.name = 'tuwThemeLight' - break - case 'light-contrast': - this.$vuetify.theme.global.name = 'tuwThemeLightContrast' - break - case 'dark-contrast': - this.$vuetify.theme.global.name = 'tuwThemeDarkContrast' - break - } - this.userStore.setUser(user) - this.$router.push('/database') - }) - .catch(({code}) => { - const toast = useToastInstance() - if (typeof code !== 'string') { - return - } - toast.error(this.$t(code)) - }) - }) - .catch(({code}) => { - this.loading = false - const toast = useToastInstance() - if (typeof code !== 'string') { - return - } - toast.error(this.$t(code)) - }) - .finally(() => { - this.loading = false - }) - } - } -} -</script> diff --git a/dbrepo-ui/pages/search.vue b/dbrepo-ui/pages/search.vue index 15475b212bc724e0e55d9f0765461990e574618e..507f81b731407a3059e61f7956368d25481de9a0 100644 --- a/dbrepo-ui/pages/search.vue +++ b/dbrepo-ui/pages/search.vue @@ -65,7 +65,6 @@ <script> import AdvancedSearch from '@/components/search/AdvancedSearch.vue' -import { useUserStore } from '@/stores/user.js' export default { components: { @@ -75,14 +74,10 @@ export default { return { results: [], type: 'database', - loading: false, - userStore: useUserStore() + loading: false } }, computed: { - roles () { - return this.userStore.getRoles - }, q () { if (!this.$route.query || !this.$route.query.q) { return null @@ -92,12 +87,6 @@ export default { header () { return `${this.results.length} ${this.results.length !== 1 ? this.$t('toolbars.search.results') : this.$t('toolbars.search.result')}` }, - canCreateDatabase () { - if (!this.roles) { - return false - } - return this.roles.includes('create-database') - }, isDatabaseSearch () { return this.type === 'database' } diff --git a/dbrepo-ui/pages/semantic/index.vue b/dbrepo-ui/pages/semantic/index.vue deleted file mode 100644 index db555b4c0d4778401e6988015b1064cc8bd0c82e..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/semantic/index.vue +++ /dev/null @@ -1,194 +0,0 @@ -<template> - <div v-if="canListOntologies"> - <v-toolbar flat> - <v-toolbar-title> - {{ $t('pages.semantics.title') }} - </v-toolbar-title> - <v-spacer /> - <v-btn - v-if="canListOntologies" - to="/semantic/ontology" - variant="flat" - :text="ontologies.length + ' ' + $t('toolbars.semantic.ontologies.text')" - color="secondary" /> - <template v-slot:extension> - <v-tabs - v-model="tab" - color="primary"> - <v-tab> - {{ $t('toolbars.semantic.ontologies.concepts') }} - </v-tab> - <v-tab> - {{ $t('toolbars.semantic.ontologies.units') }} - </v-tab> - </v-tabs> - </template> - </v-toolbar> - <v-card flat> - <v-card-text> - <v-data-table - :headers="headers" - :items="rows" - :options.sync="options" - :server-items-length="total" - :footer-props="footerProps" - :items-per-page-options="footerProps.itemsPerPageOptions"> - <template v-slot:item.uri="{ item }"> - <a :href="item.uri" - target="_blank"> - {{ item.uri }} - </a> - </template> - <template v-slot:item.action="{ item }"> - <v-btn - small - :disabled="disabled(item)" - :text="$t('pages.semantics.usages.text')" - @click="view(item)" /> - </template> - </v-data-table> - </v-card-text> - </v-card> - <v-dialog - v-model="viewSemanticEntityDialog" - max-width="640"> - <ViewSemanticEntity - :mode="mode" - :entity="entity" - @close="close" /> - </v-dialog> - <v-breadcrumbs - :items="items" - class="pa-0 mt-2" /> - </div> -</template> - -<script> -import ViewSemanticEntity from '@/components/dialogs/ViewSemanticEntity.vue' -import { useUserStore } from '@/stores/user.js' -import { useCacheStore } from '@/stores/cache.js' - -export default { - components: { - ViewSemanticEntity - }, - data () { - return { - loadingConcepts: false, - loadingUnits: false, - entity: null, - viewSemanticEntityDialog: false, - headers: [ - { text: 'URI', value: 'uri' }, - { text: 'Name', value: 'name' }, - { text: 'Description', value: 'description' }, - { text: 'Usages', value: 'usages' }, - { text: null, value: 'action' } - ], - options: { - page: 1, - itemsPerPage: 10 - }, - total: -1, - footerProps: { - itemsPerPageOptions: [10, 25, 50, 100] - }, - tab: 0, - tabs: [ - 'concepts', 'units' - ], - concepts: [], - units: [], - createOntologyDialog: false, - items: [ - { - title: `${this.$t('navigation.semantics')}`, - to: '/semantic' - } - ], - userStore: useUserStore(), - cacheStore: useCacheStore() - } - }, - computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, - ontologies () { - return this.cacheStore.getOntologies - }, - rows () { - return this.tab === 0 ? this.concepts : this.units - }, - mode () { - return this.tab === 0 ? 'concept' : 'unit' - }, - canListOntologies () { - if (!this.roles) { - return false - } - return this.roles.includes('list-ontologies') - } - }, - mounted () { - this.loadUnits() - this.loadConcepts() - }, - methods: { - loadConcepts () { - this.loadingConcepts = true - const conceptService = useConceptService() - conceptService.findAll() - .then((concepts) => { - concepts = concepts.map((column) => { - column.usages = column.columns.length - return column - }) - this.concepts = concepts - }) - .catch(() => { - this.loadingConcepts = false - }) - .finally(() => { - this.loadingConcepts = false - }) - }, - loadUnits () { - this.loadingUnits = true - const unitService = useUnitService() - unitService.findAll() - .then((units) => { - units = units.map((unit) => { - unit.usages = unit.columns.length - return unit - }) - this.units = units - }) - .catch(() => { - this.loadingUnits = false - }) - .finally(() => { - this.loadingUnits = false - }) - }, - disabled (item) { - return !item.usages || this.usages === 0 - }, - view (entity) { - this.entity = entity - this.viewSemanticEntityDialog = true - }, - close (event) { - if (this.mode === 'unit') { - this.loadUnits() - } else if (this.mode === 'concept') { - this.loadConcepts() - } - this.viewSemanticEntityDialog = false - } - } -} -</script> diff --git a/dbrepo-ui/pages/semantic/ontology/_ontology_id/index.vue b/dbrepo-ui/pages/semantic/ontology/_ontology_id/index.vue deleted file mode 100644 index 14749460c931c70c8430a28e51a7aa3c13d26ed1..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/semantic/ontology/_ontology_id/index.vue +++ /dev/null @@ -1,273 +0,0 @@ -<template> - <div - v-if="canListOntologies"> - <v-toolbar flat> - <v-toolbar-title> - <v-btn - id="back-btn" - plain - class="mr-2" - to="/semantic/ontology"> - <v-icon left>mdi-arrow-left</v-icon> - </v-btn> - </v-toolbar-title> - <v-toolbar-title> - <v-skeleton-loader - v-if="loading" - type="text" - class="skeleton-small" /> - <span v-if="!loading"> - Ontology - <a - v-if="ontology" - :href="ontology.uri" - target="_blank"> - {{ ontology.uri }} - </a> - </span> - </v-toolbar-title> - <v-spacer /> - <v-toolbar-title> - <v-btn - v-if="canDeleteOntology" - :loading="loadingDelete" - color="error" - @click="deleteOntology"> - Delete Ontology - </v-btn> - </v-toolbar-title> - </v-toolbar> - <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit"> - <v-card v-if="ontology" variant="flat"> - <v-card-text> - <v-row dense> - <v-col cols="6"> - <v-text-field - id="prefix" - v-model="ontologyChangeDto.prefix" - name="prefix" - label="Prefix *" - hint="Only lowercase alphanumeric letters, max. 8" - autofocus - :rules="[ - v => notEmpty(v) || $t('validation.required'), - v => validPrefix(v) || $t('validation.prefix.pattern'), - v => validPrefixLength(v,1,8) || $t('validation.prefix.length'), - v => validPrefixNotExists(v) || $t('validation.prefix.exists') - ]" - required /> - </v-col> - </v-row> - <v-row dense> - <v-col cols="6"> - <v-text-field - id="uri" - v-model="ontologyChangeDto.uri" - name="uri" - label="URI *" - :rules="[ - v => notEmpty(v) || $t('validation.required'), - v => validUri(v) || $t('validation.uri.pattern'), - v => validUriNotExists(v) || $t('validation.uri.exists') - ]" - required /> - </v-col> - </v-row> - <v-row dense> - <v-col cols="6"> - <v-text-field - id="sparql-endpoint" - v-model="ontologyChangeDto.sparql_endpoint" - name="sparql-endpoint" - label="SPARQL Endpoint" - :rules="[ - v => validUriOptional(v) || $t('validation.uri.pattern') - ]" /> - </v-col> - </v-row> - </v-card-text> - <v-card-actions> - <v-btn - id="createDB" - class="mb-2 ml-2" - :disabled="!valid || loading" - color="primary" - type="submit" - :loading="loading" - @click="save"> - Update - </v-btn> - </v-card-actions> - </v-card> - </v-form> - <v-breadcrumbs :items="items" class="pa-0 mt-2" /> - </div> -</template> - -<script> -import { notEmpty } from '@/utils' -import { useUserStore } from '@/stores/user.js' -import { useCacheStore } from '@/stores/cache.js' - -export default { - data () { - return { - loading: false, - loadingDelete: false, - ontology: null, - ontologyChangeDto: { - uri: null, - prefix: null, - sparql_endpoint: null - }, - valid: false, - createOntologyDialog: false, - items: [ - { text: `${this.$t('layout.semantics')}`, to: '/semantic', activeClass: '' }, - { text: `${this.$t('layout.ontologies')}`, to: '/semantic/ontology', activeClass: '' }, - { text: `${this.$route.params.ontology_id}`, to: `/semantic/ontology/${this.$route.params.ontology_id}`, activeClass: '' } - ], - userStore: useUserStore(), - cacheStore: useCacheStore() - } - }, - computed: { - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, - ontologies () { - return this.cacheStore.getOntologies - }, - canListOntologies () { - if (!this.roles) { - return false - } - return this.roles.includes('list-ontologies') - }, - canDeleteOntology () { - if (!this.roles) { - return false - } - return this.roles.includes('delete-ontology') - } - }, - mounted () { - this.loadOntology() - }, - methods: { - loadOntology () { - this.loading = true - const ontologyService = useOntologyService() - ontologyService.findOne(this.$route.params.ontology_id) - .then((ontology) => { - this.ontology = ontology - this.ontologyChangeDto = Object.assign({}, ontology) - }) - .catch(() => { - this.loading = false - }) - .finally(() => { - this.loading = false - }) - }, - deleteOntology () { - this.loadingDelete = true - const ontologyService = useOntologyService() - ontologyService.remove(this.$route.params.ontology_id) - .then(async () => { - // await this.$store.dispatch('reloadOntologies') - await this.$router.push('/semantic/ontology') - }) - .catch(() => { - this.loadingDelete = false - }) - .finally(() => { - this.loadingDelete = false - }) - }, - save () { - this.loading = true - const payload = { - uri: this.ontologyChangeDto.uri, - prefix: this.ontologyChangeDto.prefix, - sparql_endpoint: this.ontologyChangeDto.sparql_endpoint - } - const ontologyService = useOntologyService() - ontologyService.update(this.$route.params.ontology_id, payload) - .then(() => { - this.loadOntology() - // this.$store.dispatch('reloadOntologies') - const toast = useToastInstance() - toast.success('Successfully update ontology!') - }) - .catch(() => { - this.loading = false - }) - .finally(() => { - this.loading = false - }) - }, - validPrefix (str) { - if (!str) { - return false - } - return str.match(/[a-z0-9]+/g) - }, - validPrefixLength (str, min, max) { - if (!str) { - return false - } - return str.length > min && str.length <= max - }, - validPrefixNotExists (str) { - const ontologies = this.ontologies.filter(o => o.prefix === str) - if (ontologies && ontologies.length !== 0) { - /* same prefix is fine for the same ontology, but not for others */ - return ontologies[0].id === this.ontology.id - } - return !this.ontologies.map(o => o.prefix).includes(str) - }, - validUriNotExists (str) { - const ontologies = this.ontologies.filter(o => o.uri === str) - if (ontologies && ontologies.length !== 0) { - /* same uri is fine for the same ontology, but not for others */ - return ontologies[0].id === this.ontology.id - } - return !this.ontologies.map(o => o.uri).includes(str) - }, - validUriOptional (str) { - if (!str) { - return true - } - return this.validUri(str) - }, - validUri (str) { - if (!str) { - return false - } - return str.match(/^https?:\/\//g) - }, - close (event) { - if (event.success) { - // this.$store.dispatch('reloadOntologies') - } - this.createOntologyDialog = false - }, - submit () { - this.$refs.form.validate() - }, - notEmpty - } -} -</script> -<style> -.skeleton-medium > div { - width: 200px !important; -} -.skeleton-xsmall > div { - width: 50px !important; -} -</style> diff --git a/dbrepo-ui/pages/semantic/ontology/index.vue b/dbrepo-ui/pages/semantic/ontology/index.vue deleted file mode 100644 index 36898d90739c2d219e9f9987c02527e2409a676f..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/semantic/ontology/index.vue +++ /dev/null @@ -1,96 +0,0 @@ -<template> - <div v-if="canListOntologies"> - <v-toolbar flat> - <v-btn - variant="plain" - size="small" - icon="mdi-arrow-left" - to="/semantic" /> - <v-toolbar-title> - {{ ontologies.length + ' ' + $t('toolbars.semantic.ontologies.title') }} - </v-toolbar-title> - <v-spacer /> - <v-btn - v-if="canCreateOntology" - color="secondary" - variant="flat" - name="create-ontology" - prepend-icon="mdi-plus" - :text="$t('toolbars.semantic.ontology.text')" - @click.stop="createOntologyDialog = true" /> - </v-toolbar> - <OntologiesList /> - <v-dialog - v-model="createOntologyDialog" - persistent - max-width="640"> - <CreateOntology ref="ont" @close="close" /> - </v-dialog> - <v-breadcrumbs :items="items" class="pa-0 mt-2" /> - </div> -</template> - -<script> -import OntologiesList from '@/components/OntologiesList.vue' -import CreateOntology from '@/components/dialogs/CreateOntology.vue' -import { useUserStore } from '@/stores/user.js' -import { useCacheStore } from '@/stores/cache.js' - -export default { - components: { - OntologiesList, - CreateOntology - }, - data () { - return { - createOntologyDialog: false, - items: [ - { - title: `${this.$t('navigation.semantics')}`, - to: '/semantic' - }, - { - title: `${this.$t('navigation.ontologies')}`, - to: '/semantic/ontology' - } - ], - userStore: useUserStore(), - cacheStore: useCacheStore() - } - }, - computed: { - token () { - return this.userStore.getToken - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, - ontologies () { - return this.cacheStore.getOntologies - }, - canListOntologies () { - if (!this.roles) { - return false - } - return this.roles.includes('list-ontologies') - }, - canCreateOntology () { - if (!this.roles) { - return false - } - return this.roles.includes('create-ontology') - } - }, - methods: { - close (event) { - if (event.success) { - // this.$store.dispatch('reloadOntologies') - } - this.createOntologyDialog = false - } - } -} -</script> diff --git a/dbrepo-ui/pages/signup.vue b/dbrepo-ui/pages/signup.vue deleted file mode 100644 index 54c00602256c7815d8aff0b255214e66836bc727..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/signup.vue +++ /dev/null @@ -1,163 +0,0 @@ -<template> - <div> - <v-toolbar - :title="$t('pages.signup.name')" - flat /> - <v-form - ref="form" - v-model="valid" - @submit.prevent="submit"> - <v-card - variant="flat" - rounded="0"> - <v-card-text> - <v-row dense> - <v-col sm="6"> - <v-text-field - v-model="createAccount.email" - type="email" - autocomplete="off" - autofocus - required - name="email" - :rules="[v => !!v || $t('validation.required')]" - :hint="$t('pages.signup.email.hint')" - :label="$t('pages.signup.email.label')" /> - </v-col> - </v-row> - <v-row dense> - <v-col sm="6"> - <v-text-field - v-model="createAccount.username" - autocomplete="off" - required - name="username" - :rules="[v => !!v || $t('validation.required'), - v => /^[a-z0-9]{3,}$/.test(v) || $t('validation.user.pattern'), - v => !usernames.includes(v) || $t('validation.user.exists')]" - persistent-hint - :hint="$t('pages.signup.username.hint')" - :label="$t('pages.signup.username.label')" /> - </v-col> - </v-row> - <v-row dense> - <v-col sm="6"> - <v-text-field - v-model="createAccount.password" - autocomplete="off" - required - name="password" - :rules="[ - v => !!v || $t('validation.required') - ]" - type="password" - persistent-hint - :label="$t('pages.signup.password.label')" - :hint="$t('pages.signup.password.hint')" /> - </v-col> - </v-row> - <v-row dense> - <v-col sm="6"> - <v-text-field - v-model="password2" - autocomplete="off" - required - name="password-confirm" - :rules="[ - v => !!v || $t('validation.required') - ]" - :error-messages="password2 && password2 !== this.createAccount.password ? [this.$t('validation.matching')] : []" - type="password" - persistent-hint - :label="$t('pages.signup.confirm.label')" - :hint="$t('pages.signup.confirm.hint')" /> - </v-col> - </v-row> - </v-card-text> - <v-card-text> - <v-btn - id="login" - variant="flat" - :disabled="!valid" - color="primary" - type="submit" - name="submit" - :text="$t('pages.signup.submit.label')" - :loading="loading" - @click="register" /> - </v-card-text> - </v-card> - </v-form> - </div> -</template> - -<script> -export default { - data () { - return { - loading: false, - loadingUsers: false, - usernames: [], - error: false, // XXX: `error` is never changed - valid: false, - password2: null, - privacy: false, - consent: false, - createAccount: { - username: null, - email: null, - password: null - } - } - }, - mounted () { - this.loadUsers() - }, - methods: { - submit () { - this.$refs.form.validate() - }, - register () { - this.loading = true - const userService = useUserService() - userService.create(this.createAccount) - .then(() => { - const toast = useToastInstance() - toast.success(this.$t('success.signup')) - this.$router.push('/login') - this.loading = false - }) - .catch(({code}) => { - this.loading = false - const toast = useToastInstance() - if (typeof code !== 'string') { - return - } - toast.error(this.$t(code)) - }) - .finally(() => { - this.loading = false - }) - }, - loadUsers () { - this.loadingUsers = true - const userService = useUserService() - userService.findAll() - .then((users) => { - this.usernames = users.map(u => u.username) - }) - .catch(({code}) => { - this.loadingUsers = false - const toast = useToastInstance() - if (typeof code !== 'string') { - return - } - toast.error(this.$t(code)) - }) - .finally(() => { - this.loadingUsers = false - }) - } - } -} -</script> diff --git a/dbrepo-ui/pages/user/authentication.vue b/dbrepo-ui/pages/user/authentication.vue index 3a43421a709560b3e841760620321f0c640275f9..50008d3c5dab56111fb15ae7a6164e1a591af1f9 100644 --- a/dbrepo-ui/pages/user/authentication.vue +++ b/dbrepo-ui/pages/user/authentication.vue @@ -1,5 +1,6 @@ <template> - <div> + <div + v-if="loggedIn"> <UserToolbar /> <v-window v-model="tab"> <v-window-item> @@ -10,8 +11,7 @@ rounded="0"> <v-card-text> <v-form - v-model="valid2" - @submit.prevent="submit"> + v-model="valid2"> <v-row dense> <v-col md="6"> <v-text-field @@ -60,9 +60,12 @@ </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> import UserToolbar from '@/components/user/UserToolbar.vue' -import { useUserStore } from '@/stores/user.js' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -88,12 +91,12 @@ export default { email: null, password: null, password2: null, - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser + cacheUser () { + return this.cacheStore.getUser }, inputVariant () { const runtimeConfig = useRuntimeConfig() @@ -104,15 +107,11 @@ export default { return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.button.contrast : runtimeConfig.public.variant.button.normal } }, - mounted () { - }, methods: { - submit () { - }, changePassword () { this.loadingUpdate = true const userService = useUserService() - userService.updatePassword(this.user.id, {'password': this.password}) + userService.updatePassword(this.cacheUser.uid, {'password': this.password}) .then(() => { const toast = useToastInstance() toast.success(this.$t('success.user.password')) diff --git a/dbrepo-ui/pages/user/developer.vue b/dbrepo-ui/pages/user/developer.vue deleted file mode 100644 index de52a490aff050b7a7a92c1870645301bf68b9d3..0000000000000000000000000000000000000000 --- a/dbrepo-ui/pages/user/developer.vue +++ /dev/null @@ -1,230 +0,0 @@ -<template> - <div> - <UserToolbar /> - <v-window - v-model="tab"> - <v-window-item> - <v-card - v-if="canHandleMessages" - :title="$t('pages.settings.subpages.developer.maintenance.title')" - rounded="0" - variant="flat"> - <v-data-table - :headers="headers" - :items="messages" - :loading="loadingMessages" - :items-per-page="10"> - <template v-slot:item.action="{ item }"> - <v-btn - size="x-small" - variant="flat" - :text="$t('pages.settings.subpages.developer.maintenance.modify.text')" - @click="modifyMessage(item)" /> - </template> - </v-data-table> - <v-card-text> - <v-btn - size="small" - variant="flat" - :text="$t('pages.settings.subpages.developer.maintenance.add.text')" - :disabled="!canCreateMessage" - @click="createMessage" /> - </v-card-text> - </v-card> - <v-divider - v-if="canHandleMessages" /> - <v-card - :title="$t('pages.settings.subpages.developer.token.title')" - :subtitle="$t('pages.settings.subpages.developer.token.subtitle')" - variant="flat" - rounded="0"> - <v-card-text> - <v-row dense> - <v-col xl="4"> - <v-text-field - v-model="accessTokenField" - disabled - :variant="inputVariant" - :label="$t('pages.settings.subpages.developer.token.access.label')" /> - </v-col> - <v-col xl="2"> - <v-text-field - v-model="tokenExpiry" - disabled - :variant="inputVariant" - :label="expiryLabel(token)" /> - </v-col> - </v-row> - <v-row dense> - <v-col xl="4"> - <v-text-field - v-model="refreshTokenField" - disabled - :variant="inputVariant" - :label="$t('pages.settings.subpages.developer.token.refresh.label')" /> - </v-col> - <v-col xl="2"> - <v-text-field - v-model="refreshTokenExpiry" - disabled - :variant="inputVariant" - :label="expiryLabel(refreshToken)" /> - </v-col> - </v-row> - </v-card-text> - </v-card> - </v-window-item> - </v-window> - <v-breadcrumbs :items="items" class="pa-0 mt-2" /> - <v-dialog - v-model="dialog" - persistent - max-width="640"> - <EditMaintenanceMessage - :id="messageId" - @close-dialog="closeDialog" /> - </v-dialog> - </div> -</template> - -<script> -import UserToolbar from '@/components/user/UserToolbar.vue' -import EditMaintenanceMessage from '@/components/dialogs/EditMaintenanceMessage.vue' -import { formatTimestampUTCLabel, isActiveMessage, timestampsToHumanDifference } from '@/utils' -import { useUserStore } from '@/stores/user.js' -import { useCacheStore } from '@/stores/cache.js' - -export default { - components: { - UserToolbar, - EditMaintenanceMessage - }, - data () { - return { - tab: 0, - accessTokenField: null, - refreshTokenField: null, - headers: [ - { title: this.$t('pages.settings.subpages.developer.maintenance.active'), value: 'active' }, - { title: this.$t('pages.settings.subpages.developer.maintenance.type'), value: 'type' }, - { title: this.$t('pages.settings.subpages.developer.maintenance.message'), value: 'message' }, - { title: this.$t('pages.settings.subpages.developer.maintenance.action'), value: 'action' } - ], - items: [ - { - title: this.$t('navigation.user'), - to: '/user' - }, - { - title: this.$t('toolbars.user.developer'), - to: `/user/developer`, - disabled: true - } - ], - messages: [], - loadingMessages: false, - dialog: false, - messageId: null, - userStore: useUserStore(), - cacheStore: useCacheStore() - } - }, - computed: { - token () { - return this.userStore.getToken - }, - tokenExpiry () { - if (!this.token) { - return null - } - const authenticationService = useAuthenticationService() - return formatTimestampUTCLabel(authenticationService.tokenToExpiryDate(this.token)) - }, - refreshToken () { - return this.userStore.getRefreshToken - }, - refreshTokenExpiry () { - if (!this.refreshToken) { - return null - } - const authenticationService = useAuthenticationService() - return formatTimestampUTCLabel(authenticationService.tokenToExpiryDate(this.refreshToken)) - }, - user () { - return this.userStore.getUser - }, - roles () { - return this.userStore.getRoles - }, - canCreateMessage () { - if (!this.roles) { - return false - } - return this.roles.includes('create-maintenance-message') - }, - canModifyMessage () { - if (!this.roles) { - return false - } - return this.roles.includes('modify-maintenance-message') - }, - canHandleMessages () { - return this.canCreateMessage || this.canModifyMessage - }, - inputVariant () { - const runtimeConfig = useRuntimeConfig() - return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.input.contrast : runtimeConfig.public.variant.input.normal - }, - buttonVariant () { - const runtimeConfig = useRuntimeConfig() - return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.button.contrast : runtimeConfig.public.variant.button.normal - } - }, - mounted () { - this.loadMessages() - if (!this.token || !this.refreshToken) { - return - } - this.accessTokenField = this.token - this.refreshTokenField = this.refreshToken - }, - methods: { - submit () { - }, - modifyMessage (message) { - this.messageId = message.id - this.dialog = true - }, - createMessage () { - this.messageId = null - this.dialog = true - }, - expiryLabel (token) { - const authenticationService = useAuthenticationService() - return this.$t('pages.settings.subpages.developer.token.expiry') + ' ' + timestampsToHumanDifference(Date.now(), authenticationService.tokenToExpiryDate(token)) - }, - loadMessages () { - const messageService = useMessageService() - messageService.findAll() - .then((messages) => { - this.messages = messages.map((message) => { - message.active = isActiveMessage(message) ? '● true' : 'false' - return message - }) - }) - .catch(() => { - this.loadingMessages = false - }) - .finally(() => { - this.loadingMessages = false - }) - }, - closeDialog (event) { - if (event.success) { - this.cacheStore.reloadMessages() - } - this.dialog = false - } - } -} -</script> diff --git a/dbrepo-ui/pages/user/index.vue b/dbrepo-ui/pages/user/index.vue index accae861052044324dc08ae48f6858e643172c5f..aa8ee559441bfdc4dd29380d77a98662c476ed79 100644 --- a/dbrepo-ui/pages/user/index.vue +++ b/dbrepo-ui/pages/user/index.vue @@ -3,24 +3,10 @@ </template> <script> -import { useUserStore } from '@/stores/user.js' - export default { - data () { - return { - userStore: useUserStore() - } - }, - computed: { - token () { - return this.userStore.getToken - }, - user () { - return this.userStore.getUser - } - }, mounted () { - if (!this.user) { + const { loggedIn } = useOidcAuth() + if (!loggedIn) { return } this.$router.push('/user/info') diff --git a/dbrepo-ui/pages/user/info.vue b/dbrepo-ui/pages/user/info.vue index 9b94dde4dad312bb648e992efbc38120798bf869..470b7bc438a0211d80ad44f06411f1da7f54dddc 100644 --- a/dbrepo-ui/pages/user/info.vue +++ b/dbrepo-ui/pages/user/info.vue @@ -1,9 +1,12 @@ <template> - <div> + <div + v-if="loggedIn"> <UserToolbar /> - <v-window v-model="tab"> + <v-window + v-model="tab"> <v-window-item> - <v-form v-model="valid1" @submit.prevent="submit"> + <v-form + v-model="valid1"> <v-card :title="$t('pages.user.subpages.info.title')" :subtitle="$t('pages.user.subpages.info.subtitle')" @@ -19,10 +22,12 @@ :label="$t('pages.user.subpages.info.id.label')" /> </v-col> </v-row> - <v-row dense> + <v-row + v-if="cacheUser" + dense> <v-col md="6"> <v-text-field - v-model="model.username" + v-model="cacheUser.preferred_username" disabled :variant="inputVariant" :label="$t('pages.user.subpages.info.username.label')" /> @@ -122,9 +127,12 @@ </div> </template> +<script setup> +const { loggedIn } = useOidcAuth() +</script> <script> import UserToolbar from '@/components/user/UserToolbar.vue' -import { useUserStore } from '@/stores/user.js' +import { useCacheStore } from '@/stores/cache.js' export default { components: { @@ -138,15 +146,16 @@ export default { error: false, loadingUpdate: false, loadingTheme: false, - theme: null, orcidLoading: false, model: { id: null, - username: null, + preferred_username: null, firstname: null, lastname: null, theme: null, - language: null + language: null, + orcid: null, + affiliation: null }, themes: [ { name: this.$t('pages.user.subpages.theme.light'), value: 'light' }, @@ -169,23 +178,23 @@ export default { disabled: true } ], - userStore: useUserStore() + cacheStore: useCacheStore() } }, computed: { - user () { - return this.userStore.getUser + locale () { + return this.cacheStore.getLocale }, roles () { - return this.userStore.getRoles + return this.cacheStore.getRoles }, - locale () { - return this.userStore.getLocale - }, - canModifyTheme () { - return this.roles.includes('modify-user-theme') + cacheUser () { + return this.cacheStore.getUser }, canModifyInformation () { + if (!this.roles) { + return false + } return this.roles.includes('modify-user-information') }, inputVariant () { @@ -201,8 +210,6 @@ export default { this.init() }, methods: { - submit () { - }, updateInfo () { this.loadingUpdate = true const payload = { @@ -211,17 +218,16 @@ export default { orcid: this.model.orcid, affiliation: this.model.affiliation, theme: this.model.theme, - language: this.model.language, + language: this.model.language } const userService = useUserService() - userService.update(this.user.id, payload) + userService.update(this.cacheUser.uid, payload) .then((user) => { console.info('Updated user information') const toast = useToastInstance() toast.success(this.$t('success.user.info')) - this.userStore.setUser(user) /* language */ - this.userStore.setLocale(this.model.language) + this.cacheStore.setLocale(this.model.language) this.$i18n.locale = this.locale /* theme */ switch (this.model.theme) { @@ -247,18 +253,18 @@ export default { }) }, init () { - if (!this.user) { + if (!this.cacheUser) { return } this.model = { - id: this.user.id, - username: this.user.username, - firstname: this.user.given_name, - lastname: this.user.family_name, - orcid: this.user.attributes.orcid, - affiliation: this.user.attributes.affiliation, - theme: this.user.attributes.theme, - language: this.user.attributes.language + id: this.cacheUser.uid, + username: this.cacheUser.username, + firstname: this.cacheUser.given_name, + lastname: this.cacheUser.family_name, + orcid: this.cacheUser.orcid, + affiliation: this.cacheUser.affiliation, + theme: this.cacheUser.theme ? this.cacheUser.theme : 'light', + language: this.cacheUser.language ? this.cacheUser.language : 'en' } }, retrieve () { diff --git a/dbrepo-ui/stores/cache.js b/dbrepo-ui/stores/cache.js index 41059ba7270d93717998ac5b245e37d6ed239ee3..c2e34f48bbc9bedc8d4afc31c9158963a9450749 100644 --- a/dbrepo-ui/stores/cache.js +++ b/dbrepo-ui/stores/cache.js @@ -7,9 +7,14 @@ export const useCacheStore = defineStore('cache', { database: null, table: null, view: null, + access: null, subset: null, + locale: null, + identifier: null, ontologies: [], messages: [], + user: null, + roles: [], uploadProgress: null } }, @@ -17,9 +22,14 @@ export const useCacheStore = defineStore('cache', { getDatabase: (state) => state.database, getTable: (state) => state.table, getView: (state) => state.view, + getAccess: (state) => state.access, getSubset: (state) => state.subset, + getLocale: (state) => state.locale, + getIdentifier: (state) => state.identifier, getOntologies: (state) => state.ontologies, getMessages: (state) => state.messages, + getUser: (state) => state.user, + getRoles: (state) => state.roles, getUploadProgress: (state) => state.uploadProgress, }, actions: { @@ -32,12 +42,27 @@ export const useCacheStore = defineStore('cache', { setView(view) { this.view = view }, + setAccess(access) { + this.access = access + }, setSubset(subset) { this.subset = subset }, + setLocale(locale) { + this.locale = locale + }, + setIdentifier(identifier) { + this.identifier = identifier + }, setOntologies(ontologies) { this.ontologies = ontologies }, + setUser(user) { + this.user = user + }, + setRoles(roles) { + this.roles = roles + }, setUploadProgress(uploadProgress) { this.uploadProgress = uploadProgress }, @@ -110,17 +135,24 @@ export const useCacheStore = defineStore('cache', { setRouteTable(databaseId, tableId) { if (!databaseId || !tableId) { this.table = null - console.error('Cannot set route table: missing database id', databaseId, 'or table id', tableId) return } const tableService = useTableService() tableService.findOne(databaseId, tableId) .then(table => this.table = table) }, + setRouteAccess(databaseId, userId) { + if (!databaseId || !userId) { + this.access = null + return + } + const accessService = useAccessService() + accessService.findOne(databaseId, userId) + .then(access => this.access = access) + }, setRouteView(databaseId, viewId) { if (!databaseId || !viewId) { this.view = null - console.error('Cannot set route view: database view id', databaseId, 'or view id', viewId) return } const viewService = useViewService() @@ -130,7 +162,6 @@ export const useCacheStore = defineStore('cache', { setRouteSubset(databaseId, subsetId) { if (!databaseId || !subsetId) { this.subset = null - console.error('Cannot set route subset: missing database id', databaseId, 'or subset id', subsetId) return } const subsetService = useQueryService() diff --git a/dbrepo-ui/stores/user.js b/dbrepo-ui/stores/user.js deleted file mode 100644 index 522ce02a06ed1c695897d92a73c2682d791b499a..0000000000000000000000000000000000000000 --- a/dbrepo-ui/stores/user.js +++ /dev/null @@ -1,60 +0,0 @@ -import {defineStore} from 'pinia' - -export const useUserStore = defineStore('user', { - persist: true, - state: () => { - return { - /** @type String */ - token: null, - /** @type String */ - refreshToken: null, - roles: [], - user: null, - access: null, - locale: null - } - }, - getters: { - getToken: (state) => state.token, - getRefreshToken: (state) => state.refreshToken, - getRoles: (state) => state.roles, - getUser: (state) => state.user, - getAccess: (state) => state.access, - getLocale: (state) => state.locale - }, - actions: { - setToken(token) { - this.token = token - }, - setRefreshToken(refreshToken) { - this.refreshToken = refreshToken - }, - setRoles(roles) { - this.roles = roles - }, - setUser(user) { - this.user = user - }, - setAccess(access) { - this.access = access - }, - setLocale (locale) { - this.locale = locale - }, - logout() { - this.token = null - this.refreshToken = null - this.roles = [] - this.user = null - this.access = null - }, - setRouteAccess(databaseId) { - if (!databaseId || !this.user || !this.user.id) { - return - } - const accessService = useAccessService() - accessService.findOne(databaseId, this.user.id) - .then(access => this.access = access) - } - } -}) diff --git a/dbrepo-upload-service/pom.xml b/dbrepo-upload-service/pom.xml index 8f4506e150cfef06abcbb4e54e71d4b3e3ddccd1..20f379e32e7e58ab53dedc75c230071a2759c3cd 100644 --- a/dbrepo-upload-service/pom.xml +++ b/dbrepo-upload-service/pom.xml @@ -11,7 +11,7 @@ <groupId>at.tuwien</groupId> <artifactId>dbrepo-upload-service</artifactId> <name>dbrepo-upload-service</name> - <version>1.6.2</version> + <version>1.6.3</version> <url>https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/</url> <developers> diff --git a/dbrepo-upload-service/pre-create.sh b/dbrepo-upload-service/pre-create.sh index 536a2e63e060c32db1882bbaad7f4de02cc3d2f7..2d6eb4f861297db095e9dcf5268ed858fde7cd6d 100755 --- a/dbrepo-upload-service/pre-create.sh +++ b/dbrepo-upload-service/pre-create.sh @@ -1,6 +1,6 @@ #!/bin/bash REQUEST_RAW=$(cat /dev/stdin) -AUTH_SERVICE_ENDPOINT="${AUTH_SERVICE_ENDPOINT:-http://auth-service:8080}" +METADATA_SERVICE_ENDPOINT="${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080}" echo "[DEBUG] [pre-create hook] request started" >&2 if [ "$(echo "$REQUEST_RAW" | jq '.Event.HTTPRequest.Header | has("Authorization")')" == "false" ]; then @@ -21,11 +21,11 @@ END exit 0 fi -echo "[DEBUG] [pre-create hook] request has 'Authorization' header present" >&2 +echo "[DEBUG] [pre-create hook] request has 'Authorization' header p resent" >&2 BEARER="$(echo "$REQUEST_RAW" | jq -r '.Event.HTTPRequest.Header.Authorization[0]')" -echo "[DEBUG] [pre-create hook] attempting to contact ${AUTH_SERVICE_ENDPOINT}" >&2 -if [ ! "$(wget -O- --quiet --header "Authorization: ${BEARER}" ${AUTH_SERVICE_ENDPOINT}/realms/dbrepo/protocol/openid-connect/userinfo)" ]; then + +if [ ! "$(wget -O- --quiet --header='Authorization: ${BEARER}' ${METADATA_SERVICE_ENDPOINT}/api/license)" ]; then echo "[ERROR] [pre-create hook] Unauthorized" >&2 cat <<END { diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java b/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java index e360cecfdc106386a1229cfd12de189a8b57d60c..601229b5e5ae64da214820b2f91396a926d77884 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java @@ -1,13 +1,11 @@ package at.tuwien.config; -import at.tuwien.interceptor.KeycloakInterceptor; import lombok.Getter; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.DefaultUriBuilderFactory; @Log4j2 @Getter @@ -28,13 +26,4 @@ public class GatewayConfig { return new RestTemplate(); } - @Bean("keycloakRestTemplate") - public RestTemplate keycloakRestTemplate() { - final RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(keycloakEndpoint)); - restTemplate.getInterceptors() - .add(new KeycloakInterceptor(restTemplate(), keycloakUsername, keycloakPassword, keycloakEndpoint)); - return restTemplate; - } - } diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java b/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java index 01be743daa98d258391d3d9c4e6bdec7aa4f8773..6243cb3ab5ecffc614cf71391886cae1852d30a2 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java @@ -1,25 +1,11 @@ package at.tuwien.config; -import at.tuwien.api.auth.KeycloakErrorDto; -import at.tuwien.api.keycloak.UserCreateDto; -import at.tuwien.api.keycloak.UserDto; -import at.tuwien.exception.AuthServiceConnectionException; -import at.tuwien.exception.AuthServiceException; -import at.tuwien.exception.EmailExistsException; -import at.tuwien.exception.UserExistsException; import lombok.Getter; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +import org.keycloak.admin.client.Keycloak; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestTemplate; @Log4j2 @Getter @@ -29,55 +15,9 @@ public class KeycloakConfig { @Value("${dbrepo.endpoints.keycloak}") private String keycloakEndpoint; - @Autowired - @Qualifier("keycloakRestTemplate") - private RestTemplate keycloakRestTemplate; - - public Boolean existsByUsername(String username) throws AuthServiceException, AuthServiceConnectionException { - final String path = "/admin/realms/dbrepo/users/?username=" + username; - final ResponseEntity<UserDto[]> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.GET, HttpEntity.EMPTY, UserDto[].class); - } catch (HttpServerErrorException e) { - log.error("Failed to find user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (Exception e) { - log.error("Failed to find user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); - } - final UserDto[] body = response.getBody(); - if (body == null || body.length != 1) { - log.error("Failed to find user with username {}", username); - return false; - } - return true; - } - - public void createUser(UserCreateDto data) throws UserExistsException, EmailExistsException, - AuthServiceConnectionException, AuthServiceException { - final String path = "/admin/realms/dbrepo/users"; - final ResponseEntity<Void> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(data), Void.class); - } catch (HttpServerErrorException e) { - log.error("Failed to create user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.Conflict e) { - if (e.getResponseBodyAsByteArray() != null && e.getResponseBodyAsByteArray().length > 0) { - final KeycloakErrorDto error = e.getResponseBodyAs(KeycloakErrorDto.class); - if (error != null && error.getErrorMessage().contains("same email")) { - log.error("Failed to create user: email exists: {}", e.getMessage()); - throw new EmailExistsException("E-Mail exists", e); - } - } - log.error("Failed to create user: user exists: {}", e.getMessage()); - throw new UserExistsException("User exists", e); - } - if (!response.getStatusCode().equals(HttpStatus.CREATED)) { - log.error("Failed to create user: unexpected status: {}", response.getStatusCode().value()); - throw new AuthServiceException("Unexpected status: " + response.getStatusCode().value()); - } - log.debug("Created user {} at auth service", data.getUsername()); + @Bean + public Keycloak keycloak() { + return Keycloak.getInstance(keycloakEndpoint, "master", "admin", "admin", "admin-cli"); } } diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..c6b5d429e7b87b1a3a4759167f794ad183311883 --- /dev/null +++ b/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java @@ -0,0 +1,22 @@ +package at.tuwien.mapper; + + +import at.tuwien.api.keycloak.UserCreateDto; +import at.tuwien.api.user.external.ExternalResultType; +import org.keycloak.representations.idm.UserRepresentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +import java.util.LinkedList; + +@Mapper(componentModel = "spring", imports = {LinkedList.class, ExternalResultType.class}) +public interface MetadataMapper { + + org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); + + @Mappings({ + @Mapping(target = "attributes", ignore = true) + }) + UserRepresentation userCreateDtoToUserRepresentation(UserCreateDto data); +} diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java b/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java index 30fd6752c3b37a20487aa4133aefdefcbdf3e4b0..052d028d39478f3f8a61b2639bf8bfe8487af9ba 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java @@ -7,11 +7,9 @@ import at.tuwien.api.keycloak.UserCreateDto; import at.tuwien.config.KeycloakConfig; import at.tuwien.config.TusdConfig; import at.tuwien.config.TusdContainerConfig; -import at.tuwien.exception.AuthServiceConnectionException; import at.tuwien.exception.AuthServiceException; -import at.tuwien.exception.EmailExistsException; -import at.tuwien.exception.UserExistsException; import at.tuwien.interceptor.KeycloakInterceptor; +import at.tuwien.util.KeycloakUtil; import com.github.dockerjava.api.model.ExposedPort; import dasniko.testcontainers.keycloak.KeycloakContainer; import lombok.extern.log4j.Log4j2; @@ -20,7 +18,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -33,7 +34,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @Log4j2 @@ -48,6 +48,9 @@ public class UploadServiceIntegrationTest { @Autowired private TusdConfig tusdConfig; + @Autowired + private KeycloakUtil keycloakUtil; + @Autowired private KeycloakConfig keycloakConfig; @@ -55,7 +58,7 @@ public class UploadServiceIntegrationTest { private static TusdContainerConfig.TusdContainer tusdContainer = TusdContainerConfig.TusdContainer.getInstance(); @Container - private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") + private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:26.0") .withImagePullPolicy(PullPolicy.alwaysPull()) .withRealmImportFile("init/dbrepo-realm.json") .withEnv("KC_HOSTNAME_STRICT_HTTPS", "false") @@ -69,9 +72,8 @@ public class UploadServiceIntegrationTest { } @BeforeEach - public void beforeEach() throws UserExistsException, AuthServiceException, AuthServiceConnectionException, - EmailExistsException { - if (keycloakConfig.existsByUsername(keycloakContainer.getAdminUsername())) { + public void beforeEach() throws AuthServiceException { + if (keycloakUtil.existsByUsername(keycloakContainer.getAdminUsername())) { return; } final UserCreateDto payload = UserCreateDto.builder() @@ -82,7 +84,7 @@ public class UploadServiceIntegrationTest { .value(keycloakContainer.getAdminPassword()) .build())) .build(); - keycloakConfig.createUser(payload); + keycloakUtil.createUser(payload); } @Test diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java b/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b7c24cab9d5221f893e2e1fceb3f378b59e6d684 --- /dev/null +++ b/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java @@ -0,0 +1,46 @@ +package at.tuwien.util; + +import at.tuwien.api.keycloak.UserCreateDto; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.mapper.MetadataMapper; +import jakarta.ws.rs.core.Response; +import lombok.extern.log4j.Log4j2; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Log4j2 +public class KeycloakUtil { + + + private final MetadataMapper metadataMapper; + private final Keycloak keycloak; + + @Autowired + public KeycloakUtil(MetadataMapper metadataMapper, Keycloak keycloak) { + this.metadataMapper = metadataMapper; + this.keycloak = keycloak; + } + + public void createUser(UserCreateDto data) throws AuthServiceException { + final UserRepresentation user = metadataMapper.userCreateDtoToUserRepresentation(data); + try (Response response = keycloak.realm("dbrepo") + .users() + .create(user)) { + if (response.getStatus() != 200) { + log.error("Failed to delete user: unexpected response status: {}", response.getStatus()); + throw new AuthServiceException("Unexpected response status: " + response.getStatus()); + } + } + log.info("Created user at auth service"); + } + + public boolean existsByUsername(String username) { + return keycloak.realm("dbrepo") + .users() + .search(username) + .isEmpty(); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index ad1dacb6b7b19a8fb4ee3303968cd684bef75967..9176f6404a06df5e30269f4a0dde4d8e4483ebd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,20 +80,25 @@ services: restart: "no" container_name: dbrepo-auth-service hostname: auth-service - image: bitnami/keycloak:24.0.5-debian-12-r8 + image: bitnami/keycloak:26.0.4-debian-12-r0 volumes: - ./dbrepo-auth-service/import-realms.sh:/docker-entrypoint-initdb.d/import-realms.sh - ./dbrepo-auth-service/master-realm.json:/opt/keycloak/data/import/master-realm.json - ./dbrepo-auth-service/dbrepo-realm.json:/opt/keycloak/data/import/dbrepo-realm.json + - ./dbrepo-auth-service/listeners/target/create-event-listener.jar:/opt/bitnami/keycloak/providers/create-event-listener.jar ports: - "8080:8080" environment: + KEYCLOAK_ENABLE_HEALTH_ENDPOINTS: "true" KEYCLOAK_ENABLE_HTTPS: "false" KEYCLOAK_ENABLE_STATISTICS: "true" KEYCLOAK_DATABASE_HOST: "auth-db" KEYCLOAK_DATABASE_NAME: "${AUTH_DB_NAME:-keycloak}" KEYCLOAK_DATABASE_USER: "${AUTH_DB_USERNAME:-keycloak}" KEYCLOAK_DATABASE_PASSWORD: "${AUTH_DB_PASSWORD:-dbrepo}" + METADATA_SERVICE_ENDPOINT: "${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080}/api/user" + SYSTEM_USERNAME: "${SYSTEM_USERNAME:-admin}" + SYSTEM_PASSWORD: "${SYSTEM_PASSWORD:-admin}" healthcheck: test: curl -fsS http://localhost:8080/realms/master interval: 10s @@ -331,6 +336,14 @@ services: NUXT_PUBLIC_API_CLIENT: "${BASE_URL:-http://localhost}" NUXT_PUBLIC_API_SERVER: "${BASE_URL:-http://localhost}" NUXT_PUBLIC_UPLOAD_CLIENT: "${BASE_URL:-http://localhost}/api/upload/files" + NUXT_OIDC_PROVIDERS_KEYCLOAK_AUTHORIZATION_URL: "${BASE_URL:-http://localhost}/realms/dbrepo/protocol/openid-connect/auth" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_ID: "${AUTH_SERVICE_CLIENT:-dbrepo-client}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_SECRET: "${AUTH_SERVICE_CLIENT:-MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_REDIRECT_URI: "${BASE_URL:-http://localhost}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_URL: "${BASE_URL:-http://localhost}/realms/dbrepo/protocol/openid-connect/logout" + NUXT_OIDC_PROVIDERS_KEYCLOAK_REDIRECT_URI: "${BASE_URL:-http://localhost}/auth/keycloak/callback" + NUXT_OIDC_PROVIDERS_KEYCLOAK_TOKEN_URL: "${BASE_URL:-http://localhost}/realms/dbrepo/protocol/openid-connect/token" + NUXT_OIDC_PROVIDERS_KEYCLOAK_USER_INFO_URL: "${BASE_URL:-http://localhost}/realms/dbrepo/protocol/openid-connect/userinfo" depends_on: dbrepo-search-service: condition: service_healthy @@ -341,6 +354,8 @@ services: interval: 10s timeout: 5s retries: 12 + extra_hosts: + - "localhost:host-gateway" logging: driver: json-file @@ -475,7 +490,7 @@ services: LDAP_ADMIN_PASSWORD: "${IDENTITY_SERVICE_ADMIN_PASSWORD:-admin}" LDAP_ROOT: "${IDENTITY_SERVICE_ROOT:-dc=dbrepo,dc=at}" healthcheck: - test: test -f /opt/bitnami/grafana/tmp/grafana.pid + test: curl -fsSL --head http://127.0.0.1:3000 interval: 10s timeout: 5s retries: 12 @@ -523,6 +538,7 @@ services: AWS_ACCESS_KEY_ID: "${S3_ACCESS_KEY_ID:-seaweedfsadmin}" AWS_SECRET_ACCESS_KEY: "${S3_SECRET_ACCESS_KEY:-seaweedfsadmin}" AWS_REGION: "${STORAGE_REGION_NAME:-default}" + METADATA_SERVICE_ENDPOINT: "${METADATA_SERVICE_ENDPOINT:-http://metadata-service:8080}" depends_on: dbrepo-storage-service: condition: service_healthy diff --git a/helm/dbrepo/Chart.lock b/helm/dbrepo/Chart.lock index 4d2cafe9bd7d4dca5c51c603d87e0ce69a9fd0de..297b4b1a9287732c869533cd2f2fe261ae43f4a5 100644 --- a/helm/dbrepo/Chart.lock +++ b/helm/dbrepo/Chart.lock @@ -4,7 +4,7 @@ dependencies: version: 1.4.0 - name: keycloak repository: https://charts.bitnami.com/bitnami - version: 21.6.2 + version: 24.0.3 - name: mariadb-galera repository: https://charts.bitnami.com/bitnami version: 13.2.7 @@ -26,5 +26,5 @@ dependencies: - name: nginx repository: https://charts.bitnami.com/bitnami version: 18.3.1 -digest: sha256:414c043a3751945d7bd5b02fa00ee0464bee7f08efb469e00a5f059cdbff03b5 -generated: "2025-01-19T17:22:48.686050629+01:00" +digest: sha256:aa148a5f656ad17971203ea710206117d6de6f27b6940f9d532a6c5762e5df25 +generated: "2025-02-04T22:01:27.370259572+01:00" diff --git a/helm/dbrepo/Chart.yaml b/helm/dbrepo/Chart.yaml index 802f33888ca44b48351b7f501be5dd893dc6d6ab..99f8a84d23b7808e81dd94a337655594abf1721f 100644 --- a/helm/dbrepo/Chart.yaml +++ b/helm/dbrepo/Chart.yaml @@ -7,8 +7,8 @@ description: Helm Chart for installing DBRepo sources: - https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services type: application -version: "1.6.2" -appVersion: "1.6.2" +version: "1.6.3" +appVersion: "1.6.3" keywords: - dbrepo maintainers: @@ -24,7 +24,7 @@ dependencies: condition: searchdb.enabled - name: keycloak alias: authservice - version: 21.6.1 # app version: 24.0.5 + version: 24.0.3 # app version: 26.0.4 repository: https://charts.bitnami.com/bitnami condition: authservice.enabled - name: mariadb-galera diff --git a/helm/dbrepo/README.md b/helm/dbrepo/README.md index e703787206de08f933748b51f951bf80c01712a8..a45297396372cc5580e4f065e4f7172929ba7c5a 100644 --- a/helm/dbrepo/README.md +++ b/helm/dbrepo/README.md @@ -11,7 +11,7 @@ sample [ for your deployment and update the variables, especially `hostname`. ```bash -helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" --values ./values.yaml --version "1.6.2" +helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" --values ./values.yaml --version "1.6.3" ``` ## Prerequisites @@ -28,7 +28,7 @@ helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" To install the chart with the release name `my-release`: ```bash -helm install my-release "oci://oci://registry.datalab.tuwien.ac.at/dbrepo/helm" --values ./values.yaml --version "1.6.2" +helm install my-release "oci://oci://registry.datalab.tuwien.ac.at/dbrepo/helm" --values ./values.yaml --version "1.6.3" ``` The command deploys DBRepo on the Kubernetes cluster in the default configuration. The Parameters section lists the @@ -86,6 +86,7 @@ The command removes all the Kubernetes components associated with the chart and | `authservice.enabled` | Enable the Auth Service. | `true` | | `authservice.image.debug` | Set the logging level to `trace`. Otherwise, set to `info`. | `false` | | `authservice.endpoint` | The hostname for the microservices. | `http://auth-service` | +| `authservice.production` | Start Keycloak with production profile. | `true` | | `authservice.resourcesPreset` | The container resource presets | `small` | | `authservice.jwt.pubkey` | The JWT public key from the `dbrepo-client`. | `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB` | | `authservice.tls.enabled` | Enable TLS/SSL communication. Required for HTTPS. | `true` | @@ -388,7 +389,7 @@ mqtt.prefetch = 10 | `ui.public.pid.default.publisher` | The default dataset publisher for persisted identifiers. | `Example University` | | `ui.public.doi.enabled` | Enable the display that DOIs are minted. | `false` | | `ui.public.doi.endpoint` | The DOI proxy. | `https://doi.org` | -| `ui.replicaCount` | The number of replicas. | `2` | +| `ui.replicaCount` | The number of replicas. | `1` | ### Dashboard Service diff --git a/helm/dbrepo/charts/keycloak-21.6.1.tgz b/helm/dbrepo/charts/keycloak-21.6.1.tgz deleted file mode 100644 index 6479f5943846dee589d3ec90bbda649a8d7b72fe..0000000000000000000000000000000000000000 Binary files a/helm/dbrepo/charts/keycloak-21.6.1.tgz and /dev/null differ diff --git a/helm/dbrepo/charts/keycloak-24.0.3.tgz b/helm/dbrepo/charts/keycloak-24.0.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..29a964b44168bec6c04969e16cf51c94b75952fc Binary files /dev/null and b/helm/dbrepo/charts/keycloak-24.0.3.tgz differ diff --git a/helm/dbrepo/charts/seaweedfs-4.2.1.tgz b/helm/dbrepo/charts/seaweedfs-4.2.1.tgz index 6fd5807b55c4d6ad8041d96ca5d4a39ed3795138..a463170406b9c62f8f33ba315ac440fe435ad93c 100644 Binary files a/helm/dbrepo/charts/seaweedfs-4.2.1.tgz and b/helm/dbrepo/charts/seaweedfs-4.2.1.tgz differ diff --git a/helm/dbrepo/files/01-setup-schema.sql b/helm/dbrepo/files/01-setup-schema.sql index c9ce89d1be71f4791c5e55dbb7c24f46e979355a..e2bde25ed6d64f69c4f8d6e897a49a672e3f9a71 100644 --- a/helm/dbrepo/files/01-setup-schema.sql +++ b/helm/dbrepo/files/01-setup-schema.sql @@ -3,10 +3,10 @@ BEGIN; CREATE TABLE IF NOT EXISTS `mdb_users` ( id character varying(36) NOT NULL, + keycloak_id character varying(36) NOT NULL, username character varying(255) NOT NULL, firstname character varying(255), lastname character varying(255), - email character varying(255) NOT NULL, orcid character varying(255), affiliation character varying(255), is_internal BOOLEAN NOT NULL DEFAULT FALSE, @@ -14,8 +14,8 @@ CREATE TABLE IF NOT EXISTS `mdb_users` theme character varying(255) NOT NULL default ('light'), language character varying(3) NOT NULL default ('en'), PRIMARY KEY (id), - UNIQUE (username), - UNIQUE (email) + UNIQUE (keycloak_id), + UNIQUE (username) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_images` diff --git a/helm/dbrepo/files/create-event-listener.jar b/helm/dbrepo/files/create-event-listener.jar new file mode 100644 index 0000000000000000000000000000000000000000..9a9cd149f84ff92e5292a9fdc19a81edcc9ca7b7 Binary files /dev/null and b/helm/dbrepo/files/create-event-listener.jar differ diff --git a/helm/dbrepo/templates/auth-configmap.yaml b/helm/dbrepo/templates/auth-configmap.yaml index ffd14c4b1765cab2f20af5888c746fbf47a71a45..28ad32d66494ba0a284b73354af7e240bff48dd4 100644 --- a/helm/dbrepo/templates/auth-configmap.yaml +++ b/helm/dbrepo/templates/auth-configmap.yaml @@ -4,8 +4,11 @@ kind: ConfigMap metadata: name: auth-service-config namespace: {{ include "common.names.namespace" . | quote }} +binaryData: + create-event-listener.jar: |- + {{ .Files.Get "files/create-event-listener.jar" | b64enc }} data: - dbrepo-realm.json: | + dbrepo-realm.json: |- { "id" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "realm" : "dbrepo", @@ -35,7 +38,7 @@ data: "oauth2DevicePollingInterval" : 5, "enabled" : true, "sslRequired" : "none", - "registrationAllowed" : false, + "registrationAllowed" : true, "registrationEmailAsUsername" : false, "rememberMe" : false, "verifyEmail" : true, @@ -46,6 +49,7 @@ data: "bruteForceProtected" : false, "permanentLockout" : false, "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", "maxFailureWaitSeconds" : 900, "minimumQuickLoginWaitSeconds" : 60, "waitIncrementSeconds" : 60, @@ -1316,8 +1320,8 @@ data: "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "client.use.lightweight.access.token.enabled" : "true" + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : true, @@ -1391,6 +1395,38 @@ data: "fullScopeAllowed" : true, "nodeReRegistrationTimeout" : -1, "protocolMappers" : [ { + "id" : "266edf62-a19a-483b-b594-81428e4af792", + "name" : "orcid", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "ORCID", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "orcid", + "jsonType.label" : "String" + } + }, { + "id" : "1a21798a-38b6-4df5-89f0-86942415246f", + "name" : "theme", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "THEME", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "theme", + "jsonType.label" : "String" + } + }, { "id" : "da0b27c1-ae2e-4baa-bf78-db233e15c78d", "name" : "preferred_username", "protocol" : "openid-connect", @@ -1404,18 +1440,66 @@ data: "userinfo.token.claim" : "true" } }, { - "id" : "7c94de93-f60f-487b-b4b7-1891c67f74cc", - "name" : "aud", + "id" : "1bc6a1f4-4be2-439c-8c7f-b3fb0bb9956a", + "name" : "affiliation", "protocol" : "openid-connect", - "protocolMapper" : "oidc-hardcoded-claim-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "claim.value" : "dbrepo", + "introspection.token.claim" : "true", "userinfo.token.claim" : "true", + "user.attribute" : "AFFILIATION", "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", - "claim.name" : "aud", - "access.tokenResponse.claim" : "false" + "claim.name" : "affiliation", + "jsonType.label" : "String" + } + }, { + "id" : "7cbf6dc6-653e-40a9-9974-0e5bf7a363c3", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "70bbd779-d085-4204-ac4b-3a40abab9d88", + "name" : "language", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "LANGUAGE", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "language", + "jsonType.label" : "String" + } + }, { + "id" : "b817424d-7f91-43d8-b7d0-6a32582377fb", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" } }, { "id" : "030a1cd9-53d1-4a62-a375-94d50a2dc6fc", @@ -1432,9 +1516,26 @@ data: "access.token.claim" : "true", "claim.name" : "uid" } + }, { + "id" : "c304ed2f-5952-4772-838d-91998a45f154", + "name" : "aud", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-hardcoded-claim-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "claim.value" : "account", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "aud", + "jsonType.label" : "String", + "access.tokenResponse.claim" : "false" + } } ], - "defaultClientScopes" : [ "roles", "attributes", "basic" ], - "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] + "defaultClientScopes" : [ "roles", "basic" ], + "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "attributes", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ] }, { "id" : "25741f6b-4867-4138-8238-6345c6ba8702", "clientId" : "rabbitmq-client", @@ -1479,12 +1580,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "false", "user.attribute" : "username", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "client_id", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "false" } }, { "id" : "f1afc22d-f595-403b-ba2e-6ab19d98205e", @@ -1493,11 +1594,11 @@ data: "protocolMapper" : "oidc-hardcoded-claim-mapper", "consentRequired" : false, "config" : { - "claim.value" : "rabbitmq", - "userinfo.token.claim" : "false", "id.token.claim" : "false", "access.token.claim" : "true", "claim.name" : "aud", + "claim.value" : "rabbitmq", + "userinfo.token.claim" : "false", "access.tokenResponse.claim" : "false" } } ], @@ -1556,8 +1657,8 @@ data: "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, "authenticationFlowBindingOverrides" : { }, @@ -1570,12 +1671,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ], "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], @@ -1599,8 +1700,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "782819fe-ba5d-4ddb-9f95-cabb69d79c8d", @@ -1609,12 +1710,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "emailVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } }, { "id" : "ca613fc8-bbf2-4240-8b33-a1874f1559f3", @@ -1623,12 +1724,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "email", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "email", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1638,8 +1739,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "84f0487a-1d7d-470c-9b8e-5835294ae235", @@ -1648,12 +1749,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "preferred_username", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bbdcdb36-3ec0-443d-b1af-9993d40f0567", @@ -1662,12 +1763,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "gender", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "gender", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "9faa870b-5491-4ce9-b27d-c9ce07d6a95e", @@ -1676,12 +1777,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "birthdate", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "birthdate", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f0e3c012-9523-4076-83ae-e466e2d08220", @@ -1701,12 +1802,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "profile", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "profile", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "18cfbf4b-0a8e-45c7-a832-c0f72c92f3f3", @@ -1715,12 +1816,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "updatedAt", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "updated_at", - "jsonType.label" : "long" + "jsonType.label" : "long", + "userinfo.token.claim" : "true" } }, { "id" : "841ea785-26ab-429a-a420-09ce3948924d", @@ -1729,12 +1830,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "lastName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "family_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "bfba13ff-f952-4e89-bbb1-a693fdebfae8", @@ -1743,12 +1844,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "website", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "website", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "475f071d-5149-4379-b928-76482f5f519c", @@ -1757,12 +1858,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "zoneinfo", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "zoneinfo", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "b8bebfed-b5e9-4604-a0ee-9817f7d439ac", @@ -1771,12 +1872,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "middleName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "middle_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "445232c8-6830-476c-a6f1-8bbef167595a", @@ -1785,12 +1886,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "picture", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "picture", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "65f2e474-6ede-4872-86e4-e49504dd0f2a", @@ -1799,12 +1900,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "locale", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "16cd5a27-ccf3-453c-ae1e-8621813ab73c", @@ -1813,12 +1914,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "firstName", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "given_name", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "f9efedfc-3388-457c-b10a-1dff4525ff9b", @@ -1827,12 +1928,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "nickname", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "nickname", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1866,12 +1967,12 @@ data: "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "upn", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } } ] }, { @@ -1913,8 +2014,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "dae802fb-9138-408a-b80e-a40eb0f56814", @@ -1923,12 +2024,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumber", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number", - "jsonType.label" : "String" + "jsonType.label" : "String", + "userinfo.token.claim" : "true" } }, { "id" : "feb06a8d-b0eb-4911-8464-368d93f566fa", @@ -1937,12 +2038,12 @@ data: "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", "user.attribute" : "phoneNumberVerified", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" } } ] }, { @@ -1952,8 +2053,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" + "consent.screen.text" : "", + "display.on.consent.screen" : "false" }, "protocolMappers" : [ { "id" : "c6411e3b-6478-453d-b530-5fe175a4d786", @@ -2033,6 +2134,61 @@ data: "gui.order" : "", "consent.screen.text" : "" } + }, { + "id" : "aa5c6ca7-812d-4fff-80b9-f5095ca82ce6", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "bb359b0f-97dc-4d6a-9a2f-89458b53c512", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "7aa3a4d2-3dd1-48dd-8886-562906eadb2a", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "c4882d39-e815-49f5-8a73-eb8b83572eae", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ] }, { "id" : "210cc792-6c07-45a6-a77e-827cdf3b41ba", "name" : "offline_access", @@ -2049,8 +2205,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "8d4ffe4d-1d01-4ca1-8ff4-44eacca61b30", @@ -2123,8 +2279,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "3b6b6914-8ad1-4a71-88ec-444f754aaacb", @@ -2140,11 +2296,15 @@ data: "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "false", + "multivalued" : "true", "user.attribute" : "foo", + "id.token.claim" : "true", + "lightweight.claim" : "false", "access.token.claim" : "true", "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" + "jsonType.label" : "String" } }, { "id" : "a7bd6723-e58e-47f7-95c0-2925ce99283d", @@ -2174,8 +2334,12 @@ data: "strictTransportSecurity" : "max-age=31536000; includeSubDomains" }, "smtpServer" : { }, + "loginTheme" : "keycloak.v2", + "accountTheme" : "", + "adminTheme" : "", + "emailTheme" : "", "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], + "eventsListeners" : [ "create-event-listener", "jboss-logging" ], "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "VERIFY_PROFILE_ERROR", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "OAUTH2_DEVICE_VERIFY_USER_CODE", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "TOKEN_EXCHANGE", "AUTHREQID_TO_TOKEN", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "OAUTH2_DEVICE_AUTH", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "OAUTH2_DEVICE_CODE_TO_TOKEN", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "AUTHREQID_TO_TOKEN_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "OAUTH2_DEVICE_AUTH_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "VERIFY_PROFILE", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], "adminEventsEnabled" : false, "adminEventsDetailsEnabled" : false, @@ -2223,7 +2387,7 @@ data: "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ] } }, { "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", @@ -2249,7 +2413,15 @@ data: "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "a407a1d6-a7f6-4a72-ba3a-149de03d5a43", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] } } ], "org.keycloak.storage.UserStorageProvider" : [ { @@ -2265,8 +2437,8 @@ data: "config" : { "ldap.attribute" : [ "createTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "true" ], "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], "user.model.attribute" : [ "createTimestamp" ] } }, { @@ -2277,8 +2449,8 @@ data: "config" : { "ldap.attribute" : [ "sn" ], "is.mandatory.in.ldap" : [ "true" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "lastName" ] } }, { @@ -2301,8 +2473,8 @@ data: "config" : { "ldap.attribute" : [ "mail" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "email" ] } }, { @@ -2311,19 +2483,19 @@ data: "providerId" : "group-ldap-mapper", "subComponents" : { }, "config" : { + "mode" : [ "LDAP_ONLY" ], "membership.attribute.type" : [ "DN" ], + "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "group.name.ldap.attribute" : [ "cn" ], - "preserve.group.inheritance" : [ "false" ], "membership.user.ldap.attribute" : [ "uid" ], - "groups.dn" : [ "ou=users,{{ .Values.identityservice.global.ldapDomain }}" ], - "mode" : [ "LDAP_ONLY" ], - "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], - "membership.ldap.attribute" : [ "member" ], "ignore.missing.groups" : [ "false" ], + "preserve.group.inheritance" : [ "false" ], + "membership.ldap.attribute" : [ "member" ], "memberof.ldap.attribute" : [ "memberOf" ], "group.object.classes" : [ "groupOfNames" ], - "drop.non.existing.groups.during.sync" : [ "false" ], - "groups.path" : [ "/" ] + "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], + "groups.path" : [ "/" ], + "drop.non.existing.groups.during.sync" : [ "false" ] } }, { "id" : "b6ff3285-35af-4e86-8bb4-d94b8e0d70bb", @@ -2347,15 +2519,15 @@ data: "is.mandatory.in.ldap" : [ "true" ], "attribute.force.default" : [ "false" ], "is.binary.attribute" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "username" ] } } ] }, "config" : { - "pagination" : [ "false" ], "fullSyncPeriod" : [ "-1" ], + "pagination" : [ "false" ], "startTls" : [ "false" ], "connectionPooling" : [ "true" ], "usersDn" : [ "ou=users,{{ .Values.identityservice.global.ldapDomain }}" ], @@ -2363,15 +2535,15 @@ data: "useKerberosForPasswordAuthentication" : [ "false" ], "importEnabled" : [ "true" ], "enabled" : [ "true" ], + "bindCredential" : [ "{{ .Values.identityservice.global.adminPassword }}" ], "changedSyncPeriod" : [ "-1" ], - "bindDn" : [ "cn={{ .Values.identityservice.global.adminUser }},{{ .Values.identityservice.global.ldapDomain }}" ], "usernameLDAPAttribute" : [ "uid" ], - "bindCredential" : [ "{{ .Values.identityservice.global.adminPassword }}" ], + "bindDn" : [ "cn={{ .Values.identityservice.global.adminUser }},{{ .Values.identityservice.global.ldapDomain }}" ], "lastSync" : [ "1719252666" ], "vendor" : [ "other" ], "uuidLDAPAttribute" : [ "entryUUID" ], - "connectionUrl" : [ "ldap://identity-service:389" ], "allowKerberosAuthentication" : [ "false" ], + "connectionUrl" : [ "ldap://identity-service:389" ], "syncRegistrations" : [ "true" ], "authType" : [ "simple" ], "useTruststoreSpi" : [ "always" ], @@ -2383,14 +2555,6 @@ data: "validatePasswordPolicy" : [ "false" ] } } ], - "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "a407a1d6-a7f6-4a72-ba3a-149de03d5a43", - "providerId" : "declarative-user-profile", - "subComponents" : { }, - "config" : { - "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] - } - } ], "org.keycloak.keys.KeyProvider" : [ { "id" : "2f53ccf3-37b0-4d34-83e7-ed497499ee51", "name" : "rsa-enc-generated", @@ -3003,10 +3167,12 @@ data: "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email" : "", "parRequestUriLifespan" : "60", "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false", "shortVerificationUri" : "" }, - "keycloakVersion" : "24.0.5", + "keycloakVersion" : "26.0.4", "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, "clientProfiles" : { "profiles" : [ ] }, @@ -3014,7 +3180,7 @@ data: "policies" : [ ] } } - master-realm.json: | + master-realm.json: |- { "id" : "afe47bd0-61f8-40c3-95cb-04930407ebdd", "realm" : "master", @@ -3057,6 +3223,7 @@ data: "bruteForceProtected" : false, "permanentLockout" : false, "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", "maxFailureWaitSeconds" : 900, "minimumQuickLoginWaitSeconds" : 60, "waitIncrementSeconds" : 60, @@ -3681,8 +3848,8 @@ data: "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "client.use.lightweight.access.token.enabled" : "true" + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : true, @@ -3800,8 +3967,8 @@ data: "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "post.logout.redirect.uris" : "+", "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", "pkce.code.challenge.method" : "S256" }, "authenticationFlowBindingOverrides" : { }, @@ -3833,8 +4000,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "4aed5e41-0d8d-4c24-80a0-cd9822072756", @@ -3862,8 +4029,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${organizationScopeConsentText}" + "consent.screen.text" : "${organizationScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "5e80a7d2-c9d0-48e1-aadc-d8848ff90f92", @@ -3881,6 +4048,61 @@ data: "jsonType.label" : "String" } } ] + }, { + "id" : "1be1e284-2749-4bbb-890a-2d519cc1531c", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "c913a673-cf66-4493-a2ed-14556c07617c", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "5c244d68-5c63-4356-ac71-5a586f40c77e", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "600285d4-ae51-4b39-a7be-bb83cf5870db", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + } ] }, { "id" : "0411ea86-a074-4781-850d-ea3ca94590a2", "name" : "offline_access", @@ -3932,8 +4154,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "2d1400be-4053-4393-ba87-91b64f699054", @@ -4234,8 +4456,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" + "consent.screen.text" : "", + "display.on.consent.screen" : "false" }, "protocolMappers" : [ { "id" : "635cbac1-7cab-43bd-99fc-f7084aca2fa2", @@ -4271,8 +4493,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "2b5a3df4-1adb-402d-bc28-2bd43224e682", @@ -4281,12 +4503,12 @@ data: "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", "user.attribute" : "foo", + "introspection.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "realm_access.roles", - "jsonType.label" : "String" + "jsonType.label" : "String", + "multivalued" : "true" } }, { "id" : "f3b60071-ef26-48a7-9554-67f62f84d543", @@ -4295,12 +4517,12 @@ data: "protocolMapper" : "oidc-usermodel-client-role-mapper", "consentRequired" : false, "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", "user.attribute" : "foo", + "introspection.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String" + "jsonType.label" : "String", + "multivalued" : "true" } }, { "id" : "b757200e-494a-4585-857e-e4c18aef7a0c", @@ -4320,8 +4542,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "e18769b3-778b-47d8-be52-dd2769deebd1", @@ -4361,8 +4583,8 @@ data: "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" }, "protocolMappers" : [ { "id" : "98cc724c-3f53-47f7-bf9f-baf2f7e08026", @@ -4448,7 +4670,7 @@ data: "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper" ] } }, { "id" : "4b976576-c880-48a0-9b4d-2956cfd19b4a", @@ -4457,7 +4679,7 @@ data: "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "e1861ec9-2761-46fb-8048-149492269ff0", @@ -4487,6 +4709,14 @@ data: "allow-default-scopes" : [ "true" ] } } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "34049725-5a66-456c-b895-87ca7c11bb6b", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], "org.keycloak.storage.UserStorageProvider" : [ { "id" : "3a6f24e8-128b-4ac1-b3ab-694836db82fd", "name" : "Identity Service", @@ -4500,8 +4730,8 @@ data: "config" : { "ldap.attribute" : [ "mail" ], "is.mandatory.in.ldap" : [ "false" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "email" ] } }, { @@ -4512,8 +4742,8 @@ data: "config" : { "ldap.attribute" : [ "sn" ], "is.mandatory.in.ldap" : [ "true" ], - "read.only" : [ "false" ], "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "false" ], "user.model.attribute" : [ "lastName" ] } }, { @@ -4524,8 +4754,8 @@ data: "config" : { "ldap.attribute" : [ "modifyTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "modifyTimestamp" ] } }, { @@ -4558,17 +4788,17 @@ data: "providerId" : "group-ldap-mapper", "subComponents" : { }, "config" : { + "mode" : [ "LDAP_ONLY" ], "membership.attribute.type" : [ "DN" ], + "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "group.name.ldap.attribute" : [ "cn" ], + "ignore.missing.groups" : [ "false" ], "membership.user.ldap.attribute" : [ "uid" ], "preserve.group.inheritance" : [ "false" ], - "groups.dn" : [ "ou=users,{{ .Values.identityservice.global.ldapDomain }}" ], - "mode" : [ "LDAP_ONLY" ], - "user.roles.retrieve.strategy" : [ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" ], "membership.ldap.attribute" : [ "member" ], - "ignore.missing.groups" : [ "false" ], - "group.object.classes" : [ "groupOfNames" ], + "groups.dn" : [ "ou=users,dc=dbrepo,dc=at" ], "memberof.ldap.attribute" : [ "memberOf" ], + "group.object.classes" : [ "groupOfNames" ], "drop.non.existing.groups.during.sync" : [ "false" ], "groups.path" : [ "/" ] } @@ -4580,8 +4810,8 @@ data: "config" : { "ldap.attribute" : [ "createTimestamp" ], "is.mandatory.in.ldap" : [ "false" ], - "always.read.value.from.ldap" : [ "true" ], "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], "user.model.attribute" : [ "createTimestamp" ] } } ] @@ -4597,13 +4827,13 @@ data: "importEnabled" : [ "true" ], "enabled" : [ "true" ], "changedSyncPeriod" : [ "-1" ], + "usernameLDAPAttribute" : [ "uid" ], "bindCredential" : [ "{{ .Values.identityservice.global.adminPassword }}" ], "bindDn" : [ "cn={{ .Values.identityservice.global.adminUser }},{{ .Values.identityservice.global.ldapDomain }}" ], - "usernameLDAPAttribute" : [ "uid" ], "vendor" : [ "other" ], "uuidLDAPAttribute" : [ "entryUUID" ], "allowKerberosAuthentication" : [ "false" ], - "connectionUrl" : [ "ldap://identity-service:1389" ], + "connectionUrl" : [ "ldap://identity-service:389" ], "syncRegistrations" : [ "true" ], "authType" : [ "simple" ], "krbPrincipalAttribute" : [ "krb5PrincipalName" ], @@ -4617,14 +4847,6 @@ data: "validatePasswordPolicy" : [ "false" ] } } ], - "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "34049725-5a66-456c-b895-87ca7c11bb6b", - "providerId" : "declarative-user-profile", - "subComponents" : { }, - "config" : { - "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] - } - } ], "org.keycloak.keys.KeyProvider" : [ { "id" : "5b1052d2-fb71-47d2-86f9-908c869c8d1b", "name" : "hmac-generated-hs512", @@ -5236,10 +5458,12 @@ data: "parRequestUriLifespan" : "60", "clientSessionMaxLifespan" : "0", "frontendUrl" : "", + "organizationsEnabled" : "false", "acr.loa.map" : "{}" }, - "keycloakVersion" : "24.0.5", + "keycloakVersion" : "26.0.4", "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, "clientProfiles" : { "profiles" : [ ] }, diff --git a/helm/dbrepo/templates/auth-secret.yaml b/helm/dbrepo/templates/auth-secret.yaml index a568ef25007e51d70bf4ad1ac186645e57499015..dca0f861f132ea1557a52b488c63e74e6b435917 100644 --- a/helm/dbrepo/templates/auth-secret.yaml +++ b/helm/dbrepo/templates/auth-secret.yaml @@ -11,6 +11,8 @@ stringData: AUTH_SERVICE_ENDPOINT: "{{ .Values.authservice.endpoint }}" METADATA_DB: "{{ .Values.metadatadb.db.name }}" METADATA_DB_PASSWORD: "{{ .Values.metadatadb.rootUser.password }}" + METADATA_SERVICE_ENDPOINT: "{{ .Values.metadataservice.endpoint }}/api/user" METADATA_USERNAME: "{{ .Values.metadatadb.rootUser.user }}" SYSTEM_USERNAME: "{{ .Values.identityservice.users }}" + SYSTEM_PASSWORD: "{{ .Values.identityservice.userPasswords }}" {{- end }} diff --git a/helm/dbrepo/templates/gateway-configmap.yaml b/helm/dbrepo/templates/gateway-configmap.yaml index aa314d3c65948076a4e7eaf977bf47b8735b153d..8ef358871523c80ac16ec128e54e116b3d57d52e 100644 --- a/helm/dbrepo/templates/gateway-configmap.yaml +++ b/helm/dbrepo/templates/gateway-configmap.yaml @@ -39,6 +39,24 @@ data: proxy_read_timeout 90; } + location /realms { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass https://auth-service; + proxy_read_timeout 90; + } + + location /resources { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass https://auth-service; + proxy_read_timeout 90; + } + location /api/search { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/helm/dbrepo/templates/ui-secret.yaml b/helm/dbrepo/templates/ui-secret.yaml index 5620e7b2da88226759723c4bbd24f2e5479bc444..a84ac8f5cef024c88fa41ce085f494cca396df9f 100644 --- a/helm/dbrepo/templates/ui-secret.yaml +++ b/helm/dbrepo/templates/ui-secret.yaml @@ -22,4 +22,13 @@ stringData: NUXT_PUBLIC_PID_DEFAULT_PUBLISHER: "{{ .Values.ui.public.pid.default.publisher }}" NUXT_PUBLIC_UPLOAD_CLIENT: "{{ .Values.ui.public.upload.client | default $uploadEndpoint }}" NUXT_PUBLIC_BROKER_CONNECTIONS: "{{ include "dbrepo.broker.connections" . }}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_AUTHORIZATION_URL: "{{ .Values.gateway }}/realms/dbrepo/protocol/openid-connect/auth" + NUXT_OIDC_PROVIDERS_KEYCLOAK_BASE_URL: "{{ .Values.gateway }}/realms/dbrepo" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_ID: "{{ .Values.authservice.client.id }}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_CLIENT_SECRET: "{{ .Values.authservice.client.secret }}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_REDIRECT_URI: "{{ .Values.gateway }}" + NUXT_OIDC_PROVIDERS_KEYCLOAK_LOGOUT_URL: "{{ .Values.gateway }}/realms/dbrepo/protocol/openid-connect/logout" + NUXT_OIDC_PROVIDERS_KEYCLOAK_REDIRECT_URI: "{{ .Values.gateway }}/auth/keycloak/callback" + NUXT_OIDC_PROVIDERS_KEYCLOAK_TOKEN_URL: "{{ .Values.gateway }}/realms/dbrepo/protocol/openid-connect/token" + NUXT_OIDC_PROVIDERS_KEYCLOAK_USER_INFO_URL: "{{ .Values.gateway }}/realms/dbrepo/protocol/openid-connect/userinfo" {{- end }} diff --git a/helm/dbrepo/values.schema.json b/helm/dbrepo/values.schema.json index 9ade1b07493e1aa92d11a726d38f869b4ad69461..641287b434aff0bf533a2416081a9d6c0e447742 100644 --- a/helm/dbrepo/values.schema.json +++ b/helm/dbrepo/values.schema.json @@ -126,7 +126,7 @@ "endpoint": { "type": "string" }, - "extraEnvVarsCM": { + "extraEnvVarsSecret": { "type": "string" }, "extraVolumeMounts": { @@ -137,6 +137,9 @@ }, "name": { "type": "string" + }, + "subPath": { + "type": "string" } }, "type": "object" @@ -224,6 +227,9 @@ }, "type": "object" }, + "production": { + "type": "boolean" + }, "replicaCount": { "type": "integer" }, diff --git a/helm/dbrepo/values.yaml b/helm/dbrepo/values.yaml index 84cf7b5dddb964fa107f496b6c41d5bbcdfff34c..7515778b33d38b5e427a85742d5aa0d71b9e6a3a 100644 --- a/helm/dbrepo/values.yaml +++ b/helm/dbrepo/values.yaml @@ -87,6 +87,8 @@ authservice: fullnameOverride: auth-db auth: postgresPassword: postgres + ## @param authservice.production Start Keycloak with production profile. + production: true ## @param authservice.resourcesPreset The container resource presets resourcesPreset: "small" jwt: @@ -116,7 +118,7 @@ authservice: setupJob: image: ## @skip authservice.setupJob.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/auth-service-init:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/auth-service-init:1.6.3 ## @param authservice.setupJob.resourcesPreset The container resource preset resourcesPreset: "nano" ## @param authservice.setupJob.resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) @@ -127,8 +129,8 @@ authservice: ## limits: ## cpu: 500m ## memory: 1024Mi - ## @skip authservice.extraEnvVarsCM - extraEnvVarsCM: auth-service-config + ## @skip authservice.extraEnvVarsSecret + extraEnvVarsSecret: auth-service-secret ## @skip authservice.extraVolumes extraVolumes: - name: config-map @@ -140,7 +142,14 @@ authservice: ## @skip authservice.extraVolumeMounts extraVolumeMounts: - name: config-map - mountPath: /opt/keycloak/data/import + mountPath: /opt/keycloak/data/import/dbrepo-realm.json + subPath: dbrepo-realm.json + - name: config-map + mountPath: /opt/keycloak/data/import/master-realm.json + subPath: master-realm.json + - name: config-map + mountPath: /opt/bitnami/keycloak/providers/create-event-listener.jar + subPath: create-event-listener.jar - name: cache mountPath: /bitnami/keycloak/ ## @skip authservice.replicaCount The number of replicas. @@ -392,7 +401,7 @@ analyseservice: enabled: true image: ## @skip analyseservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.6.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param analyseservice.podSecurityContext.enabled Enable pods' Security Context @@ -453,7 +462,7 @@ metadataservice: enabled: true image: ## @skip metadataservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.6.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param metadataservice.podSecurityContext.enabled Enable pods' Security Context @@ -550,7 +559,7 @@ dataservice: endpoint: http://data-service image: ## @skip dataservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.6.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param dataservice.podSecurityContext.enabled Enable pods' Security Context @@ -636,7 +645,7 @@ searchservice: endpoint: http://search-service image: ## @skip searchservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.6.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param searchservice.podSecurityContext.enabled Enable pods' Security Context @@ -683,7 +692,7 @@ searchservice: init: image: ## @skip searchservice.init.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.6.3 ## @param searchservice.init.resourcesPreset The container resource preset resourcesPreset: "nano" ## @param searchservice.init.resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) @@ -744,7 +753,7 @@ storageservice: init: image: ## @skip storageservice.init.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.6.3 s3: ## @param storageservice.init.s3.endpoint The S3-capable endpoint the microservice connects to. endpoint: http://storage-service-s3:8333 @@ -853,7 +862,7 @@ ui: enabled: true image: ## @skip ui.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/ui:1.6.2 + name: registry.datalab.tuwien.ac.at/dbrepo/ui:1.6.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param ui.podSecurityContext.enabled Enable pods' Security Context @@ -941,7 +950,7 @@ ui: ## @param ui.public.doi.endpoint The DOI proxy. endpoint: https://doi.org ## @param ui.replicaCount The number of replicas. - replicaCount: 2 + replicaCount: 1 ## @skip ui.extraVolumes extraVolumes: [ ] # - name: images-map diff --git a/helm/seaweedfs/Chart.lock b/helm/seaweedfs/Chart.lock index edcc38c41f0c6b2b35f8d740566918ddb16f17fc..96e5c8dce913d2f7a73bf8adc5d7f121fb2a3b70 100644 --- a/helm/seaweedfs/Chart.lock +++ b/helm/seaweedfs/Chart.lock @@ -1,12 +1,12 @@ dependencies: - name: mariadb repository: oci://registry-1.docker.io/bitnamicharts - version: 20.2.1 + version: 20.2.2 - name: postgresql repository: oci://registry-1.docker.io/bitnamicharts - version: 16.4.3 + version: 16.4.6 - name: common repository: oci://registry-1.docker.io/bitnamicharts - version: 2.29.0 -digest: sha256:4c967f771b303ca0db9ba2e355790152448c77a05d3f6c69eda6c234bc3f60c6 -generated: "2025-01-17T15:24:18.141765362+01:00" + version: 2.29.1 +digest: sha256:bc14ae7bbe7be291adc4a6329ae64835c367b09277a2678c4e10cc74b19ee491 +generated: "2025-02-04T22:22:11.88596441+01:00" diff --git a/helm/seaweedfs/charts/common-2.29.0.tgz b/helm/seaweedfs/charts/common-2.29.0.tgz deleted file mode 100644 index f36e9e24ec32ce1237d1ee774541c5586fce222d..0000000000000000000000000000000000000000 Binary files a/helm/seaweedfs/charts/common-2.29.0.tgz and /dev/null differ diff --git a/helm/seaweedfs/charts/common-2.29.1.tgz b/helm/seaweedfs/charts/common-2.29.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8b9abbbc5b6c62a743cc0fb041d26b199c4313c8 Binary files /dev/null and b/helm/seaweedfs/charts/common-2.29.1.tgz differ diff --git a/helm/seaweedfs/charts/mariadb-20.2.1.tgz b/helm/seaweedfs/charts/mariadb-20.2.1.tgz deleted file mode 100644 index 9bba8eed49e866977396c0076b1a9c5946ec88b1..0000000000000000000000000000000000000000 Binary files a/helm/seaweedfs/charts/mariadb-20.2.1.tgz and /dev/null differ diff --git a/helm/seaweedfs/charts/mariadb-20.2.2.tgz b/helm/seaweedfs/charts/mariadb-20.2.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a983469f30dfce693288215705891f387c517319 Binary files /dev/null and b/helm/seaweedfs/charts/mariadb-20.2.2.tgz differ diff --git a/helm/seaweedfs/charts/postgresql-16.4.3.tgz b/helm/seaweedfs/charts/postgresql-16.4.3.tgz deleted file mode 100644 index 429f7ed063f6655796792fbe711b027e147ddda4..0000000000000000000000000000000000000000 Binary files a/helm/seaweedfs/charts/postgresql-16.4.3.tgz and /dev/null differ diff --git a/helm/seaweedfs/charts/postgresql-16.4.6.tgz b/helm/seaweedfs/charts/postgresql-16.4.6.tgz new file mode 100644 index 0000000000000000000000000000000000000000..9016ba352dfd2b553e3cac4a8a80fb7c8c539d65 Binary files /dev/null and b/helm/seaweedfs/charts/postgresql-16.4.6.tgz differ diff --git a/install.sh b/install.sh index 3ccfd30b2162c145bac392457b5ceccc7f7d81eb..fa48d9bc35c83375ca02c9810b7e6a0ee780af21 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash # preset -VERSION="1.6.2" +VERSION="1.6.3" MIN_CPU=8 MIN_RAM=4 MIN_MAP_COUNT=262144 diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py index 50cd161bc300e249e7cb904a055e4f8d7390e818..bd0d13dc1894d1a83891abdcdd6fb438de27f3f4 100644 --- a/lib/python/dbrepo/api/dto.py +++ b/lib/python/dbrepo/api/dto.py @@ -124,7 +124,7 @@ class TableBrief(BaseModel): id: int database_id: int name: str - description: Optional[str] + description: Optional[str] = None internal_name: str is_versioned: bool is_public: bool diff --git a/lib/python/docs/index.rst b/lib/python/docs/index.rst index 13561e9c13dd7f78798aca5b5e9e7162f309a7c2..b3c05fe3645e558aaa92a470c19be4d27db46add 100644 --- a/lib/python/docs/index.rst +++ b/lib/python/docs/index.rst @@ -6,7 +6,7 @@ Pandas `DataFrame <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame provides an object-oriented API as well as low-level access to DBRepo services. .. note:: - The SDK has been implemented and documented for DBRepo version 1.6.2, earlier versions may be supported but are not tested for compatibility. + The SDK has been implemented and documented for DBRepo version 1.6.3, earlier versions may be supported but are not tested for compatibility. Quickstart ---------- diff --git a/lib/python/pyproject.toml b/lib/python/pyproject.toml index 5b8deb84080301d7d2eaa2738c2e30b43a4ecca0..99f62c9a44dcdb10f63d03c97048a0676a1b1d02 100644 --- a/lib/python/pyproject.toml +++ b/lib/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dbrepo" -version = "1.6.2" +version = "1.6.3" description = "DBRepo Python Library" keywords = [ "DBRepo", diff --git a/lib/python/setup.py b/lib/python/setup.py index c6deff531d7840f390eb16936af94cff9f9fa622..dfe9a897cab556846dbf7dfb6ffb5303d0e77b03 100644 --- a/lib/python/setup.py +++ b/lib/python/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup setup(name="dbrepo", - version="1.6.2", + version="1.6.3", description="A library for communicating with DBRepo", url="https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/", author="Martin Weise", diff --git a/make/build.mk b/make/build.mk index bc6dfc56a7421b53c6ad02f5bd4518f5f6b614ab..270b2cee6fd80b9dea9a475399081ae34ad65c91 100644 --- a/make/build.mk +++ b/make/build.mk @@ -14,23 +14,27 @@ build-data-service: ## Build the Data Service. build-metadata-service: ## Build the Metadata Service. mvn -f ./dbrepo-metadata-service/pom.xml clean package -DskipTests +.PHONY: build-auth-event-listener +build-auth-event-listener: ## Build the Auth Service Event Listener. + mvn -f ./dbrepo-auth-service/listeners/pom.xml clean package -DskipTests + .PHONY: build-ui build-ui: ## Build the UI. bun --cwd ./dbrepo-ui build .PHONY: build-lib build-lib: ## Build the Python Library. - rm -f ./dbrepo-analyse-service/Pipfile.lock ./dbrepo-analyse-service/lib/dbrepo-${APP_VERSION}* - rm -f ./dbrepo-search-service/Pipfile.lock ./dbrepo-search-service/lib/dbrepo-${APP_VERSION}* - rm -f ./dbrepo-search-service/init/Pipfile.lock ./dbrepo-search-service/init/lib/dbrepo-${APP_VERSION}* + rm -rf ./dbrepo-analyse-service/venv/ ./dbrepo-analyse-service/Pipfile.lock ./dbrepo-analyse-service/lib/dbrepo-${APP_VERSION}* + rm -rf ./dbrepo-search-service/venv/ ./dbrepo-search-service/Pipfile.lock ./dbrepo-search-service/lib/dbrepo-${APP_VERSION}* + rm -rf ./dbrepo-search-service/init/venv/ ./dbrepo-search-service/init/Pipfile.lock ./dbrepo-search-service/init/lib/dbrepo-${APP_VERSION}* python3 -m build --sdist ./lib/python python3 -m build --wheel ./lib/python cp -r ./lib/python/dist/dbrepo-${APP_VERSION}* ./dbrepo-analyse-service/lib - (cd ./dbrepo-analyse-service && PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock) + (cd ./dbrepo-analyse-service && python3 -m venv venv && PIPENV_IGNORE_VIRTUALENVS=1 pipenv install --dev) cp -r ./lib/python/dist/dbrepo-${APP_VERSION}* ./dbrepo-search-service/lib - (cd ./dbrepo-search-service && PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock) + (cd ./dbrepo-search-service && python3 -m venv venv && PIPENV_IGNORE_VIRTUALENVS=1 pipenv install --dev) cp -r ./lib/python/dist/dbrepo-${APP_VERSION}* ./dbrepo-search-service/init/lib - (cd ./dbrepo-search-service/init && PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock) + (cd ./dbrepo-search-service/init && python3 -m venv venv && PIPENV_IGNORE_VIRTUALENVS=1 pipenv install --dev) .PHONY: build-helm build-helm: ## Build the DBRepo and DBRepo MariaDB Galera Helm Charts. diff --git a/make/dev.mk b/make/dev.mk index 0282dbbce287356e207822fdc9dbf1be7e26e0b8..d8da31086b3dfcba7f85aeeaa6db64564b87c036 100644 --- a/make/dev.mk +++ b/make/dev.mk @@ -1,7 +1,7 @@ ##@ Development .PHONY: start-dev -start-dev: build-images ## Start the development deployment. +start-dev: build-images build-auth-event-listener ## Start the development deployment. docker container stop dbrepo-gateway-service || true docker container rm dbrepo-gateway-service || true docker compose up -d diff --git a/sonar-project.properties b/sonar-project.properties index 83f00a3a2497898a97eaf0d12851ce7ab9383709..4442bf46ff84667faa8f20672f21d14be29fef3e 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=fair-data-austria-db-repository_fda-services_a57fa043-ab99-4cdd-a721-162d9a916d77 sonar.host.url=https://s39.datalab.tuwien.ac.at # project -sonar.projectVersion=1.6.2 +sonar.projectVersion=1.6.3 # general sonar.qualitygate.wait=true sonar.projectCreation.mainBranchName=master