Praneshrajan15 commited on
Commit
aa39862
·
verified ·
1 Parent(s): 791c076

Deploy DataForge playground API

Browse files
dataforge/release/playground_check.py CHANGED
@@ -204,6 +204,12 @@ def _check_cors(
204
  headers={"Origin": origin},
205
  )
206
  negative, negative_latency_ms = _timed_request(
 
 
 
 
 
 
207
  client,
208
  "OPTIONS",
209
  f"{backend_url}/api/health",
@@ -213,12 +219,19 @@ def _check_cors(
213
  },
214
  )
215
  allowed_origin = positive.headers.get("access-control-allow-origin")
216
- rejected_origin = negative.headers.get("access-control-allow-origin")
217
- ok = allowed_origin == origin and rejected_origin is None
 
 
 
 
 
 
 
218
  detail = (
219
- "CORS allows only the configured frontend origin."
220
  if ok
221
- else "CORS allowlist is incorrect."
222
  )
223
  return PlaygroundCheck(
224
  "cors_correct",
@@ -229,9 +242,13 @@ def _check_cors(
229
  "positive_status_code": positive.status_code,
230
  "negative_status_code": negative.status_code,
231
  "allowed_origin": allowed_origin,
232
- "negative_allowed_origin": rejected_origin,
 
 
 
233
  "positive_latency_ms": round(positive_latency_ms, 2),
234
  "negative_latency_ms": round(negative_latency_ms, 2),
 
235
  },
236
  )
237
  except Exception as exc:
 
204
  headers={"Origin": origin},
205
  )
206
  negative, negative_latency_ms = _timed_request(
207
+ client,
208
+ "GET",
209
+ f"{backend_url}/api/health",
210
+ headers={"Origin": NEGATIVE_CORS_ORIGIN},
211
+ )
212
+ preflight, preflight_latency_ms = _timed_request(
213
  client,
214
  "OPTIONS",
215
  f"{backend_url}/api/health",
 
219
  },
220
  )
221
  allowed_origin = positive.headers.get("access-control-allow-origin")
222
+ negative_allowed_origin = negative.headers.get("access-control-allow-origin")
223
+ preflight_allowed_origin = preflight.headers.get("access-control-allow-origin")
224
+ negative_error = ""
225
+ try:
226
+ negative_error = str(negative.json().get("error", ""))
227
+ except ValueError:
228
+ negative_error = ""
229
+ negative_denied = negative.status_code == 403 and negative_error == "origin_not_allowed"
230
+ ok = allowed_origin == origin and positive.status_code == 200 and negative_denied
231
  detail = (
232
+ "Configured origin is allowed and disallowed origins cannot read API data."
233
  if ok
234
+ else "CORS origin enforcement is incorrect."
235
  )
236
  return PlaygroundCheck(
237
  "cors_correct",
 
242
  "positive_status_code": positive.status_code,
243
  "negative_status_code": negative.status_code,
244
  "allowed_origin": allowed_origin,
245
+ "negative_allowed_origin": negative_allowed_origin,
246
+ "negative_error": negative_error,
247
+ "negative_preflight_status_code": preflight.status_code,
248
+ "negative_preflight_allowed_origin": preflight_allowed_origin,
249
  "positive_latency_ms": round(positive_latency_ms, 2),
250
  "negative_latency_ms": round(negative_latency_ms, 2),
251
+ "negative_preflight_latency_ms": round(preflight_latency_ms, 2),
252
  },
253
  )
254
  except Exception as exc:
playground/api/app.py CHANGED
@@ -13,6 +13,7 @@ import asyncio
13
  import io
14
  import logging
15
  import os
 
16
  import tempfile
17
  import time
18
  import uuid
@@ -355,6 +356,50 @@ class FallbackRateLimitMiddleware(BaseHTTPMiddleware):
355
  return await call_next(request)
