-
Notifications
You must be signed in to change notification settings - Fork 24
Adding route dependencies module. #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 26 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
5ee3c2d
Adding route dependencies module.
rhysrevans3 10d621e
Fixing pre-commit errors.
rhysrevans3 bb04359
Merge branch 'main' into route_dependencies
jonhealy1 c2c12bd
Adding to opensearch.
rhysrevans3 7b8ef5a
Adding testing.
rhysrevans3 aedf077
Merge branch 'main' into route_dependencies
jonhealy1 6a36f6b
Allow route dependencies to be passed as a variable for testing.
rhysrevans3 19b97f6
Merge branch 'route_dependencies' of github.com:rhysrevans3/stac-fast…
rhysrevans3 211d04d
Removing context extension.
rhysrevans3 8c14390
Allowing fuctions or class dependencies.
rhysrevans3 4b90d15
Updating test conf.
rhysrevans3 6891357
Fix for import error.
rhysrevans3 d718601
Add test directory to sys path for import.
rhysrevans3 a1d3929
Fix for sys path import.
rhysrevans3 65d367c
Add test dir to sys path.
rhysrevans3 53f17f8
Switching tests to use collections endpoint for rd tests.
rhysrevans3 51de3bf
Switch from post to get on test_authenticated.
rhysrevans3 ffd3912
Removing unneeded length check.
rhysrevans3 bde4b3c
Adding docker compose file and readme help for route dependencies.
rhysrevans3 bd29dcd
Merge branch 'main' of github.com:stac-utils/stac-fastapi-elasticsear…
rhysrevans3 cc1a30e
Adding to changelog.
rhysrevans3 caa45dd
Merge branch 'main' into route_dependencies
jonhealy1 fb931bc
Moving basic auth to route dependencies.
rhysrevans3 c08d25c
Merge branch 'route_dependencies' of github.com:rhysrevans3/stac-fast…
rhysrevans3 b689275
Removing basic_auth copy.
rhysrevans3 370b401
Adding route that aren't lists.
rhysrevans3 6a7308e
Adding schema validation to route dependencies.
rhysrevans3 a88f04c
pre-commit.
rhysrevans3 b5231f4
Merge branch 'main' of github.com:stac-utils/stac-fastapi-elasticsear…
rhysrevans3 deba511
Adding jsonschema install.
rhysrevans3 e9bba36
Fixing schema.
rhysrevans3 87a2294
Add docker file example for route_dependencies.json.
rhysrevans3 9617d39
Merge branch 'main' of github.com:stac-utils/stac-fastapi-elasticsear…
rhysrevans3 01b0af5
Adding Oauth2 example.
rhysrevans3 f9314d9
Updating changelog and opensearch auth in compose files.
rhysrevans3 4ab9d48
Update CHANGELOG.md
jonhealy1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[ | ||
{ | ||
"routes": [ | ||
{ | ||
"method": "GET", | ||
"path": "/collections" | ||
} | ||
], | ||
"dependencies": [ | ||
{ | ||
"method": "fastapi.security.OAuth2PasswordBearer", | ||
"kwargs": { | ||
"tokenUrl": "token" | ||
} | ||
}, | ||
{ | ||
"method": "stac_fastapi.conftest.must_be_bob" | ||
} | ||
] | ||
} | ||
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.