diff --git a/CHANGELOG.md b/CHANGELOG.md index d530d270..b2ab1063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + - Added support for route depndencies configuration through the STAC_FASTAPI_ROUTE_DEPENDENCIES environment variable, directly or via json file. Allows for fastapi's inbuilt OAuth2 flows to be used as dependencies. Custom dependencies can also be written, see Basic Auth for an example. [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) + - Added docker-compose.route_dependencies_file.yml that gives an example of OAuth2 workflow using keycloak as the identity provider. [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) + - Added docker-compose.route_dependencies_env.yml that gives an example using the STAC_FASTAPI_ROUTE_DEPENDENCIES environment variable. [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) + +### Changed +- Converted Basic auth to a route dependency and merged with new route depndencies method. [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) +- Updated docker-compose.basic_auth_protected.yml to use STAC_FASTAPI_ROUTE_DEPENDENCIES environment variable. [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) + ## [v3.0.0a2] ### Added @@ -234,4 +243,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [v1.0.0]: [v0.3.0]: [v0.2.0]: -[v0.1.0]: \ No newline at end of file +[v0.1.0]: diff --git a/README.md b/README.md index 77a60974..f50a4047 100644 --- a/README.md +++ b/README.md @@ -279,75 +279,106 @@ The modified Items with lowercase identifiers will now be visible to users acces #### Environment Variable Configuration -Basic authentication is an optional feature. You can enable it by setting the environment variable `BASIC_AUTH` as a JSON string. +Basic authentication is an optional feature that can be enabled through [Route Dependencies](#route-dependencies). -Example: -``` -BASIC_AUTH={"users":[{"username":"user","password":"pass","permissions":"*"}]} -``` -### User Permissions Configuration +### Example Configuration -In order to set endpoints with specific access permissions, you can configure the `users` key with a list of user objects. Each user object should contain the username, password, and their respective permissions. - -Example: This example illustrates the configuration for two users: an **admin** user with full permissions (*) and a **reader** user with limited permissions to specific read-only endpoints. +This example illustrates the configuration for two users: an **admin** user with full permissions (*) and a **reader** user with limited permissions to specific read-only endpoints. ```json -{ - "users": [ - { - "username": "admin", - "password": "admin", - "permissions": "*" - }, - { - "username": "reader", - "password": "reader", - "permissions": [ - {"path": "/", "method": ["GET"]}, - {"path": "/conformance", "method": ["GET"]}, - {"path": "/collections/{collection_id}/items/{item_id}", "method": ["GET"]}, - {"path": "/search", "method": ["GET", "POST"]}, - {"path": "/collections", "method": ["GET"]}, - {"path": "/collections/{collection_id}", "method": ["GET"]}, - {"path": "/collections/{collection_id}/items", "method": ["GET"]}, - {"path": "/queryables", "method": ["GET"]}, - {"path": "/queryables/collections/{collection_id}/queryables", "method": ["GET"]}, - {"path": "/_mgmt/ping", "method": ["GET"]} - ] +[ + { + "routes": [ + { + "method": "*", + "path": "*" + } + ], + "dependencies": [ + { + "method": "stac_fastapi.core.basic_auth.BasicAuth", + "kwargs": { + "credentials":[ + { + "username": "admin", + "password": "admin" + } + ] + } + } + ] + }, + { + "routes": [ + {"path": "/", "method": ["GET"]}, + {"path": "/conformance", "method": ["GET"]}, + {"path": "/collections/{collection_id}/items/{item_id}", "method": ["GET"]}, + {"path": "/search", "method": ["GET", "POST"]}, + {"path": "/collections", "method": ["GET"]}, + {"path": "/collections/{collection_id}", "method": ["GET"]}, + {"path": "/collections/{collection_id}/items", "method": ["GET"]}, + {"path": "/queryables", "method": ["GET"]}, + {"path": "/queryables/collections/{collection_id}/queryables", "method": ["GET"]}, + {"path": "/_mgmt/ping", "method": ["GET"]} + ], + "dependencies": [ + { + "method": "stac_fastapi.core.basic_auth.BasicAuth", + "kwargs": { + "credentials":[ + { + "username": "reader", + "password": "reader" + } + ] } + } ] -} + } +] ``` +## Route Dependencies -### Public Endpoints Configuration +### Configuration -In order to set endpoints with public access, you can configure the public_endpoints key with a list of endpoint objects. Each endpoint object should specify the path and method of the endpoint. +Route dependencies for endpoints can enable through the `STAC_FASTAPI_ROUTE_DEPENDENCIES` +environment variable as a path to a JSON file or a JSON string. -Example: This example demonstrates the configuration for public endpoints, allowing access without authentication to read-only endpoints. -```json -{ - "public_endpoints": [ - {"path": "/", "method": "GET"}, - {"path": "/conformance", "method": "GET"}, - {"path": "/collections/{collection_id}/items/{item_id}", "method": "GET"}, - {"path": "/search", "method": "GET"}, - {"path": "/search", "method": "POST"}, - {"path": "/collections", "method": "GET"}, - {"path": "/collections/{collection_id}", "method": "GET"}, - {"path": "/collections/{collection_id}/items", "method": "GET"}, - {"path": "/queryables", "method": "GET"}, - {"path": "/queryables/collections/{collection_id}/queryables", "method": "GET"}, - {"path": "/_mgmt/ping", "method": "GET"} +#### Route Dependency + +A Route Dependency must include `routes`, a list of at least one [Route](#routes), and `dependencies` a +list of at least one [Dependency](#dependencies). + +#### Routes + +A Route must include a `path`, the relative path to the endpoint, and a `method`, the request method of the path. + +#### Dependencies + +A Dependency must include the `method`, a dot seperated path to the Class or Function, and +can include any `args` or `kwargs` for the method. + +#### Example +``` +STAC_FASTAPI_ROUTE_DEPENDENCIES=[ + { + "routes": [ + { + "method": "GET", + "path": "/collections" + } ], - "users": [ - { - "username": "admin", - "password": "admin", - "permissions": "*" + "dependencies": [ + { + "method": "fastapi.security.OAuth2PasswordBearer", + "kwargs": { + "tokenUrl": "token" } + } ] -} + } +] ``` ### Docker Compose Configurations diff --git a/docker-compose.basic_auth_protected.yml b/docker-compose.basic_auth_protected.yml index c260f3af..012a9953 100644 --- a/docker-compose.basic_auth_protected.yml +++ b/docker-compose.basic_auth_protected.yml @@ -22,7 +22,7 @@ services: - ES_USE_SSL=false - ES_VERIFY_CERTS=false - BACKEND=elasticsearch - - BASIC_AUTH={"users":[{"username":"admin","password":"admin","permissions":"*"},{"username":"reader","password":"reader","permissions":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET"]},{"path":"/collections/{collection_id}","method":["GET"]},{"path":"/collections/{collection_id}/items","method":["GET"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}]}]} + - STAC_FASTAPI_ROUTE_DEPENDENCIES=[{"routes":[{"method":"*","path":"*"}],"dependencies":[{"method":"stac_fastapi.core.basic_auth.BasicAuth","kwargs":{"credentials":[{"username":"admin","password":"admin"}]}}]},{"routes":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET"]},{"path":"/collections/{collection_id}","method":["GET"]},{"path":"/collections/{collection_id}/items","method":["GET"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}],"dependencies":[{"method":"stac_fastapi.core.basic_auth.BasicAuth","kwargs":{"credentials":[{"username":"reader","password":"reader"}]}}]}] ports: - "8080:8080" volumes: @@ -55,7 +55,7 @@ services: - ES_USE_SSL=false - ES_VERIFY_CERTS=false - BACKEND=opensearch - - BASIC_AUTH={"users":[{"username":"admin","password":"admin","permissions":"*"},{"username":"reader","password":"reader","permissions":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET"]},{"path":"/collections/{collection_id}","method":["GET"]},{"path":"/collections/{collection_id}/items","method":["GET"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}]}]} + - STAC_FASTAPI_ROUTE_DEPENDENCIES=[{"routes":[{"method":"*","path":"*"}],"dependencies":[{"method":"stac_fastapi.core.basic_auth.BasicAuth","kwargs":{"credentials":[{"username":"admin","password":"admin"}]}}]},{"routes":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET"]},{"path":"/collections/{collection_id}","method":["GET"]},{"path":"/collections/{collection_id}/items","method":["GET"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}],"dependencies":[{"method":"stac_fastapi.core.basic_auth.BasicAuth","kwargs":{"credentials":[{"username":"reader","password":"reader"}]}}]}] ports: - "8082:8082" volumes: diff --git a/docker-compose.basic_auth_public.yml b/docker-compose.route_dependencies_env.yml similarity index 54% rename from docker-compose.basic_auth_public.yml rename to docker-compose.route_dependencies_env.yml index 0a5454cf..9adf19b1 100644 --- a/docker-compose.basic_auth_public.yml +++ b/docker-compose.route_dependencies_env.yml @@ -22,7 +22,7 @@ services: - ES_USE_SSL=false - ES_VERIFY_CERTS=false - BACKEND=elasticsearch - - BASIC_AUTH={"public_endpoints":[{"path":"/","method":"GET"},{"path":"/conformance","method":"GET"},{"path":"/collections/{collection_id}/items/{item_id}","method":"GET"},{"path":"/search","method":"GET"},{"path":"/search","method":"POST"},{"path":"/collections","method":"GET"},{"path":"/collections/{collection_id}","method":"GET"},{"path":"/collections/{collection_id}/items","method":"GET"},{"path":"/queryables","method":"GET"},{"path":"/queryables/collections/{collection_id}/queryables","method":"GET"},{"path":"/_mgmt/ping","method":"GET"}],"users":[{"username":"admin","password":"admin","permissions":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET","POST","PUT","DELETE"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET","PUT","POST"]},{"path":"/collections/{collection_id}","method":["GET","DELETE"]},{"path":"/collections/{collection_id}/items","method":["GET","POST"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}]}]} + - STAC_FASTAPI_ROUTE_DEPENDENCIES=[{"routes":[{"method":"GET","path":"/collections"}],"dependencies":[{"method":"conftest.must_be_bob"}]}] ports: - "8080:8080" volumes: @@ -55,7 +55,7 @@ services: - ES_USE_SSL=false - ES_VERIFY_CERTS=false - BACKEND=opensearch - - BASIC_AUTH={"public_endpoints":[{"path":"/","method":"GET"},{"path":"/conformance","method":"GET"},{"path":"/collections/{collection_id}/items/{item_id}","method":"GET"},{"path":"/search","method":"GET"},{"path":"/search","method":"POST"},{"path":"/collections","method":"GET"},{"path":"/collections/{collection_id}","method":"GET"},{"path":"/collections/{collection_id}/items","method":"GET"},{"path":"/queryables","method":"GET"},{"path":"/queryables/collections/{collection_id}/queryables","method":"GET"},{"path":"/_mgmt/ping","method":"GET"}],"users":[{"username":"admin","password":"admin","permissions":[{"path":"/","method":["GET"]},{"path":"/conformance","method":["GET"]},{"path":"/collections/{collection_id}/items/{item_id}","method":["GET","POST","PUT","DELETE"]},{"path":"/search","method":["GET","POST"]},{"path":"/collections","method":["GET","PUT","POST"]},{"path":"/collections/{collection_id}","method":["GET","DELETE"]},{"path":"/collections/{collection_id}/items","method":["GET","POST"]},{"path":"/queryables","method":["GET"]},{"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]},{"path":"/_mgmt/ping","method":["GET"]}]}]} + - STAC_FASTAPI_ROUTE_DEPENDENCIES=[{"routes":[{"method":"GET","path":"/collections"}],"dependencies":[{"method":"conftest.must_be_bob"}]}] ports: - "8082:8082" volumes: diff --git a/docker-compose.route_dependencies_file.yml b/docker-compose.route_dependencies_file.yml new file mode 100644 index 00000000..3ac11b16 --- /dev/null +++ b/docker-compose.route_dependencies_file.yml @@ -0,0 +1,129 @@ +version: '3.9' + +services: + app-elasticsearch: + container_name: stac-fastapi-es + image: stac-utils/stac-fastapi-es + restart: always + build: + context: . + dockerfile: dockerfiles/Dockerfile.dev.es + environment: + - STAC_FASTAPI_TITLE=stac-fastapi-elasticsearch + - STAC_FASTAPI_DESCRIPTION=A STAC FastAPI with an Elasticsearch backend + - STAC_FASTAPI_VERSION=3.0.0a1 + - APP_HOST=0.0.0.0 + - APP_PORT=8080 + - RELOAD=true + - ENVIRONMENT=local + - WEB_CONCURRENCY=10 + - ES_HOST=elasticsearch + - ES_PORT=9200 + - ES_USE_SSL=false + - ES_VERIFY_CERTS=false + - BACKEND=elasticsearch + - STAC_FASTAPI_ROUTE_DEPENDENCIES=/app/route_dependencies/route_dependencies.json + ports: + - "8080:8080" + volumes: + - ./stac_fastapi:/app/stac_fastapi + - ./examples/route_dependencies:/app/route_dependencies + - ./scripts:/app/scripts + - ./esdata:/usr/share/elasticsearch/data + depends_on: + - elasticsearch + command: + bash -c "./scripts/wait-for-it-es.sh es-container:9200 && python -m stac_fastapi.elasticsearch.app" + + app-opensearch: + container_name: stac-fastapi-os + image: stac-utils/stac-fastapi-os + restart: always + build: + context: . + dockerfile: dockerfiles/Dockerfile.dev.os + environment: + - STAC_FASTAPI_TITLE=stac-fastapi-opensearch + - STAC_FASTAPI_DESCRIPTION=A STAC FastAPI with an Opensearch backend + - STAC_FASTAPI_VERSION=2.1 + - APP_HOST=0.0.0.0 + - APP_PORT=8082 + - RELOAD=true + - ENVIRONMENT=local + - WEB_CONCURRENCY=10 + - ES_HOST=opensearch + - ES_PORT=9202 + - ES_USE_SSL=false + - ES_VERIFY_CERTS=false + - BACKEND=opensearch + - STAC_FASTAPI_ROUTE_DEPENDENCIES=/app/route_dependencies/route_dependencies.json + ports: + - "8082:8082" + volumes: + - ./stac_fastapi:/app/stac_fastapi + - ./scripts:/app/scripts + - ./osdata:/usr/share/opensearch/data + depends_on: + - opensearch + command: + bash -c "./scripts/wait-for-it-es.sh os-container:9202 && python -m stac_fastapi.opensearch.app" + + elasticsearch: + container_name: es-container + image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION:-8.11.0} + hostname: elasticsearch + environment: + ES_JAVA_OPTS: -Xms512m -Xmx1g + volumes: + - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml + - ./elasticsearch/snapshots:/usr/share/elasticsearch/snapshots + ports: + - "9200:9200" + + opensearch: + container_name: os-container + image: opensearchproject/opensearch:${OPENSEARCH_VERSION:-2.11.1} + hostname: opensearch + environment: + - discovery.type=single-node + - plugins.security.disabled=true + - OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m + volumes: + - ./opensearch/config/opensearch.yml:/usr/share/opensearch/config/opensearch.yml + - ./opensearch/snapshots:/usr/share/opensearch/snapshots + ports: + - "9202:9202" + + postgres: + image: postgres:15 + container_name: postgres + hostname: keycloakdb + environment: + - POSTGRES_DB=keycloak + - POSTGRES_USER=keycloak + - POSTGRES_PASSWORD=password + volumes: + - postgres:/var/lib/postgresql/data + + keycloak: + image: quay.io/keycloak/keycloak:25.0.0 + container_name: keycloak + ports: + - 8083:8083 + environment: + - KEYCLOAK_IMPORT=/tmp/keycloak-realm.json + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=admin + - KC_HTTP_PORT=8083 + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://keycloakdb:5432/keycloak + - KC_DB_USERNAME=keycloak + - KC_DB_PASSWORD=password + volumes: + - ./example/route_dependencies/stac-realm.json:/opt/keycloak/data/import + command: start-dev --import-realm + depends_on: + - postgres + +volumes: + postgres: \ No newline at end of file diff --git a/examples/route_dependencies/route_dependencies.json b/examples/route_dependencies/route_dependencies.json new file mode 100644 index 00000000..21fd7248 --- /dev/null +++ b/examples/route_dependencies/route_dependencies.json @@ -0,0 +1,50 @@ +[ + { + "routes": [ + { + "method": "*", + "path": "*" + } + ], + "dependencies": [ + { + "method": "fastapi.security.OAuth2PasswordBearer", + "kwargs": { + "tokenUrl": "http://keycloak:8083/auth/realms/stac/protocol/openid-connect/token" + } + } + ] + }, + { + "routes": [ + { + "path": "/collections/{collection_id}/items/{item_id}", + "method": "GET" + }, + { + "path": "/search", + "method": [ + "GET", + "POST" + ] + }, + { + "path": "/collections", + "method": "GET" + } + ], + "dependencies": [ + { + "method": "stac_fastapi.core.basic_auth.BasicAuth", + "kwargs": { + "credentials": [ + { + "username": "reader", + "password": "reader" + } + ] + } + } + ] + } +] \ No newline at end of file diff --git a/examples/route_dependencies/stac-realm.json b/examples/route_dependencies/stac-realm.json new file mode 100644 index 00000000..6e8cc270 --- /dev/null +++ b/examples/route_dependencies/stac-realm.json @@ -0,0 +1,1847 @@ +{ + "id" : "1caaafcd-8ffe-49aa-be85-ee4570026527", + "realm" : "stac", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "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" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "9be68c8d-805a-4e3a-bed6-c1a490c3ee74", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "1caaafcd-8ffe-49aa-be85-ee4570026527", + "attributes" : { } + }, { + "id" : "012b3963-7395-4d0a-b13a-47a6aec319c9", + "name" : "User", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "1caaafcd-8ffe-49aa-be85-ee4570026527", + "attributes" : { } + }, { + "id" : "7a0cf8db-382a-46c7-8498-eb4a5047be57", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "1caaafcd-8ffe-49aa-be85-ee4570026527", + "attributes" : { } + }, { + "id" : "2aa25cf8-e988-4476-974a-4451085754eb", + "name" : "default-roles-stac", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "1caaafcd-8ffe-49aa-be85-ee4570026527", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "222ce8da-d28e-4dee-8485-d9b1d8bf1f56", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "9eff5e6d-834e-45a8-84be-f3f6c104fec5", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "c03151f9-9975-4af5-92eb-ba71f9e7804e", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-identity-providers", "create-client", "view-realm", "query-users", "manage-realm", "manage-users", "manage-events", "view-users", "impersonation", "view-authorization", "view-events", "manage-clients", "manage-identity-providers", "query-groups", "query-clients", "query-realms", "view-clients", "manage-authorization" ] + } + }, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "372a5d31-08d1-4670-9fcc-4a39170ddbd0", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "c4b319ac-2210-484d-b150-8ae85bfb7d6c", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "4185ce58-d5a9-4384-9d92-c3e87b71ed7d", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "ca6dc51f-4a4e-44e7-8370-786d8e45be84", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "f7e3e22c-ee2b-423c-915c-5e2306cb954f", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "df19b834-f571-4c01-a98e-ad84f03ec2e4", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "a1af7fbf-acf1-4232-87ed-48d9d2b9e581", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "59e6f3e1-af94-4cb7-a3aa-165a375ff33e", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "7912fce0-6bb9-41fb-8bbe-3b50511924eb", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "e3a7cf77-1075-4ea3-b0ae-156858e42755", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "9dbe0303-8fac-4cc8-8524-3363bd9cfe55", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "1cd301e5-3f3c-46e1-9062-ab3d18d33f16", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "8221d745-ab0b-4bb8-9a92-784323e1c29b", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "ada74ffc-469d-4c49-aa04-5f68080269bc", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "9517ac49-5857-40d3-94b3-c92d9d6126ec", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + }, { + "id" : "528bb738-db63-403b-9241-fcd73c1c4090", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "e2863113-adff-4a59-b8b6-394c86165b11", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "a49adbc0-5e57-4cc2-962a-460f2c251b90", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "edb7c8a8-ad0c-4d32-9af3-a85be9116574", + "attributes" : { } + } ], + "account" : [ { + "id" : "4dd291a9-8dd9-4196-8761-44b694d8d0e0", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "2dddf554-4c0a-417c-b89b-e33ddd221643", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "bdcaedd8-653c-4e8c-917d-9509c0d85099", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "7b7bb055-e8d1-4a43-a1e6-4b366f4f9d0b", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "8d909fe5-f89f-4ead-9253-0a0a5ac0f75b", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "04c9d9be-6b8c-49eb-8f8c-ba21292635c4", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "c6876402-c8d7-4c34-b9b0-db3b6a472026", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + }, { + "id" : "ddb45cd5-f718-4984-a82c-42a741b22237", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "2aa25cf8-e988-4476-974a-4451085754eb", + "name" : "default-roles-stac", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "1caaafcd-8ffe-49aa-be85-ee4570026527" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "users" : [ { + "id" : "5e6e4c56-c8e3-4771-9909-cfbd9dd2bca7", + "username" : "bob", + "email" : "bob@stac.com", + "emailVerified" : true, + "createdTimestamp" : 1718698916564, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "3c49a238-7cd6-4fdf-8ef8-e18833df8f44", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1718699071433, + "secretData" : "{\"value\":\"4Fbsz4SyhFBiz6L9tBZdUjgeMs2nVviDg8DmTL45B/w=\",\"salt\":\"6l7lp+tg7vrqqQOYNgFlOQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-stac" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "02dbc4b5-aa43-4695-bcce-7b0cff4e6d34", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/stac/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/stac/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", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "2e387533-7f2c-4ce5-a42c-35f45f96eef8", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/stac/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/stac/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" : "4a7947af-e489-4064-b27b-6923c63635a9", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "df99d038-2436-4253-9a71-ebe125def09d", + "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" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "edb7c8a8-ad0c-4d32-9af3-a85be9116574", + "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" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "e2863113-adff-4a59-b8b6-394c86165b11", + "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" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b67af554-019e-4cfa-ab21-79f6ca3076e9", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/stac/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/stac/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" : "82128b1b-5aef-41c5-b756-8bf8a9b64c2f", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "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", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "f639b3e7-a9e2-4cae-9bdc-3e7207d1925b", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "3ab13e7d-8eeb-4395-8c5b-a22d5696e4d1", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + }, { + "id" : "196dfe37-053f-4bae-91af-f879dbb4106c", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "f8aa552e-05c4-41f9-beb6-4567513fa344", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "a7326396-c468-469e-b411-79de2d3f01e8", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "68df6016-3e4e-4957-b495-6915ac6ab112", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "b5815ae9-a6b7-4e76-8dbe-24fe747e038d", + "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" : "edc26ee1-ff1b-4de6-883a-60e5a742bb0f", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "82817d96-96ac-4bef-be07-a909a041e25a", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "1b1f891e-7dd4-4082-ac7e-f7db7a506c0e", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "f74b0ae9-e230-4670-8a09-1b6c48fadbff", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "7150378e-933a-4a1a-a583-cc0431d4443d", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "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" : "8a6dbea9-3479-483b-aa44-e042862effd9", + "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" : "05c39de1-260e-484e-9190-7cb50b9ea06e", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "19f6cd9a-4e4d-4d32-9842-1d3114e9298a", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "0ca31a48-821d-4810-821e-7e0203e1ed1e", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "6f83bc5e-9c54-4a39-b52e-928c435c32b4", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "77e9a11f-54d5-4b44-9574-ef055e0261ff", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "2c3d348e-a33b-4771-ac86-26b7da72019e", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "7d361bcb-0c36-4274-af93-fca8c80334af", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "45c978dc-8480-431c-ade6-a40dd295ebc3", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "8c34df7f-19db-4307-beea-2a406c0f4497", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "52d29a2e-4c94-46c6-a0fa-a4906239a23f", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "2993e921-2fde-47da-ac12-fc0d359fad55", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "9e48626e-da1b-4e92-83b0-a71126bdb1e0", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "f334d464-b1c5-4749-bf65-548696fb687d", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "c21aed7a-8225-4dd2-8254-f78c8b01c05b", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "16423a74-7540-4e8c-ae5d-776c5cc6dc90", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "86e01f03-49d3-4e58-a303-f63343e7410c", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "ac657675-66fa-41c2-805a-cfd1b0f892ae", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "6de3467c-e5e1-4265-9b90-f3d92f94b301", + "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" : "da5c833a-829b-485c-90d1-718141df2528", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "476b0140-948a-4606-895d-2dbb89c3483f", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "0c106190-6250-4fef-9ccc-cc481cc23a0f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "3bd569c4-fdd7-4680-b50f-4751cf2cc93f", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "160854ae-2af4-4b25-b689-def61287995a", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "ba76d973-6c44-4b7b-8f55-61d2c63600e7", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "63bed6a9-4d31-48af-bf8e-0341716da498", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "0a111c14-c8a1-4c74-81d6-a5075e4d96d8", + "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", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "e6fb7d2c-fc22-41ac-b0f0-8e64393a0e76", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], + "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" : "1bae13c5-6e86-40be-83a7-034c26706a2b", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "ae86326a-c9c5-48ae-9ebe-b2c46254a62e", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "cd22fae4-14bb-4053-9b0e-85e39fd6d6d9", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "754a1ac4-238a-411d-b45e-9c6e4d73ee15", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "25e54a2f-7c8d-420e-875f-6a89ae9624c4", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "e9f924ad-59b8-423f-9ddb-52900fd7fa35", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "651a8108-0adf-43a9-bb18-fabf36707b66", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + }, { + "id" : "aee7551d-dff9-440e-a0f1-c7a33ec66d6e", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "2622c5a1-cff0-4147-a244-cb9aa323647e", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA3zWFQLG3LAo+sHJkoKL5Uq+h6Wq9ZdccznePfoMR8wvnFzElvsDwtP3nhxeT/5RIu9JbZkElWW/q0montlceHlt6fq/8NOjB47OxBNrsya6BHoYn6XJt9u0AIoYP3CoraaL+As6NlAyEh28+OUejz9D1HKDmaIwm04FFHKyS1EW9DaJoI3KovOHD0xgGomgmD5+kDnOcpmaiNKEyoVQiwDQyJ1jsVGxvRaY1slmZmmQXecs0bWpT99Yg3mKwVSACNgtBrYXOzDJQvxkbS+ZeMP9JzJ2uwSgsFLVGmEo0Tj2x98c5uczdX3layfrlqopxQ4PgKONh1shmyVCqZNKLywIDAQABAoIBAFwz/5d50fCfSsYYevjgWZ2Ob5O8RACNm5iuStg3zHJZuFwcMTta+BPLzpg/ZSNuXZ04iAj1YDhRwu6oaz8nPYnMwA0VJVm1zGIDGFypEqc9LpktUc9dNY+K10NrnaGUydoZOlRufo1pnlVT3qBKt6Bg8N+il4sYWfGEtMfjgcOtRXVQN44GoqxK0s57nLJrVLmI9T1+Ch5+qliY/UqgSJTsiPmNkHFjkKyNH9kF9VKRQo5cOQRmKHl4GmODdgOr++lQUBqL+fkoZWq1JqABNROSebzk30KFykh08sQSj+bCTMmtjfgmuXZG572SYh+bNNxSio6Y6iQ9GEenhkmVwgECgYEA+pW3RIMVJW1rVqYsyF9Be4rKHEQa8Zq8dJCDwgvWb7eY1Ah5cbicMS/RLhYcmNQGfkDijGZC1xNdD7S3TqBSIB5SN+ufUjJPwfUZoKbmHPtuc6mMRUlgu9j2uXTMgjimIixLMbdFur5GudIyUm6KajqEieNJ04kNfJAwcL3Sgk8CgYEA5AhbSEH4lszqB0MszUJ4TPgllHBKrTJQhmK4xPmsuTAMQevSrNmoR524d9Ejcu0JRp2vspzzBCiNdMKAxrUWgt/twLpfVTbph/OBbvIXDZIXELI2ECF9e5jz2qucXzwnHLGDrN+Ie1HpuIdFjVCaKQGqPcAexMxziL2qrzO/K8UCgYBkUZ3OKuBDXJvVU6+oBCKWEAk76bQTt4vs6pIlFFIj5Y+ki0P7WBoHwwnudmG6eV+kGdvYs3Pc4N6n1ARy0NIwE5N82bKt2IB/uN0qqMaFIc+lNGJ4tpioe4LC2lSpaX5xPeRYofOjgFuWNuV4hNKbFpRLE5hRvJOOo7cQ2520FwKBgQDTdqhn9r234qk4OORIKNb00b3PzN6DhMBGDzC3ga3aQiNr0mwZXPMADtQtUKWmAwjyEnMHmSKHfa/IUkMngnEwxsZtTqfvly+zom4qW5hjPlHjatBV3yjFwI6K/0/QxTCkHD42x0iCy/CI7fDi3pdHZcLg5tPhvpN5gCHTvZIP3QKBgQCLcIVgn3iS9Quug5fO+8WgDmCc9mUJPHWsAFoiVipsWySHAuvvn2AecZ9dqzmipy/DiJrLQM0sZuGcp6LR29dc0qj+6DNbSO/5oiDkCnnwgpFJRzk3ks/K13YYGILzIgl2gpih9L759VK0xcWjiLmSDrPUpgladmdyw5jg4ACGjA==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIIClzCCAX8CBgGQKm2wNTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARzdGFjMB4XDTI0MDYxODA4MTczMFoXDTM0MDYxODA4MTkxMFowDzENMAsGA1UEAwwEc3RhYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN81hUCxtywKPrByZKCi+VKvoelqvWXXHM53j36DEfML5xcxJb7A8LT954cXk/+USLvSW2ZBJVlv6tJqJ7ZXHh5ben6v/DToweOzsQTa7MmugR6GJ+lybfbtACKGD9wqK2mi/gLOjZQMhIdvPjlHo8/Q9Ryg5miMJtOBRRysktRFvQ2iaCNyqLzhw9MYBqJoJg+fpA5znKZmojShMqFUIsA0MidY7FRsb0WmNbJZmZpkF3nLNG1qU/fWIN5isFUgAjYLQa2FzswyUL8ZG0vmXjD/ScydrsEoLBS1RphKNE49sffHObnM3V95Wsn65aqKcUOD4CjjYdbIZslQqmTSi8sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAM1ad0ng7TWDvmvZQJljlTG6PtBiTDz9Aaaqh6570utbSHvbjDtGhavHS+st1IIrmZuRccY5hhjNKEVGdf+HoFrs6mCKbjRMyhIAfoAtg+rZlW+pFL5OQlJZNNoZXNT0D5U2bumZZn2MpSc4je6/0mxhj70zjCKZ34P1WdltN7zCu1nmwr8KrSWO4ZZeYfDS+6JFkzcR2N7iAjn2GEeY8J1PXSLcyuy1jgu9tDHyJ+8KZ5xw4da59pDc6YmG1DQqWi3T31LjNiKbYuQErVNIR8fvijkF5YiGbRtGVRDfY+nL4jZuoMJKYOufAlnX+89KzoIKEKtExNtw8PWkSIyO4/Q==" ], + "priority" : [ "100" ] + } + }, { + "id" : "1e7ae2fa-ba78-41b2-941e-ff47ade54cce", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAuJx1/+3t/Y+KOY1ODY+uKO1MFHOREZhwEDdk6kF3FBxtnS37gQJ+P32UQKMyxegPJRtkLyLIQkH64E6SMVA99GJzJmaCSdQVedH2tp9hTgoGW9dwYacwNxrc4sVcrC1BpfrXsfxjv+DT2pVUgHuCijsEmctMavOddvZQOm/SFXplwdlWq7gG8MsAKh88XaHg4Z8LgYrfeGJ/4l14vYxczKvx5FK5ulBbZ1NIv6ixvZsH7XYDMy1Gx5IAu05ZHSDC5JNT/r725Sz0veYsJo6wLhZ7daytU5F6LpsbkN0wygHVEezKIJvrP0sbC38rb4i1UkM5Sow02C1RiN4sw2l58QIDAQABAoIBABHBBPLGpatLm/Yfn3CxGgVYQjIlUGo/crzcgZlVumdLZJnydiGDZTN4lu1bGeLr3P5V6AibG7njhoZT3WizWCyh2y5yOqLHAVm51jQx7QsIQ8b0AnF3Fq1LVBAq/YKWx514G45DwNlkfHtPqSoXYV0nLTDChb2IuDgHRX1D4RGs4wS/tzFk11BFn9gD94M/W0C6tb5HuY2I88APAIsmNQkoZ/F4Hhl5fPsyOwbJbo6dwFHLqNoFFrzY3R+1n1Kr4U2tcKhr6T24UcP6UOqsNupz3XueFdO2OEtiucpUNP/v2rEHi+sjZ5Vvv2p5Ze8I4USOk7cg/5yECgqcr+5axJECgYEA8TNt46WFz6V+RkSRzNv42ZOa/7WAEkTxKVD4n1rN2W/5akW3MibvBglWpoEiJcfoJv3ZuG9PVbJXlYksEPXCwpWB0WY/dOd44yNe+BaXT9tLDMcE/a1/KHBnOx/Zo7jh9naL/S1ZzwgHQg9OM2SeqT0xSHb3w5isJ9X/YVKUXWMCgYEAw/ArmcFqWRjXat6FD3bEONraPqoUjn17W/FNbcv3v+GYHFAF9y7F9zbq42hsqMTx6AyH++YD7dz5rN1t3sCcEO0KiY98IvzT4Tq4nMMEeM1mwdWOqT1GonrfBOaB7TCOFJ6lG7GX5IIjGidt/LyY6GN+5Lv+GYGk9xAEjA/WBZsCgYEA00HhWMcsKU/hBrieHxj3n9H5q0gUMWwy8aYa7LACphS+FseTChkxt07Mly95ci/idOxYGoNbsEpxDedEMmKewAFk37w3jjQDHKZpUs+uneILnNhf1bR1MD0rHYzq+cxAFbeFKy3igEOe38w6CSdzE/YlyTtyDN4WwxlbAanritMCgYBn0MUJPnW+p27b7PLLcr4c8bhZI3yo0sudt4iy+DNYs4sI1U2T3nB9v9dMjnOkKFWUAa+7Q/ApA8A2W0xvjdYjEbVXlXLMhvEskRRn2txvtUUQxrpD6XeXd0rbKdcFeYiOXFBXEA3OwTUgEmwwUS0jVDRTtVe44/wn+5CtNWbMqQKBgQDChYrU6lwTvE3gQyTvhxn5aagWe84j36jm1gF2j54L6TfCUQkVvQ1r9Ksis9XnCQlQ3M3Pnew0Sqa4ydnVmjUoV89/mVLzUWuj4ki66PqS/l1KLZly9vOuqEGh2xsQ4B6pjaFjLlA+fO+HKRI9BgAX8wqZ1oGBUKcgCYUruI1UkQ==" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIIClzCCAX8CBgGQKm2wvTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARzdGFjMB4XDTI0MDYxODA4MTczMFoXDTM0MDYxODA4MTkxMFowDzENMAsGA1UEAwwEc3RhYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALicdf/t7f2PijmNTg2PrijtTBRzkRGYcBA3ZOpBdxQcbZ0t+4ECfj99lECjMsXoDyUbZC8iyEJB+uBOkjFQPfRicyZmgknUFXnR9rafYU4KBlvXcGGnMDca3OLFXKwtQaX617H8Y7/g09qVVIB7goo7BJnLTGrznXb2UDpv0hV6ZcHZVqu4BvDLACofPF2h4OGfC4GK33hif+JdeL2MXMyr8eRSubpQW2dTSL+osb2bB+12AzMtRseSALtOWR0gwuSTU/6+9uUs9L3mLCaOsC4We3WsrVORei6bG5DdMMoB1RHsyiCb6z9LGwt/K2+ItVJDOUqMNNgtUYjeLMNpefECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKu0BnGYka/yOLoWA8yk+yXbBmVjL3Akd6K88Q90qsh+xmjj6zQBL0UGHRtPCQUkznnD4aW3oWtNzrPtsecy865R2u/oPbkaHrd5kyH1BGebDr61ztdA6EThzYdkfIfH4ZGxwjg3oOOCWbcceP3312JcivYFMIB6/KyY6GVXRJdcDdU6SYmUCza6mu83xec2Anu/4Rx5PHAWiOxBj9PGIT/Fs3Ep8g22tx33Hp88VulumSorujAKipw+cAtulhuPu/EvuwvhipGueY4hnjc/F0BQkLYzFneGyDROpyIcu0+M3rKGZGc4LaHnLCnk0vJ2ERsy2PDTJyTmZrqyPFyXXww==" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "347a0d9a-eb20-497d-a6ea-4299186c170d", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "49c0c89d-c13f-49e8-8782-8cf3c9dd31a9" ], + "secret" : [ "wCNkEH_x3H52r3uDe-JB-cXqBbwNVlXX85xeGQv-M5j3KwWSJTb-JJz9FZ0XARdUdULQAaaFd63eMTYf74Quh5JaEI4MnGEg0WLvNETNcj71AfSZmGmKRWTLltdpE0N16UwIOto1iPVCVjPW80DcGAo9tu-HWNA48J2jFntzYF4" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "e0f7bb0b-fb2f-4ea8-be38-9515e3c6a562", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "ae385782-9329-4e45-bce7-b59807f04b3a" ], + "secret" : [ "Pa2y9tDs1E0EYnXk-G0aeQ" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "4e34122b-021f-4121-beb6-81063e6f234a", + "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" : "b4372135-3905-4cd1-99c1-f1630241b2b2", + "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" : "a847eb46-cbcd-4be3-a9d1-03ad3f8c7a1a", + "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" : "9182e6b9-3126-4fbe-ba96-c0b0e44867a9", + "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" : "d296ea62-7e8b-4acb-b38a-5125e1c58679", + "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" : "362e2778-1820-47c0-a811-64e2068f0c1b", + "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" : "9265fae7-cad9-4f4c-856f-3909abaa66f9", + "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" : "b7b18e85-6054-46fd-badc-51c40dc9724d", + "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" : "537f7948-a37f-4623-a417-b127400135c9", + "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" : "075d0748-6048-4ccc-8544-9aa1af4c5b39", + "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" : "2fcca4a9-af33-490e-8c98-e17862ba4a8c", + "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" : "f8de4820-f344-4f7f-879a-0a9147ef0e5c", + "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" : "be11fdad-10cf-431f-b180-90d4d0486408", + "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" : "1e9afe5a-4a5f-4994-9697-2a0be1ee658c", + "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" : "344111a0-e674-4e4a-8736-ade110d3cd53", + "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" : "5e850af5-363e-43c3-a477-12497faf6d6c", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "8a317887-6357-4075-ada2-4d299d2a0ff9", + "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" : "34d03725-6714-413d-9df7-15ccd9ac23a5", + "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" : "175b507d-11fd-4ec6-a690-6092de625aac", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "a28c62b6-2e0c-487c-a3c3-a6ca76b2ea1d", + "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" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "oauth2DevicePollingInterval" : "5", + "parRequestUriLifespan" : "60", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "25.0.0", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/stac_fastapi/core/setup.py b/stac_fastapi/core/setup.py index 90e5b141..80376031 100644 --- a/stac_fastapi/core/setup.py +++ b/stac_fastapi/core/setup.py @@ -18,6 +18,7 @@ "geojson-pydantic", "pygeofilter==0.2.1", "typing_extensions==4.8.0", + "jsonschema", ] setup( diff --git a/stac_fastapi/core/stac_fastapi/core/basic_auth.py b/stac_fastapi/core/stac_fastapi/core/basic_auth.py index e9964b62..df09b904 100644 --- a/stac_fastapi/core/stac_fastapi/core/basic_auth.py +++ b/stac_fastapi/core/stac_fastapi/core/basic_auth.py @@ -1,115 +1,61 @@ """Basic Authentication Module.""" -import json import logging -import os import secrets -from typing import Any, Dict -from fastapi import Depends, HTTPException, Request, status -from fastapi.routing import APIRoute +from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials from typing_extensions import Annotated -from stac_fastapi.api.app import StacApi - _LOGGER = logging.getLogger("uvicorn.default") _SECURITY = HTTPBasic() -_BASIC_AUTH: Dict[str, Any] = {} - - -def has_access( - request: Request, credentials: Annotated[HTTPBasicCredentials, Depends(_SECURITY)] -) -> str: - """Check if the provided credentials match the expected \ - username and password stored in environment variables for basic authentication. - - Args: - request (Request): The FastAPI request object. - credentials (HTTPBasicCredentials): The HTTP basic authentication credentials. - - Returns: - str: The username if authentication is successful. - Raises: - HTTPException: If authentication fails due to incorrect username or password. - """ - global _BASIC_AUTH - users = _BASIC_AUTH.get("users") - user: Dict[str, Any] = next( - (u for u in users if u.get("username") == credentials.username), {} - ) - - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Basic"}, - ) - - # Compare the provided username and password with the correct ones using compare_digest - if not secrets.compare_digest( - credentials.username.encode("utf-8"), user.get("username").encode("utf-8") - ) or not secrets.compare_digest( - credentials.password.encode("utf-8"), user.get("password").encode("utf-8") - ): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Basic"}, - ) - - permissions = user.get("permissions", []) - path = request.scope.get("route").path - method = request.method - - if permissions == "*": - return credentials.username - for permission in permissions: - if permission["path"] == path and method in permission.get("method", []): - return credentials.username - - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"Insufficient permissions for [{method} {path}]", - ) - - -def apply_basic_auth(api: StacApi) -> None: +class BasicAuth: """Apply basic authentication to the provided FastAPI application \ - based on environment variables for username, password, and endpoints. - - Args: - api (StacApi): The FastAPI application. - - Raises: - HTTPException: If there are issues with the configuration or format - of the environment variables. - """ - global _BASIC_AUTH + based on environment variables for username, password, and endpoints.""" + + def __init__(self, credentials: list) -> None: + """Generate basic_auth property.""" + self.basic_auth = {} + for credential in credentials: + self.basic_auth[credential["username"]] = credential + + async def __call__( + self, + credentials: Annotated[HTTPBasicCredentials, Depends(_SECURITY)], + ) -> str: + """Check if the provided credentials match the expected \ + username and password stored in basic_auth. + + Args: + credentials (HTTPBasicCredentials): The HTTP basic authentication credentials. + + Returns: + str: The username if authentication is successful. + + Raises: + HTTPException: If authentication fails due to incorrect username or password. + """ + user = self.basic_auth.get(credentials.username) + + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + + # Compare the provided username and password with the correct ones using compare_digest + if not secrets.compare_digest( + credentials.username.encode("utf-8"), user.get("username").encode("utf-8") + ) or not secrets.compare_digest( + credentials.password.encode("utf-8"), user.get("password").encode("utf-8") + ): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) - basic_auth_json_str = os.environ.get("BASIC_AUTH") - if not basic_auth_json_str: - _LOGGER.info("Basic authentication disabled.") - return - - try: - _BASIC_AUTH = json.loads(basic_auth_json_str) - except json.JSONDecodeError as exception: - _LOGGER.error(f"Invalid JSON format for BASIC_AUTH. {exception=}") - raise - public_endpoints = _BASIC_AUTH.get("public_endpoints", []) - users = _BASIC_AUTH.get("users") - if not users: - raise Exception("Invalid JSON format for BASIC_AUTH. Key 'users' undefined.") - - app = api.app - for route in app.routes: - if isinstance(route, APIRoute): - for method in route.methods: - endpoint = {"path": route.path, "method": method} - if endpoint not in public_endpoints: - api.add_route_dependencies([endpoint], [Depends(has_access)]) - - _LOGGER.info("Basic authentication enabled.") + return credentials.username diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py new file mode 100644 index 00000000..acf06a86 --- /dev/null +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -0,0 +1,175 @@ +"""Route Dependencies Module.""" + +import importlib +import inspect +import json +import logging +import os + +from fastapi import Depends +from jsonschema import validate + +_LOGGER = logging.getLogger("uvicorn.default") + + +route_dependencies_schema = { + "type": "array", + "items": { + "type": "object", + "properties": { + "routes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "anyOf": [ + {"$ref": "#/$defs/method"}, + { + "type": "array", + "items": {"$ref": "#/$defs/method"}, + "uniqueItems": True, + }, + ] + }, + "path": { + "anyOf": [ + {"$ref": "#/$defs/path"}, + { + "type": "array", + "items": {"$ref": "#/$defs/path"}, + "uniqueItems": True, + }, + ] + }, + "type": {"type": "string"}, + }, + "required": ["method", "path"], + "additionalProperties": False, + }, + }, + "dependencies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": {"type": "string"}, + "args": {"type": "string"}, + "kwargs": {"type": "object"}, + }, + "required": ["method"], + "additionalProperties": False, + }, + }, + }, + "dependencies": { + "routes": ["dependencies"], + "dependencies": ["routes"], + }, + "additionalProperties": False, + }, + "$defs": { + "method": { + "type": "string", + "enum": ["*", "GET", "POST", "PUT", "PATCH", "DELETE"], + }, + "path": { + "type": "string", + "pattern": r"^\*$|\/.*", + }, + }, +} + + +def get_route_dependencies_conf(route_dependencies_env: str) -> list: + """Get Route dependencies configuration from file or environment variable.""" + if os.path.exists(route_dependencies_env): + with open(route_dependencies_env, encoding="utf-8") as route_dependencies_file: + route_dependencies_conf = json.load(route_dependencies_file) + + else: + try: + route_dependencies_conf = json.loads(route_dependencies_env) + except json.JSONDecodeError as exception: + _LOGGER.error("Invalid JSON format for route dependencies. %s", exception) + raise + + validate(instance=route_dependencies_conf, schema=route_dependencies_schema) + + return route_dependencies_conf + + +def get_routes(route_dependency_conf: dict) -> list: + """Get routes from route dependency configuration.""" + # seperate out any path lists + intermediate_routes = [] + for route in route_dependency_conf["routes"]: + + if isinstance(route["path"], list): + for path in route["path"]: + intermediate_routes.append({**route, "path": path}) + + else: + intermediate_routes.append(route) + + # seperate out any method lists + routes = [] + for route in intermediate_routes: + + if isinstance(route["method"], list): + for method in route["method"]: + routes.append({**route, "method": method}) + + else: + routes.append(route) + + return routes + + +def get_dependencies(route_dependency_conf: dict) -> list: + """Get dependencies from route dependency configuration.""" + dependencies = [] + for dependency_conf in route_dependency_conf["dependencies"]: + + module_name, method_name = dependency_conf["method"].rsplit(".", 1) + module = importlib.import_module(module_name) + dependency = getattr(module, method_name) + + if inspect.isclass(dependency): + + dependency = dependency( + *dependency_conf.get("args", []), **dependency_conf.get("kwargs", {}) + ) + + dependencies.append(Depends(dependency)) + + return dependencies + + +def get_route_dependencies(route_dependencies_env: str = "") -> list: + """ + Route dependencies generator. + + Generate a set of route dependencies for authentication to the + provided FastAPI application. + """ + route_dependencies_env = os.environ.get( + "STAC_FASTAPI_ROUTE_DEPENDENCIES", route_dependencies_env + ) + route_dependencies: list[tuple] = [] + + if not route_dependencies_env: + _LOGGER.info("Authentication skipped.") + return route_dependencies + + _LOGGER.info("Authentication enabled.") + + route_dependencies_conf = get_route_dependencies_conf(route_dependencies_env) + + for route_dependency_conf in route_dependencies_conf: + + routes = get_routes(route_dependency_conf) + dependencies = get_dependencies(route_dependency_conf) + route_dependencies.append((routes, dependencies)) + + return route_dependencies diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py index ebaf5f7b..56a29c42 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py @@ -4,7 +4,6 @@ from stac_fastapi.api.app import StacApi from stac_fastapi.api.models import create_get_request_model, create_post_request_model -from stac_fastapi.core.basic_auth import apply_basic_auth from stac_fastapi.core.core import ( BulkTransactionsClient, CoreClient, @@ -13,6 +12,7 @@ ) from stac_fastapi.core.extensions import QueryExtension from stac_fastapi.core.extensions.fields import FieldsExtension +from stac_fastapi.core.route_dependencies import get_route_dependencies from stac_fastapi.core.session import Session from stac_fastapi.elasticsearch.config import ElasticsearchSettings from stac_fastapi.elasticsearch.database_logic import ( @@ -74,12 +74,11 @@ ), search_get_request_model=create_get_request_model(extensions), search_post_request_model=post_request_model, + route_dependencies=get_route_dependencies(), ) app = api.app app.root_path = os.getenv("STAC_FASTAPI_ROOT_PATH", "") -apply_basic_auth(api) - @app.on_event("startup") async def _startup_event() -> None: diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py index 5e23e96a..40318860 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py @@ -1,4 +1,5 @@ """API configuration.""" + import os import ssl from typing import Any, Dict, Set @@ -23,6 +24,16 @@ def _es_config() -> Dict[str, Any]: "headers": {"accept": "application/vnd.elasticsearch+json; compatible-with=7"}, } + # Handle API key + if api_key := os.getenv("ES_API_KEY"): + if isinstance(config["headers"], dict): + headers = {**config["headers"], "x-api-key": api_key} + + else: + config["headers"] = {"x-api-key": api_key} + + config["headers"] = headers + # Explicitly exclude SSL settings when not using SSL if not use_ssl: return config @@ -39,15 +50,6 @@ def _es_config() -> Dict[str, Any]: if (u := os.getenv("ES_USER")) and (p := os.getenv("ES_PASS")): config["http_auth"] = (u, p) - if api_key := os.getenv("ES_API_KEY"): - if isinstance(config["headers"], dict): - headers = {**config["headers"], "x-api-key": api_key} - - else: - config["headers"] = {"x-api-key": api_key} - - config["headers"] = headers - return config diff --git a/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py b/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py index fbf43934..0e7bb05c 100644 --- a/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py +++ b/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py @@ -4,7 +4,6 @@ from stac_fastapi.api.app import StacApi from stac_fastapi.api.models import create_get_request_model, create_post_request_model -from stac_fastapi.core.basic_auth import apply_basic_auth from stac_fastapi.core.core import ( BulkTransactionsClient, CoreClient, @@ -13,6 +12,7 @@ ) from stac_fastapi.core.extensions import QueryExtension from stac_fastapi.core.extensions.fields import FieldsExtension +from stac_fastapi.core.route_dependencies import get_route_dependencies from stac_fastapi.core.session import Session from stac_fastapi.extensions.core import ( FilterExtension, @@ -74,12 +74,11 @@ ), search_get_request_model=create_get_request_model(extensions), search_post_request_model=post_request_model, + route_dependencies=get_route_dependencies(), ) app = api.app app.root_path = os.getenv("STAC_FASTAPI_ROOT_PATH", "") -apply_basic_auth(api) - @app.on_event("startup") async def _startup_event() -> None: diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 619a257c..5ef29482 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -2,22 +2,24 @@ import copy import json import os +import sys from typing import Any, Callable, Dict, Optional import pytest import pytest_asyncio +from fastapi import Depends, HTTPException, security, status from httpx import AsyncClient from stac_pydantic import api from stac_fastapi.api.app import StacApi from stac_fastapi.api.models import create_get_request_model, create_post_request_model -from stac_fastapi.core.basic_auth import apply_basic_auth from stac_fastapi.core.core import ( BulkTransactionsClient, CoreClient, TransactionsClient, ) from stac_fastapi.core.extensions import QueryExtension +from stac_fastapi.core.route_dependencies import get_route_dependencies if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch": from stac_fastapi.opensearch.config import AsyncOpensearchSettings as AsyncSettings @@ -227,6 +229,39 @@ async def app_client(app): @pytest_asyncio.fixture(scope="session") async def app_basic_auth(): + + stac_fastapi_route_dependencies = """[ + { + "routes":[{"method":"*","path":"*"}], + "dependencies":[ + { + "method":"stac_fastapi.core.basic_auth.BasicAuth", + "kwargs":{"credentials":[{"username":"admin","password":"admin"}]} + } + ] + }, + { + "routes":[ + {"path":"/","method":["GET"]}, + {"path":"/conformance","method":["GET"]}, + {"path":"/collections/{collection_id}/items/{item_id}","method":["GET"]}, + {"path":"/search","method":["GET","POST"]}, + {"path":"/collections","method":["GET"]}, + {"path":"/collections/{collection_id}","method":["GET"]}, + {"path":"/collections/{collection_id}/items","method":["GET"]}, + {"path":"/queryables","method":["GET"]}, + {"path":"/queryables/collections/{collection_id}/queryables","method":["GET"]}, + {"path":"/_mgmt/ping","method":["GET"]} + ], + "dependencies":[ + { + "method":"stac_fastapi.core.basic_auth.BasicAuth", + "kwargs":{"credentials":[{"username":"reader","password":"reader"}]} + } + ] + } + ]""" + settings = AsyncSettings() extensions = [ TransactionExtension( @@ -255,42 +290,93 @@ async def app_basic_auth(): extensions=extensions, search_get_request_model=create_get_request_model(extensions), search_post_request_model=post_request_model, + route_dependencies=get_route_dependencies(stac_fastapi_route_dependencies), + ) + + return stac_api.app + + +@pytest_asyncio.fixture(scope="session") +async def app_client_basic_auth(app_basic_auth): + await create_index_templates() + await create_collection_index() + + async with AsyncClient(app=app_basic_auth, base_url="http://test-server") as c: + yield c + + +def must_be_bob( + credentials: security.HTTPBasicCredentials = Depends(security.HTTPBasic()), +): + if credentials.username == "bob": + return True + + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="You're not Bob", + headers={"WWW-Authenticate": "Basic"}, ) - os.environ[ - "BASIC_AUTH" - ] = """{ - "public_endpoints": [ - {"path": "/", "method": "GET"}, - {"path": "/search", "method": "GET"} - ], - "users": [ - {"username": "admin", "password": "admin", "permissions": "*"}, + +@pytest_asyncio.fixture(scope="session") +async def route_dependencies_app(): + # Add file to python path to allow get_route_dependencies to import must_be_bob + sys.path.append(os.path.dirname(__file__)) + + stac_fastapi_route_dependencies = """[ { - "username": "reader", "password": "reader", - "permissions": [ - {"path": "/conformance", "method": ["GET"]}, - {"path": "/collections/{collection_id}/items/{item_id}", "method": ["GET"]}, - {"path": "/search", "method": ["POST"]}, - {"path": "/collections", "method": ["GET"]}, - {"path": "/collections/{collection_id}", "method": ["GET"]}, - {"path": "/collections/{collection_id}/items", "method": ["GET"]}, - {"path": "/queryables", "method": ["GET"]}, - {"path": "/queryables/collections/{collection_id}/queryables", "method": ["GET"]}, - {"path": "/_mgmt/ping", "method": ["GET"]} + "routes": [ + { + "method": "GET", + "path": "/collections" + } + ], + "dependencies": [ + { + "method": "conftest.must_be_bob" + } ] } - ] - }""" - apply_basic_auth(stac_api) + ]""" - return stac_api.app + settings = AsyncSettings() + extensions = [ + TransactionExtension( + client=TransactionsClient( + database=database, session=None, settings=settings + ), + settings=settings, + ), + SortExtension(), + FieldsExtension(), + QueryExtension(), + TokenPaginationExtension(), + FilterExtension(), + ] + + post_request_model = create_post_request_model(extensions) + + return StacApi( + settings=settings, + client=CoreClient( + database=database, + session=None, + extensions=extensions, + post_request_model=post_request_model, + ), + extensions=extensions, + search_get_request_model=create_get_request_model(extensions), + search_post_request_model=post_request_model, + route_dependencies=get_route_dependencies(stac_fastapi_route_dependencies), + ).app @pytest_asyncio.fixture(scope="session") -async def app_client_basic_auth(app_basic_auth): +async def route_dependencies_client(route_dependencies_app): await create_index_templates() await create_collection_index() - async with AsyncClient(app=app_basic_auth, base_url="http://test-server") as c: + async with AsyncClient( + app=route_dependencies_app, base_url="http://test-server" + ) as c: yield c diff --git a/stac_fastapi/tests/route_dependencies/__init__.py b/stac_fastapi/tests/route_dependencies/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py new file mode 100644 index 00000000..b6e3e266 --- /dev/null +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -0,0 +1,22 @@ +import pytest + + +@pytest.mark.asyncio +async def test_not_authenticated(route_dependencies_client): + """Test protected endpoint [GET /collections] without permissions""" + + response = await route_dependencies_client.get("/collections") + + assert response.status_code == 401 + + +@pytest.mark.asyncio +async def test_authenticated(route_dependencies_client): + """Test protected endpoint [GET /collections] with permissions""" + + response = await route_dependencies_client.get( + "/collections", + auth=("bob", "dobbs"), + ) + + assert response.status_code == 200