356
 
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  if _SlowapiLimiter is not None:
359
  limiter: _LimiterLike = cast(
360
  _LimiterLike,
@@ -385,6 +430,10 @@ def _build_cors_origin_regex() -> str | None:
385
  return "^(" + "|".join(patterns) + ")$"
386
 
387
 
 
 
 
 
388
  app = FastAPI(
389
  title="DataForge Playground API",
390
  description="Stateless backend for the hosted DataForge playground.",
@@ -401,12 +450,17 @@ if not SLOWAPI_AVAILABLE:
401
  app.add_middleware(FallbackRateLimitMiddleware)
402
  app.add_middleware(
403
  CORSMiddleware,
404
- allow_origins=_build_cors_origins(),
405
- allow_origin_regex=_build_cors_origin_regex(),
406
  allow_methods=["GET", "POST", "OPTIONS"],
407
  allow_headers=["*"],
408
  allow_credentials=False,
409
  )
 
 
 
 
 
410
  app.add_middleware(RequestContextMiddleware)
411
  app.state.limiter = limiter
412
  app.add_exception_handler(HTTPException, problem_exception_handler)
@@ -747,7 +801,7 @@ async def health() -> dict[str, Any]:
747
  "server_time_utc": datetime.now(UTC).isoformat(),
748
  "environment": _environment_name(),
749
  "limits": _limits_payload(),
750
- "cors_configured": bool(_build_cors_origins() or _build_cors_origin_regex()),
751
  "otel_enabled": os.environ.get("DATAFORGE_OTEL_ENABLED", "").strip().lower()
752
  in OTEL_ENABLED_VALUES,
753
  "otel_instrumented": OTEL_INSTRUMENTED,
 
13
  import io
14
  import logging
15
  import os
16
+ import re
17
  import tempfile
18
  import time
19
  import uuid
 
356
  return await call_next(request)
357
 
358
 
359
+ class OriginGuardMiddleware(BaseHTTPMiddleware):
360
+ """Reject browser requests from origins outside the configured allowlist."""
361
+
362
+ def __init__(
363
+ self,
364
+ app: ASGIApp,
365
+ *,
366
+ allow_origins: list[str],
367
+ allow_origin_regex: str | None,
368
+ ) -> None:
369
+ super().__init__(app)
370
+ self._allow_origins = frozenset(allow_origins)
371
+ self._allow_origin_pattern = (
372
+ re.compile(allow_origin_regex) if allow_origin_regex is not None else None
373
+ )
374
+
375
+ def _allowed(self, origin: str) -> bool:
376
+ if origin in self._allow_origins:
377
+ return True
378
+ return bool(
379
+ self._allow_origin_pattern is not None
380
+ and self._allow_origin_pattern.fullmatch(origin)
381
+ )
382
+
383
+ async def dispatch(
384
+ self,
385
+ request: Request,
386
+ call_next: RequestResponseEndpoint,
387
+ ) -> Response:
388
+ """Deny disallowed browser origins before endpoint handlers run."""
389
+ origin = request.headers.get("origin")
390
+ if origin and not self._allowed(origin):
391
+ return problem_response(
392
+ status=403,
393
+ type_="https://dataforge.local/problems/origin_not_allowed",
394
+ title="Origin Not Allowed",
395
+ detail="This playground backend only accepts browser requests from configured frontend origins.",
396
+ instance=str(request.url.path),
397
+ error="origin_not_allowed",
398
+ request_id=_request_id(request),
399
+ )
400
+ return await call_next(request)
401
+
402
+
403
  if _SlowapiLimiter is not None:
404
  limiter: _LimiterLike = cast(
405
  _LimiterLike,
 
430
  return "^(" + "|".join(patterns) + ")$"
431
 
432
 
433
+ CORS_ORIGINS = _build_cors_origins()
434
+ CORS_ORIGIN_REGEX = _build_cors_origin_regex()
435
+
436
+
437
  app = FastAPI(
438
  title="DataForge Playground API",
439
  description="Stateless backend for the hosted DataForge playground.",
 
450
  app.add_middleware(FallbackRateLimitMiddleware)
451
  app.add_middleware(
452
  CORSMiddleware,
453
+ allow_origins=CORS_ORIGINS,
454
+ allow_origin_regex=CORS_ORIGIN_REGEX,
455
  allow_methods=["GET", "POST", "OPTIONS"],
456
  allow_headers=["*"],
457
  allow_credentials=False,
458
  )
459
+ app.add_middleware(
460
+ OriginGuardMiddleware,
461
+ allow_origins=CORS_ORIGINS,
462
+ allow_origin_regex=CORS_ORIGIN_REGEX,
463
+ )
464
  app.add_middleware(RequestContextMiddleware)
465
  app.state.limiter = limiter
466
  app.add_exception_handler(HTTPException, problem_exception_handler)
 
801
  "server_time_utc": datetime.now(UTC).isoformat(),
802
  "environment": _environment_name(),
803
  "limits": _limits_payload(),
804
+ "cors_configured": bool(CORS_ORIGINS or CORS_ORIGIN_REGEX),
805
  "otel_enabled": os.environ.get("DATAFORGE_OTEL_ENABLED", "").strip().lower()
806
  in OTEL_ENABLED_VALUES,
807
  "otel_instrumented": OTEL_INSTRUMENTED,