rp_python_sdk 1.4.1__tar.gz → 1.5.0__tar.gz
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.
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/PKG-INFO +175 -1
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/README.md +172 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/pyproject.toml +5 -1
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/pushed_authorisation_request.py +41 -31
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/retrieve_tokens.py +46 -46
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/user_info.py +22 -6
- rp_python_sdk-1.5.0/rp_python_sdk/endpoints/util/client_assertion.py +28 -0
- rp_python_sdk-1.5.0/rp_python_sdk/endpoints/util/version_info.py +2 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/relying_party_client_sdk.py +58 -2
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/sdk_config.py +2 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/__init__.py +21 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/async_telemetry_sender.py +36 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/sync_telemetry_sender.py +21 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_config.py +27 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_context.py +69 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_dispatcher.py +120 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_event.py +45 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_event_factory.py +107 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_sender.py +24 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_session.py +55 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_token_provider.py +92 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_transport.py +79 -0
- rp_python_sdk-1.5.0/rp_python_sdk/telemetry/telemetry_url_resolver.py +64 -0
- rp_python_sdk-1.4.1/rp_python_sdk/endpoints/util/version_info.py +0 -2
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/__init__.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/cache/__init__.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/cache/http_cache.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/get_participants.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/util/fapi.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/util/mapping.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/util/system_info.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/util/user_agent.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/filters/participant_filters.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/model.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/relying_party_client_sdk_exception.py +0 -0
- {rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/setup_logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rp_python_sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Erik Pragt
|
|
@@ -11,7 +11,9 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
14
15
|
Requires-Dist: authlib (>=1.7.2,<2.0.0)
|
|
16
|
+
Requires-Dist: certifi (>=2026.4.22,<2027.0.0)
|
|
15
17
|
Requires-Dist: cffi (>=2.0.0,<3.0.0)
|
|
16
18
|
Requires-Dist: cryptography (>=50.0.0)
|
|
17
19
|
Requires-Dist: joserfc (>=1.6.8,<2.0.0)
|
|
@@ -414,6 +416,173 @@ The required function parameters are:
|
|
|
414
416
|
The method will return a `Tokenset` that contains the access_token and id_token with the requested claims along
|
|
415
417
|
with a unique `x-fapi-interaction-id` that was used for this request to the authorisation server.
|
|
416
418
|
|
|
419
|
+
## Telemetry
|
|
420
|
+
|
|
421
|
+
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
422
|
+
centralized ConnectID telemetry service over mTLS. Telemetry is **enabled by default** and requires no
|
|
423
|
+
configuration. It is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
424
|
+
|
|
425
|
+
### Opting out
|
|
426
|
+
|
|
427
|
+
To disable telemetry, set `enabled=False` on the `TelemetryConfig`:
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from rp_python_sdk.relying_party_client_sdk import RelyingPartyClientSdk
|
|
431
|
+
from rp_python_sdk.sdk_config import SdkConfig, CustomConfig
|
|
432
|
+
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig
|
|
433
|
+
|
|
434
|
+
client = RelyingPartyClientSdk(
|
|
435
|
+
config=SdkConfig(
|
|
436
|
+
# ... other SdkConfig fields ...
|
|
437
|
+
custom_config=CustomConfig(
|
|
438
|
+
telemetry_config=TelemetryConfig(enabled=False)
|
|
439
|
+
)
|
|
440
|
+
),
|
|
441
|
+
)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
445
|
+
threads, so there is no overhead.
|
|
446
|
+
|
|
447
|
+
### Delivery mode and budget
|
|
448
|
+
|
|
449
|
+
The `delivery_mode` field selects how events are delivered:
|
|
450
|
+
|
|
451
|
+
- `TelemetryDeliveryMode.ASYNC` (default): fire-and-forget. Events are placed on a bounded in-memory queue
|
|
452
|
+
that is drained by daemon worker threads, so the SDK operation returns without waiting for any network I/O.
|
|
453
|
+
- `TelemetryDeliveryMode.SYNC`: events are delivered inline, before the SDK operation returns. Use this for
|
|
454
|
+
hosts that may freeze or terminate before background work completes (for example AWS Lambda), where a
|
|
455
|
+
fire-and-forget event could be lost. Inline delivery is bounded by `sync_send_budget_seconds` (default `1.0`).
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig, TelemetryDeliveryMode
|
|
459
|
+
|
|
460
|
+
CustomConfig(
|
|
461
|
+
telemetry_config=TelemetryConfig(
|
|
462
|
+
delivery_mode=TelemetryDeliveryMode.SYNC,
|
|
463
|
+
sync_send_budget_seconds=1.0,
|
|
464
|
+
)
|
|
465
|
+
)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Note on the SYNC budget: `sync_send_budget_seconds` bounds the **per-phase inactivity** (the connect and read
|
|
469
|
+
timeouts) of the underlying HTTP calls, not the total wall-clock latency of delivery. A degraded telemetry
|
|
470
|
+
endpoint that stays marginally responsive can therefore add more than the configured budget to your SDK call.
|
|
471
|
+
The budget is applied as a remaining-time budget across the token refresh and the event POST, so no single
|
|
472
|
+
phase is given the full budget afresh.
|
|
473
|
+
|
|
474
|
+
`TelemetryConfig` fields:
|
|
475
|
+
|
|
476
|
+
| Field | Default | Description |
|
|
477
|
+
| --- | --- | --- |
|
|
478
|
+
| `enabled` | `True` | Master on/off switch for telemetry. |
|
|
479
|
+
| `endpoint_url` | `None` | Override for the telemetry ingest URL. When `None`, the URL is derived from the SDK environment. |
|
|
480
|
+
| `registry_discovery_document_url` | `None` | Override for the Registry OIDC discovery document URL used to obtain a telemetry access token. When `None`, derived from the environment. |
|
|
481
|
+
| `delivery_mode` | `TelemetryDeliveryMode.ASYNC` | `ASYNC` (background dispatch) or `SYNC` (inline delivery). |
|
|
482
|
+
| `sync_send_budget_seconds` | `1.0` | SYNC-only per-phase inactivity budget in seconds. Must be greater than 0. |
|
|
483
|
+
| `queue_capacity` | `200` | ASYNC-only bound on the in-memory event queue. Events are dropped when the queue is full. Must be greater than 0. |
|
|
484
|
+
| `max_concurrent_sends` | `3` | ASYNC-only number of daemon worker threads draining the queue. Must be greater than 0. |
|
|
485
|
+
|
|
486
|
+
Under `SYNC` the queue is bypassed entirely, so `queue_capacity` and `max_concurrent_sends` are inert.
|
|
487
|
+
|
|
488
|
+
### What is sent
|
|
489
|
+
|
|
490
|
+
One event per operation, tagged `par_request`, `token_request` or `userinfo_request`. Every event carries the same
|
|
491
|
+
top-level envelope; the `data` object varies by operation. Optional fields are omitted entirely when absent rather
|
|
492
|
+
than sent as `null`.
|
|
493
|
+
|
|
494
|
+
#### Top-level fields
|
|
495
|
+
|
|
496
|
+
| Field | Type | Description |
|
|
497
|
+
|-------------------------|---------|--------------------------------------------------------------------------------------|
|
|
498
|
+
| `sourceService` | string | The SDK that emitted the event; always `rp-python-sdk` |
|
|
499
|
+
| `userAgent` | string | The SDK user-agent string, including SDK name and version |
|
|
500
|
+
| `sdkVersion` | string | SDK version, for diagnosing version-specific issues |
|
|
501
|
+
| `authorisationServerId` | string | The identity provider the request was sent to |
|
|
502
|
+
| `clientId` | string | Your client id, as configured on the SDK |
|
|
503
|
+
| `timestamp` | string | When the operation completed (RFC 3339 / ISO-8601, UTC, `Z`-suffixed) |
|
|
504
|
+
| `xFapiInteractionId` | string | The same interaction id returned to you, for correlation |
|
|
505
|
+
| `durationMs` | number | How long the operation took, in milliseconds |
|
|
506
|
+
| `success` | boolean | Whether the operation succeeded |
|
|
507
|
+
| `type` | string | The operation: `par_request`, `token_request` or `userinfo_request` |
|
|
508
|
+
| `schemaVersion` | string | Telemetry schema version (currently `1.0`) |
|
|
509
|
+
| `data` | object | Per-operation detail (see below) |
|
|
510
|
+
|
|
511
|
+
#### `data` by operation
|
|
512
|
+
|
|
513
|
+
`errorDetails` is present only on failure; `expires_in` / `request_uri` and `transaction_id` are present only on
|
|
514
|
+
success.
|
|
515
|
+
|
|
516
|
+
**`par_request`**
|
|
517
|
+
|
|
518
|
+
| Field | Type | Description |
|
|
519
|
+
|------------------------------|---------|-------------------------------------------------------------------|
|
|
520
|
+
| `state` | string | The OAuth `state` sent on the request |
|
|
521
|
+
| `request.purpose` | string | The requested purpose |
|
|
522
|
+
| `request.body.claims` | object | The requested claims structure (claim *names* only, never values) |
|
|
523
|
+
| `response.statusCode` | number | HTTP status code of the PAR response |
|
|
524
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
525
|
+
| `response.body.expires_in` | number | `expires_in` from the PAR response, on success |
|
|
526
|
+
| `response.body.request_uri` | string | `request_uri` from the PAR response, on success |
|
|
527
|
+
|
|
528
|
+
**`token_request`**
|
|
529
|
+
|
|
530
|
+
| Field | Type | Description |
|
|
531
|
+
|---------------------------|---------|-----------------------------------------------------------------|
|
|
532
|
+
| `state` | string | The OAuth `state` associated with the token exchange |
|
|
533
|
+
| `request.grant_type` | string | The grant type used (e.g. `authorization_code`) |
|
|
534
|
+
| `response.statusCode` | number | HTTP status code of the token response |
|
|
535
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
536
|
+
| `response.transaction_id` | string | The `txn` transaction identifier from the token response, on success |
|
|
537
|
+
|
|
538
|
+
**`userinfo_request`**
|
|
539
|
+
|
|
540
|
+
| Field | Type | Description |
|
|
541
|
+
|---------------------------|---------|---------------------------------------------|
|
|
542
|
+
| `response.statusCode` | number | HTTP status code of the userinfo response |
|
|
543
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
544
|
+
|
|
545
|
+
UserInfo events carry no `state` and no `request` object.
|
|
546
|
+
|
|
547
|
+
The following are **never** collected:
|
|
548
|
+
|
|
549
|
+
- no access, refresh or ID tokens
|
|
550
|
+
- no authorization codes
|
|
551
|
+
- no client credentials or client assertions
|
|
552
|
+
- no nonces
|
|
553
|
+
- no code verifiers or code challenges
|
|
554
|
+
- no request-object JWTs
|
|
555
|
+
- no full request or response bodies
|
|
556
|
+
- no claim values (beyond the transaction id)
|
|
557
|
+
|
|
558
|
+
Events are authenticated with the SDK's existing transport certificate (mutual TLS) and a signed client assertion —
|
|
559
|
+
the same credentials the SDK already uses for the ConnectID Registry — so no extra configuration or credentials are
|
|
560
|
+
needed.
|
|
561
|
+
|
|
562
|
+
### Releasing telemetry resources (`close()` and context-manager use)
|
|
563
|
+
|
|
564
|
+
`RelyingPartyClientSdk` exposes a `close()` method and supports the context-manager protocol. Closing signals
|
|
565
|
+
the background dispatcher to stop, discards any queued events, and joins the worker threads within a bounded
|
|
566
|
+
grace period, releasing the transport session. `close()` is idempotent and never raises.
|
|
567
|
+
|
|
568
|
+
Prefer the context manager so resources are released automatically:
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
with RelyingPartyClientSdk(config) as sdk:
|
|
572
|
+
participants = sdk.get_participants()
|
|
573
|
+
# telemetry resources are released on exit
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Or call `close()` explicitly when you manage the lifecycle yourself:
|
|
577
|
+
|
|
578
|
+
```python
|
|
579
|
+
sdk = RelyingPartyClientSdk(config)
|
|
580
|
+
try:
|
|
581
|
+
participants = sdk.get_participants()
|
|
582
|
+
finally:
|
|
583
|
+
sdk.close()
|
|
584
|
+
```
|
|
585
|
+
|
|
417
586
|
## Conformance test
|
|
418
587
|
|
|
419
588
|
An automated conformance test has been added. To execute the conformance test, a token is required,
|
|
@@ -451,6 +620,11 @@ poetry publish
|
|
|
451
620
|
|
|
452
621
|
## Release Notes
|
|
453
622
|
|
|
623
|
+
### 1.5.0 (Sep 22, 2026)
|
|
624
|
+
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
625
|
+
- Supports ASYNC (default) and SYNC delivery modes
|
|
626
|
+
- Configurable via `TelemetryConfig` on `CustomConfig` — disable with `TelemetryConfig(enabled=False)`
|
|
627
|
+
|
|
454
628
|
### 1.4.1 (Aug 4, 2026)
|
|
455
629
|
- Security: require cryptography 50.0.0 or newer to address CVE-2026-69247 and CVE-2026-69249
|
|
456
630
|
- Removed the cryptography major-version ceiling so consumers can adopt future security releases without waiting for an SDK release
|
|
@@ -393,6 +393,173 @@ The required function parameters are:
|
|
|
393
393
|
The method will return a `Tokenset` that contains the access_token and id_token with the requested claims along
|
|
394
394
|
with a unique `x-fapi-interaction-id` that was used for this request to the authorisation server.
|
|
395
395
|
|
|
396
|
+
## Telemetry
|
|
397
|
+
|
|
398
|
+
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
399
|
+
centralized ConnectID telemetry service over mTLS. Telemetry is **enabled by default** and requires no
|
|
400
|
+
configuration. It is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
401
|
+
|
|
402
|
+
### Opting out
|
|
403
|
+
|
|
404
|
+
To disable telemetry, set `enabled=False` on the `TelemetryConfig`:
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
from rp_python_sdk.relying_party_client_sdk import RelyingPartyClientSdk
|
|
408
|
+
from rp_python_sdk.sdk_config import SdkConfig, CustomConfig
|
|
409
|
+
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig
|
|
410
|
+
|
|
411
|
+
client = RelyingPartyClientSdk(
|
|
412
|
+
config=SdkConfig(
|
|
413
|
+
# ... other SdkConfig fields ...
|
|
414
|
+
custom_config=CustomConfig(
|
|
415
|
+
telemetry_config=TelemetryConfig(enabled=False)
|
|
416
|
+
)
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
422
|
+
threads, so there is no overhead.
|
|
423
|
+
|
|
424
|
+
### Delivery mode and budget
|
|
425
|
+
|
|
426
|
+
The `delivery_mode` field selects how events are delivered:
|
|
427
|
+
|
|
428
|
+
- `TelemetryDeliveryMode.ASYNC` (default): fire-and-forget. Events are placed on a bounded in-memory queue
|
|
429
|
+
that is drained by daemon worker threads, so the SDK operation returns without waiting for any network I/O.
|
|
430
|
+
- `TelemetryDeliveryMode.SYNC`: events are delivered inline, before the SDK operation returns. Use this for
|
|
431
|
+
hosts that may freeze or terminate before background work completes (for example AWS Lambda), where a
|
|
432
|
+
fire-and-forget event could be lost. Inline delivery is bounded by `sync_send_budget_seconds` (default `1.0`).
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig, TelemetryDeliveryMode
|
|
436
|
+
|
|
437
|
+
CustomConfig(
|
|
438
|
+
telemetry_config=TelemetryConfig(
|
|
439
|
+
delivery_mode=TelemetryDeliveryMode.SYNC,
|
|
440
|
+
sync_send_budget_seconds=1.0,
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Note on the SYNC budget: `sync_send_budget_seconds` bounds the **per-phase inactivity** (the connect and read
|
|
446
|
+
timeouts) of the underlying HTTP calls, not the total wall-clock latency of delivery. A degraded telemetry
|
|
447
|
+
endpoint that stays marginally responsive can therefore add more than the configured budget to your SDK call.
|
|
448
|
+
The budget is applied as a remaining-time budget across the token refresh and the event POST, so no single
|
|
449
|
+
phase is given the full budget afresh.
|
|
450
|
+
|
|
451
|
+
`TelemetryConfig` fields:
|
|
452
|
+
|
|
453
|
+
| Field | Default | Description |
|
|
454
|
+
| --- | --- | --- |
|
|
455
|
+
| `enabled` | `True` | Master on/off switch for telemetry. |
|
|
456
|
+
| `endpoint_url` | `None` | Override for the telemetry ingest URL. When `None`, the URL is derived from the SDK environment. |
|
|
457
|
+
| `registry_discovery_document_url` | `None` | Override for the Registry OIDC discovery document URL used to obtain a telemetry access token. When `None`, derived from the environment. |
|
|
458
|
+
| `delivery_mode` | `TelemetryDeliveryMode.ASYNC` | `ASYNC` (background dispatch) or `SYNC` (inline delivery). |
|
|
459
|
+
| `sync_send_budget_seconds` | `1.0` | SYNC-only per-phase inactivity budget in seconds. Must be greater than 0. |
|
|
460
|
+
| `queue_capacity` | `200` | ASYNC-only bound on the in-memory event queue. Events are dropped when the queue is full. Must be greater than 0. |
|
|
461
|
+
| `max_concurrent_sends` | `3` | ASYNC-only number of daemon worker threads draining the queue. Must be greater than 0. |
|
|
462
|
+
|
|
463
|
+
Under `SYNC` the queue is bypassed entirely, so `queue_capacity` and `max_concurrent_sends` are inert.
|
|
464
|
+
|
|
465
|
+
### What is sent
|
|
466
|
+
|
|
467
|
+
One event per operation, tagged `par_request`, `token_request` or `userinfo_request`. Every event carries the same
|
|
468
|
+
top-level envelope; the `data` object varies by operation. Optional fields are omitted entirely when absent rather
|
|
469
|
+
than sent as `null`.
|
|
470
|
+
|
|
471
|
+
#### Top-level fields
|
|
472
|
+
|
|
473
|
+
| Field | Type | Description |
|
|
474
|
+
|-------------------------|---------|--------------------------------------------------------------------------------------|
|
|
475
|
+
| `sourceService` | string | The SDK that emitted the event; always `rp-python-sdk` |
|
|
476
|
+
| `userAgent` | string | The SDK user-agent string, including SDK name and version |
|
|
477
|
+
| `sdkVersion` | string | SDK version, for diagnosing version-specific issues |
|
|
478
|
+
| `authorisationServerId` | string | The identity provider the request was sent to |
|
|
479
|
+
| `clientId` | string | Your client id, as configured on the SDK |
|
|
480
|
+
| `timestamp` | string | When the operation completed (RFC 3339 / ISO-8601, UTC, `Z`-suffixed) |
|
|
481
|
+
| `xFapiInteractionId` | string | The same interaction id returned to you, for correlation |
|
|
482
|
+
| `durationMs` | number | How long the operation took, in milliseconds |
|
|
483
|
+
| `success` | boolean | Whether the operation succeeded |
|
|
484
|
+
| `type` | string | The operation: `par_request`, `token_request` or `userinfo_request` |
|
|
485
|
+
| `schemaVersion` | string | Telemetry schema version (currently `1.0`) |
|
|
486
|
+
| `data` | object | Per-operation detail (see below) |
|
|
487
|
+
|
|
488
|
+
#### `data` by operation
|
|
489
|
+
|
|
490
|
+
`errorDetails` is present only on failure; `expires_in` / `request_uri` and `transaction_id` are present only on
|
|
491
|
+
success.
|
|
492
|
+
|
|
493
|
+
**`par_request`**
|
|
494
|
+
|
|
495
|
+
| Field | Type | Description |
|
|
496
|
+
|------------------------------|---------|-------------------------------------------------------------------|
|
|
497
|
+
| `state` | string | The OAuth `state` sent on the request |
|
|
498
|
+
| `request.purpose` | string | The requested purpose |
|
|
499
|
+
| `request.body.claims` | object | The requested claims structure (claim *names* only, never values) |
|
|
500
|
+
| `response.statusCode` | number | HTTP status code of the PAR response |
|
|
501
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
502
|
+
| `response.body.expires_in` | number | `expires_in` from the PAR response, on success |
|
|
503
|
+
| `response.body.request_uri` | string | `request_uri` from the PAR response, on success |
|
|
504
|
+
|
|
505
|
+
**`token_request`**
|
|
506
|
+
|
|
507
|
+
| Field | Type | Description |
|
|
508
|
+
|---------------------------|---------|-----------------------------------------------------------------|
|
|
509
|
+
| `state` | string | The OAuth `state` associated with the token exchange |
|
|
510
|
+
| `request.grant_type` | string | The grant type used (e.g. `authorization_code`) |
|
|
511
|
+
| `response.statusCode` | number | HTTP status code of the token response |
|
|
512
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
513
|
+
| `response.transaction_id` | string | The `txn` transaction identifier from the token response, on success |
|
|
514
|
+
|
|
515
|
+
**`userinfo_request`**
|
|
516
|
+
|
|
517
|
+
| Field | Type | Description |
|
|
518
|
+
|---------------------------|---------|---------------------------------------------|
|
|
519
|
+
| `response.statusCode` | number | HTTP status code of the userinfo response |
|
|
520
|
+
| `response.errorDetails` | string | Error detail from the response, on failure |
|
|
521
|
+
|
|
522
|
+
UserInfo events carry no `state` and no `request` object.
|
|
523
|
+
|
|
524
|
+
The following are **never** collected:
|
|
525
|
+
|
|
526
|
+
- no access, refresh or ID tokens
|
|
527
|
+
- no authorization codes
|
|
528
|
+
- no client credentials or client assertions
|
|
529
|
+
- no nonces
|
|
530
|
+
- no code verifiers or code challenges
|
|
531
|
+
- no request-object JWTs
|
|
532
|
+
- no full request or response bodies
|
|
533
|
+
- no claim values (beyond the transaction id)
|
|
534
|
+
|
|
535
|
+
Events are authenticated with the SDK's existing transport certificate (mutual TLS) and a signed client assertion —
|
|
536
|
+
the same credentials the SDK already uses for the ConnectID Registry — so no extra configuration or credentials are
|
|
537
|
+
needed.
|
|
538
|
+
|
|
539
|
+
### Releasing telemetry resources (`close()` and context-manager use)
|
|
540
|
+
|
|
541
|
+
`RelyingPartyClientSdk` exposes a `close()` method and supports the context-manager protocol. Closing signals
|
|
542
|
+
the background dispatcher to stop, discards any queued events, and joins the worker threads within a bounded
|
|
543
|
+
grace period, releasing the transport session. `close()` is idempotent and never raises.
|
|
544
|
+
|
|
545
|
+
Prefer the context manager so resources are released automatically:
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
with RelyingPartyClientSdk(config) as sdk:
|
|
549
|
+
participants = sdk.get_participants()
|
|
550
|
+
# telemetry resources are released on exit
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Or call `close()` explicitly when you manage the lifecycle yourself:
|
|
554
|
+
|
|
555
|
+
```python
|
|
556
|
+
sdk = RelyingPartyClientSdk(config)
|
|
557
|
+
try:
|
|
558
|
+
participants = sdk.get_participants()
|
|
559
|
+
finally:
|
|
560
|
+
sdk.close()
|
|
561
|
+
```
|
|
562
|
+
|
|
396
563
|
## Conformance test
|
|
397
564
|
|
|
398
565
|
An automated conformance test has been added. To execute the conformance test, a token is required,
|
|
@@ -430,6 +597,11 @@ poetry publish
|
|
|
430
597
|
|
|
431
598
|
## Release Notes
|
|
432
599
|
|
|
600
|
+
### 1.5.0 (Sep 22, 2026)
|
|
601
|
+
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
602
|
+
- Supports ASYNC (default) and SYNC delivery modes
|
|
603
|
+
- Configurable via `TelemetryConfig` on `CustomConfig` — disable with `TelemetryConfig(enabled=False)`
|
|
604
|
+
|
|
433
605
|
### 1.4.1 (Aug 4, 2026)
|
|
434
606
|
- Security: require cryptography 50.0.0 or newer to address CVE-2026-69247 and CVE-2026-69249
|
|
435
607
|
- Removed the cryptography major-version ceiling so consumers can adopt future security releases without waiting for an SDK release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "rp_python_sdk"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.5.0"
|
|
4
4
|
description = "Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem"
|
|
5
5
|
authors = ["Erik Pragt <erik.pragt@connectid.com.au>"]
|
|
6
6
|
license = "Proprietary"
|
|
@@ -14,11 +14,15 @@ cryptography = ">=50.0.0"
|
|
|
14
14
|
joserfc = "^1.6.8"
|
|
15
15
|
cffi = "^2.0.0"
|
|
16
16
|
urllib3 = "^2.7.0"
|
|
17
|
+
# Already present transitively via requests; declared explicitly because the telemetry TLS
|
|
18
|
+
# context imports it directly to trust the public roots that sign the Registry endpoints.
|
|
19
|
+
certifi = "^2026.4.22"
|
|
17
20
|
|
|
18
21
|
[tool.poetry.group.test.dependencies]
|
|
19
22
|
pytest = "^9.0.0"
|
|
20
23
|
httpx = "^0.28.1"
|
|
21
24
|
toml = "^0.10.2"
|
|
25
|
+
jsonschema = { extras = ["format-nongpl"], version = "^4.25.1" }
|
|
22
26
|
|
|
23
27
|
[tool.poetry.group.dev.dependencies]
|
|
24
28
|
black = "^26.5.1"
|
{rp_python_sdk-1.4.1 → rp_python_sdk-1.5.0}/rp_python_sdk/endpoints/pushed_authorisation_request.py
RENAMED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
|
-
import uuid
|
|
4
3
|
from typing import Any
|
|
5
4
|
from urllib.parse import quote
|
|
6
5
|
|
|
@@ -11,12 +10,16 @@ from authlib.oauth2.rfc7636 import create_s256_code_challenge
|
|
|
11
10
|
|
|
12
11
|
from rp_python_sdk.endpoints.get_participants import get_auth_server_details, \
|
|
13
12
|
get_issuer_metadata
|
|
13
|
+
from rp_python_sdk.endpoints.util.client_assertion import create_client_assertion
|
|
14
14
|
from rp_python_sdk.endpoints.util.fapi import create_x_fapi_interaction_id, log_x_fapi_interaction_id_matches
|
|
15
15
|
from rp_python_sdk.model import PARResponse
|
|
16
16
|
from rp_python_sdk.relying_party_client_sdk_exception import RelyingPartyClientSdkException
|
|
17
17
|
from rp_python_sdk.sdk_config import SdkConfig
|
|
18
18
|
from rp_python_sdk.setup_logger import logger
|
|
19
19
|
from rp_python_sdk.cache.http_cache import HttpCache
|
|
20
|
+
from rp_python_sdk.telemetry.telemetry_context import ParTelemetryContext
|
|
21
|
+
from rp_python_sdk.telemetry.telemetry_event_factory import create_par_request_event
|
|
22
|
+
from rp_python_sdk.telemetry.telemetry_sender import NoopTelemetrySender, TelemetrySender
|
|
20
23
|
|
|
21
24
|
extended_claims = {"over16", "over18", "over21", "over25", "over65", "beneficiary_account_au",
|
|
22
25
|
"beneficiary_account_au_payid", "beneficiary_account_international"}
|
|
@@ -27,7 +30,8 @@ def send_pushed_authorisation_request(config: SdkConfig,
|
|
|
27
30
|
essential_claims: set[str],
|
|
28
31
|
voluntary_claims: set[str],
|
|
29
32
|
purpose: str,
|
|
30
|
-
cache: HttpCache
|
|
33
|
+
cache: HttpCache,
|
|
34
|
+
telemetry_sender: TelemetrySender = NoopTelemetrySender.instance()) -> PARResponse:
|
|
31
35
|
_check_valid_purpose(purpose)
|
|
32
36
|
|
|
33
37
|
checked_essential_claims = _ensure_mandatory_claims_present(essential_claims, voluntary_claims)
|
|
@@ -39,7 +43,7 @@ def send_pushed_authorisation_request(config: SdkConfig,
|
|
|
39
43
|
auth_server_url = issuer_metadata.issuer
|
|
40
44
|
x_fapi_interaction_id = create_x_fapi_interaction_id()
|
|
41
45
|
|
|
42
|
-
logger.
|
|
46
|
+
logger.debug(
|
|
43
47
|
f"Sending PAR to auth server: {authorisation_server_id} - {authorisation_server.customer_friendly_name}, "
|
|
44
48
|
f"essential claims requested: {checked_essential_claims}, voluntary claims requested: {voluntary_claims}, "
|
|
45
49
|
f"x-fapi-interaction-id: {x_fapi_interaction_id}, purpose: {purpose}")
|
|
@@ -57,7 +61,6 @@ def send_pushed_authorisation_request(config: SdkConfig,
|
|
|
57
61
|
state = generate_token()
|
|
58
62
|
nonce = generate_token()
|
|
59
63
|
scope = "openid"
|
|
60
|
-
jti = str(uuid.uuid4())
|
|
61
64
|
|
|
62
65
|
code_verifier = generate_token(48)
|
|
63
66
|
code_challenge = create_s256_code_challenge(code_verifier)
|
|
@@ -84,22 +87,12 @@ def send_pushed_authorisation_request(config: SdkConfig,
|
|
|
84
87
|
jws = JsonWebSignature()
|
|
85
88
|
request_jwt = jws.serialize_compact(header, json.dumps(request_payload), config.signing_key)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
'sub': client_id,
|
|
89
|
-
'aud': auth_server_url,
|
|
90
|
-
'iss': client_id,
|
|
91
|
-
'exp': exp,
|
|
92
|
-
'jti': jti,
|
|
93
|
-
'iat': iat,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
client_assertion_jwt = jws.serialize_compact(header, json.dumps(client_assertion_payload),
|
|
97
|
-
config.signing_key)
|
|
90
|
+
client_assertion_jwt = create_client_assertion(config.signing_key, config.signing_kid, client_id, auth_server_url)
|
|
98
91
|
|
|
99
92
|
# Data payload
|
|
100
93
|
data = {
|
|
101
94
|
"request": request_jwt.decode("utf-8"),
|
|
102
|
-
"client_assertion": client_assertion_jwt
|
|
95
|
+
"client_assertion": client_assertion_jwt,
|
|
103
96
|
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
|
104
97
|
}
|
|
105
98
|
|
|
@@ -109,25 +102,42 @@ def send_pushed_authorisation_request(config: SdkConfig,
|
|
|
109
102
|
"x-fapi-interaction-id": x_fapi_interaction_id
|
|
110
103
|
}
|
|
111
104
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
context = ParTelemetryContext()
|
|
106
|
+
http_status_code = None
|
|
107
|
+
try:
|
|
108
|
+
# Making the POST request
|
|
109
|
+
response = requests.post(par_endpoint, data=data, headers=headers,
|
|
110
|
+
cert=(config.transport_pem, config.transport_key),
|
|
111
|
+
verify=config.ca_pem)
|
|
112
|
+
http_status_code = response.status_code
|
|
113
|
+
|
|
114
|
+
log_x_fapi_interaction_id_matches(authorisation_server, "send_pushed_authorisation_request",
|
|
115
|
+
x_fapi_interaction_id, response)
|
|
116
|
+
|
|
117
|
+
if not response.ok:
|
|
118
|
+
raise RelyingPartyClientSdkException(
|
|
119
|
+
f"Response code for PAR to {authorisation_server_id} not successful, "
|
|
120
|
+
f"was: {response.status_code} x-fapi-interaction-id: {x_fapi_interaction_id}, "
|
|
121
|
+
f"response body: {response.text}")
|
|
122
|
+
|
|
123
|
+
body = response.json()
|
|
124
|
+
raw_expires_in = body.get("expires_in")
|
|
125
|
+
expires_in = raw_expires_in if isinstance(raw_expires_in, int) else None
|
|
126
|
+
request_uri = body['request_uri']
|
|
127
|
+
logger.debug(f"PAR was sent, request_uri='{request_uri}'")
|
|
128
|
+
|
|
129
|
+
context.record_success(http_status_code, expires_in, request_uri)
|
|
130
|
+
except Exception as exception:
|
|
131
|
+
context.record_failure(http_status_code, str(exception))
|
|
132
|
+
raise
|
|
133
|
+
finally:
|
|
134
|
+
event = create_par_request_event(config, x_fapi_interaction_id, authorisation_server_id,
|
|
135
|
+
state, purpose, claim_request, context)
|
|
136
|
+
telemetry_sender.send(event)
|
|
126
137
|
|
|
127
138
|
return PARResponse(_construct_auth_url(auth_endpoint, client_id, request_uri),
|
|
128
139
|
code_verifier, state, nonce, x_fapi_interaction_id)
|
|
129
140
|
|
|
130
|
-
|
|
131
141
|
def _check_valid_purpose(purpose: str) -> None:
|
|
132
142
|
if len(purpose) < 3 or len(purpose) > 300:
|
|
133
143
|
raise RelyingPartyClientSdkException("Purpose must be between 3 and 300 characters")
|