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.
- easyssf/receiver/__init__.py +437 -0
- easyssf/receiver/asgi.py +84 -0
- easyssf/receiver/authorizer.py +381 -0
- easyssf/receiver/caep.py +145 -0
- easyssf/receiver/config.py +935 -0
- easyssf/receiver/dedup.py +202 -0
- easyssf/receiver/errors.py +203 -0
- easyssf/receiver/event.py +236 -0
- easyssf/receiver/jwks.py +260 -0
- easyssf/receiver/metrics.py +77 -0
- easyssf/receiver/poll.py +742 -0
- easyssf/receiver/processor.py +370 -0
- easyssf/receiver/push.py +209 -0
- easyssf/receiver/py.typed +0 -0
- easyssf/receiver/receiver.py +793 -0
- easyssf/receiver/revocation.py +360 -0
- easyssf/receiver/risc.py +168 -0
- easyssf/receiver/scim.py +140 -0
- easyssf/receiver/session.py +311 -0
- easyssf/receiver/stream.py +821 -0
- easyssf/receiver/transmitter.py +1046 -0
- easyssf/receiver/verifier.py +392 -0
- easyssf/receiver/wsgi.py +72 -0
- easyssf_receiver-0.7.0.dist-info/METADATA +31 -0
- easyssf_receiver-0.7.0.dist-info/RECORD +26 -0
- easyssf_receiver-0.7.0.dist-info/WHEEL +4 -0
|
@@ -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
|
+
]
|
easyssf/receiver/asgi.py
ADDED
|
@@ -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})
|