Skip to content

Commit 28c64c8

Browse files
committed
Merge branch 'main' into examples/oidc-docker-compose
2 parents 61fedb4 + 8f3ac8c commit 28c64c8

File tree

5 files changed

+147
-114
lines changed

5 files changed

+147
-114
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ ENV PYTHONUNBUFFERED=1
1414

1515
RUN uv sync --no-dev --locked
1616

17-
CMD ["uv", "run", "--locked", "python", "-m", "stac_auth_proxy"]
17+
CMD ["uv", "run", "--locked", "--no-dev", "python", "-m", "stac_auth_proxy"]

README.md

Lines changed: 140 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -53,82 +53,75 @@ uvicorn --factory stac_auth_proxy:create_app
5353

5454
The application is configurable via environment variables.
5555

56-
- `UPSTREAM_URL`
57-
- The STAC API to proxy requests to
58-
- **Type:** HTTP(S) URL
59-
- **Required:** Yes
60-
- **Example:** `https://your-stac-api.com/stac`
61-
- `OIDC_DISCOVERY_URL`
62-
- OpenID Connect discovery document URL
63-
- **Type:** HTTP(S) URL
64-
- **Required:** Yes
65-
- **Example:** `https://auth.example.com/.well-known/openid-configuration`
66-
- `OIDC_DISCOVERY_INTERNAL_URL`
67-
- The internal network OpenID Connect discovery document URL
68-
- **Type:** HTTP(S) URL
69-
- **Required:** No, defaults to the value of `OIDC_DISCOVERY_URL`
70-
- **Example:** `http://auth/.well-known/openid-configuration`
71-
- `DEFAULT_PUBLIC`
72-
- **Description:** Default access policy for endpoints
73-
- **Type:** boolean
74-
- **Required:** No, defaults to `false`
75-
- **Example:** `false`, `1`, `True`
76-
- `PRIVATE_ENDPOINTS`
77-
- **Description:** Endpoints explicitly marked as requiring authentication, for use when `DEFAULT_PUBLIC == True`
78-
- **Type:** JSON object mapping regex patterns to HTTP methods OR tuples of HTTP methods and an array of strings representing required scopes
79-
- **Required:** No, defaults to the following:
80-
```json
81-
{
82-
"^/collections$": ["POST"],
83-
"^/collections/([^/]+)$": ["PUT", "PATCH", "DELETE"],
84-
"^/collections/([^/]+)/items$": ["POST"],
85-
"^/collections/([^/]+)/items/([^/]+)$": ["PUT", "PATCH", "DELETE"],
86-
"^/collections/([^/]+)/bulk_items$": ["POST"]
87-
}
88-
```
89-
- `PUBLIC_ENDPOINTS`
90-
- **Description:** Endpoints explicitly marked as not requiring authentication, for use when `DEFAULT_PUBLIC == False`
91-
- **Type:** JSON object mapping regex patterns to HTTP methods
92-
- **Required:** No, defaults to the following:
93-
```json
94-
{
95-
"^/api.html$": ["GET"],
96-
"^/api$": ["GET"]
97-
}
98-
```
99-
- `OPENAPI_SPEC_ENDPOINT`
100-
- Path to serve OpenAPI specification
101-
- **Type:** string or null
102-
- **Required:** No, defaults to `null` (disabled)
103-
- **Example:** `/api`
104-
- `ITEMS_FILTER`
105-
- Configuration for item-level filtering
106-
- **Type:** JSON object with class configuration
107-
- **Required:** No, defaults to `null` (disabled)
108-
- Components:
109-
- `cls`: Python import path
110-
- `args`: List of positional arguments
111-
- `kwargs`: Dictionary of keyword arguments
112-
- **Example:**
113-
```json
114-
{
115-
"cls": "my_package.filters.OrganizationFilter",
116-
"args": ["org1"],
117-
"kwargs": {
118-
"field_name": "properties.organization"
56+
- Core
57+
- **`UPSTREAM_URL`**, STAC API URL
58+
- **Type:** HTTP(S) URL
59+
- **Required:** Yes
60+
- **Example:** `https://your-stac-api.com/stac`
61+
- **`WAIT_FOR_UPSTREAM`**, wait for upstream API to become available before starting proxy
62+
- **Type:** boolean
63+
- **Required:** No, defaults to `true`
64+
- **Example:** `false`, `1`, `True`
65+
- **`HEALTHZ_PREFIX`**, path prefix for health check endpoints
66+
- **Type:** string
67+
- **Required:** No, defaults to `/healthz`
68+
- **Example:** `''` (disabled)
69+
- Authentication
70+
- **`OIDC_DISCOVERY_URL`**, OpenID Connect discovery document URL
71+
- **Type:** HTTP(S) URL
72+
- **Required:** Yes
73+
- **Example:** `https://auth.example.com/.well-known/openid-configuration`
74+
- **`OIDC_DISCOVERY_INTERNAL_URL`**, internal network OpenID Connect discovery document URL
75+
- **Type:** HTTP(S) URL
76+
- **Required:** No, defaults to the value of `OIDC_DISCOVERY_URL`
77+
- **Example:** `http://auth/.well-known/openid-configuration`
78+
- **`DEFAULT_PUBLIC`**, default access policy for endpoints
79+
- **Type:** boolean
80+
- **Required:** No, defaults to `false`
81+
- **Example:** `false`, `1`, `True`
82+
- **`PRIVATE_ENDPOINTS`**, endpoints explicitly marked as requiring authentication, used when `DEFAULT_PUBLIC == True`
83+
- **Type:** JSON object mapping regex patterns to HTTP methods OR tuples of HTTP methods and an array of strings representing required scopes
84+
- **Required:** No, defaults to the following:
85+
```json
86+
{
87+
"^/collections$": ["POST"],
88+
"^/collections/([^/]+)$": ["PUT", "PATCH", "DELETE"],
89+
"^/collections/([^/]+)/items$": ["POST"],
90+
"^/collections/([^/]+)/items/([^/]+)$": ["PUT", "PATCH", "DELETE"],
91+
"^/collections/([^/]+)/bulk_items$": ["POST"]
11992
}
120-
}
121-
```
122-
- `ITEMS_FILTER_ENDPOINTS`
123-
- Where to apply item filtering
124-
- **Type:** JSON object mapping regex patterns to HTTP methods
125-
- **Required:** No, defaults to the following:
126-
```json
127-
{
128-
"^/search$": ["GET", "POST"],
129-
"^/collections/([^/]+)/items$": ["GET", "POST"]
130-
}
131-
```
93+
```
94+
- **`PUBLIC_ENDPOINTS`**, endpoints explicitly marked as not requiring authentication, used when `DEFAULT_PUBLIC == False`
95+
- **Type:** JSON object mapping regex patterns to HTTP methods
96+
- **Required:** No, defaults to the following:
97+
```json
98+
{
99+
"^/api.html$": ["GET"],
100+
"^/api$": ["GET"]
101+
}
102+
```
103+
- **`OPENAPI_SPEC_ENDPOINT`**, path of OpenAPI specification, used for augmenting spec response with auth configuration
104+
- **Type:** string or null
105+
- **Required:** No, defaults to `null` (disabled)
106+
- **Example:** `/api`
107+
- Filtering
108+
- **`ITEMS_FILTER`**, [cql2 expression](https://developmentseed.org/cql2-rs/latest/python/#cql2.Expr) generator for item-level filtering
109+
- **Type:** JSON object with class configuration
110+
- **Required:** No, defaults to `null` (disabled)
111+
- **Components**:
112+
- `cls`: Python import path
113+
- `args`: List of positional arguments
114+
- `kwargs`: Dictionary of keyword arguments
115+
- **Example:**
116+
```json
117+
{
118+
"cls": "my_package.filters.OrganizationFilter",
119+
"args": ["org1"],
120+
"kwargs": {
121+
"field_name": "properties.organization"
122+
}
123+
}
124+
```
132125

133126
### Customization
134127

@@ -140,31 +133,31 @@ While the project is designed to work out-of-the-box as an application, it might
140133

141134
The majority of the proxy's functionality occurs within a chain of middlewares. Each request passes through this chain, wherein each middleware performs a specific task:
142135

143-
1. **EnforceAuthMiddleware**
136+
1. **`EnforceAuthMiddleware`**
144137

145138
- Handles authentication and authorization
146139
- Configurable public/private endpoints
147140
- OIDC integration
148141
- Places auth token payload in request state
149142

150-
2. **BuildCql2FilterMiddleware**
143+
2. **`BuildCql2FilterMiddleware`**
151144

152145
- Builds CQL2 filters based on request context/state
153146
- Places [CQL2 expression](http://developmentseed.org/cql2-rs/latest/python/#cql2.Expr) in request state
154147

155-
3. **ApplyCql2FilterMiddleware**
148+
3. **`ApplyCql2FilterMiddleware`**
156149

157150
- Retrieves [CQL2 expression](http://developmentseed.org/cql2-rs/latest/python/#cql2.Expr) from request state
158151
- Augments request with CQL2 filter:
159152
- Modifies query strings for `GET` requests
160153
- Modifies JSON bodies for `POST`/`PUT`/`PATCH` requests
161154

162-
4. **OpenApiMiddleware**
155+
4. **`OpenApiMiddleware`**
163156

164157
- Modifies OpenAPI specification based on endpoint configuration, adding security requirements
165158
- Only active if `openapi_spec_endpoint` is configured
166159

167-
5. **AddProcessTimeHeaderMiddleware**
160+
5. **`AddProcessTimeHeaderMiddleware`**
168161
- Adds processing time headers
169162
- Useful for monitoring/debugging
170163

@@ -178,31 +171,79 @@ The system supports generating CQL2 filters based on request context to provide
178171
> [!TIP]
179172
> Integration with external authorization systems (e.g. [Open Policy Agent](https://www.openpolicyagent.org/)) can be achieved by specifying an `ITEMS_FILTER` that points to a class/function that, once initialized, returns a [`cql2.Expr` object](https://developmentseed.org/cql2-rs/latest/python/#cql2.Expr) when called with the request context.
180173

174+
#### Filters
175+
176+
If enabled, filters are intended to be applied to the following endpoints:
177+
178+
- `GET /search`
179+
- **Supported:** ✅
180+
- **Action:** Read Item
181+
- **Applied Filter:** `ITEMS_FILTER`
182+
- **Strategy:** Append query params with generated CQL2 query.
183+
- `POST /search`
184+
- **Supported:** ✅
185+
- **Action:** Read Item
186+
- **Applied Filter:** `ITEMS_FILTER`
187+
- **Strategy:** Append body with generated CQL2 query.
188+
- `GET /collections/{collection_id}`
189+
- **Supported:** ❌ ([#23](https://github.com/developmentseed/stac-auth-proxy/issues/23))
190+
- **Action:** Read Collection
191+
- **Applied Filter:** `COLLECTIONS_FILTER`
192+
- **Strategy:** Append query params with generated CQL2 query.
193+
- `GET /collections/{collection_id}/items`
194+
- **Supported:** ✅
195+
- **Action:** Read Item
196+
- **Applied Filter:** `ITEMS_FILTER`
197+
- **Strategy:** Append query params with generated CQL2 query.
198+
- `GET /collections/{collection_id}/items/{item_id}`
199+
- **Supported:** ❌ ([#25](https://github.com/developmentseed/stac-auth-proxy/issues/25))
200+
- **Action:** Read Item
201+
- **Applied Filter:** `ITEMS_FILTER`
202+
- **Strategy:** Validate response against CQL2 query.
203+
- `POST /collections/`
204+
- **Supported:** ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22))
205+
- **Action:** Create Collection
206+
- **Applied Filter:** `COLLECTIONS_FILTER`
207+
- **Strategy:** Validate body with generated CQL2 query.
208+
- `PUT /collections/{collection_id}}`
209+
- **Supported:** ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22))
210+
- **Action:** Update Collection
211+
- **Applied Filter:** `COLLECTIONS_FILTER`
212+
- **Strategy:** Fetch Collection and validate CQL2 query; merge Item with body and validate with generated CQL2 query.
213+
- `DELETE /collections/{collection_id}`
214+
- **Supported:** ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22))
215+
- **Action:** Delete Collection
216+
- **Applied Filter:** `COLLECTIONS_FILTER`
217+
- **Strategy:** Fetch Collectiion and validate with CQL2 query.
218+
- `POST /collections/{collection_id}/items`
219+
- **Supported:** ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21))
220+
- **Action:** Create Item
221+
- **Applied Filter:** `ITEMS_FILTER`
222+
- **Strategy:** Validate body with generated CQL2 query.
223+
- `PUT /collections/{collection_id}/items/{item_id}`
224+
- **Supported:** ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21))
225+
- **Action:** Update Item
226+
- **Applied Filter:** `ITEMS_FILTER`
227+
- **Strategy:** Fetch Item and validate CQL2 query; merge Item with body and validate with generated CQL2 query.
228+
- `DELETE /collections/{collection_id}/items/{item_id}`
229+
- **Supported:** ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21))
230+
- **Action:** Delete Item
231+
- **Applied Filter:** `ITEMS_FILTER`
232+
- **Strategy:** Fetch Item and validate with CQL2 query.
233+
- `POST /collections/{collection_id}/bulk_items`
234+
- **Supported:** ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21))
235+
- **Action:** Create Items
236+
- **Applied Filter:** `ITEMS_FILTER`
237+
- **Strategy:** Validate items in body with generated CQL2 query.
238+
181239
#### Example GET Request Flow
182240

183241
```mermaid
184242
sequenceDiagram
185243
Client->>Proxy: GET /collections
186244
Note over Proxy: EnforceAuth checks credentials
187-
Note over Proxy: BuildCql2Filter creates filter immediately
188-
Note over Proxy: ApplyCql2Filter modifies query string
245+
Note over Proxy: BuildCql2Filter creates filter
246+
Note over Proxy: ApplyCql2Filter applies filter to request
189247
Proxy->>STAC API: GET /collection?filter=(collection=landsat)
190248
STAC API->>Client: Response
191249
```
192-
193-
#### Filters
194-
195-
| Supported | Method | Endpoint | Action | Filter | Strategy |
196-
| -------------------------------------------------------- | -------- | ---------------------------------------------- | ------ | ---------- | ------------------------------------------------------------------------------------------------------ |
197-
|| `POST` | `/search` | Read | Item | Append body with generated CQL2 query. |
198-
|| `GET` | `/search` | Read | Item | Append query params with generated CQL2 query. |
199-
| ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22)) | `POST` | `/collections/` | Create | Collection | Validate body with generated CQL2 query. |
200-
| ❌ ([#23](https://github.com/developmentseed/stac-auth-proxy/issues/23)) | `GET` | `/collections/{collection_id}` | Read | Collection | Append query params with generated CQL2 query. |
201-
| ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22)) | `PUT` | `/collections/{collection_id}}` | Update | Collection | Fetch Collection and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
202-
| ❌ ([#22](https://github.com/developmentseed/stac-auth-proxy/issues/22)) | `DELETE` | `/collections/{collection_id}` | Delete | Collection | Fetch Collectiion and validate with CQL2 query. |
203-
|| `GET` | `/collections/{collection_id}/items` | Read | Item | Append query params with generated CQL2 query. |
204-
| ❌ ([#25](https://github.com/developmentseed/stac-auth-proxy/issues/25)) | `GET` | `/collections/{collection_id}/items/{item_id}` | Read | Item | Validate response against CQL2 query. |
205-
| ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21)) | `POST` | `/collections/{collection_id}/items` | Create | Item | Validate body with generated CQL2 query. |
206-
| ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21)) | `PUT` | `/collections/{collection_id}/items/{item_id}` | Update | Item | Fetch Item and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
207-
| ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21)) | `DELETE` | `/collections/{collection_id}/items/{item_id}` | Delete | Item | Fetch Item and validate with CQL2 query. |
208-
| ❌ ([#21](https://github.com/developmentseed/stac-auth-proxy/issues/21)) | `POST` | `/collections/{collection_id}/bulk_items` | Create | Item | Validate items in body with generated CQL2 query. |

src/stac_auth_proxy/app.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,13 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
3131
#
3232
# Application
3333
#
34-
upstream_urls = [
35-
settings.upstream_url,
36-
settings.oidc_discovery_internal_url or settings.oidc_discovery_url,
37-
]
34+
upstream_urls = (
35+
[settings.upstream_url, settings.oidc_discovery_internal_url]
36+
if settings.wait_for_upstream
37+
else []
38+
)
3839
lifespan = LifespanManager(
39-
on_startup=(
40-
[ServerHealthCheck(url=url) for url in upstream_urls]
41-
if settings.wait_for_upstream
42-
else []
43-
)
40+
on_startup=([ServerHealthCheck(url=url) for url in upstream_urls])
4441
)
4542

4643
app = FastAPI(

src/stac_auth_proxy/config.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ class Settings(BaseSettings):
6565

6666
# Filters
6767
items_filter: Optional[ClassInput] = None
68-
items_filter_endpoints: Optional[EndpointMethods] = {
69-
r"^/search$": ["GET", "POST"],
70-
r"^/collections/([^/]+)/items$": ["GET", "POST"],
71-
}
7268

7369
model_config = SettingsConfigDict()
7470

src/stac_auth_proxy/middleware/BuildCql2FilterMiddleware.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ async def receive_build_filter() -> Message:
7575
def _get_filter(self, path: str) -> Optional[Callable[..., Expr]]:
7676
"""Get the CQL2 filter builder for the given path."""
7777
endpoint_filters = [
78-
# TODO: Use collections_filter_endpoints & items_filter_endpoints
7978
(filters.is_collection_endpoint, self.collections_filter),
8079
(filters.is_item_endpoint, self.items_filter),
8180
(filters.is_search_endpoint, self.items_filter),

0 commit comments

Comments
 (0)