From 88b6085a7920a96e118e81aec9e4ae6c9c3a77c7 Mon Sep 17 00:00:00 2001 From: Jon Eubank Date: Thu, 27 Jun 2024 10:59:50 -0400 Subject: [PATCH 1/2] Versioning 5.10.1 --- pom.xml | 2 +- score-client/pom.xml | 2 +- score-core/pom.xml | 2 +- score-fs/pom.xml | 2 +- score-server/pom.xml | 2 +- score-test/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 9d044a6b..11ca7ac5 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 pom ${project.artifactId} ${project.name} diff --git a/score-client/pom.xml b/score-client/pom.xml index 19e80b6e..d022464d 100644 --- a/score-client/pom.xml +++ b/score-client/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 ../pom.xml diff --git a/score-core/pom.xml b/score-core/pom.xml index b8db5a9b..17f86b8d 100644 --- a/score-core/pom.xml +++ b/score-core/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 ../pom.xml diff --git a/score-fs/pom.xml b/score-fs/pom.xml index fd4fac1e..0bae4d22 100644 --- a/score-fs/pom.xml +++ b/score-fs/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 ../pom.xml diff --git a/score-server/pom.xml b/score-server/pom.xml index cd4719ee..5d52dd36 100644 --- a/score-server/pom.xml +++ b/score-server/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 ../pom.xml diff --git a/score-test/pom.xml b/score-test/pom.xml index 91a00667..53dabb43 100644 --- a/score-test/pom.xml +++ b/score-test/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1-SNAPSHOT + 5.10.1 ../pom.xml From 4bcd90661b7b720072527c6c055c23e8caf6a645 Mon Sep 17 00:00:00 2001 From: Leonardo Rivera Date: Thu, 4 Jul 2024 16:09:46 -0400 Subject: [PATCH 2/2] Rc/5.11.0 (#418) * Keycloak permission manager (#387) * enable Keycloak apiKeys * setup security config and update mvn dependencies * junit missing dependency * unit test mock Jwt Decoder * fix unit test - use a JWT decoder for testing - Remove unused clases - Remove JWT expired unit tests as validation is now implemented by Spring Security * code format * test profile * docker-compose update images * add keycloak to docker compose * fix merge conflict * fix typo curl command * update keycloak system client and apikeys * version 5.11.0 --- Makefile | 8 +- docker-compose.yml | 88 +- docker/ego-init/init.sql | 756 ------ .../data_import/myrealm-realm.json | 2086 +++++++++++++++++ .../data_import/myrealm-users-0.json | 67 + docker/score-client-init/run_tests.sh | 4 +- docker/score-client-init/test_manifests.sh | 2 +- pom.xml | 10 +- score-client/pom.xml | 7 +- .../score/client/config/ProfileConfig.java | 5 +- .../src/main/resources/application.yml | 15 +- .../score/client/AbstractClientMainTest.java | 4 +- score-core/pom.xml | 2 +- score-fs/pom.xml | 2 +- score-server/pom.xml | 33 +- .../score/server/config/KeycloakConfig.java | 43 + .../score/server/config/SecurityConfig.java | 105 +- .../score/server/config/SwaggerConfig.java | 48 + .../server/config/TokenServicesConfig.java | 84 - .../auth/KeycloakAuthorizationService.java | 112 + .../AccessTokenConverterWithExpiry.java | 27 - .../security/ApiKeyIntrospectResponse.java | 15 + .../server/security/ApiKeyIntrospector.java | 154 ++ .../security/CachingRemoteTokenServices.java | 34 - .../security/DefaultPublicKeyFetcher.java | 21 - .../ExpiringOauth2Authentication.java | 22 - .../score/server/security/JWTConverter.java | 51 - .../server/security/KeycloakPermission.java | 15 + .../security/MergedServerTokenServices.java | 50 - .../server/security/PublicKeyFetcher.java | 7 - .../score/server/security/TokenChecker.java | 25 - .../AbstractScopeAuthorizationStrategy.java | 24 + .../DownloadScopeAuthorizationStrategy.java | 16 +- .../UploadScopeAuthorizationStrategy.java | 16 +- .../overture/score/server/util/JsonUtils.java | 88 + .../overture/score/server/util/Scopes.java | 76 +- .../src/main/resources/application.yml | 109 +- .../overture/score/server/JWTTestConfig.java | 31 +- .../AccessTokenConverterWithExpiryTest.java | 31 - ...ownloadScopeAuthorizationStrategyTest.java | 130 +- .../server/security/JWTSecurityTest.java | 2 +- .../MergedServerTokenServicesTest.java | 63 - .../UploadScopeAuthorizationStrategyTest.java | 132 +- .../score/server/utils/JWTGenerator.java | 8 +- score-test/pom.xml | 2 +- 45 files changed, 3186 insertions(+), 1444 deletions(-) delete mode 100644 docker/ego-init/init.sql create mode 100644 docker/keycloak-init/data_import/myrealm-realm.json create mode 100644 docker/keycloak-init/data_import/myrealm-users-0.json create mode 100644 score-server/src/main/java/bio/overture/score/server/config/KeycloakConfig.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/config/TokenServicesConfig.java create mode 100644 score-server/src/main/java/bio/overture/score/server/repository/auth/KeycloakAuthorizationService.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/AccessTokenConverterWithExpiry.java create mode 100644 score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospectResponse.java create mode 100644 score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospector.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/CachingRemoteTokenServices.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/DefaultPublicKeyFetcher.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/ExpiringOauth2Authentication.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/JWTConverter.java create mode 100644 score-server/src/main/java/bio/overture/score/server/security/KeycloakPermission.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/MergedServerTokenServices.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/PublicKeyFetcher.java delete mode 100644 score-server/src/main/java/bio/overture/score/server/security/TokenChecker.java create mode 100644 score-server/src/main/java/bio/overture/score/server/util/JsonUtils.java delete mode 100644 score-server/src/test/java/bio/overture/score/server/security/AccessTokenConverterWithExpiryTest.java delete mode 100644 score-server/src/test/java/bio/overture/score/server/security/MergedServerTokenServicesTest.java diff --git a/Makefile b/Makefile index c6ee7bd7..9afa4710 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ _ping_song_server: --retry 5 \ --retry-delay 0 \ --retry-max-time 40 \ - --retry-connrefuse \ + --retry-connrefused \ 'http://localhost:8080/isAlive' @echo "" @@ -192,10 +192,10 @@ rebuild-server: clean-mvn package rebuild-all: clean-mvn package @$(DOCKER_COMPOSE_CMD) build score-server score-client -# Start ego, song, and object-storage. +# Start keycloak, song, and object-storage. start-deps: _setup package - @echo $(YELLOW)$(INFO_HEADER) "Starting dependencies: ego, song and object-storage" $(END) - @$(DC_UP_CMD) ego-api song-server object-storage + @echo $(YELLOW)$(INFO_HEADER) "Starting dependencies: keycloak, song and object-storage" $(END) + @$(DC_UP_CMD) keycloak-server song-server object-storage # Start score-server and all dependencies. Affected by DEMO_MODE start-score-server: _setup package start-deps _setup-object-storage diff --git a/docker-compose.yml b/docker-compose.yml index ebf80daf..645517f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,31 +1,36 @@ version: '3.7' services: - ego-api: - image: "overture/ego:3.1.0" + keycloak-server: + image: docker.io/bitnami/keycloak:22 environment: - SERVER_PORT: 8080 - SPRING_DATASOURCE_URL: jdbc:postgresql://ego-postgres:5432/ego?stringtype=unspecified - SPRING_DATASOURCE_USERNAME: postgres - SPRING_DATASOURCE_PASSWORD: password - SPRING_FLYWAY_ENABLED: "true" - SPRING_FLYWAY_LOCATIONS: "classpath:flyway/sql,classpath:db/migration" - SPRING_PROFILES: demo, auth - expose: - - "8080" + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://keycloak-postgresql/bitnami_keycloak + - KC_DB_USERNAME=bn_keycloak + # default expiration days of apiKeys is 365 + # - APIKEY_DURATION_DAYS=365 ports: - "9082:8080" - command: java -jar /srv/ego/install/ego.jar depends_on: - - ego-postgres - ego-postgres: - image: postgres:9.5 + - keycloak-postgresql + volumes: + - type: bind + source: ./docker/keycloak-init/data_import + target: /opt/bitnami/keycloak/data/import + command: + - /bin/bash + - -c + - | + curl -sL https://github.com/oicr-softeng/keycloak-apikeys/releases/download/1.0.1/keycloak-apikeys-1.0.1.jar -o /opt/bitnami/keycloak/providers/keycloak-apikeys-1.0.1.jar + kc.sh start-dev --import-realm + keycloak-postgresql: + image: docker.io/bitnami/postgresql:11 environment: - - POSTGRES_DB=ego - - POSTGRES_PASSWORD=password + # ALLOW_EMPTY_PASSWORD is recommended only for development. + - ALLOW_EMPTY_PASSWORD=yes + - POSTGRESQL_USERNAME=bn_keycloak + - POSTGRESQL_DATABASE=bitnami_keycloak expose: - "5432" - volumes: - - "./docker/ego-init:/docker-entrypoint-initdb.d" ports: - "9444:5432" object-storage: @@ -36,7 +41,7 @@ services: MINIO_SECRET_KEY: minio123 command: server /data healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] interval: 30s timeout: 20s retries: 3 @@ -61,11 +66,15 @@ services: S3_ACCESSKEY: minio S3_SECRETKEY: minio123 S3_SIGV4ENABLED: "true" - AUTH_SERVER_URL: http://ego-api:8080/o/check_api_key/ + SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://keycloak-server:8080/realms/myrealm/protocol/openid-connect/certs + AUTH_SERVER_URL: http://keycloak-server:8080/realms/myrealm/apikey/check_api_key/ AUTH_SERVER_TOKENNAME: apiKey - AUTH_SERVER_CLIENTID: score - AUTH_SERVER_CLIENTSECRET: scoresecret - AUTH_SERVER_SCOPE_STUDY_PREFIX: score. + AUTH_SERVER_CLIENTID: system + AUTH_SERVER_CLIENTSECRET: systemsecret + AUTH_SERVER_PROVIDER: keycloak + AUTH_SERVER_KEYCLOAK_HOST: http://keycloak-server:8080 + AUTH_SERVER_KEYCLOAK_REALM: myrealm + AUTH_SERVER_SCOPE_STUDY_PREFIX: PROGRAMDATA. AUTH_SERVER_SCOPE_UPLOAD_SUFFIX: .WRITE AUTH_SERVER_SCOPE_DOWNLOAD_SUFFIX: .READ AUTH_SERVER_SCOPE_DOWNLOAD_SYSTEM: score.WRITE @@ -86,7 +95,6 @@ services: depends_on: - object-storage - song-server - - ego-api volumes: - "./docker/scratch/storage-server-logs:/opt/dcc/storage_server_logs" score-client: @@ -95,7 +103,7 @@ services: dockerfile: "$DOCKERFILE_NAME" target: client environment: - ACCESSTOKEN: f69b726d-d40f-4261-b105-1ec7e6bf04d5 + ACCESSTOKEN: 07a5a12e-a85f-4248-a9a1-851a8062b6ac METADATA_URL: http://song-server:8080 STORAGE_URL: http://score-server:8080 JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,address=*:5005,server=y,suspend=n @@ -107,7 +115,7 @@ services: command: bin/score-client user: "$MY_UID:$MY_GID" song-db: - image: "postgres:9.6" + image: "postgres:11.1" environment: POSTGRES_DB: song POSTGRES_USER: postgres @@ -118,6 +126,11 @@ services: - "12345:5432" volumes: - "./docker/song-db-init:/docker-entrypoint-initdb.d" + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 15s + timeout: 15s + retries: 5 aws-cli: image: "mesosphere/aws-cli:latest" environment: @@ -127,18 +140,23 @@ services: volumes: - "./docker/object-storage-init/data/oicr.icgc.test/data:/score-data:ro" song-server: - image: overture/song-server:4.2.2 + image: ghcr.io/overture-stack/song-server:438c2c42 environment: SERVER_PORT: 8080 SPRING_PROFILES_ACTIVE: "prod,secure,default" - AUTH_SERVER_URL: http://ego-api:8080/o/check_token/ - AUTH_SERVER_CLIENTID: song - AUTH_SERVER_CLIENTSECRET: songsecret - AUTH_SERVER_SCOPE_STUDY_PREFIX: song. + SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://keycloak-server:8080/realms/myrealm/protocol/openid-connect/certs + AUTH_SERVER_INTROSPECTIONURI: http://keycloak-server:8080/realms/myrealm/apikey/check_api_key/ + AUTH_SERVER_TOKENNAME: apiKey + AUTH_SERVER_CLIENTID: system + AUTH_SERVER_CLIENTSECRET: systemsecret + AUTH_SERVER_PROVIDER: keycloak + AUTH_SERVER_KEYCLOAK_HOST: http://keycloak-server:8080 + AUTH_SERVER_KEYCLOAK_REALM: myrealm + AUTH_SERVER_SCOPE_STUDY_PREFIX: PROGRAMDATA. AUTH_SERVER_SCOPE_STUDY_SUFFIX: .WRITE AUTH_SERVER_SCOPE_SYSTEM: song.WRITE SCORE_URL: http://score-server:8080 - SCORE_ACCESSTOKEN: f69b726d-d40f-4261-b105-1ec7e6bf04d5 + SCORE_ACCESSTOKEN: 07a5a12e-a85f-4248-a9a1-851a8062b6ac MANAGEMENT_SERVER_PORT: 8081 ID_USELOCAL: "true" SPRING_DATASOURCE_USERNAME: postgres @@ -149,8 +167,8 @@ services: ports: - "8080:8080" depends_on: - - song-db - - ego-api + song-db: + condition: service_healthy volumes: - "./docker/scratch/song-server-logs:/opt/dcc/server_logs" diff --git a/docker/ego-init/init.sql b/docker/ego-init/init.sql deleted file mode 100644 index d2ad8b74..00000000 --- a/docker/ego-init/init.sql +++ /dev/null @@ -1,756 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 9.5.16 --- Dumped by pg_dump version 9.5.16 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: --- - -CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; - - --- --- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; - - --- --- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: --- - -CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; - - --- --- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; - - --- --- Name: aclmask; Type: TYPE; Schema: public; Owner: postgres --- - -CREATE TYPE public.aclmask AS ENUM ( - 'READ', - 'WRITE', - 'DENY' -); - - -ALTER TYPE public.aclmask OWNER TO postgres; - --- --- Name: applicationtype; Type: TYPE; Schema: public; Owner: postgres --- - -CREATE TYPE public.applicationtype AS ENUM ( - 'CLIENT', - 'ADMIN' -); - - -ALTER TYPE public.applicationtype OWNER TO postgres; - --- --- Name: languagetype; Type: TYPE; Schema: public; Owner: postgres --- - -CREATE TYPE public.languagetype AS ENUM ( - 'ENGLISH', - 'FRENCH', - 'SPANISH' -); - - -ALTER TYPE public.languagetype OWNER TO postgres; - --- --- Name: statustype; Type: TYPE; Schema: public; Owner: postgres --- - -CREATE TYPE public.statustype AS ENUM ( - 'APPROVED', - 'REJECTED', - 'DISABLED', - 'PENDING' -); - - -ALTER TYPE public.statustype OWNER TO postgres; - --- --- Name: usertype; Type: TYPE; Schema: public; Owner: postgres --- - -CREATE TYPE public.usertype AS ENUM ( - 'USER', - 'ADMIN' -); - - -ALTER TYPE public.usertype OWNER TO postgres; - -SET default_tablespace = ''; - -SET default_with_oids = false; - --- --- Name: egoapplication; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.egoapplication ( - name character varying(255) NOT NULL, - clientid character varying(255) NOT NULL, - clientsecret character varying(255) NOT NULL, - redirecturi text, - description text, - status public.statustype DEFAULT 'PENDING'::public.statustype NOT NULL, - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - type public.applicationtype DEFAULT 'CLIENT'::public.applicationtype NOT NULL -); - - -ALTER TABLE public.egoapplication OWNER TO postgres; - --- --- Name: egogroup; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.egogroup ( - name character varying(255) NOT NULL, - description character varying(255), - status public.statustype DEFAULT 'PENDING'::public.statustype NOT NULL, - id uuid DEFAULT public.uuid_generate_v4() NOT NULL -); - - -ALTER TABLE public.egogroup OWNER TO postgres; - --- --- Name: egouser; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.egouser ( - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - name character varying(255) NOT NULL, - email character varying(255) NOT NULL, - type public.usertype DEFAULT 'USER'::public.usertype NOT NULL, - firstname text DEFAULT ''::text NOT NULL, - lastname text DEFAULT ''::text NOT NULL, - createdat timestamp without time zone NOT NULL, - lastlogin timestamp without time zone, - status public.statustype DEFAULT 'PENDING'::public.statustype NOT NULL, - preferredlanguage public.languagetype -); - - -ALTER TABLE public.egouser OWNER TO postgres; - --- --- Name: flyway_schema_history; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.flyway_schema_history ( - installed_rank integer NOT NULL, - version character varying(50), - description character varying(200) NOT NULL, - type character varying(20) NOT NULL, - script character varying(1000) NOT NULL, - checksum integer, - installed_by character varying(100) NOT NULL, - installed_on timestamp without time zone DEFAULT now() NOT NULL, - execution_time integer NOT NULL, - success boolean NOT NULL -); - - -ALTER TABLE public.flyway_schema_history OWNER TO postgres; - --- --- Name: groupapplication; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.groupapplication ( - group_id uuid NOT NULL, - application_id uuid NOT NULL -); - - -ALTER TABLE public.groupapplication OWNER TO postgres; - --- --- Name: grouppermission; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.grouppermission ( - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - policy_id uuid, - group_id uuid, - access_level public.aclmask NOT NULL -); - - -ALTER TABLE public.grouppermission OWNER TO postgres; - --- --- Name: policy; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.policy ( - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - owner uuid, - name character varying(255) NOT NULL -); - - -ALTER TABLE public.policy OWNER TO postgres; - --- --- Name: token; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.token ( - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - name character varying(2048) NOT NULL, - owner uuid NOT NULL, - issuedate timestamp without time zone DEFAULT now() NOT NULL, - isrevoked boolean DEFAULT false NOT NULL, - description character varying(255), - expirydate timestamp without time zone DEFAULT now() NOT NULL -); - - -ALTER TABLE public.token OWNER TO postgres; - --- --- Name: tokenscope; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.tokenscope ( - token_id uuid NOT NULL, - policy_id uuid NOT NULL, - access_level public.aclmask NOT NULL -); - - -ALTER TABLE public.tokenscope OWNER TO postgres; - --- --- Name: userapplication; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.userapplication ( - user_id uuid NOT NULL, - application_id uuid NOT NULL -); - - -ALTER TABLE public.userapplication OWNER TO postgres; - --- --- Name: usergroup; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.usergroup ( - user_id uuid NOT NULL, - group_id uuid NOT NULL -); - - -ALTER TABLE public.usergroup OWNER TO postgres; - --- --- Name: userpermission; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.userpermission ( - id uuid DEFAULT public.uuid_generate_v4() NOT NULL, - policy_id uuid, - user_id uuid, - access_level public.aclmask NOT NULL -); - - -ALTER TABLE public.userpermission OWNER TO postgres; - --- --- Data for Name: egoapplication; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.egoapplication (name, clientid, clientsecret, redirecturi, description, status, id, type) FROM stdin; -\. - - --- --- Data for Name: egogroup; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.egogroup (name, description, status, id) FROM stdin; -overture-admin Admin APPROVED f2885e96-f74e-4f7a-b935-fb48b18e761d -\. - - --- --- Data for Name: egouser; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.egouser (id, name, email, type, firstname, lastname, createdat, lastlogin, status, preferredlanguage) FROM stdin; -c6608c3e-1181-4957-99c4-094493391096 john.doe@example.com john.doe@example.com USER John Doe 2019-11-20 20:38:33.815 \N APPROVED ENGLISH -\. - - --- --- Data for Name: flyway_schema_history; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, installed_on, execution_time, success) FROM stdin; -1 1 initial database SQL V1__initial_database.sql -556387533 postgres 2019-11-20 20:35:19.523848 54 t -2 1.1 complete uuid migration SPRING_JDBC db.migration.V1_1__complete_uuid_migration \N postgres 2019-11-20 20:35:19.591892 75 t -3 1.2 acl expansion SQL V1_2__acl_expansion.sql -125082215 postgres 2019-11-20 20:35:19.675498 19 t -4 1.3 string to date SPRING_JDBC db.migration.V1_3__string_to_date \N postgres 2019-11-20 20:35:19.701898 28 t -5 1.4 score integration SQL V1_4__score_integration.sql 323452398 postgres 2019-11-20 20:35:19.738191 13 t -6 1.5 table renaming SQL V1_5__table_renaming.sql 480984865 postgres 2019-11-20 20:35:19.758578 11 t -7 1.6 add not null constraint SQL V1_6__add_not_null_constraint.sql 1562044084 postgres 2019-11-20 20:35:19.776433 7 t -8 1.7 token modification SQL V1_7__token_modification.sql -11736908 postgres 2019-11-20 20:35:19.789578 5 t -9 1.8 application types SQL V1_8__application_types.sql -1894533468 postgres 2019-11-20 20:35:19.801421 13 t -10 1.9 new enum types SQL V1_9__new_enum_types.sql 1135272560 postgres 2019-11-20 20:35:19.821124 84 t -11 1.10 remove apps from apitokens SQL V1_10__remove_apps_from_apitokens.sql -1412739333 postgres 2019-11-20 20:35:19.915229 2 t -12 1.11 add expiry date api tokens SQL V1_11__add_expiry_date_api_tokens.sql -774407414 postgres 2019-11-20 20:35:19.923177 11 t -13 1.12 egoapplication unique constraints SQL V1_12__egoapplication_unique_constraints.sql 1415229200 postgres 2019-11-20 20:35:19.940859 4 t -14 1.13 fname lname not null constraints SQL V1_13__fname_lname_not_null_constraints.sql 148150980 postgres 2019-11-20 20:35:19.950402 3 t -15 1.14 indices SQL V1_14__indices.sql 1170056158 postgres 2019-11-20 20:35:19.959073 40 t -\. - - --- --- Data for Name: groupapplication; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.groupapplication (group_id, application_id) FROM stdin; -\. - - --- --- Data for Name: grouppermission; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.grouppermission (id, policy_id, group_id, access_level) FROM stdin; -9e361c69-7d3e-4144-a638-0ebda207b50a 4b7718ce-ad94-4ec5-b0fb-bf91a520a816 f2885e96-f74e-4f7a-b935-fb48b18e761d WRITE -f781e10a-f4b8-4dd1-a9c6-75a9193d91ff 7978c66c-7bd6-4d7b-a6e2-418ab6714859 f2885e96-f74e-4f7a-b935-fb48b18e761d WRITE -\. - - --- --- Data for Name: policy; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.policy (id, owner, name) FROM stdin; -4b7718ce-ad94-4ec5-b0fb-bf91a520a816 \N song -7978c66c-7bd6-4d7b-a6e2-418ab6714859 \N score -ed5149c4-d8c3-46f8-ab01-b903f82b5fe3 \N score.TEST-CA -\. - - --- --- Data for Name: token; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.token (id, name, owner, issuedate, isrevoked, description, expirydate) FROM stdin; -f7d708ef-41f8-493f-ad8e-cb0ac97b0688 f69b726d-d40f-4261-b105-1ec7e6bf04d5 c6608c3e-1181-4957-99c4-094493391096 2023-11-02 20:52:08.247 f \N 2030-11-02 20:52:08.247 -fafaac34-6b01-47ef-9ae6-6d8cb30af5ca fd0c6c40-254b-4a5f-82e7-cf21a380ccb3 c6608c3e-1181-4957-99c4-094493391096 2023-11-02 20:55:56.186 f \N 2030-11-02 20:55:56.186 -7df26ca6-801f-4302-a318-6f766d759b1d 1f070fb0-0ee4-4815-8097-b5b065c661cc c6608c3e-1181-4957-99c4-094493391096 2023-11-02 20:57:38.345 f \N 2030-11-02 20:57:38.345 -\. - - --- --- Data for Name: tokenscope; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.tokenscope (token_id, policy_id, access_level) FROM stdin; -f7d708ef-41f8-493f-ad8e-cb0ac97b0688 4b7718ce-ad94-4ec5-b0fb-bf91a520a816 WRITE -f7d708ef-41f8-493f-ad8e-cb0ac97b0688 7978c66c-7bd6-4d7b-a6e2-418ab6714859 WRITE -fafaac34-6b01-47ef-9ae6-6d8cb30af5ca ed5149c4-d8c3-46f8-ab01-b903f82b5fe3 WRITE -7df26ca6-801f-4302-a318-6f766d759b1d ed5149c4-d8c3-46f8-ab01-b903f82b5fe3 WRITE -7df26ca6-801f-4302-a318-6f766d759b1d 4b7718ce-ad94-4ec5-b0fb-bf91a520a816 WRITE -\. - - --- --- Data for Name: userapplication; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.userapplication (user_id, application_id) FROM stdin; -\. - - --- --- Data for Name: usergroup; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.usergroup (user_id, group_id) FROM stdin; -c6608c3e-1181-4957-99c4-094493391096 f2885e96-f74e-4f7a-b935-fb48b18e761d -\. - - --- --- Data for Name: userpermission; Type: TABLE DATA; Schema: public; Owner: postgres --- - -COPY public.userpermission (id, policy_id, user_id, access_level) FROM stdin; -a66e447c-0f11-45b4-aaa0-a878b911d688 ed5149c4-d8c3-46f8-ab01-b903f82b5fe3 c6608c3e-1181-4957-99c4-094493391096 WRITE -\. - - --- --- Name: egoapplication_clientid_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egoapplication - ADD CONSTRAINT egoapplication_clientid_key UNIQUE (clientid); - - --- --- Name: egoapplication_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egoapplication - ADD CONSTRAINT egoapplication_name_key UNIQUE (name); - - --- --- Name: egoapplication_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egoapplication - ADD CONSTRAINT egoapplication_pkey PRIMARY KEY (id); - - --- --- Name: egogroup_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egogroup - ADD CONSTRAINT egogroup_name_key UNIQUE (name); - - --- --- Name: egogroup_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egogroup - ADD CONSTRAINT egogroup_pkey PRIMARY KEY (id); - - --- --- Name: egouser_email_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egouser - ADD CONSTRAINT egouser_email_key UNIQUE (email); - - --- --- Name: egouser_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egouser - ADD CONSTRAINT egouser_name_key UNIQUE (name); - - --- --- Name: egouser_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.egouser - ADD CONSTRAINT egouser_pkey PRIMARY KEY (id); - - --- --- Name: flyway_schema_history_pk; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.flyway_schema_history - ADD CONSTRAINT flyway_schema_history_pk PRIMARY KEY (installed_rank); - - --- --- Name: grouppermission_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.grouppermission - ADD CONSTRAINT grouppermission_pkey PRIMARY KEY (id); - - --- --- Name: policy_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.policy - ADD CONSTRAINT policy_name_key UNIQUE (name); - - --- --- Name: policy_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.policy - ADD CONSTRAINT policy_pkey PRIMARY KEY (id); - - --- --- Name: token_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.token - ADD CONSTRAINT token_name_key UNIQUE (name); - - --- --- Name: token_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.token - ADD CONSTRAINT token_pkey PRIMARY KEY (id); - - --- --- Name: userpermission_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.userpermission - ADD CONSTRAINT userpermission_pkey PRIMARY KEY (id); - - --- --- Name: flyway_schema_history_s_idx; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX flyway_schema_history_s_idx ON public.flyway_schema_history USING btree (success); - - --- --- Name: idx_grouppermission_both; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_grouppermission_both ON public.grouppermission USING btree (group_id, policy_id); - - --- --- Name: idx_grouppermission_group; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_grouppermission_group ON public.grouppermission USING btree (group_id); - - --- --- Name: idx_grouppermission_policy; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_grouppermission_policy ON public.grouppermission USING btree (policy_id); - - --- --- Name: idx_token_owner; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_token_owner ON public.token USING btree (owner); - - --- --- Name: idx_tokenscope; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_tokenscope ON public.tokenscope USING btree (token_id, policy_id, access_level); - - --- --- Name: idx_tokenscope_policy; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_tokenscope_policy ON public.tokenscope USING btree (policy_id); - - --- --- Name: idx_usergroup_both; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_usergroup_both ON public.usergroup USING btree (user_id, group_id); - - --- --- Name: idx_usergroup_group; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_usergroup_group ON public.usergroup USING btree (group_id); - - --- --- Name: idx_usergroup_user; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_usergroup_user ON public.usergroup USING btree (user_id); - - --- --- Name: idx_userpermission_both; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_userpermission_both ON public.userpermission USING btree (user_id, policy_id); - - --- --- Name: idx_userpermission_policy; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_userpermission_policy ON public.userpermission USING btree (policy_id); - - --- --- Name: idx_userpermission_user; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_userpermission_user ON public.userpermission USING btree (user_id); - - --- --- Name: groupapplication_application_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.groupapplication - ADD CONSTRAINT groupapplication_application_fkey FOREIGN KEY (application_id) REFERENCES public.egoapplication(id); - - --- --- Name: groupapplication_group_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.groupapplication - ADD CONSTRAINT groupapplication_group_fkey FOREIGN KEY (group_id) REFERENCES public.egogroup(id); - - --- --- Name: grouppermission_group_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.grouppermission - ADD CONSTRAINT grouppermission_group_fkey FOREIGN KEY (group_id) REFERENCES public.egogroup(id); - - --- --- Name: grouppermission_policy_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.grouppermission - ADD CONSTRAINT grouppermission_policy_fkey FOREIGN KEY (policy_id) REFERENCES public.policy(id); - - --- --- Name: policy_owner_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.policy - ADD CONSTRAINT policy_owner_fkey FOREIGN KEY (owner) REFERENCES public.egogroup(id); - - --- --- Name: token_owner_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.token - ADD CONSTRAINT token_owner_fkey FOREIGN KEY (owner) REFERENCES public.egouser(id); - - --- --- Name: tokenscope_policy_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.tokenscope - ADD CONSTRAINT tokenscope_policy_fkey FOREIGN KEY (policy_id) REFERENCES public.policy(id); - - --- --- Name: tokenscope_token_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.tokenscope - ADD CONSTRAINT tokenscope_token_fkey FOREIGN KEY (token_id) REFERENCES public.token(id); - - --- --- Name: userapplication_application_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.userapplication - ADD CONSTRAINT userapplication_application_fkey FOREIGN KEY (application_id) REFERENCES public.egoapplication(id); - - --- --- Name: userapplication_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.userapplication - ADD CONSTRAINT userapplication_user_fkey FOREIGN KEY (user_id) REFERENCES public.egouser(id); - - --- --- Name: usergroup_group_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.usergroup - ADD CONSTRAINT usergroup_group_fkey FOREIGN KEY (group_id) REFERENCES public.egogroup(id); - - --- --- Name: usergroup_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.usergroup - ADD CONSTRAINT usergroup_user_fkey FOREIGN KEY (user_id) REFERENCES public.egouser(id); - - --- --- Name: userpermission_policy_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.userpermission - ADD CONSTRAINT userpermission_policy_fkey FOREIGN KEY (policy_id) REFERENCES public.policy(id); - - --- --- Name: userpermission_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres --- - -ALTER TABLE ONLY public.userpermission - ADD CONSTRAINT userpermission_user_fkey FOREIGN KEY (user_id) REFERENCES public.egouser(id); - - --- --- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres --- - -REVOKE ALL ON SCHEMA public FROM PUBLIC; -REVOKE ALL ON SCHEMA public FROM postgres; -GRANT ALL ON SCHEMA public TO postgres; -GRANT ALL ON SCHEMA public TO PUBLIC; - - --- --- PostgreSQL database dump complete --- - diff --git a/docker/keycloak-init/data_import/myrealm-realm.json b/docker/keycloak-init/data_import/myrealm-realm.json new file mode 100644 index 00000000..83a46014 --- /dev/null +++ b/docker/keycloak-init/data_import/myrealm-realm.json @@ -0,0 +1,2086 @@ +{ + "id" : "b4227e2f-0b93-4820-871a-eada93058032", + "realm" : "myrealm", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 3600, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 3600, + "ssoSessionMaxLifespan" : 36000, + "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" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : true, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "55150a6d-487f-438d-bb78-8273342afbfc", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "b4227e2f-0b93-4820-871a-eada93058032", + "attributes" : { } + }, { + "id" : "38f9ce75-1682-425f-a492-52dba15ad4e3", + "name" : "ADMIN", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "b4227e2f-0b93-4820-871a-eada93058032", + "attributes" : { } + }, { + "id" : "b0332d2c-fc13-4dd4-a741-dad72ad60ee0", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "b4227e2f-0b93-4820-871a-eada93058032", + "attributes" : { } + }, { + "id" : "87ab9b69-bc6f-42b0-8c86-fd19fd0ece42", + "name" : "default-roles-myrealm", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "b4227e2f-0b93-4820-871a-eada93058032", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "fc5157b3-a7b1-4fa5-b98a-194c2f01c1ff", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "1e1cf9d0-490e-45f9-82f6-17db0b008e2c", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "0c472f7b-9c75-46b5-86d5-032df1fcfa50", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "36e84f62-20aa-496f-965d-84fb46a54d3d", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "7f06dabb-723e-48f0-b56f-6099576c2625", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "81b3eed6-7a81-4269-af82-42650de43928", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "697115e9-954b-4e5a-885c-25bc1c0009a3", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "afe57081-7497-45f4-9a5a-562091d5e9f8", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "83eae3c9-fd9c-47a9-813c-32201d7f0b99", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "e6af4c93-3780-469e-a2da-86a89fccac11", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "5fc10a91-5107-4363-bc14-d3acfad8525e", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "8bb53c21-f800-46e8-bedb-6e57ce88f4e6", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "e2b693fe-0022-46b7-b975-2727ace408fb", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "249a235d-0be7-4f75-8710-d8274a394619", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "097ab593-1532-469e-9ef1-4652a92d0468", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "730cfec2-33ba-4199-90f1-2ecd3761ed82", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "5e0e469c-96ca-4216-995b-d5eb9c6b889b", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "dc5ad077-f1f3-46e2-b55c-7fd4afcbc868", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients", "view-events", "manage-realm", "view-users", "manage-events", "view-clients", "manage-authorization", "manage-identity-providers", "query-realms", "view-identity-providers", "manage-clients", "view-realm", "query-users", "manage-users", "query-groups", "create-client", "impersonation", "view-authorization" ] + } + }, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + }, { + "id" : "5325f1d2-e053-4b20-959c-08ee3c2dc29b", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "attributes" : { } + } ], + "system" : [ { + "id" : "19e7f3f0-78af-4c1b-95cf-b7a1e2ffebbb", + "name" : "uma_protection", + "composite" : false, + "clientRole" : true, + "containerId" : "b665238c-bc33-44ff-9183-d44a92e4b97a", + "attributes" : { } + } ], + "webclient" : [ ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "4cc9442c-3848-43f5-b009-11af4f6a4bf9", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "16e760bb-2c0d-453b-b823-460913a22744", + "attributes" : { } + } ], + "account" : [ { + "id" : "8ebaccec-3448-4f0b-863e-06e450d800df", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "8cbeae7a-5939-499e-9ae6-addfc3052572", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "bc0a2426-54b1-4aac-8ee0-5491ca5536da", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "38125e01-359f-4010-aa2f-c4a47689465e", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "64d12ccd-ec4b-486a-ada5-8b33fcf77730", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "5af8bd59-9e5d-4d37-9975-1c69adb08623", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "9fd80d81-09c9-4c78-8df3-988129e28989", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + }, { + "id" : "a63682fb-9894-4d4b-899c-fade0779e3db", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "attributes" : { } + } ] + } + }, + "groups" : [ { + "id" : "08a3c1f3-d09e-47d1-acc3-2a5bd1f241f0", + "name" : "ABC123GROUP", + "path" : "/ABC123GROUP", + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + }, { + "id" : "c5de35e5-a449-4803-8034-600acde61ddb", + "name" : "ADMIN", + "path" : "/ADMIN", + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + }, { + "id" : "ec658952-8332-4415-9ff4-80d1698e392d", + "name" : "TESTCAGROUP", + "path" : "/TESTCAGROUP", + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + }, { + "id" : "76f13e80-ff1f-4b83-8fc2-ee096a9cf779", + "name" : "TESTCASONG_GROUP", + "path" : "/TESTCASONG_GROUP", + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { }, + "subGroups" : [ ] + } ], + "defaultRole" : { + "id" : "87ab9b69-bc6f-42b0-8c86-fd19fd0ece42", + "name" : "default-roles-myrealm", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "b4227e2f-0b93-4820-871a-eada93058032" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "601df4b2-0f47-4266-aed2-6702f0da7c59", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/myrealm/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/myrealm/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" : "46d9d0f5-0165-4f28-a3f9-4a0f00d5ea18", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/myrealm/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/myrealm/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" : "3a6f2608-6c13-4fbd-a2fa-57f8dcd1f68b", + "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" : "23ba58c4-e8ee-47d1-afdf-7e2aabffe9e5", + "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" : "16e760bb-2c0d-453b-b823-460913a22744", + "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" : "05acca3b-a42f-44ca-9015-5af54b6335a0", + "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" : "3fcc7088-469e-48de-88cb-1f8800249e60", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/myrealm/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/myrealm/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" : "71b820af-0ed7-482b-a8cd-04863b10d429", + "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" ] + }, { + "id" : "b665238c-bc33-44ff-9183-d44a92e4b97a", + "clientId" : "system", + "name" : "system", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "systemsecret", + "redirectUris" : [ "http://localhost:8080" ], + "webOrigins" : [ "http://localhost:8080" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : true, + "authorizationServicesEnabled" : true, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1695916686", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "display.on.consent.screen" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "94d756f4-66b5-4d48-b56f-6291ccdd9289", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "a7dd29f8-b57a-4818-8c3d-4522b2ce8e1e", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "9a1a7793-3c38-474a-bae9-297cee0101c5", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], + "authorizationSettings" : { + "allowRemoteResourceManagement" : true, + "policyEnforcementMode" : "PERMISSIVE", + "resources" : [ { + "name" : "TEST-CA", + "ownerManagedAccess" : false, + "displayName" : "TEST-CA", + "attributes" : { }, + "_id" : "ae3ecca6-a49e-468f-b90f-913f68763366", + "uris" : [ ], + "scopes" : [ { + "name" : "READ" + }, { + "name" : "WRITE" + } ], + "icon_uri" : "" + }, { + "name" : "score", + "ownerManagedAccess" : false, + "displayName" : "score", + "attributes" : { }, + "_id" : "83aecbcf-e84b-43c6-b153-817977a98466", + "uris" : [ ], + "scopes" : [ { + "name" : "READ" + }, { + "name" : "WRITE" + } ], + "icon_uri" : "" + }, { + "name" : "song", + "ownerManagedAccess" : false, + "displayName" : "song", + "attributes" : { }, + "_id" : "9b06b365-ae03-4f4a-bffc-bd108b4c7560", + "uris" : [ ], + "scopes" : [ { + "name" : "READ" + }, { + "name" : "WRITE" + } ], + "icon_uri" : "" + }, { + "name" : "ABC123", + "ownerManagedAccess" : false, + "displayName" : "ABC123", + "attributes" : { }, + "_id" : "2aedfcbe-dc01-42b5-81ae-7fb5de04e629", + "uris" : [ ], + "scopes" : [ { + "name" : "READ" + }, { + "name" : "WRITE" + } ], + "icon_uri" : "" + } ], + "policies" : [ { + "id" : "47a074b5-137f-476b-a9a0-c9dc79c40b51", + "name" : "POLICY_TESTCA_SONG_GROUP", + "description" : "", + "type" : "group", + "logic" : "POSITIVE", + "decisionStrategy" : "UNANIMOUS", + "config" : { + "groups" : "[{\"path\":\"/TESTCASONG_GROUP\",\"extendChildren\":false}]", + "groupsClaim" : "" + } + }, { + "id" : "2fde7fef-b1e5-4751-b96c-9b7dbf4b5f64", + "name" : "POLICY_TESTCA_GROUP", + "description" : "", + "type" : "group", + "logic" : "POSITIVE", + "decisionStrategy" : "UNANIMOUS", + "config" : { + "groups" : "[{\"path\":\"/TESTCAGROUP\",\"extendChildren\":false}]", + "groupsClaim" : "" + } + }, { + "id" : "3805981d-4c11-43be-ac81-edf49840da72", + "name" : "POLICY_ABC123_GROUP", + "description" : "", + "type" : "group", + "logic" : "POSITIVE", + "decisionStrategy" : "UNANIMOUS", + "config" : { + "groups" : "[{\"path\":\"/ABC123GROUP\",\"extendChildren\":false}]", + "groupsClaim" : "" + } + }, { + "id" : "1369ca58-ff1a-42e3-b3a1-44fe978d3733", + "name" : "POLICY_ADMINGROUP", + "description" : "", + "type" : "group", + "logic" : "POSITIVE", + "decisionStrategy" : "UNANIMOUS", + "config" : { + "groups" : "[{\"path\":\"/ADMIN\",\"extendChildren\":false}]", + "groupsClaim" : "" + } + }, { + "id" : "b8cfd545-5f5b-4d70-aac9-ade1967fb89c", + "name" : "PERMISSION_ABC123_GROUP", + "description" : "", + "type" : "resource", + "logic" : "POSITIVE", + "decisionStrategy" : "AFFIRMATIVE", + "config" : { + "resources" : "[\"ABC123\"]", + "applyPolicies" : "[\"POLICY_ABC123_GROUP\"]" + } + }, { + "id" : "657147aa-93e6-4268-8d4b-80028d2dc875", + "name" : "PERMISSION_TESTCA_GROUP", + "description" : "", + "type" : "resource", + "logic" : "POSITIVE", + "decisionStrategy" : "AFFIRMATIVE", + "config" : { + "resources" : "[\"TEST-CA\"]", + "applyPolicies" : "[\"POLICY_TESTCA_GROUP\"]" + } + }, { + "id" : "ac01c5ea-c766-4da9-b097-e54772cfaa2b", + "name" : "PERMISSION_TESTCA_SONG_GROUP", + "description" : "", + "type" : "resource", + "logic" : "POSITIVE", + "decisionStrategy" : "AFFIRMATIVE", + "config" : { + "resources" : "[\"TEST-CA\",\"song\"]", + "applyPolicies" : "[\"POLICY_TESTCA_SONG_GROUP\"]" + } + }, { + "id" : "a1555c72-c357-4ac0-a6ca-1b6a27fa6318", + "name" : "PERMISSION_ADMINGROUP", + "description" : "", + "type" : "resource", + "logic" : "POSITIVE", + "decisionStrategy" : "AFFIRMATIVE", + "config" : { + "resources" : "[\"score\",\"song\"]", + "applyPolicies" : "[\"POLICY_ADMINGROUP\"]" + } + } ], + "scopes" : [ { + "id" : "0657415e-9516-4486-bb94-d70ce77c0f92", + "name" : "READ", + "iconUri" : "" + }, { + "id" : "69154ae9-bc18-4920-bf09-031256670cd0", + "name" : "WRITE", + "iconUri" : "" + } ], + "decisionStrategy" : "AFFIRMATIVE" + } + }, { + "id" : "e8f6f77c-74a2-4b57-803b-5c0c9715c170", + "clientId" : "webclient", + "name" : "webclient", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "ikksyrYaKX07acf4hpGrpKWcUGaFkEdM", + "redirectUris" : [ "http://localhost:3000/api/auth/callback/keycloak", "http://localhost:3000/explorer" ], + "webOrigins" : [ "http://localhost:3000" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "client.secret.creation.time" : "1696010381", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false", + "use.refresh.tokens" : "true", + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "false", + "acr.loa.map" : "{}", + "require.pushed.authorization.requests" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "display.on.consent.screen" : "false", + "token.response.type.bearer.lower-case" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "db0ca862-d66b-4251-966c-7b7c3117553e", + "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" : "f7fd9d03-b481-4d2e-aad8-5bc1bee34b6f", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "6896aee5-5d91-424d-a238-7adcc8d883b6", + "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" : "f94d56d5-25a0-4315-8861-818decb0fe15", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-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" : "dafeb681-a2da-4403-b391-3c164c37855a", + "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" : "e96af174-19fd-4423-9a25-62c292209c32", + "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" : "9716ad78-d384-40dc-b49f-061b85ffb16b", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-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" : "c9624d47-67c8-4039-9c8a-7c59df9546f2", + "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" : "00f4e577-309a-4ded-b984-87f5131fb327", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "744253d2-d5bd-4786-a55c-294689b11b1b", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "3c230c30-bb40-4250-9e9c-ab8de9aea417", + "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" : "2ee80b14-eb26-40a2-8a47-d3e1ec6f711d", + "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" : "b02e136a-4023-4940-b47d-c68d2cb0990c", + "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" : "d45005c5-3647-4890-b7b6-39ead108adc2", + "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" : "ab314612-542d-4f88-8225-2902ec21f1ec", + "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" : "cb66f54d-457a-410b-b1ac-fbb0028732d3", + "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" : "7de083e7-b306-448f-bb0d-c68ef1d75de8", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-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" : "18278c84-7039-4682-ae89-a4dbb85b0184", + "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" : "de64ceb7-39e5-4ee1-98cb-8b5e013c8ceb", + "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" : "54e5ca9d-a39d-4929-abec-4b15a5743aad", + "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" : "f1ac259e-e622-46d2-bba6-720adee1f082", + "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" : "eecdfc5d-077b-4845-ba70-c404edaa988d", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-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" : "29677acf-644d-4de6-a77d-bd492d14c747", + "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" : "25c8eb9b-d577-4d9c-94d8-0e3ae1700017", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-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" : "c576cf32-c382-4476-afa3-c0e4ccf310e3", + "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" : "36811e4f-1793-4f85-b537-6302f6b285d5", + "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" : "45d2de44-244a-4167-a5fc-1a50ea601d64", + "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" : "fa0646ca-0893-4b40-a1d1-b8bcc1a20769", + "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" : "d68a8101-3862-4381-814f-41d72ad2d759", + "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" : "eccd4934-7602-400e-b2c7-0ab7af462db3", + "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" : "5c3a9f01-3608-4261-a08b-1dc7a9bc7b51", + "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" : "7b19085b-b3b1-4250-8dac-48a5a97f8d5b", + "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" : "954552e3-569d-4352-a4e9-ccff51b3cc7e", + "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" : "37cef1a5-2600-4b1d-a69b-3bdd0d103a36", + "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" : "01cc6e37-f893-4eb6-b486-49c5abdebabd", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "1ee3b6de-4b2f-4b54-983e-c22a2e5d497b", + "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" : "2971369a-d7eb-4a7f-bbd8-48f693198bd7", + "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" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "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" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "64009752-276e-43a0-b286-54b6e86e1e38", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "689ac444-a7e6-497a-a9ab-7c8d384bcf06", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "4204bb0b-59bd-4bbd-9528-e0bdb0bea477", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper" ] + } + }, { + "id" : "60e95445-5b19-456a-b4ab-f38d190a5692", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper" ] + } + }, { + "id" : "a80fe473-48e2-4d14-a341-d90a0b92216a", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "56e5d0a2-354b-4e89-a45c-65f37bc6b9e6", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "22ebd897-189f-4b8e-8dd7-62ad567e01fc", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "f0807c15-e127-49ed-9743-23ff6bf7b067", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "8221fdb5-e73e-4f2f-8dba-d28cfeb11329", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "73353bf5-70ce-4105-bbf0-4d580e746c6a", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "3e74502b-424c-4b7c-8266-d06259fc699e" ], + "secret" : [ "GEGoK7aaUQFf3GVrzwC6PSMlCUt8ksSoOpAQQzYaolGXvtujqlrRuMZKpHnsbpAOwNI-W4SvW23ryhALc9kvjg" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "a55565bb-b3de-47f7-92b8-b0b318d6c2de", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA15EWVTRgUZC8PMSee3yQ970bRB1qD1RnN2E+0pmMUhk7DGKybSfM0NtdT1qGVQy0a0L25/vwPKuDUkQqZghY7D4g2FSWq/T65lMBSpBY/behhhiVzAziePMmx/TQYnqbhxr+TvTinkphglCirFRgWOVDKM9/EEVeZFuvQmPQ/lD3sCbgecseskX/IeY/IYK4kcNYzQtuW/dIeGsYy2fIMxNq7ggaQ5w48OXdhfjmNHXh6Kdud2SFQRR9AAz31hYZ9UFaLaO252AtKXJOmGIgNIMhEfiUME1EcU08KZOzwQfVDMvFkajKc7B931ZwmLJwPnwnR4UfZk+XNmFh3FP9iQIDAQABAoIBAEfc/mz5QQLwFVDM22ifZlSwnl5ez/S8VoyHxsG+nqDf+Gdwn7r0Abu+5aSGsTG5QoxfjqBXxQb0xpquTtQlBD/9lkWILZK14M7X7R5GcORkS1zA5W9Y/EcGCG+wlae+6ApqXU1FJidO9KLU7uY0WspH49O/GMT72zPpvMuNKyccilT5vFjHeNBeCO6urAeVqlRE0atPKtuQPWSQtVVpJRo2yLURXYpMQCzkPyB0CYacrVyA3gsTATXZxud9tYYre3CGTuCfIc8/bwD+pgEVzoeFynH9XHwr6neOWoHyOhe+Mqwed2X0QJZ54kpwQN9LGmhEJY2gMsq0SNmI75qHbWECgYEA60kpCFQ8Ivxq2+500xP1QxPHuuOgBWcfBjvro5BlbzOEXBhXyNGzV/2OQDUtGGFfLhJ0lg34Xhc22UcpOZM/Al803NSRDNOPGQGjzqEOzGl2xfiT5jpP0V4l/3AxGS3j6pP5O8dPK20KHt73bx+ajc2RyVuDHJbQbdPOijRB3IMCgYEA6ouAgBjD7UbKD9XliEnmfPfyXGMVGSclZPT/Y4k0d+SEPmilgfVnZZPFVdTuLtAcL77I5BU7o9NnYScqdQF+HYnXoMstEfVuTHzC41UE92XyjgB2Cf6D+vR9u9SDjW2kNzd5KggUvY2CvdBw3vuH4dbThS12tekxISlfRdo7eAMCgYEAqNQ8Xz+iTzB0tQ+sQHHHwbQF03LWNkpClsSUVy+buWlsBnFpPC5M1EyasDP4AdCM7ZBMnAe2Oj3KG6rWR/wCcH9EfVkCJAQCYF0u32vuJHtgwLmX1tHsyD0YYuxsLrchHgfEBUME6hI5+uDfB5vT2QCzJZtGv1LwiH49bCoHQGUCgYEAwmIpdoCP6NeRYXxphgGRR6MKtyza8IS0Bi7SdoDg/jhirYJ3IPTs44+Lra4SVLPfmGZrAjTiv8zWUftuwZgiGIMENVwOF2MsLbH8pwHwYsWYN74EFhZc9aCpkAD5oj3rKmQMRBx8a/ibEYtt8C/Qlwg/N5HNX8hLEmvCbRcH0FcCgYAQAurHiGOJl19rNQsvGAGWjt2iw49mYxRdKhxbQAla34jUES7T6uL4DXsGIyeGrSqjRT6ZvfeifgnOV6sfec/Dz4kbSCbAL9YK4OlpkXyQL7VxYh3q2U35MCV5Yv7BH++X/hhURoKazQ5TlQsbOjsuBWm7eahj1sJIDaCRcoqGNg==" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICnTCCAYUCBgGK3IFC9zANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTIzMDkyODE1NTQzM1oXDTMzMDkyODE1NTYxM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANeRFlU0YFGQvDzEnnt8kPe9G0Qdag9UZzdhPtKZjFIZOwxism0nzNDbXU9ahlUMtGtC9uf78Dyrg1JEKmYIWOw+INhUlqv0+uZTAUqQWP23oYYYlcwM4njzJsf00GJ6m4ca/k704p5KYYJQoqxUYFjlQyjPfxBFXmRbr0Jj0P5Q97Am4HnLHrJF/yHmPyGCuJHDWM0Lblv3SHhrGMtnyDMTau4IGkOcOPDl3YX45jR14einbndkhUEUfQAM99YWGfVBWi2jtudgLSlyTphiIDSDIRH4lDBNRHFNPCmTs8EH1QzLxZGoynOwfd9WcJiycD58J0eFH2ZPlzZhYdxT/YkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAsM2x/4i8M3fZfolsxormrljwwdHmUe2H3pjO7QJfo/ZTgLvzR2QYwdvLNi7Gpk/7RVioygduLz1MBIa753wezP/3A5certZ8znChFJuR0xCvj+++M+3XCo1Ah8KUzBih3knw+aBaVvo8waYNqwoIKDW45kbcW6ohi+cernBgsdXNRYwpAMe96Ogi45REbPa4qaLiTKj7Ifi/tbrDdcelDEi2mpzEslbI/5Fi22+Agoc3GGufz6xSXVtvYGFVHiibSmVq7PcreYhVm89Ig21HI+mPb9y2kQzZuKqJ62wh82EtYzLIZTu+ATulI53rIclyV5gdW2K8B2hSmnGxI8a8Cw==" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "24b11e9b-8efc-4e19-81cb-cae278512a9f", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAtPZ5ZeRS2744CMCHohZBbwEAgF0qu4BjAchNxWfSuyCCsa4LeyjkF/JOKqiOP522ULFlzEirfmntGYluxKNDr40JIMPBx6qnASZvT83mY9kbMkqeTuZ2nrRf9Z2o4aTBEyjWbKY2plWGML9WGUzOBUqMXxDc4/8FGFXzOZdmn2RItfD+hQM6F8RHWTE6P9+h4S/oPhqZ+4Ih3Jrk6BPG0Fv1u+9Dd7f7lptDaXALCEkuMqfwEwcpSUppkmErgGJ5Ujg3rtcbdFwgcr+cVaUkmZmSXn60JG0usiAO/d19DHa8CzETGlU4vaEvDm43h9ZpAOMIkoz8gos9IqYuBsYylQIDAQABAoIBACLfIDOvVPw9YqTlHP3yFffjFIqn6XUqX2nXhI0W3bfrouPEazf3gETRD5kO1CKULK7OEJTWKB37IZJ7Nlo0L/XjUq/6qRvl2brSAj85qTzyeFgvouQHazJYBenZ0NJyojYj93YGbZ+N+YbpSBkmAMlqPTSQllBlM0EmRvGBKGhsYtPljkb1Fc10zksNrEHZdyTXyiJpiXQpe/feRSVh64fORsREl/0CrjWOXgJoilerLInjdO6/UuEXnT+eY9ttJpGfXRjqdb4QzHHmkKb4HH6X3YMBvQX8NhS0EF9VGvzDVq2JZagwc97UDLKkKpFv82BydtwWx4zkcACBKZfFg/0CgYEA2qgbyJaK86vcrwlqzt2ykXmcPzeuueFpPRIYlKwZaoBS7hYJZBbjZq72/TU8oVvMEQH6sV9u1+dU7EBUJ64UEoQkShGd5deYTav5UD0fvjnCddz+rK9Z0K/nS9zeYc/eAYgSfNPAYd1zHfBfn2MTvBYuay4xy9CFLm/j7ePA8McCgYEA095Z0ZnU9UQ47TA20JXFVqguY0TnFYWIpN68Biv7o7ETVgti9WFNp5wpa/PkBGBXUzXoHW7Z+4wxe/UoNW2KOIgF959M0XbEN+niKIzcK3kxF2FDs926o/w3+FocJeGs6Mr/jGmGqzoaVSw6o3Z2KCzu996jE+riLiO0UpB13cMCgYBNy2PfMRiM64efyxTyNtRyh7b8kv4aakV8EfUm6Dg+uRtIVBTRRIdxoCyGGCvTKQrovjCIbPDN5iNDzvtiBsBjehpDNBNelB8++0G/t4+UqY4zSwZdQCIPapY7WoDQghl1qAkT2m7nItfzPfN3jNOXprirL4tN/Yl05SBOIisiPwKBgQDNXGTrSZSl9+7F2UoIfGO/T11HU2456ik8xbiyssdDL0xyxq6w8hP3NuLfhJOrukZqnYHTpbMcpBMC9+p1fyvPB+ngz0QCdIBVQhq4+3Ado2b2Jo0dNvrGIJ+P1qgZ/9k9/CYfz9l89uC3Vhuwfg6heoxXLjIcCDwcRPdwYB4fSQKBgQC+P05eo2IVMRtkWhGNBtYyWiWdC+3S52O10J5pwbn3DzYSFeVGGV3Mu2PWidI+26+0lKKrAgqoIuj5G/XKjBVx0g0diNR2AHOiu9bIlaSWiVTOpsb9xNRrrRQ8mcbe6d9YicyRhj6uVAI3cE2X44T4QV0Fq/j6ygepcVV6+TGtgA==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICnTCCAYUCBgGK3IFB7TANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTIzMDkyODE1NTQzM1oXDTMzMDkyODE1NTYxM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALT2eWXkUtu+OAjAh6IWQW8BAIBdKruAYwHITcVn0rsggrGuC3so5BfyTiqojj+dtlCxZcxIq35p7RmJbsSjQ6+NCSDDwceqpwEmb0/N5mPZGzJKnk7mdp60X/WdqOGkwRMo1mymNqZVhjC/VhlMzgVKjF8Q3OP/BRhV8zmXZp9kSLXw/oUDOhfER1kxOj/foeEv6D4amfuCIdya5OgTxtBb9bvvQ3e3+5abQ2lwCwhJLjKn8BMHKUlKaZJhK4BieVI4N67XG3RcIHK/nFWlJJmZkl5+tCRtLrIgDv3dfQx2vAsxExpVOL2hLw5uN4fWaQDjCJKM/IKLPSKmLgbGMpUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFzF4YARB1b6vba/9K11EreYaf07zO7xC0cr9eQCq2otkaHmWlpXzId/k/UUgVAO7SUOMYHaO4e2OlIJhUNHvURcTrGpPIoks+g5JThvlugFgwNOgUKLIoRiew80Q+jq43f4UHJGe9EE8TIyEKJPZWylqRJJIYzCJA55YSPGIziHeAIq80jYTpW3/QL7jwuRlzI1CGrFXfP5LB6tA2EKU9aIQ7GuJOSUxRGWFqOp7kvR3x/VewYJH7J/VuAt3CXNuQgX1gK1OOLbCUELo87opP/01CKSBrL3H59fGOOQqYJTvbowkgxRKtV+nG9HYCiEnSaGyyBAiWj8VgWsOGFF2ew==" ], + "priority" : [ "100" ] + } + }, { + "id" : "c55901db-a908-43e0-af7c-081843b0b092", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "7676e96f-9f72-4e4c-b768-ea16e9d9dafe" ], + "secret" : [ "hXFMxKJtJjXAs2XmlTOnXw" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "c7d69cbe-8041-47f5-b9d3-6da167de223e", + "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" : "70545ca0-25f6-4fda-a7a7-1bad57e14225", + "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" : "269ae920-f17e-42e0-b708-3f9b1e85fab1", + "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" : "0d9fb4d5-0e11-4d83-8208-8d8c917692cc", + "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" : "7c5af45a-74c3-471d-8fb3-032d91f81101", + "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" : "c3d188dd-38b8-4b23-a6d9-ddb4b8920651", + "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" : "799ec675-fe88-4c70-8395-c7dd6e93ccdd", + "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" : "b67f488b-af1c-4f7d-a167-8593d3424390", + "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" : "b2f2d03e-471f-4dcc-89a2-12edd5728007", + "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" : "4ef7b700-472a-4259-96d8-14185063336b", + "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" : "0899b39d-c961-493a-81c6-f3161923a476", + "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" : "7351f8b2-383d-4c25-aece-105aff101ba8", + "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" : "866f40c4-7a10-40b4-9bb0-320938f25e47", + "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" : "5c6742df-ce47-4ebb-bf1d-816d9fb813f3", + "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" : "a609a901-382e-40a9-b837-d6ae1acf9add", + "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" : "58c0329a-e16e-4412-bae6-3cfd009e662d", + "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-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "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" : "97c81f1a-fc4b-4c50-bbbb-e6fe40096ddf", + "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" : "aed7672e-b59d-477c-9798-55f59b04ff9d", + "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" : "5ce31743-aee3-4684-842f-7f63a1ed9e37", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "44e63ced-c494-4a4d-8708-af0d492be5f8", + "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" : true, + "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" : true, + "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" : "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", + "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" : "22.0.5", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/docker/keycloak-init/data_import/myrealm-users-0.json b/docker/keycloak-init/data_import/myrealm-users-0.json new file mode 100644 index 00000000..01eb8bfd --- /dev/null +++ b/docker/keycloak-init/data_import/myrealm-users-0.json @@ -0,0 +1,67 @@ +{ + "realm" : "myrealm", + "users" : [ { + "id" : "96dfb80d-8b4c-4f97-a86e-05e9de4571d5", + "createdTimestamp" : 1714963439023, + "username" : "admin", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "attributes" : { + "api-keys" : [ "{\"name\":\"4978D340EDFA11ABF4A6B282C762676DCFAAEF55496631A6474D55C17CB96DCB\",\"scope\":[\"song.READ\",\"score.WRITE\",\"score.READ\",\"song.WRITE\"],\"expiryDate\":1746499545473,\"issueDate\":1714963545473,\"isRevoked\":false,\"description\":\"write and read\"}" ] + }, + "credentials" : [ { + "id" : "b2152773-6246-4f74-bddd-6440e99dc860", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1714963450546, + "secretData" : "{\"value\":\"92EMxAP2Ze8gZVA+elHSaFIa7VAmVoeXk6ck7Pgm5HM=\",\"salt\":\"p4jYgjne+B+4yiwQVoCySg==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-myrealm" ], + "notBefore" : 0, + "groups" : [ "/ADMIN" ] + }, { + "id" : "76fa81e0-ae36-4f80-b891-a939d67a82e0", + "createdTimestamp" : 1695916686570, + "username" : "service-account-system", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "serviceAccountClientId" : "system", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-myrealm" ], + "clientRoles" : { + "system" : [ "uma_protection" ] + }, + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "a5797177-24ab-437a-8110-8ab238d5804e", + "createdTimestamp" : 1720008009839, + "username" : "testca_user", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "attributes" : { + "api-keys" : [ "{\"name\":\"7EC9363B25465C07FD885B297EAF7AACD702C5752551FBEE8E5B802BC390A927\",\"scope\":[\"TEST-CA.READ\",\"song.READ\",\"TEST-CA.WRITE\",\"song.WRITE\"],\"expiryDate\":1751546629422,\"issueDate\":1720010629421,\"isRevoked\":false,\"description\":\"write and read\"}" ] + }, + "credentials" : [ { + "id" : "97cb294d-d79f-4d36-87b1-040dbf4ac9de", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1720008034778, + "secretData" : "{\"value\":\"nh/z1H4LWnOMSL/7d/BIYADiaeMu8uTWW2EgWLDtc88=\",\"salt\":\"gjDcFeQhaVKRLRsLMck6MQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-myrealm" ], + "notBefore" : 0, + "groups" : [ "/TESTCASONG_GROUP" ] + } ] +} \ No newline at end of file diff --git a/docker/score-client-init/run_tests.sh b/docker/score-client-init/run_tests.sh index 71c871c0..25092ea8 100755 --- a/docker/score-client-init/run_tests.sh +++ b/docker/score-client-init/run_tests.sh @@ -1,8 +1,8 @@ #!/bin/bash export PATH="/data:$PATH" token1="" # empty -token2="1f070fb0-0ee4-4815-8097-b5b065c661cc" # score.TEST-CA.write,song.write -token3="f69b726d-d40f-4261-b105-1ec7e6bf04d5" # score.write, song.write +token2="d5b12aed-b3c7-4075-87dc-d46a89e54d18" # score.TEST-CA.write,song.write +token3="07a5a12e-a85f-4248-a9a1-851a8062b6ac" # score.write, song.write /data/unpublish-analysis.sh $token3 TEST-CA a25ae8b4-097d-11ea-b4b9-41f0d4c18919 /data/unpublish-analysis.sh $token3 ABC123 a22f44d3-097d-11ea-b4b9-374b8c686482 diff --git a/docker/score-client-init/test_manifests.sh b/docker/score-client-init/test_manifests.sh index 276494c5..9437e55a 100755 --- a/docker/score-client-init/test_manifests.sh +++ b/docker/score-client-init/test_manifests.sh @@ -1,7 +1,7 @@ #!/bin/bash set -x SCORE_CLIENT="/score-client/bin/score-client" -token="f69b726d-d40f-4261-b105-1ec7e6bf04d5" # score.write, song.write +token="07a5a12e-a85f-4248-a9a1-851a8062b6ac" # score.write, song.write function upload() { $SCORE_CLIENT upload --manifest /data/$1 && echo "OK" || echo "FAILED" diff --git a/pom.xml b/pom.xml index 11ca7ac5..22b99150 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 pom ${project.artifactId} ${project.name} @@ -253,11 +253,11 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S ${jdk.version} - 2.1.6.RELEASE + 2.6.6 1.1.2.RELEASE - 2.3.5.RELEASE + 2.5.1.RELEASE 1.1.1.RELEASE - Greenwich.SR3 + 2021.0.8 1.11.219 @@ -270,7 +270,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S 2.2 1.3.9 1.1.7 - 4.12 + 4.13.2 1.3.1 diff --git a/score-client/pom.xml b/score-client/pom.xml index d022464d..677908bf 100644 --- a/score-client/pom.xml +++ b/score-client/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 ../pom.xml @@ -172,6 +172,11 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S spring-boot-starter-test test + + junit + junit + test + diff --git a/score-client/src/main/java/bio/overture/score/client/config/ProfileConfig.java b/score-client/src/main/java/bio/overture/score/client/config/ProfileConfig.java index 4495b64a..f47e19a3 100644 --- a/score-client/src/main/java/bio/overture/score/client/config/ProfileConfig.java +++ b/score-client/src/main/java/bio/overture/score/client/config/ProfileConfig.java @@ -1,6 +1,5 @@ package bio.overture.score.client.config; -import bio.overture.score.client.exception.NotRetryableException; import bio.overture.score.core.model.StorageProfiles; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -54,8 +53,8 @@ private String getStorageProfile() { .exchange(endpoint + "/profile", HttpMethod.GET, defaultEntity(), String.class) .getBody(); return storageProfile; - } catch (NotRetryableException nre) { - log.error("received exception when getting profiles: " + nre.getMessage()); + } catch (Exception e) { + log.error("received exception when getting profiles: " + e.getMessage()); } return StorageProfiles.getProfileValue(defaultProfile); } diff --git a/score-client/src/main/resources/application.yml b/score-client/src/main/resources/application.yml index 331e3612..dfb27361 100644 --- a/score-client/src/main/resources/application.yml +++ b/score-client/src/main/resources/application.yml @@ -86,7 +86,10 @@ defaultProfile: collaboratory # Profile - "kf" ############################################################################### -spring.profiles: kf +spring: + config: + activate: + on-profile: kf # Storage server storage: @@ -103,7 +106,10 @@ client: # Profile - "oicr" ############################################################################### -spring.profiles: oicr +spring: + config: + activate: + on-profile: oicr # uses storage.url and client.ssl.custom define in common @@ -114,7 +120,10 @@ spring.profiles: oicr # Profile - "debug" ############################################################################### -spring.profiles: debug +spring: + config: + activate: + on-profile: debug logging: level: diff --git a/score-client/src/test/java/bio/overture/score/client/AbstractClientMainTest.java b/score-client/src/test/java/bio/overture/score/client/AbstractClientMainTest.java index d454fdc3..8ef3840a 100644 --- a/score-client/src/test/java/bio/overture/score/client/AbstractClientMainTest.java +++ b/score-client/src/test/java/bio/overture/score/client/AbstractClientMainTest.java @@ -20,11 +20,11 @@ import java.util.function.Consumer; import lombok.Getter; import org.junit.Rule; -import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.boot.test.system.OutputCaptureRule; public abstract class AbstractClientMainTest { - @Rule public OutputCapture capture = new OutputCapture(); + @Rule public OutputCaptureRule capture = new OutputCaptureRule(); public ExitCodeCapture exitCodeCapture = new ExitCodeCapture(); protected void executeMain(String... args) { diff --git a/score-core/pom.xml b/score-core/pom.xml index 17f86b8d..d1bd5a68 100644 --- a/score-core/pom.xml +++ b/score-core/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 ../pom.xml diff --git a/score-fs/pom.xml b/score-fs/pom.xml index 0bae4d22..7320fc7c 100644 --- a/score-fs/pom.xml +++ b/score-fs/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 ../pom.xml diff --git a/score-server/pom.xml b/score-server/pom.xml index 5d52dd36..244dbac7 100644 --- a/score-server/pom.xml +++ b/score-server/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 ../pom.xml @@ -50,10 +50,9 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S - - ${project.groupId} - score-core + ${project.groupId} + score-core @@ -61,6 +60,10 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-actuator @@ -75,11 +78,24 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S spring-security-jwt + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + org.springframework.cloud spring-cloud-starter-vault-config + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + org.springframework.retry spring-retry @@ -89,12 +105,12 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S io.springfox springfox-swagger-ui - 2.8.0 + ${springfox.version} io.springfox springfox-swagger2 - 2.8.0 + ${springfox.version} @@ -157,7 +173,10 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S - 19.0 + + 20.0 + + 2.9.1 2.8.3 4.4.0 diff --git a/score-server/src/main/java/bio/overture/score/server/config/KeycloakConfig.java b/score-server/src/main/java/bio/overture/score/server/config/KeycloakConfig.java new file mode 100644 index 00000000..6a834788 --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/config/KeycloakConfig.java @@ -0,0 +1,43 @@ +package bio.overture.score.server.config; + +import java.net.URI; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +@Configuration +@Profile("secure") +@Getter +public class KeycloakConfig { + + @Value("${auth.server.clientID}") + private String uma_audience; + + @Value("${auth.server.keycloak.host}") + private String host; + + @Value("${auth.server.keycloak.realm}") + private String realm; + + private static final String UMA_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket"; + private static final String UMA_RESPONSE_MODE = "permissions"; + + public URI permissionUrl() { + return UriComponentsBuilder.fromHttpUrl(host) + .pathSegment("realms", realm, "protocol/openid-connect/token") + .build() + .toUri(); + } + + public MultiValueMap getUmaParams() { + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", UMA_GRANT_TYPE); + map.add("audience", uma_audience); + map.add("response_mode", UMA_RESPONSE_MODE); + return map; + } +} diff --git a/score-server/src/main/java/bio/overture/score/server/config/SecurityConfig.java b/score-server/src/main/java/bio/overture/score/server/config/SecurityConfig.java index 43c6aa1c..aa01712e 100644 --- a/score-server/src/main/java/bio/overture/score/server/config/SecurityConfig.java +++ b/score-server/src/main/java/bio/overture/score/server/config/SecurityConfig.java @@ -19,26 +19,30 @@ import bio.overture.score.server.metadata.MetadataService; import bio.overture.score.server.properties.ScopeProperties; +import bio.overture.score.server.security.ApiKeyIntrospector; import bio.overture.score.server.security.scope.DownloadScopeAuthorizationStrategy; import bio.overture.score.server.security.scope.UploadScopeAuthorizationStrategy; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import lombok.val; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.*; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor; -import org.springframework.security.oauth2.provider.authentication.TokenExtractor; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; /** * Resource service configuration file.
@@ -48,13 +52,21 @@ @Configuration @Profile("secure") @EnableWebSecurity -@EnableResourceServer -public class SecurityConfig extends ResourceServerConfigurerAdapter { +@Getter +@Setter +@ConfigurationProperties("auth.server") +public class SecurityConfig extends WebSecurityConfigurerAdapter { - private TokenExtractor tokenExtractor = new BearerTokenExtractor(); + private String url; + private String clientId; + private String clientSecret; + private String provider; + private String tokenName; private final ScopeProperties scopeProperties; + @Autowired private JwtDecoder jwtDecoder; + @Autowired public SecurityConfig(@NonNull ScopeProperties scopeProperties) { this.scopeProperties = scopeProperties; @@ -62,33 +74,12 @@ public SecurityConfig(@NonNull ScopeProperties scopeProperties) { @Override public void configure(@NonNull HttpSecurity http) throws Exception { - http.addFilterAfter( - new OncePerRequestFilter() { - - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) - throws ServletException, IOException { - - // We don't want to allow access to a resource with no token so clear - // the security context in case it is actually an OAuth2Authentication - if (tokenExtractor.extract(request) == null) { - SecurityContextHolder.clearContext(); - } - filterChain.doFilter(request, response); - } - }, - AbstractPreAuthenticatedProcessingFilter.class); - http.csrf().disable(); configureAuthorization(http); } private void configureAuthorization(HttpSecurity http) throws Exception { scopeProperties.logScopeProperties(); - ; // @formatter:off http.authorizeRequests() @@ -106,17 +97,35 @@ private void configureAuthorization(HttpSecurity http) throws Exception { .authorizeRequests() .anyRequest() .authenticated(); + + http.oauth2ResourceServer( + oauth2 -> oauth2.authenticationManagerResolver(this.tokenAuthenticationManagerResolver())); // @formatter:on log.info("initialization done"); } + @Bean + public AuthenticationManagerResolver tokenAuthenticationManagerResolver() { + + // Auth Managers for JWT and for ApiKeys. JWT uses the default auth provider, + // but OpaqueTokens are handled by the custom ApiKeyIntrospector + AuthenticationManager jwt = new ProviderManager(new JwtAuthenticationProvider(jwtDecoder)); + AuthenticationManager opaqueToken = + new ProviderManager( + new OpaqueTokenAuthenticationProvider( + new ApiKeyIntrospector(url, clientId, clientSecret, tokenName))); + + return (request) -> useJwt(request) ? jwt : opaqueToken; + } + @Bean public UploadScopeAuthorizationStrategy projectSecurity(@Autowired MetadataService song) { return new UploadScopeAuthorizationStrategy( scopeProperties.getUpload().getStudy().getPrefix(), scopeProperties.getUpload().getStudy().getSuffix(), scopeProperties.getUpload().getSystem(), - song); + song, + provider); } @Bean @@ -126,10 +135,32 @@ public DownloadScopeAuthorizationStrategy accessSecurity(@Autowired MetadataServ scopeProperties.getDownload().getStudy().getPrefix(), scopeProperties.getDownload().getStudy().getSuffix(), scopeProperties.getDownload().getSystem(), - song); + song, + provider); } public ScopeProperties getScopeProperties() { return this.scopeProperties; } + + @Bean + public OpaqueTokenIntrospector introspector() { + return new ApiKeyIntrospector(url, clientId, clientSecret, tokenName); + } + + private boolean useJwt(HttpServletRequest request) { + val authorizationHeaderValue = request.getHeader(HttpHeaders.AUTHORIZATION); + if (authorizationHeaderValue != null && authorizationHeaderValue.startsWith("Bearer")) { + String token = authorizationHeaderValue.substring(7); + try { + UUID.fromString(token); + // able to parse as UUID, so this token matches our EgoApiKey format + return false; + } catch (IllegalArgumentException e) { + // unable to parse as UUID, use our JWT resolvers + return true; + } + } + return true; + } } diff --git a/score-server/src/main/java/bio/overture/score/server/config/SwaggerConfig.java b/score-server/src/main/java/bio/overture/score/server/config/SwaggerConfig.java index 0d0154d7..c383aee9 100644 --- a/score-server/src/main/java/bio/overture/score/server/config/SwaggerConfig.java +++ b/score-server/src/main/java/bio/overture/score/server/config/SwaggerConfig.java @@ -2,10 +2,23 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePackage; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.web.*; +import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; @@ -56,4 +69,39 @@ private ApiInfo apiInfo() { .version(serverVersion) .build(); } + + @Bean + public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping( + WebEndpointsSupplier webEndpointsSupplier, + ServletEndpointsSupplier servletEndpointsSupplier, + ControllerEndpointsSupplier controllerEndpointsSupplier, + EndpointMediaTypes endpointMediaTypes, + CorsEndpointProperties corsProperties, + WebEndpointProperties webEndpointProperties, + Environment environment) { + List> allEndpoints = new ArrayList(); + Collection webEndpoints = webEndpointsSupplier.getEndpoints(); + allEndpoints.addAll(webEndpoints); + allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); + allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); + String basePath = webEndpointProperties.getBasePath(); + EndpointMapping endpointMapping = new EndpointMapping(basePath); + boolean shouldRegisterLinksMapping = + this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); + return new WebMvcEndpointHandlerMapping( + endpointMapping, + webEndpoints, + endpointMediaTypes, + corsProperties.toCorsConfiguration(), + new EndpointLinksResolver(allEndpoints, basePath), + shouldRegisterLinksMapping, + null); + } + + private boolean shouldRegisterLinksMapping( + WebEndpointProperties webEndpointProperties, Environment environment, String basePath) { + return webEndpointProperties.getDiscovery().isEnabled() + && (StringUtils.hasText(basePath) + || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); + } } diff --git a/score-server/src/main/java/bio/overture/score/server/config/TokenServicesConfig.java b/score-server/src/main/java/bio/overture/score/server/config/TokenServicesConfig.java deleted file mode 100644 index 4ebb7e0d..00000000 --- a/score-server/src/main/java/bio/overture/score/server/config/TokenServicesConfig.java +++ /dev/null @@ -1,84 +0,0 @@ -package bio.overture.score.server.config; - -import bio.overture.score.server.security.*; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.security.oauth2.provider.token.AccessTokenConverter; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.RemoteTokenServices; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.springframework.web.client.RestTemplate; - -@Configuration -@Slf4j -@Profile("secure") -public class TokenServicesConfig { - - @Value("${auth.server.url}") - private String checkTokenUrl; - - @Value("${auth.server.tokenName:token}") - private String tokenName; - - @Value("${auth.server.clientId}") - private String clientId; - - @Value("${auth.server.clientSecret}") - private String clientSecret; - - @Bean - @Profile("!jwt") - public RemoteTokenServices remoteTokenServices() { - return createRemoteTokenServices(); - } - - @Bean - @Autowired - @Profile("jwt") - public MergedServerTokenServices mergedServerTokenServices( - @NonNull PublicKeyFetcher publicKeyFetcher, @NonNull RetryTemplate retryTemplate) { - val jwtTokenServices = createJwtTokenServices(publicKeyFetcher.getPublicKey()); - val remoteTokenServices = createRemoteTokenServices(); - return new MergedServerTokenServices(jwtTokenServices, remoteTokenServices, retryTemplate); - } - - @Bean - @Autowired - @Profile("jwt") - public PublicKeyFetcher publicKeyFetcher( - @Value("${auth.jwt.publicKeyUrl}") @NonNull String publicKeyUrl, - @NonNull RetryTemplate retryTemplate) { - return new DefaultPublicKeyFetcher(publicKeyUrl, new RestTemplate(), retryTemplate); - } - - private AccessTokenConverter accessTokenConverter() { - return new AccessTokenConverterWithExpiry(); - } - - private RemoteTokenServices createRemoteTokenServices() { - val remoteTokenServices = new CachingRemoteTokenServices(); - remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl); - remoteTokenServices.setClientId(clientId); - remoteTokenServices.setClientSecret(clientSecret); - remoteTokenServices.setTokenName(tokenName); - remoteTokenServices.setAccessTokenConverter(accessTokenConverter()); - - log.debug("using auth server: " + checkTokenUrl); - - return remoteTokenServices; - } - - private DefaultTokenServices createJwtTokenServices(String publicKey) { - val tokenStore = new JwtTokenStore(new JWTConverter(publicKey)); - val defaultTokenServices = new DefaultTokenServices(); - defaultTokenServices.setTokenStore(tokenStore); - return defaultTokenServices; - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/repository/auth/KeycloakAuthorizationService.java b/score-server/src/main/java/bio/overture/score/server/repository/auth/KeycloakAuthorizationService.java new file mode 100644 index 00000000..16be79f2 --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/repository/auth/KeycloakAuthorizationService.java @@ -0,0 +1,112 @@ +package bio.overture.score.server.repository.auth; + +import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR; +import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; + +import bio.overture.score.server.config.KeycloakConfig; +import bio.overture.score.server.security.KeycloakPermission; +import java.io.IOException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.http.*; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +@Slf4j +@Service +@Profile("secure") +public class KeycloakAuthorizationService { + + private final KeycloakConfig keycloakConfig; + + public KeycloakAuthorizationService(@Autowired KeycloakConfig keycloakConfig) { + this.keycloakConfig = keycloakConfig; + } + + public List fetchAuthorizationGrants(String accessToken) { + + val serviceUrl = keycloakConfig.permissionUrl(); + + HttpEntity> request = + new HttpEntity<>(keycloakConfig.getUmaParams(), getBearerAuthHeader(accessToken)); + + ResponseEntity response; + + try { + // Get response from Keycloak + val template = new RestTemplate(); + template.setErrorHandler(new RestTemplateResponseErrorHandler()); + response = template.postForEntity(serviceUrl, request, KeycloakPermission[].class); + } catch (ResourceAccessException e) { + log.error( + "KeycloakAuthorizationService - error cause:" + + e.getCause() + + " message:" + + e.getMessage()); + throw new OAuth2IntrospectionException("Bad Response from Keycloak Server"); + } + + // Ensure response was OK + if ((response.getStatusCode() != HttpStatus.OK + && response.getStatusCode() != HttpStatus.MULTI_STATUS + && response.getStatusCode() != HttpStatus.UNAUTHORIZED) + || !response.hasBody()) { + throw new OAuth2IntrospectionException("Bad Response from Keycloak Server"); + } + + val isValid = validateIntrospectResponse(response.getStatusCode()); + if (!isValid) { + throw new BadOpaqueTokenException("ApiKey is revoked or expired."); + } + + return List.of(response.getBody()); + } + + private HttpHeaders getBearerAuthHeader(String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.setBearerAuth(token); + return headers; + } + + private boolean validateIntrospectResponse(HttpStatus status) { + if (status != HttpStatus.OK && status != HttpStatus.MULTI_STATUS) { + log.debug( + "Check Token response is unauthorized but does not list the error. Rejecting token."); + return false; + } + return true; + } + + private static class RestTemplateResponseErrorHandler implements ResponseErrorHandler { + + @Override + public boolean hasError(ClientHttpResponse httpResponse) throws IOException { + + return (httpResponse.getStatusCode().series() == CLIENT_ERROR + || httpResponse.getStatusCode().series() == SERVER_ERROR); + } + + @Override + public void handleError(ClientHttpResponse httpResponse) throws IOException { + + if (httpResponse.getStatusCode().series() == CLIENT_ERROR) { + // throw 401 HTTP error code + throw new BadCredentialsException(httpResponse.getStatusText()); + } else { + // throw 500 HTTP error code + throw new OAuth2IntrospectionException(httpResponse.getStatusText()); + } + } + } +} diff --git a/score-server/src/main/java/bio/overture/score/server/security/AccessTokenConverterWithExpiry.java b/score-server/src/main/java/bio/overture/score/server/security/AccessTokenConverterWithExpiry.java deleted file mode 100644 index 81a106d1..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/AccessTokenConverterWithExpiry.java +++ /dev/null @@ -1,27 +0,0 @@ -package bio.overture.score.server.security; - -import static bio.overture.score.server.security.ExpiringOauth2Authentication.from; - -import java.util.Map; -import lombok.val; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; - -/*** - * RemoteTokenServices uses a postForMap call to convert the Oauth2 JSON response that we get from Ego into a Java - * Map from string to an unknown object type. - * - * The default converter just extracts the scope field; we also want to extract the "exp" field, which holds the time - * to expiry for our token in seconds. - * - */ - -public class AccessTokenConverterWithExpiry extends DefaultAccessTokenConverter { - @Override - public OAuth2Authentication extractAuthentication(Map map) { - val exp = map.get("exp"); - - int expiryInSeconds = (exp instanceof Integer) ? (Integer) exp : 0; - return from(super.extractAuthentication(map), expiryInSeconds); - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospectResponse.java b/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospectResponse.java new file mode 100644 index 00000000..2f8aee11 --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospectResponse.java @@ -0,0 +1,15 @@ +package bio.overture.score.server.security; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApiKeyIntrospectResponse { + private long exp; + private String user_id; + public List scope; +} diff --git a/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospector.java b/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospector.java new file mode 100644 index 00000000..f406956d --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/security/ApiKeyIntrospector.java @@ -0,0 +1,154 @@ +package bio.overture.score.server.security; + +import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR; +import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; + +import bio.overture.score.server.util.JsonUtils; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.util.*; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +@AllArgsConstructor +public class ApiKeyIntrospector implements OpaqueTokenIntrospector { + + private String introspectionUri; + private String clientId; + private String clientSecret; + private String tokenName; + + @Override + public OAuth2AuthenticatedPrincipal introspect(String token) { + + // Add token to body + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add(tokenName, token); + + // URI format + val uriWithToken = UriComponentsBuilder.fromHttpUrl(introspectionUri).build().toUri(); + + // Get response from Auth Server + val template = new RestTemplate(); + template.setErrorHandler(new RestTemplateResponseErrorHandler()); + val response = + template.postForEntity( + uriWithToken, new HttpEntity<>(formData, getBasicAuthHeader()), JsonNode.class); + + // Ensure response was OK + if ((response.getStatusCode() != HttpStatus.OK + && response.getStatusCode() != HttpStatus.MULTI_STATUS + && response.getStatusCode() != HttpStatus.UNAUTHORIZED) + || !response.hasBody()) { + throw new OAuth2IntrospectionException("Bad Response from Ego Server"); + } + + val responseBody = response.getBody(); + + val isValid = validateIntrospectResponse(response.getStatusCode(), responseBody); + if (!isValid) { + throw new BadOpaqueTokenException("ApiKey is revoked or expired."); + } + + // ApiKey check is successful. Build authenticated principal and return. + return convertResponseToPrincipal(responseBody); + } + + private HttpHeaders getBasicAuthHeader() { + val headers = new HttpHeaders(); + headers.setBasicAuth(clientId, clientSecret); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE); + return headers; + } + + private boolean validateIntrospectResponse(HttpStatus status, JsonNode response) { + if (response.has("error")) { + log.debug("Check Token response includes an error: {}", response.has("error")); + return false; + } + if (status != HttpStatus.OK && status != HttpStatus.MULTI_STATUS) { + log.debug( + "Check Token response is unauthorized but does not list the error. Rejecting token."); + return false; + } + + if (response.has("exp") && response.get("exp").asLong() == 0) { + log.debug("Token is expired. Rejecting token."); + return false; + } + + if (response.has("revoked") && response.get("revoked").asBoolean() == true) { + log.debug("Token is revoked. Rejecting token."); + return false; + } + + if (response.has("valid") && response.get("valid").asBoolean() == false) { + log.debug("Check Token response 'valid' field is false. Rejecting token."); + return false; + } + + return true; + } + + private OAuth2AuthenticatedPrincipal convertResponseToPrincipal(JsonNode responseJson) { + val response = JsonUtils.convertValue(responseJson, ApiKeyIntrospectResponse.class); + + Collection authorities = new ArrayList(); + Map claims = new HashMap<>(); + + if (!response.getScope().isEmpty()) { + List scopes = Collections.unmodifiableList(response.getScope()); + claims.put("scope", scopes); + val var5 = scopes.iterator(); + + while (var5.hasNext()) { + String scope = (String) var5.next(); + authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); + } + } + + return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities); + } + + public class RestTemplateResponseErrorHandler implements ResponseErrorHandler { + + @Override + public boolean hasError(ClientHttpResponse httpResponse) throws IOException { + + return (httpResponse.getStatusCode().series() == CLIENT_ERROR + || httpResponse.getStatusCode().series() == SERVER_ERROR); + } + + @Override + public void handleError(ClientHttpResponse httpResponse) throws IOException { + + if (httpResponse.getStatusCode().series() == CLIENT_ERROR) { + // throw 401 HTTP error code + throw new BadCredentialsException(httpResponse.getStatusText()); + } else { + // throw 500 HTTP error code + throw new OAuth2IntrospectionException(httpResponse.getStatusText()); + } + } + } +} diff --git a/score-server/src/main/java/bio/overture/score/server/security/CachingRemoteTokenServices.java b/score-server/src/main/java/bio/overture/score/server/security/CachingRemoteTokenServices.java deleted file mode 100644 index 8d294bf6..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/CachingRemoteTokenServices.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2016 The Ontario Institute for Cancer Research. All rights reserved. - * - * This program and the accompanying materials are made available under the terms of the GNU Public License v3.0. - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package bio.overture.score.server.security; - -import org.springframework.cache.annotation.Cacheable; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.RemoteTokenServices; - -public class CachingRemoteTokenServices extends RemoteTokenServices { - - @Override - @Cacheable("tokens") - public OAuth2Authentication loadAuthentication(String accessToken) - throws AuthenticationException, InvalidTokenException { - return super.loadAuthentication(accessToken); - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/DefaultPublicKeyFetcher.java b/score-server/src/main/java/bio/overture/score/server/security/DefaultPublicKeyFetcher.java deleted file mode 100644 index 4f3448db..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/DefaultPublicKeyFetcher.java +++ /dev/null @@ -1,21 +0,0 @@ -package bio.overture.score.server.security; - -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.web.client.RestTemplate; - -@RequiredArgsConstructor -public class DefaultPublicKeyFetcher implements PublicKeyFetcher { - - @NonNull private final String url; - @NonNull private final RestTemplate restTemplate; - @NonNull private final RetryTemplate retryTemplate; - - @Override - public String getPublicKey() { - val resp = retryTemplate.execute(x -> restTemplate.getForEntity(url, String.class)); - return resp.hasBody() ? resp.getBody() : null; - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/ExpiringOauth2Authentication.java b/score-server/src/main/java/bio/overture/score/server/security/ExpiringOauth2Authentication.java deleted file mode 100644 index e77156ab..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/ExpiringOauth2Authentication.java +++ /dev/null @@ -1,22 +0,0 @@ -package bio.overture.score.server.security; - -import lombok.Getter; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; - -@Getter -public class ExpiringOauth2Authentication extends OAuth2Authentication { - private final int expiry; // expiry time of the authentication token in seconds - - public ExpiringOauth2Authentication( - OAuth2Request storedRequest, Authentication userAuthentication, int expiry) { - super(storedRequest, userAuthentication); - this.expiry = expiry; - } - - public static ExpiringOauth2Authentication from(OAuth2Authentication existing, int expiry) { - return new ExpiringOauth2Authentication( - existing.getOAuth2Request(), existing.getUserAuthentication(), expiry); - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/JWTConverter.java b/score-server/src/main/java/bio/overture/score/server/security/JWTConverter.java deleted file mode 100644 index 7dbef5e1..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/JWTConverter.java +++ /dev/null @@ -1,51 +0,0 @@ -package bio.overture.score.server.security; - -import java.util.*; -import lombok.NonNull; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; - -@Slf4j -public class JWTConverter extends JwtAccessTokenConverter { - - private static final String CONTEXT = "context"; - private static final String SCOPE = "scope"; - - @SneakyThrows - public JWTConverter(String publicKey) { - super(); - this.setVerifierKey(publicKey); - this.afterPropertiesSet(); - } - - @Override - public OAuth2Authentication extractAuthentication(@NonNull Map map) { - // Currently EGO's JWT spec places scopes in map at 'context.scope' - // but extractAuthentication expects them in map's root at 'scope' - // so put all scopes into root level for spring security processing - val allScopes = getRootAndContextScopes(map); - HashMap updatedMap = new HashMap<>(map); - updatedMap.put(SCOPE, allScopes); - - return super.extractAuthentication(updatedMap); - } - - private Collection getRootAndContextScopes(Map map) { - List extractedScopes = new ArrayList<>(Collections.emptyList()); - try { - if (map.containsKey(CONTEXT)) { - val context = (Map) map.get(CONTEXT); - extractedScopes.addAll((Collection) context.get(SCOPE)); - } - if (map.containsKey(SCOPE)) { - extractedScopes.addAll((Collection) map.get(SCOPE)); - } - } catch (Exception e) { - log.error("Failed to extract scopes from JWT"); - } - return extractedScopes; - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/KeycloakPermission.java b/score-server/src/main/java/bio/overture/score/server/security/KeycloakPermission.java new file mode 100644 index 00000000..0abe8b8c --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/security/KeycloakPermission.java @@ -0,0 +1,15 @@ +package bio.overture.score.server.security; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KeycloakPermission { + private String rsid; + private String rsname; + private List scopes; +} diff --git a/score-server/src/main/java/bio/overture/score/server/security/MergedServerTokenServices.java b/score-server/src/main/java/bio/overture/score/server/security/MergedServerTokenServices.java deleted file mode 100644 index f4c02d82..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/MergedServerTokenServices.java +++ /dev/null @@ -1,50 +0,0 @@ -package bio.overture.score.server.security; - -import static com.google.common.base.Strings.isNullOrEmpty; - -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.RemoteTokenServices; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; - -@RequiredArgsConstructor -public class MergedServerTokenServices implements ResourceServerTokenServices { - private final DefaultTokenServices jwtTokenService; - private final RemoteTokenServices remoteTokenServices; - private final RetryTemplate retryTemplate; - - @Override - public OAuth2Authentication loadAuthentication(String accessToken) - throws AuthenticationException, InvalidTokenException { - if (isApiKey(accessToken)) { - return retryTemplate.execute(x -> remoteTokenServices.loadAuthentication(accessToken)); - } - return jwtTokenService.loadAuthentication(accessToken); - } - - @Override - public OAuth2AccessToken readAccessToken(String accessToken) { - if (isApiKey(accessToken)) { - return retryTemplate.execute(x -> remoteTokenServices.readAccessToken(accessToken)); - } - return jwtTokenService.readAccessToken(accessToken); - } - - private static boolean isApiKey(String value) { - if (isNullOrEmpty(value)) { - return false; - } - try { - UUID.fromString(value); - } catch (IllegalArgumentException e) { - return false; - } - return true; - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/PublicKeyFetcher.java b/score-server/src/main/java/bio/overture/score/server/security/PublicKeyFetcher.java deleted file mode 100644 index 7cb6c3e5..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/PublicKeyFetcher.java +++ /dev/null @@ -1,7 +0,0 @@ -package bio.overture.score.server.security; - -@FunctionalInterface -public interface PublicKeyFetcher { - - String getPublicKey(); -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/TokenChecker.java b/score-server/src/main/java/bio/overture/score/server/security/TokenChecker.java deleted file mode 100644 index 6ecc67f5..00000000 --- a/score-server/src/main/java/bio/overture/score/server/security/TokenChecker.java +++ /dev/null @@ -1,25 +0,0 @@ -package bio.overture.score.server.security; - -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.springframework.security.core.Authentication; - -@Slf4j -public class TokenChecker { - public static boolean isExpired(Authentication authentication) { - log.info("Checking for token expiry..."); - if (authentication instanceof ExpiringOauth2Authentication) { - val auth = (ExpiringOauth2Authentication) authentication; - - if (auth.getExpiry() == 0) { - log.info("Expired token detected; authorization denied."); - return true; - } - log.info("Token is not expired; authorization continues."); - return false; - } - - log.error("Unknown authentication type detected!"); - return false; - } -} diff --git a/score-server/src/main/java/bio/overture/score/server/security/scope/AbstractScopeAuthorizationStrategy.java b/score-server/src/main/java/bio/overture/score/server/security/scope/AbstractScopeAuthorizationStrategy.java index f2a13cf8..62de3d53 100644 --- a/score-server/src/main/java/bio/overture/score/server/security/scope/AbstractScopeAuthorizationStrategy.java +++ b/score-server/src/main/java/bio/overture/score/server/security/scope/AbstractScopeAuthorizationStrategy.java @@ -16,15 +16,21 @@ */ package bio.overture.score.server.security.scope; +import static bio.overture.score.server.util.Scopes.extractGrantedScopes; +import static bio.overture.score.server.util.Scopes.extractGrantedScopesFromRpt; + import bio.overture.score.server.exception.NotRetryableException; import bio.overture.score.server.metadata.MetadataService; +import bio.overture.score.server.repository.auth.KeycloakAuthorizationService; import java.util.Set; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; @Slf4j @Getter @@ -35,6 +41,9 @@ public abstract class AbstractScopeAuthorizationStrategy { @NonNull private final String studySuffix; @NonNull private final String systemScope; @NonNull private final MetadataService metadataService; + @NonNull private final String provider; + + @Autowired private KeycloakAuthorizationService keycloakAuthorizationService; public abstract boolean authorize(Authentication authentication, String objectId); @@ -89,4 +98,19 @@ protected String fetchFileAccessType(@NonNull final String objectId) { private String getStudyScope(@NonNull String studyId) { return getStudyPrefix() + studyId + getStudySuffix(); } + + protected Set getGrantedScopes(Authentication authentication) { + Set grantedScopes; + if ("keycloak".equalsIgnoreCase(this.getProvider()) + && authentication instanceof JwtAuthenticationToken) { + val authGrants = + keycloakAuthorizationService.fetchAuthorizationGrants( + ((JwtAuthenticationToken) authentication).getToken().getTokenValue()); + + grantedScopes = extractGrantedScopesFromRpt(authGrants); + } else { + grantedScopes = extractGrantedScopes(authentication); + } + return grantedScopes; + } } diff --git a/score-server/src/main/java/bio/overture/score/server/security/scope/DownloadScopeAuthorizationStrategy.java b/score-server/src/main/java/bio/overture/score/server/security/scope/DownloadScopeAuthorizationStrategy.java index cf9b38fe..23fda48f 100644 --- a/score-server/src/main/java/bio/overture/score/server/security/scope/DownloadScopeAuthorizationStrategy.java +++ b/score-server/src/main/java/bio/overture/score/server/security/scope/DownloadScopeAuthorizationStrategy.java @@ -17,12 +17,10 @@ */ package bio.overture.score.server.security.scope; -import static bio.overture.score.server.security.TokenChecker.isExpired; -import static bio.overture.score.server.util.Scopes.extractGrantedScopes; - import bio.overture.score.server.exception.NotRetryableException; import bio.overture.score.server.metadata.MetadataService; import bio.overture.score.server.security.Access; +import java.util.Set; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -35,16 +33,16 @@ public DownloadScopeAuthorizationStrategy( @NonNull String studyPrefix, @NonNull String studySuffix, @NonNull String systemScope, - MetadataService metadataService) { - super(studyPrefix, studySuffix, systemScope, metadataService); + MetadataService metadataService, + @NonNull String provider) { + super(studyPrefix, studySuffix, systemScope, metadataService, provider); } @Override public boolean authorize(@NonNull Authentication authentication, @NonNull final String objectId) { - if (isExpired(authentication)) { - return false; - } - val grantedScopes = extractGrantedScopes(authentication); + + Set grantedScopes = getGrantedScopes(authentication); + log.info("Checking system-level authorization for objectId {}", objectId); if (verifyOneOfSystemScope(grantedScopes)) { return true; diff --git a/score-server/src/main/java/bio/overture/score/server/security/scope/UploadScopeAuthorizationStrategy.java b/score-server/src/main/java/bio/overture/score/server/security/scope/UploadScopeAuthorizationStrategy.java index 3eaec752..661faee7 100644 --- a/score-server/src/main/java/bio/overture/score/server/security/scope/UploadScopeAuthorizationStrategy.java +++ b/score-server/src/main/java/bio/overture/score/server/security/scope/UploadScopeAuthorizationStrategy.java @@ -16,10 +16,8 @@ */ package bio.overture.score.server.security.scope; -import static bio.overture.score.server.security.TokenChecker.isExpired; -import static bio.overture.score.server.util.Scopes.extractGrantedScopes; - import bio.overture.score.server.metadata.MetadataService; +import java.util.Set; import lombok.*; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -31,15 +29,15 @@ public UploadScopeAuthorizationStrategy( @NonNull String studyPrefix, @NonNull String studySuffix, @NonNull String systemScope, - @NonNull MetadataService metadataService) { - super(studyPrefix, studySuffix, systemScope, metadataService); + @NonNull MetadataService metadataService, + @NonNull String provider) { + super(studyPrefix, studySuffix, systemScope, metadataService, provider); } public boolean authorize(@NonNull Authentication authentication, @NonNull final String objectId) { - if (isExpired(authentication)) { - return false; - } - val grantedScopes = extractGrantedScopes(authentication); + + Set grantedScopes = getGrantedScopes(authentication); + if (verifyOneOfSystemScope(grantedScopes)) { log.info("System-level upload authorization granted"); return true; diff --git a/score-server/src/main/java/bio/overture/score/server/util/JsonUtils.java b/score-server/src/main/java/bio/overture/score/server/util/JsonUtils.java new file mode 100644 index 00000000..136931dd --- /dev/null +++ b/score-server/src/main/java/bio/overture/score/server/util/JsonUtils.java @@ -0,0 +1,88 @@ +package bio.overture.score.server.util; + +import static com.google.common.base.Strings.emptyToNull; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.deser.std.StringDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import java.io.IOException; +import java.net.URL; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import lombok.SneakyThrows; +import lombok.val; + +/** Utility functions related to deal with JSON */ +public class JsonUtils { + + protected static final ObjectMapper mapper = mapper(); + + public static ObjectMapper mapper() { + val mapper = new ObjectMapper(); + + /* Register Modules */ + val specialModule = new SimpleModule(); + specialModule.addDeserializer(String.class, SpecialStringJsonDeserializer.instance); + + val isoDateFormatter = DateTimeFormatter.ISO_DATE_TIME; + val dateTimeDeserializer = new LocalDateTimeDeserializer(isoDateFormatter); + val dateTimeSerializer = new LocalDateTimeSerializer(isoDateFormatter); + + val javaTimeModule = new JavaTimeModule(); + javaTimeModule.addDeserializer(LocalDateTime.class, dateTimeDeserializer); + javaTimeModule.addSerializer(LocalDateTime.class, dateTimeSerializer); + + mapper.registerModule(specialModule); + mapper.registerModule(javaTimeModule); + + mapper.disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); + mapper.disable(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + mapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); + + // Doesn't work! Fields with the value '""' (empty string) are not being deserialized as null. + // mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); + + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); + return mapper; + } + + @SneakyThrows + public static JsonNode read(URL url) { + return mapper.readTree(url); + } + + public static T convertValue(Object fromValue, Class toValue) { + return mapper().convertValue(fromValue, toValue); + } + + /** + * Since the ACCEPT_EMPTY_STRING_AS_NULL_OBJECT DeserializationFeature is not working properly, + * created custom string deserialization handling of empty string. + */ + public static class SpecialStringJsonDeserializer extends StdDeserializer { + public static final SpecialStringJsonDeserializer instance = + new SpecialStringJsonDeserializer(); + + public SpecialStringJsonDeserializer() { + super(String.class); + } + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + val result = StringDeserializer.instance.deserialize(jsonParser, deserializationContext); + return emptyToNull(result); + } + } +} diff --git a/score-server/src/main/java/bio/overture/score/server/util/Scopes.java b/score-server/src/main/java/bio/overture/score/server/util/Scopes.java index 234b7343..83947044 100644 --- a/score-server/src/main/java/bio/overture/score/server/util/Scopes.java +++ b/score-server/src/main/java/bio/overture/score/server/util/Scopes.java @@ -2,26 +2,86 @@ import static lombok.AccessLevel.PRIVATE; -import java.util.Collections; -import java.util.Set; +import bio.overture.score.server.security.KeycloakPermission; +import com.nimbusds.jose.shaded.json.JSONArray; +import com.nimbusds.jose.shaded.json.JSONObject; +import java.util.*; import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; +@Slf4j @NoArgsConstructor(access = PRIVATE) public class Scopes { + private static final String EXP = "exp"; + public static Set extractGrantedScopes(Authentication authentication) { // if not OAuth2, then no scopes available at all Set grantedScopes = Collections.emptySet(); - if (authentication instanceof OAuth2Authentication) { - OAuth2Authentication o2auth = (OAuth2Authentication) authentication; - grantedScopes = getScopes(o2auth); + if (authentication instanceof JwtAuthenticationToken) { + grantedScopes = getJwtScopes((JwtAuthenticationToken) authentication); + } else if (authentication instanceof BearerTokenAuthentication) { + grantedScopes = getApiKeyScopes((BearerTokenAuthentication) authentication); } return grantedScopes; } - private static Set getScopes(OAuth2Authentication o2auth) { - return o2auth.getOAuth2Request().getScope(); + public static Set extractGrantedScopesFromRpt(List permissionList) { + Set grantedScopes = new HashSet(); + + permissionList.stream() + .filter(perm -> perm.getScopes() != null) + .forEach( + permission -> { + permission.getScopes().stream() + .forEach( + scope -> { + val fullScope = permission.getRsname() + "." + scope; + grantedScopes.add(fullScope); + }); + }); + + return grantedScopes; + } + + public static long extractExpiry(Map map) { + Object exp = map.get(EXP); + if (exp instanceof Integer) { + return (Integer) exp; + } else if (exp instanceof Long) { + return (Long) exp; + } + return 0L; + } + + private static Set getJwtScopes(JwtAuthenticationToken jwt) { + Set output = new HashSet(); + try { + val context = jwt.getToken().getClaim("context"); + if (context instanceof JSONObject) { + val scopes = ((JSONObject) context).get("scope"); + if (scopes instanceof JSONArray) { + val scopeArray = (JSONArray) scopes; + scopeArray.stream() + .filter(value -> value instanceof String) + .forEach(value -> output.add((String) value)); + } + } + } catch (ClassCastException e) { + log.debug("Received JWT not structured as expected. No scopes found."); + } + return output; + } + + private static Set getApiKeyScopes(BearerTokenAuthentication tokenAuthentication) { + val scopes = + ((OAuth2IntrospectionAuthenticatedPrincipal) tokenAuthentication.getPrincipal()) + .getScopes(); + return Set.copyOf(scopes); } } diff --git a/score-server/src/main/resources/application.yml b/score-server/src/main/resources/application.yml index f9a86c25..47587ca1 100644 --- a/score-server/src/main/resources/application.yml +++ b/score-server/src/main/resources/application.yml @@ -94,6 +94,18 @@ management: cors: allowedOrigins: http://localhost:8081 +# SpringBoot >=2.6 default strategy for matching the request path has been changed +# from antpathmatcher to pathpatternparser +spring: + mvc: + pathmatch: + matching-strategy: ant_path_matcher + + +logging: + level: + root: INFO + org.springframework.web: INFO --- ############################################################################### @@ -103,7 +115,10 @@ management: # be specified in the properties file ############################################################################### -spring.profiles: ssl +spring: + config: + activate: + on-profile: ssl # Server server: @@ -119,8 +134,9 @@ server: ############################################################################### spring: - profiles: amazon - profiles.include: prod + config: + activate: + on-profile: amazon s3: endpoint: s3-external-1.amazonaws.com @@ -141,7 +157,9 @@ metadata: ############################################################################### spring: - profiles: collaboratory + config: + activate: + on-profile: collaboratory s3: endpoint: https://object.cancercollaboratory.org:9080 @@ -158,7 +176,9 @@ metadata: ############################################################################### spring: - profiles: azure + config: + activate: + on-profile: azure azure: endpointProtocol: https @@ -179,7 +199,10 @@ download: # Profile - "prod" ############################################################################### -spring.profiles: prod +spring: + config: + activate: + on-profile: prod s3: secured: true @@ -194,70 +217,86 @@ metadata: # Profile - "secure" ############################################################################### -spring.profiles: secure +spring: + config: + activate: + on-profile: secure + security: + oauth2: + resourceserver: + jwt: + # EGO public key + public-key-location: "https://localhost:8443/oauth/token/public_key" + # Keycloak JWK + #jwk-set-uri: http://localhost/realms/myrealm/protocol/openid-connect/certs # OAuth authentication server auth: server: + # check API Key endpoint url: https://localhost:8443/oauth/check_token - tokenName: token - clientId: resource - clientSecret: pass + tokenName: apiToken + clientID: score + clientSecret: scoresecret + # Define a valid auth provider: ego or keycloak + provider: ego + # Keycloak config + keycloak: + host: http://localhost + realm: "myrealm" scope: download: - system: aws.download + system: score.READ study: - prefix: aws. - suffix: .download + prefix: PROGRAMDATA- + suffix: .READ upload: - system: aws.upload + system: score.WRITE study: - prefix: aws. - suffix: .upload ---- + prefix: PROGRAMDATA- + suffix: .WRITE -############################################################################### -# Profile - "jwt" -############################################################################### - -spring: - profiles: jwt - profiles.include: secure - -auth: - jwt: - publicKeyUrl: "https://localhost:8443/oauth/token/public_key" --- ############################################################################### # Profile - "dev" ############################################################################### -spring.profiles: dev +spring: + config: + activate: + on-profile: dev s3: secured: false - endpoint: localhost:9444/s3 - accessKey: AKIAIOSFODNN7EXAMPLE - secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + endpoint: http://localhost:8085 + accessKey: minio + secretKey: minio123 # Server server: + port: 8087 bucket: - name.object: test.icgc - name.state: test.icgc + name.object: test.icgc.test + name.state: test.icgc.test upload: clean.enabled: false +metadata: + url: http://localhost:8080 + --- ############################################################################### # Profile - "benchmark" ############################################################################### -spring.profiles: benchmark +spring: + config: + activate: + on-profile: benchmark # Server server: diff --git a/score-server/src/test/java/bio/overture/score/server/JWTTestConfig.java b/score-server/src/test/java/bio/overture/score/server/JWTTestConfig.java index 44119137..15aad339 100644 --- a/score-server/src/test/java/bio/overture/score/server/JWTTestConfig.java +++ b/score-server/src/test/java/bio/overture/score/server/JWTTestConfig.java @@ -1,18 +1,20 @@ package bio.overture.score.server; -import bio.overture.score.server.security.PublicKeyFetcher; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.util.Base64; +import java.security.interfaces.RSAPublicKey; import lombok.SneakyThrows; import lombok.val; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; @Configuration -@Profile("test & jwt") +@Profile("test") public class JWTTestConfig { private final KeyPair keyPair; @@ -31,24 +33,9 @@ public KeyPair keyPair() { } @Bean - @Primary - public PublicKeyFetcher testPublicKeyFetcher() { - return this::getPublicKey; - } - - public String getPublicKey() { - return convertToPublicKeyWithHeader(getDecodedPublicKey()); - } - - public String getDecodedPublicKey() { - return Base64.getEncoder().encodeToString(keyPair().getPublic().getEncoded()); - } - - private static String convertToPublicKeyWithHeader(String key) { - val result = new StringBuilder(); - result.append("-----BEGIN PUBLIC KEY-----\n"); - result.append(key); - result.append("\n-----END PUBLIC KEY-----"); - return result.toString(); + public JwtDecoder jwtDecoder() { + return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()) + .signatureAlgorithm(SignatureAlgorithm.from(String.valueOf(SignatureAlgorithm.RS256))) + .build(); } } diff --git a/score-server/src/test/java/bio/overture/score/server/security/AccessTokenConverterWithExpiryTest.java b/score-server/src/test/java/bio/overture/score/server/security/AccessTokenConverterWithExpiryTest.java deleted file mode 100644 index 63a94239..00000000 --- a/score-server/src/test/java/bio/overture/score/server/security/AccessTokenConverterWithExpiryTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package bio.overture.score.server.security; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Set; -import java.util.TreeMap; -import lombok.val; -import org.junit.Test; - -public class AccessTokenConverterWithExpiryTest { - @Test - public void test_sets_expiry() { - val expiry = 1000; - val sut = new AccessTokenConverterWithExpiry(); - val inputs = new TreeMap(); - - val scopes = Set.of("test.TEST1-CA.download", "test.TEST1-CA.upload"); - inputs.put(sut.EXP, expiry); - inputs.put(sut.SCOPE, scopes); - - val auth = sut.extractAuthentication(inputs); - assertTrue(auth instanceof ExpiringOauth2Authentication); - - val expiringAuth = (ExpiringOauth2Authentication) auth; - val exp = expiringAuth.getExpiry(); - - assertEquals(expiry, exp); - assertEquals(scopes, expiringAuth.getOAuth2Request().getScope()); - } -} diff --git a/score-server/src/test/java/bio/overture/score/server/security/DownloadScopeAuthorizationStrategyTest.java b/score-server/src/test/java/bio/overture/score/server/security/DownloadScopeAuthorizationStrategyTest.java index 42022376..4dfc4435 100644 --- a/score-server/src/test/java/bio/overture/score/server/security/DownloadScopeAuthorizationStrategyTest.java +++ b/score-server/src/test/java/bio/overture/score/server/security/DownloadScopeAuthorizationStrategyTest.java @@ -17,22 +17,52 @@ */ package bio.overture.score.server.security; +import static bio.overture.score.server.utils.JwtContext.buildJwtContext; +import static java.util.concurrent.TimeUnit.HOURS; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import bio.overture.score.server.config.SecurityConfig; import bio.overture.score.server.exception.NotRetryableException; import bio.overture.score.server.metadata.MetadataEntity; import bio.overture.score.server.metadata.MetadataService; +import bio.overture.score.server.repository.DownloadService; +import bio.overture.score.server.repository.UploadService; import bio.overture.score.server.security.scope.DownloadScopeAuthorizationStrategy; +import bio.overture.score.server.utils.JWTGenerator; +import bio.overture.score.server.utils.JwtContext; +import com.nimbusds.jose.shaded.json.JSONArray; +import com.nimbusds.jose.shaded.json.JSONObject; +import java.security.KeyPair; +import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; +import lombok.SneakyThrows; import lombok.val; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +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.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +@SpringBootTest +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles({"test", "secure", "default", "dev"}) public class DownloadScopeAuthorizationStrategyTest { private static final String STUDY_PREFIX = "PROGRAMDATA-"; @@ -44,6 +74,29 @@ public class DownloadScopeAuthorizationStrategyTest { private static final String OPEN_ACESSS_ID = "123"; // file id of a mock open access file private static final String CONTROLLED_ACCESS_ID = "2345"; + private static final String PROVIDER_EGO = "ego"; + + // -- Dependencies -- + @Autowired private WebApplicationContext webApplicationContext; + @Autowired private SecurityConfig securityConfig; + @Autowired private KeyPair keyPair; + + private JWTGenerator jwtGenerator; + private MockMvc mockMvc; + @MockBean private MetadataService metadataService; + @MockBean private DownloadService downloadService; + @MockBean private UploadService uploadService; + + @Before + @SneakyThrows + public void beforeEachTest() { + jwtGenerator = new JWTGenerator(keyPair); + if (mockMvc == null) { + this.mockMvc = + MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + } + private DownloadScopeAuthorizationStrategy sut = init(); // System Under Test public MetadataService getMetadataService() { @@ -61,7 +114,7 @@ public MetadataService getMetadataService() { public DownloadScopeAuthorizationStrategy init() { return new DownloadScopeAuthorizationStrategy( - STUDY_PREFIX, DOWNLOAD_SUFFIX, SYSTEM_SCOPE, getMetadataService()); + STUDY_PREFIX, DOWNLOAD_SUFFIX, SYSTEM_SCOPE, getMetadataService(), PROVIDER_EGO); } public MetadataEntity entity( @@ -75,13 +128,27 @@ public MetadataEntity entity( return entity; } - private Authentication getAuthentication(boolean isExpired, Set scopes) { - val request = mock(OAuth2Request.class); - when(request.getScope()).thenReturn(scopes); - val authentication = mock(ExpiringOauth2Authentication.class); - when(authentication.getOAuth2Request()).thenReturn(request); - when(authentication.getExpiry()).thenReturn(isExpired ? 0 : 60); - return authentication; + private Authentication getAuthentication(Set scopes) { + JwtContext jwtContext = buildJwtContext(scopes); + String jwtString = jwtGenerator.generateJwtWithContext(jwtContext, false); + + long issuedAtMs = Instant.now().toEpochMilli(); + long expiresAtMs = issuedAtMs + HOURS.toMillis(5); // expires in 5 hours from now + + Jwt jwt = + Jwt.withTokenValue(jwtString) + .header("typ", "JWT") + .issuedAt(Instant.ofEpochMilli(issuedAtMs)) + .expiresAt(Instant.ofEpochMilli(expiresAtMs)) + .claims( + (claims) -> { + JSONArray scopeJsonArray = new JSONArray(); + scopeJsonArray.addAll(scopes); + claims.put("context", new JSONObject(Map.of("scope", scopeJsonArray))); + }) + .build(); + + return new JwtAuthenticationToken(jwt); } public String getStudyScope(String study) { @@ -102,35 +169,12 @@ public Set getScopes(boolean hasSystem, boolean hasStudy, boolean hasOth return scopes; } - public boolean run_test( - boolean isExpired, boolean isOpen, boolean hasSystem, boolean hasStudy, boolean hasOther) { + public boolean run_test(boolean isOpen, boolean hasSystem, boolean hasStudy, boolean hasOther) { val scopes = getScopes(hasSystem, hasStudy, hasOther); - val auth = getAuthentication(isExpired, scopes); + val auth = getAuthentication(scopes); return sut.authorize(auth, isOpen ? OPEN_ACESSS_ID : CONTROLLED_ACCESS_ID); } - @Test - public void test_expired_always_fails() { - val choices = List.of(false, true); - boolean everythingPassed = true; - for (val isOpen : choices) { - for (val hasSystem : choices) { - for (val hasStudy : choices) { - for (val hasOther : choices) { - val result = run_test(true, isOpen, hasSystem, hasStudy, hasOther); - if (result) { - System.err.printf( - "Access allowed with expired token (access control='%s', scopes='%s')", - isOpen ? "open" : "controlled", getScopes(hasSystem, hasStudy, hasOther)); - everythingPassed = false; - } - } - } - } - } - assertTrue(everythingPassed); - } - @Test public void test_non_expired_open_access_always_succeeds() { val choices = List.of(false, true); @@ -138,7 +182,7 @@ public void test_non_expired_open_access_always_succeeds() { for (val hasSystem : choices) { for (val hasStudy : choices) { for (val hasOther : choices) { - val result = run_test(false, true, hasSystem, hasStudy, hasOther); + val result = run_test(true, hasSystem, hasStudy, hasOther); if (!result) { System.err.printf( "Open access wasn't granted to non-expired token (scopes='%s')", @@ -153,12 +197,12 @@ public void test_non_expired_open_access_always_succeeds() { @Test public void test_controlled_access_no_scopes_fails() { - assertFalse(run_test(false, false, false, false, false)); + assertFalse(run_test(false, false, false, false)); } @Test public void test_controlled_access_wrong_scope_fails() { - assertFalse(run_test(false, false, false, false, true)); + assertFalse(run_test(false, false, false, true)); } @Test @@ -166,7 +210,7 @@ public void test_controlled_access_non_expired_study_scope_succeeds() { val choices = List.of(false, true); boolean everythingPassed = true; for (val hasOther : choices) { - val result = run_test(false, false, false, true, hasOther); + val result = run_test(false, false, true, hasOther); if (!result) { System.err.printf( "Access wasn't granted to non-expired token (scopes='%s')", @@ -183,7 +227,7 @@ public void test_controlled_access_non_expired_system_scope_succeeds() { boolean everythingPassed = true; for (val hasStudy : choices) { for (val hasOther : choices) { - val result = run_test(false, false, true, hasStudy, hasOther); + val result = run_test(false, true, hasStudy, hasOther); if (!result) { System.err.printf( "Controlled access wasn't granted to non-expired token (scopes='%s')", @@ -198,21 +242,21 @@ public void test_controlled_access_non_expired_system_scope_succeeds() { @Test public void test_controlled_study_scope_wrong_access_fails() { val scopes = Set.of(STUDY_PREFIX + TEST_STUDY + ".wrong"); - val auth = getAuthentication(false, scopes); + val auth = getAuthentication(scopes); assertFalse(sut.authorize(auth, CONTROLLED_ACCESS_ID)); } @Test public void test_controlled_system_scope_wrong_access_fails() { val scopes = Set.of("DCCAdmin.wrong"); - val auth = getAuthentication(false, scopes); + val auth = getAuthentication(scopes); assertFalse(sut.authorize(auth, CONTROLLED_ACCESS_ID)); } @Test public void test_project_study_object_does_not_exist_fails() { val scopes = Set.of(STUDY_PREFIX + TEST_STUDY + DOWNLOAD_SUFFIX); - val auth = getAuthentication(false, scopes); + val auth = getAuthentication(scopes); Exception exception = null; try { assertFalse(sut.authorize(auth, "non-existent")); @@ -228,7 +272,7 @@ public void test_project_study_object_does_not_exist_fails() { @Test public void test_system_scope_object_not_looked_up() { val scopes = Set.of(SYSTEM_SCOPE); - val auth = getAuthentication(false, scopes); + val auth = getAuthentication(scopes); Exception exception = null; boolean status = false; try { diff --git a/score-server/src/test/java/bio/overture/score/server/security/JWTSecurityTest.java b/score-server/src/test/java/bio/overture/score/server/security/JWTSecurityTest.java index b6499cfc..1b5f4f8b 100644 --- a/score-server/src/test/java/bio/overture/score/server/security/JWTSecurityTest.java +++ b/score-server/src/test/java/bio/overture/score/server/security/JWTSecurityTest.java @@ -49,7 +49,7 @@ @SpringBootTest @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) -@ActiveProfiles({"test", "secure", "jwt", "default", "dev"}) +@ActiveProfiles({"test", "secure", "default", "dev"}) public class JWTSecurityTest { // -- constants -- diff --git a/score-server/src/test/java/bio/overture/score/server/security/MergedServerTokenServicesTest.java b/score-server/src/test/java/bio/overture/score/server/security/MergedServerTokenServicesTest.java deleted file mode 100644 index 55730b20..00000000 --- a/score-server/src/test/java/bio/overture/score/server/security/MergedServerTokenServicesTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package bio.overture.score.server.security; - -import static bio.overture.score.server.utils.JwtContext.buildJwtContext; -import static org.mockito.Mockito.*; - -import bio.overture.score.server.utils.JWTGenerator; -import java.security.KeyPairGenerator; -import java.util.List; -import java.util.UUID; -import lombok.SneakyThrows; -import lombok.val; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.RemoteTokenServices; - -@RunWith(MockitoJUnitRunner.class) -public class MergedServerTokenServicesTest { - private static final String API_KEY = UUID.randomUUID().toString(); - - private MergedServerTokenServices mergedServerTokenServices; - private RetryTemplate retryTemplate = new RetryTemplate(); - @Mock private RemoteTokenServices remoteTokenServices; - @Mock private DefaultTokenServices jwtTokenServices; - - private JWTGenerator jwtGenerator; - - @Before - @SneakyThrows - public void beforeTest() { - val keyGenerator = KeyPairGenerator.getInstance("RSA"); - keyGenerator.initialize(1024); - jwtGenerator = new JWTGenerator(keyGenerator.generateKeyPair()); - mergedServerTokenServices = - new MergedServerTokenServices(jwtTokenServices, remoteTokenServices, retryTemplate); - } - - @Test - public void accessTokenResolution_apiKey_success() { - when(remoteTokenServices.loadAuthentication(API_KEY)).thenReturn(null); - when(remoteTokenServices.readAccessToken(API_KEY)).thenReturn(null); - mergedServerTokenServices.loadAuthentication(API_KEY); - mergedServerTokenServices.readAccessToken(API_KEY); - verify(remoteTokenServices, times(1)).loadAuthentication(API_KEY); - verify(remoteTokenServices, times(1)).readAccessToken(API_KEY); - } - - @Test - public void accessTokenResolution_jwt_success() { - val jwtString = - jwtGenerator.generateJwtWithContext(buildJwtContext(List.of("score.WRITE")), false); - when(jwtTokenServices.loadAuthentication(jwtString)).thenReturn(null); - when(jwtTokenServices.readAccessToken(jwtString)).thenReturn(null); - mergedServerTokenServices.loadAuthentication(jwtString); - mergedServerTokenServices.readAccessToken(jwtString); - verify(jwtTokenServices, times(1)).loadAuthentication(jwtString); - verify(jwtTokenServices, times(1)).readAccessToken(jwtString); - } -} diff --git a/score-server/src/test/java/bio/overture/score/server/security/UploadScopeAuthorizationStrategyTest.java b/score-server/src/test/java/bio/overture/score/server/security/UploadScopeAuthorizationStrategyTest.java index 02073ed4..bbb97a8c 100644 --- a/score-server/src/test/java/bio/overture/score/server/security/UploadScopeAuthorizationStrategyTest.java +++ b/score-server/src/test/java/bio/overture/score/server/security/UploadScopeAuthorizationStrategyTest.java @@ -17,20 +17,51 @@ */ package bio.overture.score.server.security; +import static bio.overture.score.server.utils.JwtContext.buildJwtContext; +import static java.util.concurrent.TimeUnit.HOURS; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import bio.overture.score.server.config.SecurityConfig; import bio.overture.score.server.exception.NotRetryableException; import bio.overture.score.server.metadata.MetadataEntity; import bio.overture.score.server.metadata.MetadataService; +import bio.overture.score.server.repository.DownloadService; +import bio.overture.score.server.repository.UploadService; import bio.overture.score.server.security.scope.UploadScopeAuthorizationStrategy; +import bio.overture.score.server.utils.JWTGenerator; +import bio.overture.score.server.utils.JwtContext; +import com.nimbusds.jose.shaded.json.JSONArray; +import com.nimbusds.jose.shaded.json.JSONObject; +import java.security.KeyPair; +import java.time.Instant; +import java.util.Map; import java.util.Set; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +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.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; - +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@Slf4j +@SpringBootTest +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles({"test", "secure", "default", "dev"}) public class UploadScopeAuthorizationStrategyTest { private static final String TEST_SCOPE = "PROGRAMDATA-TEST1-CA.WRITE"; private static final String STUDY_PREFIX = "PROGRAMDATA-"; @@ -40,6 +71,30 @@ public class UploadScopeAuthorizationStrategyTest { private static final String PROJECT1 = "TEST1-CA"; private static final String PROJECT2 = "TEST2-DK"; + private static final String PROVIDER_EGO = "ego"; + + // -- Dependencies -- + @Autowired private WebApplicationContext webApplicationContext; + @Autowired private SecurityConfig securityConfig; + @Autowired private KeyPair keyPair; + + private JWTGenerator jwtGenerator; + + private MockMvc mockMvc; + @MockBean private MetadataService metadataService; + @MockBean private DownloadService downloadService; + @MockBean private UploadService uploadService; + + @Before + @SneakyThrows + public void beforeEachTest() { + jwtGenerator = new JWTGenerator(keyPair); + if (mockMvc == null) { + this.mockMvc = + MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + } + private UploadScopeAuthorizationStrategy sut = init(); public static UploadScopeAuthorizationStrategy init() { @@ -50,36 +105,47 @@ public static UploadScopeAuthorizationStrategy init() { when(meta.getEntity("1")).thenReturn(e1); when(meta.getEntity("2")).thenReturn(e2); - return new UploadScopeAuthorizationStrategy(STUDY_PREFIX, UPLOAD_SUFFIX, SYSTEM_SCOPE, meta); + return new UploadScopeAuthorizationStrategy( + STUDY_PREFIX, UPLOAD_SUFFIX, SYSTEM_SCOPE, meta, PROVIDER_EGO); } - private Authentication getAuthentication(boolean isExpired, Set scopes) { - val request = mock(OAuth2Request.class); - when(request.getScope()).thenReturn(scopes); - val authentication = mock(ExpiringOauth2Authentication.class); - when(authentication.getOAuth2Request()).thenReturn(request); - when(authentication.getExpiry()).thenReturn(isExpired ? 0 : 60); - return authentication; - } - - @Test - public void test_system_scope_expired() { - val scopes = Set.of("test.GBM-US.upload", SYSTEM_SCOPE); - val authentication = getAuthentication(true, scopes); - assertFalse(sut.authorize(authentication, "1")); + @SneakyThrows + private Authentication getAuthentication(Set scopes) { + ; + + JwtContext jwtContext = buildJwtContext(scopes); + String jwtString = jwtGenerator.generateJwtWithContext(jwtContext, false); + + long issuedAtMs = Instant.now().toEpochMilli(); + long expiresAtMs = issuedAtMs + HOURS.toMillis(5); // expires in 5 hours from now + + Jwt jwt = + Jwt.withTokenValue(jwtString) + .header("typ", "JWT") + .issuedAt(Instant.ofEpochMilli(issuedAtMs)) + .expiresAt(Instant.ofEpochMilli(expiresAtMs)) + .claims( + (claims) -> { + JSONArray scopeJsonArray = new JSONArray(); + scopeJsonArray.addAll(scopes); + claims.put("context", new JSONObject(Map.of("scope", scopeJsonArray))); + }) + .build(); + + return new JwtAuthenticationToken(jwt); } @Test public void test_system_scope_ok() { val scopes = Set.of("test.GBM-US.upload", SYSTEM_SCOPE); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); assertTrue(sut.authorize(authentication, "1")); } @Test public void test_study_scope_wrong_project() { val scopes = Set.of(STUDY_PREFIX + PROJECT2 + UPLOAD_SUFFIX, "test.PRAD-US.upload"); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); assertFalse(sut.authorize(authentication, "1")); } @@ -87,42 +153,28 @@ public void test_study_scope_wrong_project() { @Test public void test_study_scope_wrong_access() { val scopes = Set.of(STUDY_PREFIX + PROJECT1 + ".READ", STUDY_PREFIX + PROJECT1 + ".upload"); - val authentication = getAuthentication(false, scopes); - assertFalse(sut.authorize(authentication, "1")); - } - - @Test - public void test_study_scope_expired() { - val scopes = Set.of(TEST_SCOPE, STUDY_PREFIX + PROJECT1 + ".upload"); - val authentication = getAuthentication(true, scopes); + val authentication = getAuthentication(scopes); assertFalse(sut.authorize(authentication, "1")); } @Test public void test_study_scope_ok() { val scopes = Set.of(TEST_SCOPE, STUDY_PREFIX + PROJECT1 + ".upload"); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); assertTrue(sut.authorize(authentication, "1")); } @Test public void test_study_and_system_scope_ok() { val scopes = Set.of(TEST_SCOPE, SYSTEM_SCOPE, STUDY_PREFIX + PROJECT1 + ".upload"); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); assertTrue(sut.authorize(authentication, "1")); } - @Test - public void test_study_and_system_scope_expired() { - val scopes = Set.of(TEST_SCOPE, SYSTEM_SCOPE, STUDY_PREFIX + PROJECT1 + ".upload"); - val authentication = getAuthentication(true, scopes); - assertFalse(sut.authorize(authentication, "1")); - } - @Test public void test_study_scope_unknown_file_fails() { val scopes = Set.of("DACO.WRITE", "CLOUD.READ"); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); Exception exception = null; try { sut.authorize(authentication, "NOT-FOUND"); @@ -138,7 +190,7 @@ public void test_study_scope_unknown_file_fails() { @Test public void test_project_not_looked_up_for_system_scope() { val scopes = Set.of(SYSTEM_SCOPE, "DACO.WRITE", "CLOUD.READ"); - val authentication = getAuthentication(false, scopes); + val authentication = getAuthentication(scopes); Exception exception = null; boolean status = false; try { diff --git a/score-server/src/test/java/bio/overture/score/server/utils/JWTGenerator.java b/score-server/src/test/java/bio/overture/score/server/utils/JWTGenerator.java index 3782b3e1..1c727052 100644 --- a/score-server/src/test/java/bio/overture/score/server/utils/JWTGenerator.java +++ b/score-server/src/test/java/bio/overture/score/server/utils/JWTGenerator.java @@ -21,7 +21,7 @@ @Slf4j @Component -@Profile({"test", "jwt"}) +@Profile({"test"}) public class JWTGenerator { public static final String DEFAULT_ISSUER = "ego"; @@ -61,8 +61,10 @@ private String generate(long ttlMs, JwtContext jwtContext) { long expiry; // if ttlMs <= 0 make it expired if (ttlMs <= 0) { - expiry = nowMs - 10000; - nowMs -= 100000L; + // expired 1 hour ago + expiry = nowMs - HOURS.toMillis(1); + // created 2 hours ago + nowMs -= HOURS.toMillis(2); } else { expiry = nowMs + ttlMs; } diff --git a/score-test/pom.xml b/score-test/pom.xml index 53dabb43..178181b6 100644 --- a/score-test/pom.xml +++ b/score-test/pom.xml @@ -21,7 +21,7 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF S bio.overture score - 5.10.1 + 5.11.0 ../pom.xml