tba-app-backend-core 0.4.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tba_app_backend_core/__init__.py +15 -0
- tba_app_backend_core/app.py +146 -0
- tba_app_backend_core/plugin.py +36 -0
- tba_app_backend_core/product.py +55 -0
- tba_app_backend_core/py.typed +0 -0
- tba_app_backend_core/surfaces/__init__.py +3 -0
- tba_app_backend_core/surfaces/admin_routes.py +353 -0
- tba_app_backend_core/surfaces/advanced_protocols.py +170 -0
- tba_app_backend_core/surfaces/auth_flows.py +16 -0
- tba_app_backend_core/surfaces/authorization_server_metadata_surface.py +37 -0
- tba_app_backend_core/surfaces/authorization_surface.py +15 -0
- tba_app_backend_core/surfaces/authz_surface.py +14 -0
- tba_app_backend_core/surfaces/client_registration_surface.py +366 -0
- tba_app_backend_core/surfaces/device_authorization_surface.py +44 -0
- tba_app_backend_core/surfaces/introspection_surface.py +247 -0
- tba_app_backend_core/surfaces/login_surface.py +25 -0
- tba_app_backend_core/surfaces/logout_surface.py +38 -0
- tba_app_backend_core/surfaces/oidc_discovery_surface.py +79 -0
- tba_app_backend_core/surfaces/par_surface.py +394 -0
- tba_app_backend_core/surfaces/protected_resource_metadata_surface.py +34 -0
- tba_app_backend_core/surfaces/resource_validation_metadata_surface.py +33 -0
- tba_app_backend_core/surfaces/revocation_surface.py +39 -0
- tba_app_backend_core/surfaces/standards_manifest.py +24 -0
- tba_app_backend_core/surfaces/surface_api.py +284 -0
- tba_app_backend_core/surfaces/token_exchange_surface.py +34 -0
- tba_app_backend_core/surfaces/token_surface.py +12 -0
- tba_app_backend_core/surfaces/userinfo_surface.py +34 -0
- tba_app_backend_core-0.4.0.dev2.dist-info/METADATA +60 -0
- tba_app_backend_core-0.4.0.dev2.dist-info/RECORD +30 -0
- tba_app_backend_core-0.4.0.dev2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Runtime collaborators and composition for the RFC 9126 HTTP carrier."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from tigrbl import Request, TigrblApp
|
|
11
|
+
from tigrbl.runtime.status import HTTPException, status
|
|
12
|
+
from tba_router_oauth_pushed_authorization_request import (
|
|
13
|
+
build_pushed_authorization_router,
|
|
14
|
+
include_pushed_authorization_endpoint as include_http_par_endpoint,
|
|
15
|
+
)
|
|
16
|
+
from tba_protocol_oauth.standards.dpop import verify_proof_async
|
|
17
|
+
from tba_protocol_oauth.standards.jwt_client_auth import (
|
|
18
|
+
PRIVATE_KEY_JWT_AUTH_METHOD,
|
|
19
|
+
authenticate_client_assertion,
|
|
20
|
+
)
|
|
21
|
+
from tba_protocol_oauth.standards.jwt_secured_authorization_requests import (
|
|
22
|
+
merge_request_object_params,
|
|
23
|
+
parse_request_object,
|
|
24
|
+
)
|
|
25
|
+
from tba_protocol_oauth.standards.mutual_tls_client_authentication import (
|
|
26
|
+
SUPPORTED_MTLS_AUTH_METHODS,
|
|
27
|
+
authenticate_mtls_client,
|
|
28
|
+
presented_certificate_pem,
|
|
29
|
+
)
|
|
30
|
+
from tba_protocol_oauth.standards.oauth_security_bcp import (
|
|
31
|
+
client_certificate_thumbprint_from_request,
|
|
32
|
+
dpop_proof_from_request,
|
|
33
|
+
runtime_security_profile,
|
|
34
|
+
)
|
|
35
|
+
from tba_protocol_oauth.standards.resource_indicators import (
|
|
36
|
+
select_resource_indicator,
|
|
37
|
+
)
|
|
38
|
+
from tba_protocol_oauth.standards.rich_authorization_requests import (
|
|
39
|
+
normalize_authorization_details,
|
|
40
|
+
)
|
|
41
|
+
from tba_contracts_identity.oauth import PushedAuthorizationPersistenceRequest
|
|
42
|
+
from tba_runtime_identity.deployment import (
|
|
43
|
+
deployment_from_app,
|
|
44
|
+
deployment_from_request,
|
|
45
|
+
resolve_deployment,
|
|
46
|
+
)
|
|
47
|
+
from tba_runtime_identity.settings import settings
|
|
48
|
+
from tba_storage_oauth.tables import Client, ClientRegistration
|
|
49
|
+
from tba_storage_oauth.tables.pushed_authorization_request import (
|
|
50
|
+
DEFAULT_PAR_EXPIRY,
|
|
51
|
+
)
|
|
52
|
+
from tba_runtime_identity_storage.dpop_state import (
|
|
53
|
+
check_and_store_dpop_replay,
|
|
54
|
+
consume_dpop_nonce,
|
|
55
|
+
)
|
|
56
|
+
from tba_runtime_identity_storage.engine import get_db
|
|
57
|
+
from tigrbl import first_record, read_record
|
|
58
|
+
|
|
59
|
+
from tba_server_identity.security.pushed_authorization import (
|
|
60
|
+
build_rfc9126_pushed_authorization_service,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_CLIENT_AUTH_PARAMETERS = frozenset(
|
|
65
|
+
{"client_assertion", "client_assertion_type", "client_secret"}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _header(request: Request, name: str) -> str | None:
|
|
70
|
+
headers = getattr(request, "headers", {}) or {}
|
|
71
|
+
return headers.get(name) or headers.get(name.lower())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _resolve_request_deployment(request: Request):
|
|
75
|
+
if getattr(request, "app", None) is not None:
|
|
76
|
+
return deployment_from_request(request, settings)
|
|
77
|
+
return resolve_deployment(settings)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def _normalized_par_params(
|
|
81
|
+
params: Mapping[str, object],
|
|
82
|
+
deployment,
|
|
83
|
+
) -> dict[str, object]:
|
|
84
|
+
normalized = dict(params)
|
|
85
|
+
request_object = normalized.get("request")
|
|
86
|
+
if request_object:
|
|
87
|
+
if not deployment.flag_enabled("enable_rfc9101"):
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
status.HTTP_400_BAD_REQUEST,
|
|
90
|
+
"request object support disabled",
|
|
91
|
+
)
|
|
92
|
+
try:
|
|
93
|
+
parsed_request = await parse_request_object(
|
|
94
|
+
str(request_object),
|
|
95
|
+
secret=settings.jwt_secret,
|
|
96
|
+
algorithms=("HS256", "HS384", "HS512"),
|
|
97
|
+
expected_client_id=str(normalized.get("client_id") or "") or None,
|
|
98
|
+
expected_audience=str(deployment.issuer or settings.issuer),
|
|
99
|
+
)
|
|
100
|
+
normalized = merge_request_object_params(
|
|
101
|
+
parsed_request,
|
|
102
|
+
normalized,
|
|
103
|
+
allow_query_overrides=("request",),
|
|
104
|
+
)
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
raise HTTPException(
|
|
107
|
+
status.HTTP_400_BAD_REQUEST,
|
|
108
|
+
"invalid request object",
|
|
109
|
+
) from exc
|
|
110
|
+
|
|
111
|
+
resources = normalized.get("resource")
|
|
112
|
+
if resources not in (None, "", [], (), {}):
|
|
113
|
+
values = resources if isinstance(resources, list) else [resources]
|
|
114
|
+
if not deployment.flag_enabled("enable_rfc8707"):
|
|
115
|
+
raise HTTPException(
|
|
116
|
+
status.HTTP_400_BAD_REQUEST,
|
|
117
|
+
"resource indicators disabled",
|
|
118
|
+
)
|
|
119
|
+
try:
|
|
120
|
+
selection = select_resource_indicator(
|
|
121
|
+
[str(item) for item in values],
|
|
122
|
+
audience=str(normalized.get("audience") or "") or None,
|
|
123
|
+
)
|
|
124
|
+
except ValueError as exc:
|
|
125
|
+
raise HTTPException(status.HTTP_400_BAD_REQUEST, "invalid_target") from exc
|
|
126
|
+
normalized["resource"] = list(selection.resources)
|
|
127
|
+
if selection.audience:
|
|
128
|
+
normalized["audience"] = selection.audience
|
|
129
|
+
|
|
130
|
+
authorization_details = normalized.get("authorization_details")
|
|
131
|
+
if authorization_details not in (None, "", [], (), {}):
|
|
132
|
+
if not deployment.flag_enabled("enable_rfc9396"):
|
|
133
|
+
raise HTTPException(
|
|
134
|
+
status.HTTP_400_BAD_REQUEST,
|
|
135
|
+
"authorization_details disabled",
|
|
136
|
+
)
|
|
137
|
+
try:
|
|
138
|
+
binding = normalize_authorization_details(
|
|
139
|
+
authorization_details,
|
|
140
|
+
resource=str(normalized.get("audience") or "") or None,
|
|
141
|
+
audience=str(normalized.get("audience") or "") or None,
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
raise HTTPException(
|
|
145
|
+
status.HTTP_400_BAD_REQUEST,
|
|
146
|
+
"invalid authorization_details",
|
|
147
|
+
) from exc
|
|
148
|
+
normalized["authorization_details"] = binding.details
|
|
149
|
+
if binding.audience and normalized.get("audience") in {None, ""}:
|
|
150
|
+
normalized["audience"] = binding.audience
|
|
151
|
+
if binding.resource and normalized.get("resource") in (
|
|
152
|
+
None,
|
|
153
|
+
"",
|
|
154
|
+
[],
|
|
155
|
+
(),
|
|
156
|
+
{},
|
|
157
|
+
):
|
|
158
|
+
normalized["resource"] = [binding.resource]
|
|
159
|
+
return normalized
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def normalize_pushed_authorization_request(
|
|
163
|
+
request: Request,
|
|
164
|
+
params: Mapping[str, object],
|
|
165
|
+
) -> Mapping[str, object]:
|
|
166
|
+
return await _normalized_par_params(params, _resolve_request_deployment(request))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def _authenticate_fapi_par_client(
|
|
170
|
+
*,
|
|
171
|
+
request: Request,
|
|
172
|
+
db,
|
|
173
|
+
params: Mapping[str, object],
|
|
174
|
+
deployment,
|
|
175
|
+
):
|
|
176
|
+
client_id = str(params.get("client_id") or "").strip()
|
|
177
|
+
try:
|
|
178
|
+
client_uuid = UUID(client_id)
|
|
179
|
+
except (TypeError, ValueError, AttributeError) as exc:
|
|
180
|
+
raise HTTPException(status.HTTP_400_BAD_REQUEST, "invalid client_id") from exc
|
|
181
|
+
client = await read_record(Client, db, client_uuid)
|
|
182
|
+
if client is None:
|
|
183
|
+
raise HTTPException(status.HTTP_404_NOT_FOUND, "client not found")
|
|
184
|
+
registration = await first_record(
|
|
185
|
+
ClientRegistration,
|
|
186
|
+
db,
|
|
187
|
+
{"client_id": client.id},
|
|
188
|
+
)
|
|
189
|
+
metadata = dict(getattr(registration, "registration_metadata", None) or {})
|
|
190
|
+
auth_method = str(metadata.get("token_endpoint_auth_method") or "").strip()
|
|
191
|
+
policy = runtime_security_profile(deployment)
|
|
192
|
+
if auth_method not in set(policy.allowed_client_auth_methods):
|
|
193
|
+
raise HTTPException(
|
|
194
|
+
status.HTTP_400_BAD_REQUEST,
|
|
195
|
+
"invalid_client_metadata: FAPI clients must use private_key_jwt or mTLS",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
authorization = _header(request, "Authorization")
|
|
199
|
+
client_assertion = str(params.get("client_assertion") or "").strip()
|
|
200
|
+
client_assertion_type = str(params.get("client_assertion_type") or "").strip()
|
|
201
|
+
if authorization and authorization.startswith("Basic "):
|
|
202
|
+
raise HTTPException(
|
|
203
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
204
|
+
{
|
|
205
|
+
"error": "invalid_client",
|
|
206
|
+
"error_description": "FAPI PAR rejects HTTP Basic client authentication",
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
if auth_method == PRIVATE_KEY_JWT_AUTH_METHOD:
|
|
210
|
+
if not client_assertion:
|
|
211
|
+
raise HTTPException(
|
|
212
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
213
|
+
{
|
|
214
|
+
"error": "invalid_client",
|
|
215
|
+
"error_description": "client_assertion required for FAPI PAR",
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
try:
|
|
219
|
+
authenticate_client_assertion(
|
|
220
|
+
client_assertion_type=client_assertion_type,
|
|
221
|
+
client_assertion=client_assertion,
|
|
222
|
+
audience=str(deployment.issuer or settings.issuer),
|
|
223
|
+
client_id=str(client.id),
|
|
224
|
+
token_endpoint_auth_method=auth_method,
|
|
225
|
+
)
|
|
226
|
+
except ValueError as exc:
|
|
227
|
+
raise HTTPException(
|
|
228
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
229
|
+
{"error": "invalid_client", "error_description": str(exc)},
|
|
230
|
+
) from exc
|
|
231
|
+
elif auth_method in SUPPORTED_MTLS_AUTH_METHODS:
|
|
232
|
+
try:
|
|
233
|
+
authenticate_mtls_client(
|
|
234
|
+
metadata,
|
|
235
|
+
client_certificate_thumbprint_from_request(request),
|
|
236
|
+
presented_certificate_pem=presented_certificate_pem(request),
|
|
237
|
+
token_endpoint_auth_method=auth_method,
|
|
238
|
+
)
|
|
239
|
+
except ValueError as exc:
|
|
240
|
+
raise HTTPException(
|
|
241
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
242
|
+
{"error": "invalid_client", "error_description": str(exc)},
|
|
243
|
+
) from exc
|
|
244
|
+
else:
|
|
245
|
+
raise HTTPException(
|
|
246
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
247
|
+
{
|
|
248
|
+
"error": "invalid_client",
|
|
249
|
+
"error_description": "unsupported FAPI PAR authentication method",
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
return client
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def authorize_pushed_authorization_caller(
|
|
256
|
+
request: Request,
|
|
257
|
+
params: Mapping[str, object],
|
|
258
|
+
db,
|
|
259
|
+
) -> PushedAuthorizationPersistenceRequest:
|
|
260
|
+
deployment = _resolve_request_deployment(request)
|
|
261
|
+
policy = runtime_security_profile(deployment)
|
|
262
|
+
client_id = str(params.get("client_id") or "").strip()
|
|
263
|
+
if not client_id:
|
|
264
|
+
raise HTTPException(
|
|
265
|
+
status.HTTP_400_BAD_REQUEST,
|
|
266
|
+
"client_id parameter required",
|
|
267
|
+
)
|
|
268
|
+
if policy.par_redirect_uri_required and not params.get("redirect_uri"):
|
|
269
|
+
raise HTTPException(
|
|
270
|
+
status.HTTP_400_BAD_REQUEST,
|
|
271
|
+
"redirect_uri parameter required",
|
|
272
|
+
)
|
|
273
|
+
try:
|
|
274
|
+
client_uuid = UUID(client_id)
|
|
275
|
+
except (TypeError, ValueError, AttributeError) as exc:
|
|
276
|
+
raise HTTPException(status.HTTP_400_BAD_REQUEST, "invalid client_id") from exc
|
|
277
|
+
client = await read_record(Client, db, client_uuid)
|
|
278
|
+
if client is None:
|
|
279
|
+
raise HTTPException(status.HTTP_404_NOT_FOUND, "client not found")
|
|
280
|
+
if policy.par_client_auth_required:
|
|
281
|
+
client = await _authenticate_fapi_par_client(
|
|
282
|
+
request=request,
|
|
283
|
+
db=db,
|
|
284
|
+
params=params,
|
|
285
|
+
deployment=deployment,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
persisted_params = {
|
|
289
|
+
key: value
|
|
290
|
+
for key, value in params.items()
|
|
291
|
+
if key not in _CLIENT_AUTH_PARAMETERS
|
|
292
|
+
}
|
|
293
|
+
dpop_proof = dpop_proof_from_request(request)
|
|
294
|
+
if dpop_proof:
|
|
295
|
+
try:
|
|
296
|
+
persisted_params["_dpop_jkt"] = await verify_proof_async(
|
|
297
|
+
dpop_proof,
|
|
298
|
+
getattr(request, "method", "POST"),
|
|
299
|
+
str(getattr(request, "url", "")),
|
|
300
|
+
replay_checker=lambda claims, ttl_s=300: check_and_store_dpop_replay(
|
|
301
|
+
db,
|
|
302
|
+
claims,
|
|
303
|
+
ttl_s=ttl_s,
|
|
304
|
+
),
|
|
305
|
+
nonce_consumer=lambda nonce, now=None: consume_dpop_nonce(
|
|
306
|
+
db,
|
|
307
|
+
nonce,
|
|
308
|
+
now=now,
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
except ValueError as exc:
|
|
312
|
+
raise HTTPException(
|
|
313
|
+
status.HTTP_400_BAD_REQUEST,
|
|
314
|
+
{
|
|
315
|
+
"error": "invalid_dpop_proof",
|
|
316
|
+
"error_description": str(exc),
|
|
317
|
+
},
|
|
318
|
+
) from exc
|
|
319
|
+
cert_thumbprint = client_certificate_thumbprint_from_request(request)
|
|
320
|
+
if cert_thumbprint:
|
|
321
|
+
persisted_params["_mtls_thumbprint"] = cert_thumbprint
|
|
322
|
+
|
|
323
|
+
if DEFAULT_PAR_EXPIRY > policy.request_uri_max_lifetime_seconds:
|
|
324
|
+
raise HTTPException(
|
|
325
|
+
status.HTTP_400_BAD_REQUEST,
|
|
326
|
+
{
|
|
327
|
+
"error": "invalid_request",
|
|
328
|
+
"error_description": (
|
|
329
|
+
"request_uri lifetime exceeds the active profile limit"
|
|
330
|
+
),
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
return PushedAuthorizationPersistenceRequest(
|
|
334
|
+
client_id=str(client.id),
|
|
335
|
+
tenant_id=(str(client.tenant_id) if client.tenant_id is not None else None),
|
|
336
|
+
params=persisted_params,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _service_for_request(request: Request, db):
|
|
341
|
+
deployment = _resolve_request_deployment(request)
|
|
342
|
+
return build_rfc9126_pushed_authorization_service(
|
|
343
|
+
db,
|
|
344
|
+
SimpleNamespace(enable_rfc9126=deployment.flag_enabled("enable_rfc9126")),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _repo_root() -> Path:
|
|
349
|
+
return Path(__file__).resolve().parents[5]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def observe_pushed_authorization_response(
|
|
353
|
+
request: Request,
|
|
354
|
+
payload: Mapping[str, object],
|
|
355
|
+
) -> None:
|
|
356
|
+
from tba_runtime_identity_storage.session_service import observe_par_response
|
|
357
|
+
|
|
358
|
+
observe_par_response(
|
|
359
|
+
_repo_root(),
|
|
360
|
+
request_uri=payload.get("request_uri"),
|
|
361
|
+
details=dict(payload),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
router = build_pushed_authorization_router(
|
|
366
|
+
service_for_request=_service_for_request,
|
|
367
|
+
normalize_request=normalize_pushed_authorization_request,
|
|
368
|
+
authorize_caller=authorize_pushed_authorization_caller,
|
|
369
|
+
observe_response=observe_pushed_authorization_response,
|
|
370
|
+
get_db=get_db,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def include_par_endpoint(app: TigrblApp) -> None:
|
|
375
|
+
deployment = deployment_from_app(app, settings)
|
|
376
|
+
include_http_par_endpoint(
|
|
377
|
+
app,
|
|
378
|
+
router,
|
|
379
|
+
enabled=deployment.flag_enabled("enable_rfc9126"),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
include_rfc9126 = include_par_endpoint
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
__all__ = [
|
|
387
|
+
"_normalized_par_params",
|
|
388
|
+
"authorize_pushed_authorization_caller",
|
|
389
|
+
"include_par_endpoint",
|
|
390
|
+
"include_rfc9126",
|
|
391
|
+
"normalize_pushed_authorization_request",
|
|
392
|
+
"observe_pushed_authorization_response",
|
|
393
|
+
"router",
|
|
394
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Runtime composition for the RFC 9728 HTTP carrier."""
|
|
2
|
+
|
|
3
|
+
from tigrbl import TigrblApp
|
|
4
|
+
from tba_router_oauth_protected_resource_metadata import (
|
|
5
|
+
build_protected_resource_metadata_router,
|
|
6
|
+
include_protected_resource_metadata,
|
|
7
|
+
)
|
|
8
|
+
from tba_protocol_oauth.standards.protected_resource_metadata import (
|
|
9
|
+
PROTECTED_RESOURCE_METADATA_PATH,
|
|
10
|
+
)
|
|
11
|
+
from tba_runtime_identity.deployment import deployment_from_app
|
|
12
|
+
from tba_runtime_identity.settings import settings
|
|
13
|
+
|
|
14
|
+
from tba_server_identity.protected_resource_metadata_runtime import (
|
|
15
|
+
protected_resource_metadata,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
router = build_protected_resource_metadata_router(
|
|
20
|
+
protected_resource_metadata=protected_resource_metadata,
|
|
21
|
+
)
|
|
22
|
+
api = router
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def include_rfc9728(app: TigrblApp) -> None:
|
|
26
|
+
deployment = deployment_from_app(app, settings)
|
|
27
|
+
include_protected_resource_metadata(
|
|
28
|
+
app,
|
|
29
|
+
router,
|
|
30
|
+
enabled=deployment.route_enabled(PROTECTED_RESOURCE_METADATA_PATH),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ["api", "include_rfc9728", "router"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Runtime composition for resource-validation metadata carriers."""
|
|
2
|
+
|
|
3
|
+
from tigrbl import TigrblApp
|
|
4
|
+
from tba_router_resource_validation_metadata import (
|
|
5
|
+
build_resource_validation_metadata_router,
|
|
6
|
+
include_resource_validation_metadata as include_metadata_carrier,
|
|
7
|
+
)
|
|
8
|
+
from tba_runtime_identity.deployment import deployment_from_app
|
|
9
|
+
from tba_runtime_identity.settings import settings
|
|
10
|
+
|
|
11
|
+
from tba_server_identity.resource_validation_metadata_runtime import (
|
|
12
|
+
capability_metadata,
|
|
13
|
+
verifier_contract_metadata,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
router = build_resource_validation_metadata_router(
|
|
18
|
+
capability_metadata=capability_metadata,
|
|
19
|
+
verifier_contract_metadata=verifier_contract_metadata,
|
|
20
|
+
)
|
|
21
|
+
api = router
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def include_resource_validation_metadata(app: TigrblApp) -> None:
|
|
25
|
+
deployment = deployment_from_app(app, settings)
|
|
26
|
+
include_metadata_carrier(
|
|
27
|
+
app,
|
|
28
|
+
router,
|
|
29
|
+
enabled=deployment.product_surface == "resource-validation-app",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["api", "include_resource_validation_metadata", "router"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Runtime composition for the RFC 7009 HTTP carrier."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from tigrbl import Request, TigrblApp
|
|
6
|
+
from tba_router_oauth_revocation import (
|
|
7
|
+
build_revocation_router,
|
|
8
|
+
include_revocation_endpoint as include_http_revocation_endpoint,
|
|
9
|
+
)
|
|
10
|
+
from tba_runtime_identity.settings import settings
|
|
11
|
+
|
|
12
|
+
from tba_server_identity.security.token_revocation import (
|
|
13
|
+
build_rfc7009_revocation_service,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _service_for_request(request: Request):
|
|
18
|
+
return build_rfc7009_revocation_service(settings)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
router = build_revocation_router(service_for_request=_service_for_request)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def include_revocation_endpoint(app: TigrblApp) -> None:
|
|
25
|
+
include_http_revocation_endpoint(
|
|
26
|
+
app,
|
|
27
|
+
router,
|
|
28
|
+
enabled=bool(settings.enable_rfc7009),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
include_rfc7009 = include_revocation_endpoint
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"include_revocation_endpoint",
|
|
37
|
+
"include_rfc7009",
|
|
38
|
+
"router",
|
|
39
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from tigrbl import TigrblApp, TigrblRouter
|
|
4
|
+
from tba_runtime_identity import standards_manifest
|
|
5
|
+
|
|
6
|
+
api = TigrblRouter()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@api.route("/standards", methods=["GET"], include_in_schema=True, tags=["standards"])
|
|
10
|
+
async def get_standards_manifest() -> dict[str, object]:
|
|
11
|
+
return {"standards": standards_manifest()}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def include_standards_manifest(app: TigrblApp) -> None:
|
|
15
|
+
path = "/standards"
|
|
16
|
+
routes = getattr(getattr(app, "router", app), "routes", ())
|
|
17
|
+
if not any(
|
|
18
|
+
(getattr(route, "path", None) or getattr(route, "path_template", None)) == path
|
|
19
|
+
for route in routes
|
|
20
|
+
):
|
|
21
|
+
app.include_router(api)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["api", "get_standards_manifest", "include_standards_manifest"]
|