|
22 | 22 | from fastapi.responses import JSONResponse |
23 | 23 |
|
24 | 24 | from geoapi.config import settings |
25 | | -from geoapi.deps.auth import get_user_id, oauth2_scheme |
| 25 | +from geoapi.deps.auth import get_optional_user_id, get_user_id, oauth2_scheme |
26 | 26 | from geoapi.models.processes import ( |
27 | 27 | OGC_EXCEPTION_NO_SUCH_JOB, |
28 | 28 | OGC_EXCEPTION_NO_SUCH_PROCESS, |
|
51 | 51 |
|
52 | 52 | router = APIRouter(tags=["Processes"]) |
53 | 53 |
|
| 54 | +# Processes that can be executed without authentication (read-only analytics) |
| 55 | +# These are sync processes that only query data and don't modify anything |
| 56 | +PUBLIC_ALLOWED_PROCESSES = frozenset( |
| 57 | + { |
| 58 | + "feature-count", |
| 59 | + "unique-values", |
| 60 | + "class-breaks", |
| 61 | + "area-statistics", |
| 62 | + "extent", |
| 63 | + "aggregation-stats", |
| 64 | + "histogram", |
| 65 | + } |
| 66 | +) |
| 67 | + |
| 68 | + |
| 69 | +def is_public_allowed_process(process_id: str) -> bool: |
| 70 | + """Check if a process can be executed without authentication. |
| 71 | +
|
| 72 | + Only read-only sync analytics processes are allowed for public access. |
| 73 | + These processes only query data and don't create jobs or modify state. |
| 74 | +
|
| 75 | + Args: |
| 76 | + process_id: The process identifier |
| 77 | +
|
| 78 | + Returns: |
| 79 | + True if the process can be executed publicly, False otherwise |
| 80 | + """ |
| 81 | + return process_id in PUBLIC_ALLOWED_PROCESSES |
| 82 | + |
54 | 83 |
|
55 | 84 | def _execute_analytics_sync(process_id: str, inputs: dict[str, Any]) -> dict[str, Any]: |
56 | 85 | """Execute an analytics process synchronously. |
@@ -343,25 +372,36 @@ async def execute_process( |
343 | 372 | request: Request, |
344 | 373 | process_id: str, |
345 | 374 | execute_request: ExecuteRequest, |
346 | | - user_id: UUID = Depends(get_user_id), |
| 375 | + user_id: UUID | None = Depends(get_optional_user_id), |
347 | 376 | access_token: str | None = Depends(oauth2_scheme), |
348 | 377 | ) -> JSONResponse: |
349 | 378 | """Execute a process. |
350 | 379 |
|
351 | 380 | For analytics processes (feature-count, class-breaks, unique-values, area-statistics): |
352 | 381 | Returns results immediately (HTTP 200). |
| 382 | + These can be executed without authentication (public access). |
353 | 383 |
|
354 | 384 | For async tool processes (buffer, clip, etc.): |
355 | 385 | Creates a job and returns status info with job ID (HTTP 201). |
| 386 | + These REQUIRE authentication. |
356 | 387 | Results can be retrieved via /jobs/{jobId}/results. |
357 | 388 | """ |
358 | 389 | base_url = get_base_url(request) |
359 | 390 |
|
360 | | - # Check if this is an analytics process (sync execution) |
361 | | - if analytics_registry.is_analytics_process(process_id): |
| 391 | + # Check if this is a public-allowed analytics process (sync execution) |
| 392 | + if is_public_allowed_process(process_id): |
| 393 | + # Analytics processes are read-only and don't need authentication |
362 | 394 | result = _execute_analytics_sync(process_id, execute_request.inputs) |
363 | 395 | return JSONResponse(status_code=200, content=result) |
364 | 396 |
|
| 397 | + # For all other processes, authentication is required |
| 398 | + if not user_id: |
| 399 | + raise HTTPException( |
| 400 | + status_code=status.HTTP_401_UNAUTHORIZED, |
| 401 | + detail="Authentication required for this process", |
| 402 | + headers={"WWW-Authenticate": "Bearer"}, |
| 403 | + ) |
| 404 | + |
365 | 405 | # For async processes, verify tool exists |
366 | 406 | tool_info = tool_registry.get_tool(process_id) |
367 | 407 | if not tool_info: |
|
0 commit comments