From 5ee3c2d8e3adca9a6c4dd01bc60cfc17eaf76f96 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Fri, 10 May 2024 08:34:47 +0100 Subject: [PATCH 01/28] Adding route dependencies module. --- .../route_dependencies.json | 18 ++++++ .../stac_fastapi/core/route_dependencies.py | 62 +++++++++++++++++++ .../stac_fastapi/elasticsearch/app.py | 4 ++ 3 files changed, 84 insertions(+) create mode 100644 examples/route_dependencies/route_dependencies.json create mode 100644 stac_fastapi/core/stac_fastapi/core/route_dependencies.py diff --git a/examples/route_dependencies/route_dependencies.json b/examples/route_dependencies/route_dependencies.json new file mode 100644 index 00000000..a43a2dd3 --- /dev/null +++ b/examples/route_dependencies/route_dependencies.json @@ -0,0 +1,18 @@ +[ + { + "routes": [ + { + "method": "PUT", + "paths": "/collections" + } + ], + "dependencies": [ + { + "function": "fastapi.security.OAuth2PasswordBearer", + "kwargs": { + "tokenUrl": "token" + } + } + ] + } +] \ No newline at end of file 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..b72e300c --- /dev/null +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -0,0 +1,62 @@ +"""Route Dependencies Module.""" + +import importlib +import json +import logging +import os + +from fastapi import Depends + +_LOGGER = logging.getLogger("uvicorn.default") + + +def get_route_dependencies() -> list: + """ + 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 = [] + + if route_dependencies_env: + _LOGGER.info("Authentication enabled.") + + if os.path.exists(route_dependencies_env): + with os.open(route_dependencies_env) 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 + + for route_dependency_conf in route_dependencies_conf: + routes = route_dependency_conf["routes"] + dependencies_conf = route_dependency_conf["dependencies"] + + dependencies = [] + for dependency_conf in dependencies_conf: + + module_name, function_name = dependency_conf["method"].rsplit(".", 1) + + module = importlib.import_module(module_name) + + function = getattr(module, function_name) + + dependency = function( + *dependency_conf.get("input_args", []), + **dependency_conf.get("input_kwargs", {}) + ) + + dependencies.append(Depends(dependency)) + + route_dependencies.append((routes, dependencies)) + + else: + _LOGGER.info("Authentication skipped.") + + return route_dependencies diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py index 49d199d6..23f08054 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py @@ -13,6 +13,9 @@ ) from stac_fastapi.core.extensions import QueryExtension from stac_fastapi.core.session import Session +from stac_fastapi.core.stac_fastapi.core.route_dependencies import ( + get_route_dependencies, +) from stac_fastapi.elasticsearch.config import ElasticsearchSettings from stac_fastapi.elasticsearch.database_logic import ( DatabaseLogic, @@ -74,6 +77,7 @@ ), 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", "") From 10d621e17720e639f3115fc3042056b40499ede0 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Fri, 10 May 2024 09:07:19 +0100 Subject: [PATCH 02/28] Fixing pre-commit errors. --- stac_fastapi/core/stac_fastapi/core/route_dependencies.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index b72e300c..fdad3414 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -12,6 +12,8 @@ def get_route_dependencies() -> list: """ + Route dependencies generator. + Generate a set of route dependencies for authentication to the provided FastAPI application. """ @@ -22,7 +24,7 @@ def get_route_dependencies() -> list: _LOGGER.info("Authentication enabled.") if os.path.exists(route_dependencies_env): - with os.open(route_dependencies_env) as route_dependencies_file: + with open(route_dependencies_env) as route_dependencies_file: route_dependencies_conf = json.load(route_dependencies_file) else: From c2c12bdb468eadb2dc2ef5c9cf8e9506e6a9fce5 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Tue, 14 May 2024 15:34:48 +0100 Subject: [PATCH 03/28] Adding to opensearch. --- .../route_dependencies.json | 4 ++-- .../stac_fastapi/core/route_dependencies.py | 10 ++++++---- .../stac_fastapi/elasticsearch/app.py | 4 +--- .../stac_fastapi/elasticsearch/config.py | 20 ++++++++++--------- .../opensearch/stac_fastapi/opensearch/app.py | 2 ++ 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/examples/route_dependencies/route_dependencies.json b/examples/route_dependencies/route_dependencies.json index a43a2dd3..7ae91b53 100644 --- a/examples/route_dependencies/route_dependencies.json +++ b/examples/route_dependencies/route_dependencies.json @@ -2,8 +2,8 @@ { "routes": [ { - "method": "PUT", - "paths": "/collections" + "method": "GET", + "path": "/collections" } ], "dependencies": [ diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index fdad3414..747e15d2 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -24,7 +24,9 @@ def get_route_dependencies() -> list: _LOGGER.info("Authentication enabled.") if os.path.exists(route_dependencies_env): - with open(route_dependencies_env) as route_dependencies_file: + with open( + route_dependencies_env, encoding="utf-8" + ) as route_dependencies_file: route_dependencies_conf = json.load(route_dependencies_file) else: @@ -43,15 +45,15 @@ def get_route_dependencies() -> list: dependencies = [] for dependency_conf in dependencies_conf: - module_name, function_name = dependency_conf["method"].rsplit(".", 1) + module_name, function_name = dependency_conf["function"].rsplit(".", 1) module = importlib.import_module(module_name) function = getattr(module, function_name) dependency = function( - *dependency_conf.get("input_args", []), - **dependency_conf.get("input_kwargs", {}) + *dependency_conf.get("args", []), + **dependency_conf.get("kwargs", {}) ) dependencies.append(Depends(dependency)) diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py index 23f08054..a817874f 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py @@ -12,10 +12,8 @@ TransactionsClient, ) from stac_fastapi.core.extensions import QueryExtension +from stac_fastapi.core.route_dependencies import get_route_dependencies from stac_fastapi.core.session import Session -from stac_fastapi.core.stac_fastapi.core.route_dependencies import ( - get_route_dependencies, -) from stac_fastapi.elasticsearch.config import ElasticsearchSettings from stac_fastapi.elasticsearch.database_logic import ( DatabaseLogic, 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 ac697b2b..bbda35ea 100644 --- a/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py +++ b/stac_fastapi/opensearch/stac_fastapi/opensearch/app.py @@ -12,6 +12,7 @@ TransactionsClient, ) from stac_fastapi.core.extensions import QueryExtension +from stac_fastapi.core.route_dependencies import get_route_dependencies from stac_fastapi.core.session import Session from stac_fastapi.extensions.core import ( ContextExtension, @@ -74,6 +75,7 @@ ), 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", "") From 7b8ef5a65a945f8e6a493e589b0f407bb9f314b0 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Tue, 14 May 2024 15:34:58 +0100 Subject: [PATCH 04/28] Adding testing. --- stac_fastapi/tests/conftest.py | 79 +++++++++++++++++++ .../tests/route_dependencies/__init__.py | 0 .../test_route_dependencies.py | 28 +++++++ 3 files changed, 107 insertions(+) create mode 100644 stac_fastapi/tests/route_dependencies/__init__.py create mode 100644 stac_fastapi/tests/route_dependencies/test_route_dependencies.py diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 80314a45..695c5fed 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -6,6 +6,7 @@ import pytest import pytest_asyncio +from fastapi import Depends, HTTPException, security, status from httpx import AsyncClient from stac_pydantic import api @@ -18,6 +19,7 @@ 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 @@ -296,3 +298,80 @@ async def app_client_basic_auth(app_basic_auth): 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"}, + ) + + +@pytest_asyncio.fixture(scope="session") +async def route_dependencies_app(): + settings = AsyncSettings() + extensions = [ + TransactionExtension( + client=TransactionsClient( + database=database, session=None, settings=settings + ), + settings=settings, + ), + ContextExtension(), + SortExtension(), + FieldsExtension(), + QueryExtension(), + TokenPaginationExtension(), + FilterExtension(), + ] + + post_request_model = create_post_request_model(extensions) + + os.environ[ + "STAC_FASTAPI_ROUTE_DEPENDENCIES" + ] = """[ + { + "routes": [ + { + "method": "GET", + "path": "/collections" + } + ], + "dependencies": [ + { + "function": "stac_fastapi.tests.conftest.must_be_bob", + } + ] + } + ]""" + + 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(), + ).app + + +@pytest_asyncio.fixture(scope="session") +async def route_dependencies_client(route_dependencies_app): + await create_index_templates() + await create_collection_index() + + 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..b6358488 --- /dev/null +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -0,0 +1,28 @@ +import pytest + + +@pytest.mark.asyncio +async def test_not_authenticated(route_dependencies_client, ctx): + """Test public endpoint [GET /search] without authentication""" + params = {"id": ctx.item["id"]} + + response = await route_dependencies_client.get("/search", params=params) + + assert response.status_code == 401, response + + +@pytest.mark.asyncio +async def test_authenticated(route_dependencies_client, ctx): + """Test protected endpoint [POST /search] with reader auhtentication""" + + params = {"id": ctx.item["id"]} + + response = await route_dependencies_client.post( + "/search", + json=params, + auth=("bob", "dobbs"), + headers={"content-type": "application/json"}, + ) + + assert response.status_code == 200, response + assert len(response.json()["features"]) == 1 From 6a36f6b36a4ae07b23cb701c1d9c2ec4fd1c75d9 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 08:11:24 +0100 Subject: [PATCH 05/28] Allow route dependencies to be passed as a variable for testing. --- .../stac_fastapi/core/route_dependencies.py | 6 ++- stac_fastapi/tests/conftest.py | 37 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 747e15d2..42f780ef 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -10,14 +10,16 @@ _LOGGER = logging.getLogger("uvicorn.default") -def get_route_dependencies() -> list: +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 = os.environ.get( + "STAC_FASTAPI_ROUTE_DEPENDENCIES", route_dependencies_env + ) route_dependencies = [] if route_dependencies_env: diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 695c5fed..09e632a9 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -315,6 +315,23 @@ def must_be_bob( @pytest_asyncio.fixture(scope="session") async def route_dependencies_app(): + + stac_fastapi_route_dependencies = """[ + { + "routes": [ + { + "method": "GET", + "path": "/collections" + } + ], + "dependencies": [ + { + "function": "stac_fastapi.tests.conftest.must_be_bob", + } + ] + } + ]""" + settings = AsyncSettings() extensions = [ TransactionExtension( @@ -333,24 +350,6 @@ async def route_dependencies_app(): post_request_model = create_post_request_model(extensions) - os.environ[ - "STAC_FASTAPI_ROUTE_DEPENDENCIES" - ] = """[ - { - "routes": [ - { - "method": "GET", - "path": "/collections" - } - ], - "dependencies": [ - { - "function": "stac_fastapi.tests.conftest.must_be_bob", - } - ] - } - ]""" - return StacApi( settings=settings, client=CoreClient( @@ -362,7 +361,7 @@ async def route_dependencies_app(): extensions=extensions, search_get_request_model=create_get_request_model(extensions), search_post_request_model=post_request_model, - route_dependencies=get_route_dependencies(), + route_dependencies=get_route_dependencies(stac_fastapi_route_dependencies), ).app From 211d04d27343188c14509c075d1f6e54bbaecfc8 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 08:12:03 +0100 Subject: [PATCH 06/28] Removing context extension. --- stac_fastapi/tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index e2670b4a..916a89f9 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -337,7 +337,6 @@ async def route_dependencies_app(): ), settings=settings, ), - ContextExtension(), SortExtension(), FieldsExtension(), QueryExtension(), From 8c1439026c227b5d15286d5059407c26e6774869 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 09:26:44 +0100 Subject: [PATCH 07/28] Allowing fuctions or class dependencies. --- .../route_dependencies/route_dependencies.json | 5 ++++- .../core/stac_fastapi/core/route_dependencies.py | 15 +++++++++------ stac_fastapi/tests/conftest.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/route_dependencies/route_dependencies.json b/examples/route_dependencies/route_dependencies.json index 7ae91b53..66ec4097 100644 --- a/examples/route_dependencies/route_dependencies.json +++ b/examples/route_dependencies/route_dependencies.json @@ -8,10 +8,13 @@ ], "dependencies": [ { - "function": "fastapi.security.OAuth2PasswordBearer", + "method": "fastapi.security.OAuth2PasswordBearer", "kwargs": { "tokenUrl": "token" } + }, + { + "method": "stac_fastapi.conftest.must_be_bob" } ] } diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 42f780ef..22a8f06a 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -1,6 +1,7 @@ """Route Dependencies Module.""" import importlib +import inspect import json import logging import os @@ -47,16 +48,18 @@ def get_route_dependencies(route_dependencies_env: str = "") -> list: dependencies = [] for dependency_conf in dependencies_conf: - module_name, function_name = dependency_conf["function"].rsplit(".", 1) + module_name, method_name = dependency_conf["method"].rsplit(".", 1) module = importlib.import_module(module_name) - function = getattr(module, function_name) + dependency = getattr(module, method_name) - dependency = function( - *dependency_conf.get("args", []), - **dependency_conf.get("kwargs", {}) - ) + if inspect.isclass(dependency): + + dependency = dependency( + *dependency_conf.get("args", []), + **dependency_conf.get("kwargs", {}) + ) dependencies.append(Depends(dependency)) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 916a89f9..5300abf8 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -323,7 +323,7 @@ async def route_dependencies_app(): ], "dependencies": [ { - "function": "stac_fastapi.tests.conftest.must_be_bob", + "function": "stac_fastapi.tests.conftest.must_be_bob" } ] } From 4b90d15e4bd290a91fc70a5d0602215e6e7695f1 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 09:30:52 +0100 Subject: [PATCH 08/28] Updating test conf. --- stac_fastapi/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 5300abf8..3e3cbe8a 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -323,7 +323,7 @@ async def route_dependencies_app(): ], "dependencies": [ { - "function": "stac_fastapi.tests.conftest.must_be_bob" + "method": "stac_fastapi.tests.conftest.must_be_bob" } ] } From 68913578adcee9309bbc7e044c142a6be4ab72e8 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 11:41:57 +0100 Subject: [PATCH 09/28] Fix for import error. --- stac_fastapi/tests/conftest.py | 3 +++ .../tests/route_dependencies/test_route_dependencies.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 3e3cbe8a..fd4986ad 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -2,6 +2,7 @@ import copy import json import os +import sys from typing import Any, Callable, Dict, Optional import pytest @@ -312,6 +313,8 @@ def must_be_bob( @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.abspath(__file__)) stac_fastapi_route_dependencies = """[ { diff --git a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py index b6358488..9defb85b 100644 --- a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -8,7 +8,7 @@ async def test_not_authenticated(route_dependencies_client, ctx): response = await route_dependencies_client.get("/search", params=params) - assert response.status_code == 401, response + assert response.status_code == 401 @pytest.mark.asyncio @@ -24,5 +24,5 @@ async def test_authenticated(route_dependencies_client, ctx): headers={"content-type": "application/json"}, ) - assert response.status_code == 200, response + assert response.status_code == 200 assert len(response.json()["features"]) == 1 From d7186010d51fe310320d6097862abe6a29353d61 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 13:05:52 +0100 Subject: [PATCH 10/28] Add test directory to sys path for import. --- stac_fastapi/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index fd4986ad..f35d5bb6 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -314,6 +314,7 @@ def must_be_bob( @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__)) sys.path.append(os.path.abspath(__file__)) stac_fastapi_route_dependencies = """[ From a1d39296a409175af8db1e8cb2266b00e9128c07 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 13:58:12 +0100 Subject: [PATCH 11/28] Fix for sys path import. --- stac_fastapi/tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index f35d5bb6..5a09bab4 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -314,7 +314,6 @@ def must_be_bob( @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__)) sys.path.append(os.path.abspath(__file__)) stac_fastapi_route_dependencies = """[ @@ -327,7 +326,7 @@ async def route_dependencies_app(): ], "dependencies": [ { - "method": "stac_fastapi.tests.conftest.must_be_bob" + "method": "conftest.must_be_bob" } ] } From 65d367c2f5d0345aaa6dee1869ee3bd69b25bac5 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 14:43:17 +0100 Subject: [PATCH 12/28] Add test dir to sys path. --- stac_fastapi/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index 5a09bab4..ba57d75f 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -314,7 +314,7 @@ def must_be_bob( @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.abspath(__file__)) + sys.path.append(os.path.dirname(__file__)) stac_fastapi_route_dependencies = """[ { From 53f17f860d6d9fd7ce070f84c5814e5856cbeebf Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 15:15:37 +0100 Subject: [PATCH 13/28] Switching tests to use collections endpoint for rd tests. --- .../test_route_dependencies.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py index 9defb85b..bc339c5d 100644 --- a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -2,27 +2,22 @@ @pytest.mark.asyncio -async def test_not_authenticated(route_dependencies_client, ctx): - """Test public endpoint [GET /search] without authentication""" - params = {"id": ctx.item["id"]} +async def test_not_authenticated(route_dependencies_client): + """Test protected endpoint [GET /collections] without permissions""" - response = await route_dependencies_client.get("/search", params=params) + response = await route_dependencies_client.get("/collections") assert response.status_code == 401 @pytest.mark.asyncio -async def test_authenticated(route_dependencies_client, ctx): - """Test protected endpoint [POST /search] with reader auhtentication""" - - params = {"id": ctx.item["id"]} +async def test_authenticated(route_dependencies_client): + """Test protected endpoint [GET /collections] with permissions""" response = await route_dependencies_client.post( - "/search", - json=params, + "/collections", auth=("bob", "dobbs"), - headers={"content-type": "application/json"}, ) assert response.status_code == 200 - assert len(response.json()["features"]) == 1 + assert len(response.json()["collections"]) == 1 From 51de3bff6606decc05d3266cf0606ae2e691fcc6 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 15 May 2024 15:25:46 +0100 Subject: [PATCH 14/28] Switch from post to get on test_authenticated. --- .../tests/route_dependencies/test_route_dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py index bc339c5d..470a5e0b 100644 --- a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -14,7 +14,7 @@ async def test_not_authenticated(route_dependencies_client): async def test_authenticated(route_dependencies_client): """Test protected endpoint [GET /collections] with permissions""" - response = await route_dependencies_client.post( + response = await route_dependencies_client.get( "/collections", auth=("bob", "dobbs"), ) From ffd39123c20a58c58f0b2c74fc5a7b9acf9e365e Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Thu, 16 May 2024 08:06:05 +0100 Subject: [PATCH 15/28] Removing unneeded length check. --- stac_fastapi/tests/route_dependencies/test_route_dependencies.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py index 470a5e0b..b6e3e266 100644 --- a/stac_fastapi/tests/route_dependencies/test_route_dependencies.py +++ b/stac_fastapi/tests/route_dependencies/test_route_dependencies.py @@ -20,4 +20,3 @@ async def test_authenticated(route_dependencies_client): ) assert response.status_code == 200 - assert len(response.json()["collections"]) == 1 From bde4b3cf97241286f7cbc6935cfab0be86931bf6 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Fri, 24 May 2024 11:55:43 +0100 Subject: [PATCH 16/28] Adding docker compose file and readme help for route dependencies. --- README.md | 43 ++++++++++++ docker-compose.route_dependencies.yml | 94 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 docker-compose.route_dependencies.yml diff --git a/README.md b/README.md index 77a60974..47c53893 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,49 @@ Example: This example demonstrates the configuration for public endpoints, allow } ``` +## Route Dependencies + +### Configuration + +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. + +#### 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" + } + ], + "dependencies": [ + { + "method": "fastapi.security.OAuth2PasswordBearer", + "kwargs": { + "tokenUrl": "token" + } + } + ] + } +] +``` + ### Docker Compose Configurations See `docker-compose.basic_auth_protected.yml` and `docker-compose.basic_auth_public.yml` for basic authentication configurations. \ No newline at end of file diff --git a/docker-compose.route_dependencies.yml b/docker-compose.route_dependencies.yml new file mode 100644 index 00000000..55ba1ed7 --- /dev/null +++ b/docker-compose.route_dependencies.yml @@ -0,0 +1,94 @@ +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 + - BASIC_AUTH=[{"routes":[{"method":"GET","path":"/collections"}],"dependencies":[{"method":"conftest.must_be_bob"}]}] + ports: + - "8080:8080" + volumes: + - ./stac_fastapi:/app/stac_fastapi + - ./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 + - 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"]}]}]} + 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" From cc1a30edd3acc0c1a4b198a870efb03c16005726 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 3 Jun 2024 09:39:35 +0100 Subject: [PATCH 17/28] Adding to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a88e32a1..459e8452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed +- Added route dependency configuration [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) + - Updated stac-fastapi libraries to v3.0.0a1 [#265](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/265) ### Fixed From fb931bc753796977ae71b5229f475678d422c4f5 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Wed, 12 Jun 2024 16:17:11 +0100 Subject: [PATCH 18/28] Moving basic auth to route dependencies. --- README.md | 108 ++++++++---------- docker-compose.basic_auth_protected.yml | 2 +- docker-compose.basic_auth_public.yml | 94 --------------- docker-compose.route_dependencies.yml | 2 +- .../core/stac_fastapi/core/basic_auth copy.py | 68 +++++++++++ .../stac_fastapi/core/route_dependencies.py | 10 +- stac_fastapi/tests/conftest.py | 62 +++++----- 7 files changed, 161 insertions(+), 185 deletions(-) delete mode 100644 docker-compose.basic_auth_public.yml create mode 100644 stac_fastapi/core/stac_fastapi/core/basic_auth copy.py diff --git a/README.md b/README.md index 47c53893..f50a4047 100644 --- a/README.md +++ b/README.md @@ -279,75 +279,63 @@ 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" + } + ] } + } ] -} -``` - - -### Public Endpoints 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. - -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"} + }, + { + "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"]} ], - "users": [ - { - "username": "admin", - "password": "admin", - "permissions": "*" + "dependencies": [ + { + "method": "stac_fastapi.core.basic_auth.BasicAuth", + "kwargs": { + "credentials":[ + { + "username": "reader", + "password": "reader" + } + ] } + } ] -} + } +] ``` ## Route Dependencies diff --git a/docker-compose.basic_auth_protected.yml b/docker-compose.basic_auth_protected.yml index cc4546cd..cce993cc 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: diff --git a/docker-compose.basic_auth_public.yml b/docker-compose.basic_auth_public.yml deleted file mode 100644 index cc0ba374..00000000 --- a/docker-compose.basic_auth_public.yml +++ /dev/null @@ -1,94 +0,0 @@ -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 - - 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"]}]}]} - ports: - - "8080:8080" - volumes: - - ./stac_fastapi:/app/stac_fastapi - - ./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 - - 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"]}]}]} - 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" diff --git a/docker-compose.route_dependencies.yml b/docker-compose.route_dependencies.yml index 55ba1ed7..4d5d012b 100644 --- a/docker-compose.route_dependencies.yml +++ b/docker-compose.route_dependencies.yml @@ -22,7 +22,7 @@ services: - ES_USE_SSL=false - ES_VERIFY_CERTS=false - BACKEND=elasticsearch - - BASIC_AUTH=[{"routes":[{"method":"GET","path":"/collections"}],"dependencies":[{"method":"conftest.must_be_bob"}]}] + - STAC_FASTAPI_ROUTE_DEPENDENCIES=[{"routes":[{"method":"GET","path":"/collections"}],"dependencies":[{"method":"conftest.must_be_bob"}]}] ports: - "8080:8080" volumes: diff --git a/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py b/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py new file mode 100644 index 00000000..4a108bdd --- /dev/null +++ b/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py @@ -0,0 +1,68 @@ +"""Basic Authentication Module.""" + +import logging +import secrets + +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from typing_extensions import Annotated + +_LOGGER = logging.getLogger("uvicorn.default") +_SECURITY = HTTPBasic() + + +class BasicAuth: + + def __init__(self, credentials: list) -> None: + """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. + """ + 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 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. + """ + + 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"}, + ) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 22a8f06a..f443ae0e 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -42,7 +42,15 @@ def get_route_dependencies(route_dependencies_env: str = "") -> list: raise for route_dependency_conf in route_dependencies_conf: - routes = route_dependency_conf["routes"] + + routes = [] + for route in route_dependency_conf["routes"]: + + if isinstance(route["method"], list): + for method in route["method"]: + route["method"] = method + routes.append(route) + dependencies_conf = route_dependency_conf["dependencies"] dependencies = [] diff --git a/stac_fastapi/tests/conftest.py b/stac_fastapi/tests/conftest.py index ba57d75f..795d58a9 100644 --- a/stac_fastapi/tests/conftest.py +++ b/stac_fastapi/tests/conftest.py @@ -13,7 +13,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, @@ -229,6 +228,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( @@ -257,35 +289,9 @@ 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), ) - os.environ[ - "BASIC_AUTH" - ] = """{ - "public_endpoints": [ - {"path": "/", "method": "GET"}, - {"path": "/search", "method": "GET"} - ], - "users": [ - {"username": "admin", "password": "admin", "permissions": "*"}, - { - "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"]} - ] - } - ] - }""" - apply_basic_auth(stac_api) - return stac_api.app From b68927550360d26f1229991c6079fbf0df5101a5 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Thu, 13 Jun 2024 08:36:16 +0100 Subject: [PATCH 19/28] Removing basic_auth copy. --- .../core/stac_fastapi/core/basic_auth copy.py | 68 -------- .../core/stac_fastapi/core/basic_auth.py | 148 ++++++------------ 2 files changed, 47 insertions(+), 169 deletions(-) delete mode 100644 stac_fastapi/core/stac_fastapi/core/basic_auth copy.py diff --git a/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py b/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py deleted file mode 100644 index 4a108bdd..00000000 --- a/stac_fastapi/core/stac_fastapi/core/basic_auth copy.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Basic Authentication Module.""" - -import logging -import secrets - -from fastapi import Depends, HTTPException, status -from fastapi.security import HTTPBasic, HTTPBasicCredentials -from typing_extensions import Annotated - -_LOGGER = logging.getLogger("uvicorn.default") -_SECURITY = HTTPBasic() - - -class BasicAuth: - - def __init__(self, credentials: list) -> None: - """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. - """ - 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 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. - """ - - 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"}, - ) 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 From 370b4016f7dad2195ed6d53c8da5e2a7b6817d01 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Thu, 13 Jun 2024 11:29:21 +0100 Subject: [PATCH 20/28] Adding route that aren't lists. --- .../stac_fastapi/core/route_dependencies.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index f443ae0e..6e9db21f 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -43,13 +43,27 @@ def get_route_dependencies(route_dependencies_env: str = "") -> list: for route_dependency_conf in route_dependencies_conf: - routes = [] + # seperate out any route 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"]: - route["method"] = method - routes.append(route) + routes.append({**route, "method": method}) + + else: + routes.append(route) dependencies_conf = route_dependency_conf["dependencies"] From 6a7308eca4d6555f955aa372480818db27d67a3f Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 17 Jun 2024 10:45:40 +0100 Subject: [PATCH 21/28] Adding schema validation to route dependencies. --- .../stac_fastapi/core/route_dependencies.py | 204 +++++++++++++----- 1 file changed, 149 insertions(+), 55 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 6e9db21f..6df9e4cd 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -7,87 +7,181 @@ import os from fastapi import Depends +from jsonschema import validate _LOGGER = logging.getLogger("uvicorn.default") -def get_route_dependencies(route_dependencies_env: str = "") -> list: +route_dependencies_schema = { + "type": "array", + "items": { + "type": "object", + "properties": { + "routes": { + "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": "object", + "properties": { + "method": { + "anyOf": [ + {"$ref": "#/$defs/method"}, + { + "type": "array", + "items": {"$ref": "#/$defs/method"}, + "uniqueItems": True, + }, + ] + }, + "args": {"type": "string"}, + "kwargs": { + "type": "object", + "properties": {"type": "string"}, + }, + "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: """ - Route dependencies generator. - - Generate a set of route dependencies for authentication to the - provided FastAPI application. + Get Route dependencies configuration from file or environment variable. """ - route_dependencies_env = os.environ.get( - "STAC_FASTAPI_ROUTE_DEPENDENCIES", route_dependencies_env - ) - route_dependencies = [] + 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) - if route_dependencies_env: - _LOGGER.info("Authentication enabled.") + 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 - 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) + validate(instance=route_dependencies_conf, schema=route_dependencies_schema) - 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 + 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"]: - for route_dependency_conf in route_dependencies_conf: + if isinstance(route["path"], list): + for path in route["path"]: + intermediate_routes.append({**route, "path": path}) - # seperate out any route lists - intermediate_routes = [] - for route in route_dependency_conf["routes"]: + else: + intermediate_routes.append(route) - if isinstance(route["path"], list): - for path in route["path"]: - intermediate_routes.append({**route, "path": path}) + # seperate out any method lists + routes = [] + for route in intermediate_routes: - else: - intermediate_routes.append(route) + if isinstance(route["method"], list): + for method in route["method"]: + routes.append({**route, "method": method}) - # seperate out any method lists - routes = [] - for route in intermediate_routes: + else: + routes.append(route) - if isinstance(route["method"], list): - for method in route["method"]: - routes.append({**route, "method": method}) + return routes - else: - routes.append(route) - dependencies_conf = route_dependency_conf["dependencies"] +def get_dependencies(route_dependency_conf: dict) -> list: + """ + Get dependencies from route dependency configuration. + """ + dependencies = [] + for dependency_conf in route_dependency_conf["dependencies"]: - dependencies = [] - for dependency_conf in dependencies_conf: + module_name, method_name = dependency_conf["method"].rsplit(".", 1) + module = importlib.import_module(module_name) + dependency = getattr(module, method_name) - module_name, method_name = dependency_conf["method"].rsplit(".", 1) + if inspect.isclass(dependency): - module = importlib.import_module(module_name) + dependency = dependency( + *dependency_conf.get("args", []), **dependency_conf.get("kwargs", {}) + ) - dependency = getattr(module, method_name) + dependencies.append(Depends(dependency)) - if inspect.isclass(dependency): + return dependencies - dependency = dependency( - *dependency_conf.get("args", []), - **dependency_conf.get("kwargs", {}) - ) - dependencies.append(Depends(dependency)) +def get_route_dependencies(route_dependencies_env: str = "") -> list: + """ + Route dependencies generator. - route_dependencies.append((routes, dependencies)) + 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 = [] - else: + 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 From a88f04c85ddba63bd4da15ef2459469f28ff9b67 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 17 Jun 2024 11:34:20 +0100 Subject: [PATCH 22/28] pre-commit. --- .../core/stac_fastapi/core/route_dependencies.py | 14 ++++---------- .../stac_fastapi/elasticsearch/app.py | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 6df9e4cd..1b54eb0f 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -88,9 +88,7 @@ def get_route_dependencies_conf(route_dependencies_env: str) -> list: - """ - Get Route dependencies configuration from file or environment variable. - """ + """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) @@ -108,9 +106,7 @@ def get_route_dependencies_conf(route_dependencies_env: str) -> list: def get_routes(route_dependency_conf: dict) -> list: - """ - Get routes from route dependency configuration. - """ + """Get routes from route dependency configuration.""" # seperate out any path lists intermediate_routes = [] for route in route_dependency_conf["routes"]: @@ -137,9 +133,7 @@ def get_routes(route_dependency_conf: dict) -> list: def get_dependencies(route_dependency_conf: dict) -> list: - """ - Get dependencies from route dependency configuration. - """ + """Get dependencies from route dependency configuration.""" dependencies = [] for dependency_conf in route_dependency_conf["dependencies"]: @@ -168,7 +162,7 @@ def get_route_dependencies(route_dependencies_env: str = "") -> list: route_dependencies_env = os.environ.get( "STAC_FASTAPI_ROUTE_DEPENDENCIES", route_dependencies_env ) - route_dependencies = [] + route_dependencies: list[tuple] = [] if not route_dependencies_env: _LOGGER.info("Authentication skipped.") diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py index edca614e..8a85b828 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, From deba51181f6e73eade4132098cf1242665d1f6c2 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 17 Jun 2024 11:55:34 +0100 Subject: [PATCH 23/28] Adding jsonschema install. --- stac_fastapi/core/setup.py | 1 + 1 file changed, 1 insertion(+) 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( From e9bba3630785ad1325dea926bc03af8b4f0f54f5 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 17 Jun 2024 13:31:03 +0100 Subject: [PATCH 24/28] Fixing schema. --- .../stac_fastapi/core/route_dependencies.py | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py index 1b54eb0f..acf06a86 100644 --- a/stac_fastapi/core/stac_fastapi/core/route_dependencies.py +++ b/stac_fastapi/core/stac_fastapi/core/route_dependencies.py @@ -18,50 +18,44 @@ "type": "object", "properties": { "routes": { - "type": "object", - "properties": { - "method": { - "anyOf": [ - {"$ref": "#/$defs/method"}, - { - "type": "array", - "items": {"$ref": "#/$defs/method"}, - "uniqueItems": True, - }, - ] + "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"}, }, - "path": { - "anyOf": [ - {"$ref": "#/$defs/path"}, - { - "type": "array", - "items": {"$ref": "#/$defs/path"}, - "uniqueItems": True, - }, - ] - }, - "type": {"type": "string"}, "required": ["method", "path"], "additionalProperties": False, }, }, "dependencies": { - "type": "object", - "properties": { - "method": { - "anyOf": [ - {"$ref": "#/$defs/method"}, - { - "type": "array", - "items": {"$ref": "#/$defs/method"}, - "uniqueItems": True, - }, - ] - }, - "args": {"type": "string"}, - "kwargs": { - "type": "object", - "properties": {"type": "string"}, + "type": "array", + "items": { + "type": "object", + "properties": { + "method": {"type": "string"}, + "args": {"type": "string"}, + "kwargs": {"type": "object"}, }, "required": ["method"], "additionalProperties": False, @@ -77,11 +71,11 @@ "$defs": { "method": { "type": "string", - "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"], + "enum": ["*", "GET", "POST", "PUT", "PATCH", "DELETE"], }, "path": { "type": "string", - "pattern": r"^\/.*", + "pattern": r"^\*$|\/.*", }, }, } From 87a2294383266c6717c27f5841dc49280cf7d238 Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Mon, 17 Jun 2024 14:36:09 +0100 Subject: [PATCH 25/28] Add docker file example for route_dependencies.json. --- ... docker-compose.route_dependencies_env.yml | 0 docker-compose.route_dependencies_file.yml | 95 +++++++++++++++++++ .../route_dependencies.json | 44 ++++++++- 3 files changed, 134 insertions(+), 5 deletions(-) rename docker-compose.route_dependencies.yml => docker-compose.route_dependencies_env.yml (100%) create mode 100644 docker-compose.route_dependencies_file.yml diff --git a/docker-compose.route_dependencies.yml b/docker-compose.route_dependencies_env.yml similarity index 100% rename from docker-compose.route_dependencies.yml rename to docker-compose.route_dependencies_env.yml diff --git a/docker-compose.route_dependencies_file.yml b/docker-compose.route_dependencies_file.yml new file mode 100644 index 00000000..2d3e5d66 --- /dev/null +++ b/docker-compose.route_dependencies_file.yml @@ -0,0 +1,95 @@ +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 + - 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"]}]}]} + 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" diff --git a/examples/route_dependencies/route_dependencies.json b/examples/route_dependencies/route_dependencies.json index 66ec4097..74d6fca3 100644 --- a/examples/route_dependencies/route_dependencies.json +++ b/examples/route_dependencies/route_dependencies.json @@ -2,19 +2,53 @@ { "routes": [ { - "method": "GET", - "path": "/collections" + "method": "*", + "path": "*" } ], "dependencies": [ { - "method": "fastapi.security.OAuth2PasswordBearer", + "method": "stac_fastapi.core.basic_auth.BasicAuth", "kwargs": { - "tokenUrl": "token" + "credentials": [ + { + "username": "admin", + "password": "admin" + } + ] } + } + ] + }, + { + "routes": [ + { + "path": "/collections/{collection_id}/items/{item_id}", + "method": "GET" + }, + { + "path": "/search", + "method": [ + "GET", + "POST" + ] }, { - "method": "stac_fastapi.conftest.must_be_bob" + "path": "/collections", + "method": "GET" + } + ], + "dependencies": [ + { + "method": "stac_fastapi.core.basic_auth.BasicAuth", + "kwargs": { + "credentials": [ + { + "username": "reader", + "password": "reader" + } + ] + } } ] } From 01b0af5df5f5ebce2929a68fbf3b346ce4b6ba0d Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Tue, 18 Jun 2024 09:51:49 +0100 Subject: [PATCH 26/28] Adding Oauth2 example. --- docker-compose.route_dependencies_file.yml | 34 + .../route_dependencies.json | 9 +- examples/route_dependencies/stac-realm.json | 1847 +++++++++++++++++ 3 files changed, 1883 insertions(+), 7 deletions(-) create mode 100644 examples/route_dependencies/stac-realm.json diff --git a/docker-compose.route_dependencies_file.yml b/docker-compose.route_dependencies_file.yml index 2d3e5d66..116a79c5 100644 --- a/docker-compose.route_dependencies_file.yml +++ b/docker-compose.route_dependencies_file.yml @@ -93,3 +93,37 @@ services: - ./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 index 74d6fca3..21fd7248 100644 --- a/examples/route_dependencies/route_dependencies.json +++ b/examples/route_dependencies/route_dependencies.json @@ -8,14 +8,9 @@ ], "dependencies": [ { - "method": "stac_fastapi.core.basic_auth.BasicAuth", + "method": "fastapi.security.OAuth2PasswordBearer", "kwargs": { - "credentials": [ - { - "username": "admin", - "password": "admin" - } - ] + "tokenUrl": "http://keycloak:8083/auth/realms/stac/protocol/openid-connect/token" } } ] 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 From f9314d9bcd45f735de7e965000618bd063a8590e Mon Sep 17 00:00:00 2001 From: rhysrevans3 Date: Fri, 21 Jun 2024 12:03:41 +0100 Subject: [PATCH 27/28] Updating changelog and opensearch auth in compose files. --- CHANGELOG.md | 7 +++++-- docker-compose.basic_auth_protected.yml | 2 +- docker-compose.route_dependencies_env.yml | 2 +- docker-compose.route_dependencies_file.yml | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86ce67bd..c9ae3e83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Queryables landing page and collection links when the Filter Extension is enabled [#267](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/267) + - 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 -- Added route dependency configuration [#251](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/251) - - Updated stac-fastapi libraries to v3.0.0a1 [#265](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/265) - Updated stac-fastapi libraries to v3.0.0a3 [#269](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/269) +- 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) ### Fixed diff --git a/docker-compose.basic_auth_protected.yml b/docker-compose.basic_auth_protected.yml index 4fabb86d..012a9953 100644 --- a/docker-compose.basic_auth_protected.yml +++ b/docker-compose.basic_auth_protected.yml @@ -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.route_dependencies_env.yml b/docker-compose.route_dependencies_env.yml index df0fb4fa..9adf19b1 100644 --- a/docker-compose.route_dependencies_env.yml +++ b/docker-compose.route_dependencies_env.yml @@ -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":"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 index 116a79c5..3ac11b16 100644 --- a/docker-compose.route_dependencies_file.yml +++ b/docker-compose.route_dependencies_file.yml @@ -56,7 +56,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=/app/route_dependencies/route_dependencies.json ports: - "8082:8082" volumes: From 4ab9d4815ff48c7869fc1837db69fb688c1b48b2 Mon Sep 17 00:00:00 2001 From: Jonathan Healy Date: Sat, 22 Jun 2024 11:56:47 +0800 Subject: [PATCH 28/28] Update CHANGELOG.md Moved changelog entries to Unreleased section. --- CHANGELOG.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ae3e83..b2ab1063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [v3.0.0a2] - ### Added - - Queryables landing page and collection links when the Filter Extension is enabled [#267](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/267) - 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 + - Queryables landing page and collection links when the Filter Extension is enabled [#267](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/267) + ### Changed - Updated stac-fastapi libraries to v3.0.0a1 [#265](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/265) - Updated stac-fastapi libraries to v3.0.0a3 [#269](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/269) -- 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) ### Fixed @@ -239,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]: