easyssf-receiver 0.7.0__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.
@@ -0,0 +1,437 @@
1
+ """An SSF receiver, framework independent and asyncio based: SET verification, de-duplication,
2
+ push and poll delivery, stream management, typed CAEP, RISC and SCIM handlers, token revocation,
3
+ session termination and metrics. The port of ``easyssf-receiver`` of the Java library.
4
+
5
+ The modules follow the Go package: ``errors`` (the exception classes and the ``Clock``), ``event``
6
+ (``EventContext`` and the handlers), ``dedup``, ``metrics``, ``config`` (``ReceiverConfig``),
7
+ ``jwks``, ``verifier``, ``processor``, ``push``, ``authorizer``, ``transmitter``, ``stream``,
8
+ ``poll``, ``receiver`` (``Receiver``, which assembles everything from a ``ReceiverConfig``), the
9
+ typed handlers ``caep``, ``risc`` and ``scim``, ``revocation`` and ``session``; ``asgi`` and
10
+ ``wsgi`` mount the push endpoint on any web framework.
11
+ """
12
+
13
+ from .authorizer import (
14
+ DEFAULT_RETRYABLE_STATUSES,
15
+ ENDPOINT_EXTENSION,
16
+ Authorizer,
17
+ AuthorizerCallable,
18
+ AuthorizerFunc,
19
+ Authorizers,
20
+ BearerToken,
21
+ Endpoint,
22
+ EndpointHeaders,
23
+ NoAuthorization,
24
+ RetryOptions,
25
+ StaticHeaders,
26
+ TokenInvalidator,
27
+ default_retry_options,
28
+ endpoint_of,
29
+ invalidate_token,
30
+ retry_after,
31
+ retrying,
32
+ with_endpoint,
33
+ )
34
+ from .caep import (
35
+ CaepEventHandler,
36
+ CaepHandler,
37
+ caep_handler,
38
+ dispatch_caep_event,
39
+ )
40
+ from .config import (
41
+ CONFIGURATION_SECTION,
42
+ DEFAULT_SCIM_ATTRIBUTE_CLAIMS,
43
+ DEFAULT_TRANSMITTER_NAME,
44
+ SCHEMA_ALWAYS,
45
+ SCHEMA_EMBEDDED,
46
+ SCHEMA_NEVER,
47
+ STREAM_MANAGEMENT_NONE,
48
+ STREAM_MANAGEMENT_RECEIVER,
49
+ DataConfig,
50
+ DataSize,
51
+ DedupConfig,
52
+ HttpConfig,
53
+ OAuth2Config,
54
+ PollConfig,
55
+ PushConfig,
56
+ RateLimitConfig,
57
+ ReceiverConfig,
58
+ RetryConfig,
59
+ RevocationConfig,
60
+ SessionTerminationConfig,
61
+ SetValidationConfig,
62
+ StreamConfig,
63
+ TransmitterConfig,
64
+ default_receiver_config,
65
+ default_transmitter_config,
66
+ format_data_size,
67
+ format_duration,
68
+ parse_data_size,
69
+ parse_duration,
70
+ valid_table_prefix,
71
+ )
72
+ from .dedup import (
73
+ DEFAULT_DEDUP_LEASE,
74
+ InMemoryJtiDedupStore,
75
+ JtiClaim,
76
+ JtiClaimState,
77
+ JtiDedupStore,
78
+ )
79
+ from .errors import (
80
+ Clock,
81
+ ConfigurationError,
82
+ EasyssfError,
83
+ ErrorCode,
84
+ HandlingError,
85
+ InProgressError,
86
+ RejectedError,
87
+ StoreError,
88
+ TransmitterUnavailableError,
89
+ system_clock,
90
+ )
91
+ from .event import (
92
+ EventContext,
93
+ Handler,
94
+ HandlerCallable,
95
+ HandlerFunc,
96
+ as_handler,
97
+ for_event,
98
+ handler_name,
99
+ )
100
+ from .jwks import (
101
+ DEFAULT_CACHE_LIFETIME,
102
+ DEFAULT_FETCH_TIMEOUT,
103
+ DEFAULT_REFRESH_RATE_LIMIT,
104
+ JWK_SET_ACCEPT,
105
+ HttpJwkSetSource,
106
+ JwkSetFetch,
107
+ JwkSetSource,
108
+ JwkSetUrlResolver,
109
+ StaticJwkSet,
110
+ parse_jwk_set,
111
+ )
112
+ from .metrics import (
113
+ Metrics,
114
+ NoopMetrics,
115
+ SetOutcome,
116
+ )
117
+ from .poll import (
118
+ InMemoryPollAckStore,
119
+ PendingAck,
120
+ PollAckStore,
121
+ PollEndpoint,
122
+ Poller,
123
+ PollerOptions,
124
+ PollError,
125
+ default_poller_options,
126
+ fixed_poll_endpoint,
127
+ )
128
+ from .processor import (
129
+ CriticalSubjectMembers,
130
+ ProcessingOutcome,
131
+ ProcessingResult,
132
+ Processor,
133
+ ProcessorOptions,
134
+ StreamVerificationCheck,
135
+ )
136
+ from .push import (
137
+ AUTHENTICATION_FAILED,
138
+ PUSH_CONTENT_LANGUAGE,
139
+ PUSH_CONTENT_TYPE,
140
+ ExpectedAuthorizationHeaders,
141
+ PushHandler,
142
+ PushResponse,
143
+ )
144
+ from .receiver import (
145
+ Receiver,
146
+ ReceiverDisabledError,
147
+ ReceiverHealth,
148
+ TransmitterCustomizer,
149
+ )
150
+ from .revocation import (
151
+ DEFAULT_REVOCATION_TTL,
152
+ DEFAULT_SESSION_EVENT_TYPES,
153
+ REVOKED_WWW_AUTHENTICATE,
154
+ InMemoryTokenRevocationStore,
155
+ RevocationMiddleware,
156
+ TokenClaims,
157
+ TokenClaimsExtractor,
158
+ TokenRevocationHandler,
159
+ TokenRevocationStore,
160
+ is_revoked,
161
+ reject_if_revoked,
162
+ resolve_event_types,
163
+ )
164
+ from .risc import (
165
+ RiscEventHandler,
166
+ RiscHandler,
167
+ dispatch_risc_event,
168
+ risc_handler,
169
+ )
170
+ from .scim import (
171
+ ScimEventHandler,
172
+ ScimHandler,
173
+ dispatch_scim_event,
174
+ scim_handler,
175
+ )
176
+ from .session import (
177
+ DEFAULT_SESSION_TERMINATION_USER_EVENT_TYPES,
178
+ SessionTerminationHandler,
179
+ SessionTerminator,
180
+ SessionTerminatorCallable,
181
+ SessionTerminatorFunc,
182
+ as_session_terminator,
183
+ describe_subject,
184
+ subject_matches,
185
+ subject_matches_with,
186
+ )
187
+ from .stream import (
188
+ MAX_STREAM_RETRY_DELAY,
189
+ IssuerMismatchError,
190
+ ReceiverStream,
191
+ StreamClient,
192
+ StreamError,
193
+ StreamListener,
194
+ StreamRegistrar,
195
+ StreamRegistrarState,
196
+ StreamVerification,
197
+ changes_for,
198
+ has_desired_delivery,
199
+ has_desired_events,
200
+ stream_status_of,
201
+ )
202
+ from .transmitter import (
203
+ DEFAULT_HTTP_TIMEOUT,
204
+ LONG_POLL_MARGIN,
205
+ MAX_REDIRECTS,
206
+ MAX_RESPONSE_SIZE,
207
+ WELL_KNOWN_PATH,
208
+ ClientCredentialsOptions,
209
+ ClientCredentialsTokenProvider,
210
+ FixedToken,
211
+ HealthStatus,
212
+ HttpResponse,
213
+ MetadataResolver,
214
+ NoToken,
215
+ ResponseTooLargeError,
216
+ TokenProvider,
217
+ Transmitter,
218
+ TransmitterHealth,
219
+ UriPolicy,
220
+ UriPolicyError,
221
+ default_client_credentials_options,
222
+ derive_metadata_urls,
223
+ exchange,
224
+ get_json,
225
+ parse_absolute,
226
+ send,
227
+ )
228
+ from .verifier import (
229
+ MAX_SET_SIZE,
230
+ SECEVENT_JWT_TYPE,
231
+ SUPPORTED_ALGORITHMS,
232
+ ExpectedAudiencesProvider,
233
+ IssuerRoutingVerifier,
234
+ JwtVerifier,
235
+ Verifier,
236
+ VerifierOptions,
237
+ default_verifier_options,
238
+ rsa_key_bits,
239
+ supported_algorithm,
240
+ unverified_issuer,
241
+ unverified_payload,
242
+ )
243
+
244
+ __all__ = [
245
+ "AUTHENTICATION_FAILED",
246
+ "CONFIGURATION_SECTION",
247
+ "DEFAULT_CACHE_LIFETIME",
248
+ "DEFAULT_DEDUP_LEASE",
249
+ "DEFAULT_FETCH_TIMEOUT",
250
+ "DEFAULT_HTTP_TIMEOUT",
251
+ "DEFAULT_REFRESH_RATE_LIMIT",
252
+ "DEFAULT_RETRYABLE_STATUSES",
253
+ "DEFAULT_REVOCATION_TTL",
254
+ "DEFAULT_SCIM_ATTRIBUTE_CLAIMS",
255
+ "DEFAULT_SESSION_EVENT_TYPES",
256
+ "DEFAULT_SESSION_TERMINATION_USER_EVENT_TYPES",
257
+ "DEFAULT_TRANSMITTER_NAME",
258
+ "ENDPOINT_EXTENSION",
259
+ "JWK_SET_ACCEPT",
260
+ "LONG_POLL_MARGIN",
261
+ "MAX_REDIRECTS",
262
+ "MAX_RESPONSE_SIZE",
263
+ "MAX_SET_SIZE",
264
+ "MAX_STREAM_RETRY_DELAY",
265
+ "PUSH_CONTENT_LANGUAGE",
266
+ "PUSH_CONTENT_TYPE",
267
+ "REVOKED_WWW_AUTHENTICATE",
268
+ "SCHEMA_ALWAYS",
269
+ "SCHEMA_EMBEDDED",
270
+ "SCHEMA_NEVER",
271
+ "SECEVENT_JWT_TYPE",
272
+ "STREAM_MANAGEMENT_NONE",
273
+ "STREAM_MANAGEMENT_RECEIVER",
274
+ "SUPPORTED_ALGORITHMS",
275
+ "WELL_KNOWN_PATH",
276
+ "Authorizer",
277
+ "AuthorizerCallable",
278
+ "AuthorizerFunc",
279
+ "Authorizers",
280
+ "BearerToken",
281
+ "CaepEventHandler",
282
+ "CaepHandler",
283
+ "ClientCredentialsOptions",
284
+ "ClientCredentialsTokenProvider",
285
+ "Clock",
286
+ "ConfigurationError",
287
+ "CriticalSubjectMembers",
288
+ "DataConfig",
289
+ "DataSize",
290
+ "DedupConfig",
291
+ "EasyssfError",
292
+ "Endpoint",
293
+ "EndpointHeaders",
294
+ "ErrorCode",
295
+ "EventContext",
296
+ "ExpectedAudiencesProvider",
297
+ "ExpectedAuthorizationHeaders",
298
+ "FixedToken",
299
+ "Handler",
300
+ "HandlerCallable",
301
+ "HandlerFunc",
302
+ "HandlingError",
303
+ "HealthStatus",
304
+ "HttpConfig",
305
+ "HttpJwkSetSource",
306
+ "HttpResponse",
307
+ "InMemoryJtiDedupStore",
308
+ "InMemoryPollAckStore",
309
+ "InMemoryTokenRevocationStore",
310
+ "InProgressError",
311
+ "IssuerMismatchError",
312
+ "IssuerRoutingVerifier",
313
+ "JtiClaim",
314
+ "JtiClaimState",
315
+ "JtiDedupStore",
316
+ "JwkSetFetch",
317
+ "JwkSetSource",
318
+ "JwkSetUrlResolver",
319
+ "JwtVerifier",
320
+ "MetadataResolver",
321
+ "Metrics",
322
+ "NoAuthorization",
323
+ "NoToken",
324
+ "NoopMetrics",
325
+ "OAuth2Config",
326
+ "PendingAck",
327
+ "PollAckStore",
328
+ "PollConfig",
329
+ "PollEndpoint",
330
+ "PollError",
331
+ "Poller",
332
+ "PollerOptions",
333
+ "ProcessingOutcome",
334
+ "ProcessingResult",
335
+ "Processor",
336
+ "ProcessorOptions",
337
+ "PushConfig",
338
+ "PushHandler",
339
+ "PushResponse",
340
+ "RateLimitConfig",
341
+ "Receiver",
342
+ "ReceiverConfig",
343
+ "ReceiverDisabledError",
344
+ "ReceiverHealth",
345
+ "ReceiverStream",
346
+ "RejectedError",
347
+ "ResponseTooLargeError",
348
+ "RetryConfig",
349
+ "RetryOptions",
350
+ "RevocationConfig",
351
+ "RevocationMiddleware",
352
+ "RiscEventHandler",
353
+ "RiscHandler",
354
+ "ScimEventHandler",
355
+ "ScimHandler",
356
+ "SessionTerminationConfig",
357
+ "SessionTerminationHandler",
358
+ "SessionTerminator",
359
+ "SessionTerminatorCallable",
360
+ "SessionTerminatorFunc",
361
+ "SetOutcome",
362
+ "SetValidationConfig",
363
+ "StaticHeaders",
364
+ "StaticJwkSet",
365
+ "StoreError",
366
+ "StreamClient",
367
+ "StreamConfig",
368
+ "StreamError",
369
+ "StreamListener",
370
+ "StreamRegistrar",
371
+ "StreamRegistrarState",
372
+ "StreamVerification",
373
+ "StreamVerificationCheck",
374
+ "TokenClaims",
375
+ "TokenClaimsExtractor",
376
+ "TokenInvalidator",
377
+ "TokenProvider",
378
+ "TokenRevocationHandler",
379
+ "TokenRevocationStore",
380
+ "Transmitter",
381
+ "TransmitterConfig",
382
+ "TransmitterCustomizer",
383
+ "TransmitterHealth",
384
+ "TransmitterUnavailableError",
385
+ "UriPolicy",
386
+ "UriPolicyError",
387
+ "Verifier",
388
+ "VerifierOptions",
389
+ "as_handler",
390
+ "as_session_terminator",
391
+ "caep_handler",
392
+ "changes_for",
393
+ "default_client_credentials_options",
394
+ "default_poller_options",
395
+ "default_receiver_config",
396
+ "default_retry_options",
397
+ "default_transmitter_config",
398
+ "default_verifier_options",
399
+ "derive_metadata_urls",
400
+ "describe_subject",
401
+ "dispatch_caep_event",
402
+ "dispatch_risc_event",
403
+ "dispatch_scim_event",
404
+ "endpoint_of",
405
+ "exchange",
406
+ "fixed_poll_endpoint",
407
+ "for_event",
408
+ "format_data_size",
409
+ "format_duration",
410
+ "get_json",
411
+ "handler_name",
412
+ "has_desired_delivery",
413
+ "has_desired_events",
414
+ "invalidate_token",
415
+ "is_revoked",
416
+ "parse_absolute",
417
+ "parse_data_size",
418
+ "parse_duration",
419
+ "parse_jwk_set",
420
+ "reject_if_revoked",
421
+ "resolve_event_types",
422
+ "retry_after",
423
+ "retrying",
424
+ "risc_handler",
425
+ "rsa_key_bits",
426
+ "scim_handler",
427
+ "send",
428
+ "stream_status_of",
429
+ "subject_matches",
430
+ "subject_matches_with",
431
+ "supported_algorithm",
432
+ "system_clock",
433
+ "unverified_issuer",
434
+ "unverified_payload",
435
+ "valid_table_prefix",
436
+ "with_endpoint",
437
+ ]
@@ -0,0 +1,84 @@
1
+ """The push endpoint as an ASGI 3 application, for any ASGI server or framework: mount
2
+ :func:`push_app` at the push endpoint path (``/ssf/push`` by default). No framework is needed;
3
+ the FastAPI and Litestar distributions add the idiomatic pieces."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Awaitable, Callable, MutableMapping
8
+ from typing import Any, TypeAlias
9
+
10
+ from .push import MAX_SET_SIZE, PUSH_CONTENT_LANGUAGE, PUSH_CONTENT_TYPE, PushHandler
11
+
12
+ __all__ = ["AsgiApp", "push_app"]
13
+
14
+ Scope: TypeAlias = MutableMapping[str, Any]
15
+ Receive: TypeAlias = Callable[[], Awaitable[MutableMapping[str, Any]]]
16
+ Send: TypeAlias = Callable[[MutableMapping[str, Any]], Awaitable[None]]
17
+ AsgiApp: TypeAlias = Callable[[Scope, Receive, Send], Awaitable[None]]
18
+ """An ASGI 3 application."""
19
+
20
+ _METHOD_NOT_ALLOWED = 405
21
+
22
+
23
+ def push_app(push_handler: PushHandler) -> AsgiApp:
24
+ """An ASGI 3 application serving the push endpoint: ``POST`` with the SET as body
25
+ (``application/secevent+jwt``), any other method is answered with 405. The body is read up to
26
+ one byte beyond :data:`~easyssf.receiver.push.MAX_SET_SIZE`; the response carries the status
27
+ of :meth:`~easyssf.receiver.push.PushHandler.handle` and, with an error document, the
28
+ ``Content-Type`` and ``Content-Language`` headers of RFC 8935. Lifespan events are
29
+ acknowledged, so the app can be served on its own.
30
+
31
+ Example:
32
+ >>> app = push_app(receiver.push_handler) # uvicorn.run(app, port=8080)
33
+ >>> fastapi_app.mount("/ssf/push", app) # or under a framework
34
+ """
35
+
36
+ async def app(scope: Scope, receive: Receive, send: Send) -> None:
37
+ if scope["type"] == "lifespan":
38
+ while True:
39
+ message = await receive()
40
+ await send({"type": message["type"] + ".complete"})
41
+ if message["type"] == "lifespan.shutdown":
42
+ return
43
+ if scope["type"] != "http":
44
+ raise RuntimeError(f"the push endpoint serves HTTP, not {scope['type']}")
45
+ if scope["method"] != "POST":
46
+ await _respond(send, _METHOD_NOT_ALLOWED, [(b"allow", b"POST")], b"")
47
+ return
48
+ # read up to one byte beyond the limit, whatever the size of the chunks the server hands
49
+ # over: no more is needed to reject the request, and a transmitter cannot make the endpoint
50
+ # buffer anything it likes
51
+ body = b""
52
+ while len(body) <= MAX_SET_SIZE:
53
+ message = await receive()
54
+ if message["type"] == "http.disconnect":
55
+ return
56
+ body += message.get("body", b"")[: MAX_SET_SIZE + 1 - len(body)]
57
+ if not message.get("more_body", False):
58
+ break
59
+ authorization = next(
60
+ (value.decode("latin-1") for name, value in scope.get("headers", []) if name.lower() == b"authorization"),
61
+ None,
62
+ )
63
+ response = await push_handler.handle(authorization, body)
64
+ content = response.body_bytes
65
+ headers: list[tuple[bytes, bytes]] = []
66
+ if content is not None:
67
+ headers = [
68
+ (b"content-type", PUSH_CONTENT_TYPE.encode()),
69
+ (b"content-language", PUSH_CONTENT_LANGUAGE.encode()),
70
+ ]
71
+ await _respond(send, response.status, headers, content or b"")
72
+
73
+ return app
74
+
75
+
76
+ async def _respond(send: Send, status: int, headers: list[tuple[bytes, bytes]], body: bytes) -> None:
77
+ await send(
78
+ {
79
+ "type": "http.response.start",
80
+ "status": status,
81
+ "headers": [*headers, (b"content-length", str(len(body)).encode())],
82
+ }
83
+ )
84
+ await send({"type": "http.response.body", "body": body})