Skip to content

Commit

Permalink
Merge pull request #19 from Healy-Hyperspatial/move-basic-auth
Browse files Browse the repository at this point in the history
GET basic auth from stac-fastapi.core
  • Loading branch information
jonhealy1 authored May 4, 2024
2 parents 5a345a2 + 4d0e7c3 commit 226c95e
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 83 deletions.
41 changes: 1 addition & 40 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,43 +61,4 @@ jobs:
MONGO_DB: stac
MONGO_USER: root
MONGO_PASS: example
MONGO_PORT: 27017

- name: Run test suite against Mongo w/ Basic Auth
run: |
pipenv run pytest -k "basic_auth" -svvv
env:
MONGO_HOST: 172.17.0.1
BACKEND: mongo
APP_HOST: 0.0.0.0
APP_PORT: 8084
ENVIRONMENT: testing
MONGO_DB: stac
MONGO_USER: root
MONGO_PASS: example
MONGO_PORT: 27017
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"]}
]
}
]
}
MONGO_PORT: 27017
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0/

## [Unreleased]


## [v3.2.0]

### Changed

- Moved core basic auth logic to stac-fastapi.core.
- Moved core basic auth logic to stac-fastapi.core. [#19](https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/
- Updated stac-fastapi.core to v2.4.0. [#19](https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/


## [v3.1.0]
Expand Down Expand Up @@ -54,7 +58,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0/

----

[Unreleased]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/compare/v3.1.0...main>
[Unreleased]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/compare/v3.2.0...main>
[v3.2.0]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/compare/v3.1.0...v3.2.0>
[v3.1.0]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/compare/v3.0.1...v3.1.0>
[v3.0.1]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/compare/v3.0.0...v3.0.1>
[v3.0.0]: <https://github.com/Healy-Hyperspatial/stac-fastapi-mongo/tree/v3.0.0>
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

setup(
name="stac-fastapi.mongo",
version="3.1.0",
version="3.2.0",
description="Mongodb stac-fastapi backend.",
long_description=desc,
long_description_content_type="text/markdown",
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/mongo/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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,
EsAsyncBaseFiltersClient,
Expand All @@ -17,7 +18,6 @@
TokenPaginationExtension,
TransactionExtension,
)
from stac_fastapi.core.basic_auth import apply_basic_auth

# from stac_fastapi.extensions.third_party import BulkTransactionExtension
from stac_fastapi.mongo.config import AsyncMongoDBSettings
Expand Down
95 changes: 61 additions & 34 deletions stac_fastapi/tests/basic_auth/test_basic_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,92 @@

import pytest

# - 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"]}]}]}


@pytest.mark.asyncio
async def test_get_search_not_authenticated(app_client_basic_auth):
"""Test public endpoint search without authentication"""
async def test_get_search_not_authenticated(app_client_basic_auth, ctx):
"""Test public endpoint [GET /search] without authentication"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()
params = {"query": '{"gsd": {"gt": 14}}'}
params = {"id": ctx.item["id"]}

response = await app_client_basic_auth.get("/search", params=params)

assert response.status_code == 200
assert response.json() == {
"type": "FeatureCollection",
"features": [],
"links": [],
"context": {"returned": 0, "limit": 10, "matched": 0},
}
assert response.status_code == 200, response
assert response.json()["features"][0]["geometry"] == ctx.item["geometry"]


@pytest.mark.asyncio
async def test_post_search_authenticated(app_client_basic_auth):
"""Test protected post search with reader auhtentication"""
async def test_post_search_authenticated(app_client_basic_auth, ctx):
"""Test protected endpoint [POST /search] with reader auhtentication"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()
params = {
"bbox": [97.504892, -45.254738, 174.321298, -2.431580],
"fields": {"exclude": ["properties"]},
}
params = {"id": ctx.item["id"]}
headers = {"Authorization": "Basic cmVhZGVyOnJlYWRlcg=="}

response = await app_client_basic_auth.post("/search", json=params, headers=headers)

assert response.status_code == 200
assert response.json() == {
"type": "FeatureCollection",
"features": [],
"links": [],
"context": {"returned": 0, "limit": 10, "matched": 0},
}
assert response.status_code == 200, response
assert response.json()["features"][0]["geometry"] == ctx.item["geometry"]


@pytest.mark.asyncio
async def test_delete_resource_anonymous(
app_client_basic_auth,
):
"""Test protected endpoint [DELETE /collections/{collection_id}] without auhtentication"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()

response = await app_client_basic_auth.delete("/collections/test-collection")

assert response.status_code == 401
assert response.json() == {"detail": "Not authenticated"}


@pytest.mark.asyncio
async def test_delete_resource_insufficient_permissions(app_client_basic_auth):
"""Test protected delete collection with reader auhtentication"""
async def test_delete_resource_invalid_credentials(app_client_basic_auth, ctx):
"""Test protected endpoint [DELETE /collections/{collection_id}] with invalid credentials"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()
headers = {
"Authorization": "Basic cmVhZGVyOnJlYWRlcg=="
} # Assuming this is a valid authorization token

headers = {"Authorization": "Basic YWRtaW46cGFzc3dvcmQ="}

response = await app_client_basic_auth.delete(
"/collections/test-collection", headers=headers
f"/collections/{ctx.collection['id']}", headers=headers
)

assert (
response.status_code == 403
) # Expecting a 403 status code for insufficient permissions
assert response.status_code == 401
assert response.json() == {"detail": "Incorrect username or password"}


@pytest.mark.asyncio
async def test_delete_resource_insufficient_permissions(app_client_basic_auth, ctx):
"""Test protected endpoint [DELETE /collections/{collection_id}] with reader user which has insufficient permissions"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()

headers = {"Authorization": "Basic cmVhZGVyOnJlYWRlcg=="}

response = await app_client_basic_auth.delete(
f"/collections/{ctx.collection['id']}", headers=headers
)

assert response.status_code == 403
assert response.json() == {
"detail": "Insufficient permissions for [DELETE /collections/test-collection]"
}


@pytest.mark.asyncio
async def test_delete_resource_sufficient_permissions(app_client_basic_auth, ctx):
"""Test protected endpoint [DELETE /collections/{collection_id}] with admin user which has sufficient permissions"""
if not os.getenv("BASIC_AUTH"):
pytest.skip()

headers = {"Authorization": "Basic YWRtaW46YWRtaW4="}

response = await app_client_basic_auth.delete(
f"/collections/{ctx.collection['id']}", headers=headers
)

assert response.status_code == 204
33 changes: 29 additions & 4 deletions stac_fastapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

from stac_fastapi.api.app import StacApi
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
from stac_fastapi.core.basic_auth import apply_basic_auth
from stac_fastapi.core.core import (
BulkTransactionsClient,
CoreClient,
TransactionsClient,
)
from stac_fastapi.core.extensions import QueryExtension
from stac_fastapi.mongo.basic_auth import apply_basic_auth

if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch":
from stac_fastapi.opensearch.config import AsyncOpensearchSettings as AsyncSettings
Expand Down Expand Up @@ -227,7 +227,7 @@ async def app_client(app):


@pytest_asyncio.fixture(scope="session")
async def app_auth():
async def app_basic_auth():
settings = AsyncSettings()
extensions = [
TransactionExtension(
Expand Down Expand Up @@ -259,14 +259,39 @@ async def app_auth():
search_post_request_model=post_request_model,
)

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


@pytest_asyncio.fixture(scope="session")
async def app_client_basic_auth(app_auth):
async def app_client_basic_auth(app_basic_auth):
await create_collection_index()

async with AsyncClient(app=app_auth, base_url="http://test-server") as c:
async with AsyncClient(app=app_basic_auth, base_url="http://test-server") as c:
yield c
20 changes: 19 additions & 1 deletion stac_fastapi/tests/resources/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,25 @@ async def test_returns_valid_collection(ctx, app_client):


@pytest.mark.asyncio
async def test_collection_extensions(ctx, app_client):
async def test_collection_extensions_post(ctx, app_client):
"""Test that extensions can be used to define additional top-level properties"""
ctx.collection.get("stac_extensions", []).append(
"https://stac-extensions.github.io/item-assets/v1.0.0/schema.json"
)
test_asset = {"title": "test", "description": "test", "type": "test"}
ctx.collection["item_assets"] = {"test": test_asset}
ctx.collection["id"] = "test-item-assets"
resp = await app_client.post("/collections", json=ctx.collection)

assert resp.status_code == 200
assert resp.json().get("item_assets", {}).get("test") == test_asset


@pytest.mark.skip(
reason="https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/issues/238"
)
@pytest.mark.asyncio
async def test_collection_extensions_put(ctx, app_client):
"""Test that extensions can be used to define additional top-level properties"""
ctx.collection.get("stac_extensions", []).append(
"https://stac-extensions.github.io/item-assets/v1.0.0/schema.json"
Expand Down

0 comments on commit 226c95e

Please sign in to comment.