connection-hub 0.0.3__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.
- connection_hub-0.0.3/PKG-INFO +121 -0
- connection_hub-0.0.3/README.md +99 -0
- connection_hub-0.0.3/pyproject.toml +43 -0
- connection_hub-0.0.3/setup.cfg +4 -0
- connection_hub-0.0.3/src/connection_hub/__init__.py +58 -0
- connection_hub-0.0.3/src/connection_hub/agent_account_scope.py +155 -0
- connection_hub-0.0.3/src/connection_hub/authenticators/__init__.py +34 -0
- connection_hub-0.0.3/src/connection_hub/authenticators/authority.py +265 -0
- connection_hub-0.0.3/src/connection_hub/authenticators/client.py +75 -0
- connection_hub-0.0.3/src/connection_hub/authenticators/models.py +309 -0
- connection_hub-0.0.3/src/connection_hub/authority_inventory.py +342 -0
- connection_hub-0.0.3/src/connection_hub/authority_projection.py +241 -0
- connection_hub-0.0.3/src/connection_hub/authority_providers/__init__.py +13 -0
- connection_hub-0.0.3/src/connection_hub/authority_registry.py +430 -0
- connection_hub-0.0.3/src/connection_hub/authority_registry_client.py +235 -0
- connection_hub-0.0.3/src/connection_hub/authority_registry_config.py +365 -0
- connection_hub-0.0.3/src/connection_hub/client.py +135 -0
- connection_hub-0.0.3/src/connection_hub/connection_edges.py +209 -0
- connection_hub-0.0.3/src/connection_hub/connector_app_resolution.py +82 -0
- connection_hub-0.0.3/src/connection_hub/consent_state.py +227 -0
- connection_hub-0.0.3/src/connection_hub/consent_state_adapters.py +130 -0
- connection_hub-0.0.3/src/connection_hub/contract.py +320 -0
- connection_hub-0.0.3/src/connection_hub/custom_authorities/__init__.py +30 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/__init__.py +4 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/access_map.py +225 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/admission.py +506 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/automation_access.py +3318 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cache_io.py +44 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cache_settings.py +121 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/__init__.py +48 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/cache.py +356 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/handles.py +66 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/migration.py +101 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/model.py +428 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/persistence.py +159 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/resolver.py +199 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/service.py +379 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/cards/store.py +234 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/__init__.py +52 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/authorization.py +344 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/drift.py +308 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/hashing.py +33 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/models.py +129 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/publisher.py +203 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/reconcile.py +168 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/resolver.py +131 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/runtime_cache.py +131 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/source.py +26 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/catalog/store.py +124 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/consent_denial.py +533 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/credential_view.py +304 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/durable_io.py +108 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/live_grant.py +145 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/named_service_policy.py +296 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/__init__.py +42 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/account_requirements.py +335 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/authority.py +198 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/client_metadata.py +535 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/clients.py +168 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/config.py +669 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/consent.py +885 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/flow.py +126 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/grants.py +104 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/metadata.py +155 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/pkce.py +21 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/store.py +574 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/oauth/surface_policy.py +833 -0
- connection_hub-0.0.3/src/connection_hub/delegated_credentials/serving.py +208 -0
- connection_hub-0.0.3/src/connection_hub/delegated_mcp.py +309 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/__init__.py +105 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/adapters.py +239 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/broker.py +557 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/client.py +266 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/config.py +58 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/consent_demand.py +721 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/models.py +547 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/oauth.py +179 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/operations.py +675 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/preflight.py +443 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/__init__.py +27 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/email.py +28 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/generic_oauth.py +174 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/google.py +88 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/linkedin.py +202 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/providers/slack.py +92 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/public_base.py +57 -0
- connection_hub-0.0.3/src/connection_hub/delegated_to_kdcube/store.py +406 -0
- connection_hub-0.0.3/src/connection_hub/federated_tokens/__init__.py +32 -0
- connection_hub-0.0.3/src/connection_hub/federated_tokens/data_bus.py +363 -0
- connection_hub-0.0.3/src/connection_hub/hub/__init__.py +4 -0
- connection_hub-0.0.3/src/connection_hub/hub/authenticator_store.py +275 -0
- connection_hub-0.0.3/src/connection_hub/hub/edges.py +650 -0
- connection_hub-0.0.3/src/connection_hub/hub/resolver.py +507 -0
- connection_hub-0.0.3/src/connection_hub/mcp_consent.py +294 -0
- connection_hub-0.0.3/src/connection_hub/mcp_metadata.py +134 -0
- connection_hub-0.0.3/src/connection_hub/mcp_result.py +351 -0
- connection_hub-0.0.3/src/connection_hub/mcp_tool_enforcement.py +260 -0
- connection_hub-0.0.3/src/connection_hub/named_service_admission.py +511 -0
- connection_hub-0.0.3/src/connection_hub/named_service_boundary.py +229 -0
- connection_hub-0.0.3/src/connection_hub/py.typed +1 -0
- connection_hub-0.0.3/src/connection_hub/request_auth.py +241 -0
- connection_hub-0.0.3/src/connection_hub.egg-info/PKG-INFO +121 -0
- connection_hub-0.0.3/src/connection_hub.egg-info/SOURCES.txt +134 -0
- connection_hub-0.0.3/src/connection_hub.egg-info/dependency_links.txt +1 -0
- connection_hub-0.0.3/src/connection_hub.egg-info/requires.txt +6 -0
- connection_hub-0.0.3/src/connection_hub.egg-info/top_level.txt +1 -0
- connection_hub-0.0.3/tests/test_access_map.py +158 -0
- connection_hub-0.0.3/tests/test_account_store.py +67 -0
- connection_hub-0.0.3/tests/test_admission.py +225 -0
- connection_hub-0.0.3/tests/test_authority_projection.py +45 -0
- connection_hub-0.0.3/tests/test_authority_registry.py +90 -0
- connection_hub-0.0.3/tests/test_authority_registry_config.py +248 -0
- connection_hub-0.0.3/tests/test_cache_settings.py +49 -0
- connection_hub-0.0.3/tests/test_card_service.py +125 -0
- connection_hub-0.0.3/tests/test_catalog_documents.py +160 -0
- connection_hub-0.0.3/tests/test_catalog_publisher.py +64 -0
- connection_hub-0.0.3/tests/test_client.py +59 -0
- connection_hub-0.0.3/tests/test_connector_app_resolution.py +19 -0
- connection_hub-0.0.3/tests/test_consent_demand.py +145 -0
- connection_hub-0.0.3/tests/test_consent_state.py +124 -0
- connection_hub-0.0.3/tests/test_contract.py +41 -0
- connection_hub-0.0.3/tests/test_credential_view.py +101 -0
- connection_hub-0.0.3/tests/test_delegated_mcp.py +274 -0
- connection_hub-0.0.3/tests/test_federated_tokens.py +107 -0
- connection_hub-0.0.3/tests/test_host_ports.py +92 -0
- connection_hub-0.0.3/tests/test_mcp_consent.py +120 -0
- connection_hub-0.0.3/tests/test_mcp_result.py +109 -0
- connection_hub-0.0.3/tests/test_mcp_tool_enforcement.py +104 -0
- connection_hub-0.0.3/tests/test_named_service_admission.py +139 -0
- connection_hub-0.0.3/tests/test_named_service_boundary.py +96 -0
- connection_hub-0.0.3/tests/test_oauth_config.py +55 -0
- connection_hub-0.0.3/tests/test_oauth_grants.py +84 -0
- connection_hub-0.0.3/tests/test_public_import_surface.py +19 -0
- connection_hub-0.0.3/tests/test_request_auth.py +107 -0
- connection_hub-0.0.3/tests/test_serving_ports.py +71 -0
- connection_hub-0.0.3/tests/test_surface_policy.py +170 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: connection-hub
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Connection Hub delegated-access authority and client SDK.
|
|
5
|
+
Author: Elena Viter
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/elenaviter/app-ecosystem
|
|
8
|
+
Project-URL: Repository, https://github.com/elenaviter/app-ecosystem
|
|
9
|
+
Project-URL: Documentation, https://github.com/elenaviter/app-ecosystem/tree/main/docs/connection-hub
|
|
10
|
+
Project-URL: Issues, https://github.com/elenaviter/app-ecosystem/issues
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: aiohttp<4,>=3.9
|
|
18
|
+
Requires-Dist: httpx<1,>=0.27
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest<10,>=8; extra == "test"
|
|
21
|
+
Requires-Dist: pytest-asyncio<2,>=0.23; extra == "test"
|
|
22
|
+
|
|
23
|
+
# Connection Hub
|
|
24
|
+
|
|
25
|
+
The Python library and client SDK for the Connection Hub product.
|
|
26
|
+
|
|
27
|
+
Connection Hub gives every agent, automation, and connected application its own
|
|
28
|
+
delegated-access card. The card records whose authority the caller uses, which
|
|
29
|
+
resources and operations it may reach, which connected accounts it may use,
|
|
30
|
+
and when that authority expires. A service resolves the current card and
|
|
31
|
+
current capability catalog at the operation boundary, so an edit or revocation
|
|
32
|
+
applies to the next call.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m pip install connection-hub
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`0.0.3` is an alpha release. It contains the portable implementation used by
|
|
41
|
+
the Connection Hub application in this repository:
|
|
42
|
+
|
|
43
|
+
- versioned delegated cards and capability catalogs;
|
|
44
|
+
- live card/catalog admission for managed REST, MCP, and named-service calls;
|
|
45
|
+
- delegated OAuth client and connected-account policy contracts;
|
|
46
|
+
- structured, actionable denial results;
|
|
47
|
+
- direct protected-service admission with an opaque delegated bearer and an
|
|
48
|
+
independent replay-protected workload proof;
|
|
49
|
+
- explicit host ports for storage, identity, dispatch, secrets, and live
|
|
50
|
+
delivery.
|
|
51
|
+
|
|
52
|
+
The same product is currently hosted in KDCube under the technical app id
|
|
53
|
+
[`connection-hub@1-0`](../../apps/connection-hub@1-0/README.md). The library
|
|
54
|
+
owns portable authority semantics and client contracts; the application host
|
|
55
|
+
supplies authenticated sessions, storage, secret resolution, Redis protocol
|
|
56
|
+
state, HTTP surfaces, and the user interface.
|
|
57
|
+
|
|
58
|
+
## Direct Protected-Service Admission
|
|
59
|
+
|
|
60
|
+
An external backend can accept a user's opaque delegated bearer and ask the
|
|
61
|
+
Connection Hub for a live decision about one concrete operation. The backend
|
|
62
|
+
also signs the request with its own registered workload secret; possession of
|
|
63
|
+
the user bearer alone is not service identity.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import secrets
|
|
67
|
+
import time
|
|
68
|
+
|
|
69
|
+
from connection_hub.delegated_credentials.admission import (
|
|
70
|
+
AdmissionRequest,
|
|
71
|
+
sign_admission_request,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
request = AdmissionRequest(
|
|
75
|
+
resource="https://api.example.test/customers",
|
|
76
|
+
operation="customers.search",
|
|
77
|
+
)
|
|
78
|
+
timestamp = str(int(time.time()))
|
|
79
|
+
nonce = secrets.token_urlsafe(24)
|
|
80
|
+
signature = sign_admission_request(
|
|
81
|
+
secret=service_signing_secret,
|
|
82
|
+
service_id="crm-api",
|
|
83
|
+
timestamp=timestamp,
|
|
84
|
+
nonce=nonce,
|
|
85
|
+
delegated_token=user_delegated_bearer,
|
|
86
|
+
request=request,
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The service sends the semantic request body and the four `X-Connection-Hub-*` proof
|
|
91
|
+
headers to the configured Connection Hub admission endpoint, with the opaque
|
|
92
|
+
delegated bearer in `Authorization: Bearer ...`. An allow response contains a
|
|
93
|
+
service-scoped subject and only the bounded authority relevant to that
|
|
94
|
+
operation. It never returns provider credentials or the platform's internal
|
|
95
|
+
user id.
|
|
96
|
+
|
|
97
|
+
See the runnable
|
|
98
|
+
[`direct-admission-service`](../../examples/connection-hub/direct-admission-service/README.md)
|
|
99
|
+
and the [deployment recipe](../../docs/connection-hub/recipes/direct-protected-service.md).
|
|
100
|
+
|
|
101
|
+
## Integration Boundaries
|
|
102
|
+
|
|
103
|
+
- The service registry authenticates workloads and binds each service to
|
|
104
|
+
resource selectors. It does not duplicate the operation/grant catalog.
|
|
105
|
+
- Every decision intersects the delegated bearer with the current card and
|
|
106
|
+
active catalog. A cached allow is not an authority source.
|
|
107
|
+
- Connected-account credential resolution is a separate trusted operation;
|
|
108
|
+
direct admission does not export provider secrets.
|
|
109
|
+
- A protected backend still applies its own domain authorization after
|
|
110
|
+
Connection Hub admission.
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
- [Connection Hub architecture and semantic requirements](../../docs/connection-hub/connection-hub-architecture.md)
|
|
115
|
+
- [Delegated authority and admission](../../docs/connection-hub/package/delegated-authority-and-admission.md)
|
|
116
|
+
- [Delegated access cards](../../docs/connection-hub/package/delegated-cards.md)
|
|
117
|
+
- [OAuth delegated credential protocol](../../docs/connection-hub/package/oauth-delegated-credential-protocol.md)
|
|
118
|
+
- [Package extraction boundary](../../docs/connection-hub/package/extraction-architecture.md)
|
|
119
|
+
- [Package release procedure](../../docs/package-releases.md)
|
|
120
|
+
|
|
121
|
+
License: MIT. Source: https://github.com/elenaviter/app-ecosystem
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Connection Hub
|
|
2
|
+
|
|
3
|
+
The Python library and client SDK for the Connection Hub product.
|
|
4
|
+
|
|
5
|
+
Connection Hub gives every agent, automation, and connected application its own
|
|
6
|
+
delegated-access card. The card records whose authority the caller uses, which
|
|
7
|
+
resources and operations it may reach, which connected accounts it may use,
|
|
8
|
+
and when that authority expires. A service resolves the current card and
|
|
9
|
+
current capability catalog at the operation boundary, so an edit or revocation
|
|
10
|
+
applies to the next call.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
python -m pip install connection-hub
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`0.0.3` is an alpha release. It contains the portable implementation used by
|
|
19
|
+
the Connection Hub application in this repository:
|
|
20
|
+
|
|
21
|
+
- versioned delegated cards and capability catalogs;
|
|
22
|
+
- live card/catalog admission for managed REST, MCP, and named-service calls;
|
|
23
|
+
- delegated OAuth client and connected-account policy contracts;
|
|
24
|
+
- structured, actionable denial results;
|
|
25
|
+
- direct protected-service admission with an opaque delegated bearer and an
|
|
26
|
+
independent replay-protected workload proof;
|
|
27
|
+
- explicit host ports for storage, identity, dispatch, secrets, and live
|
|
28
|
+
delivery.
|
|
29
|
+
|
|
30
|
+
The same product is currently hosted in KDCube under the technical app id
|
|
31
|
+
[`connection-hub@1-0`](../../apps/connection-hub@1-0/README.md). The library
|
|
32
|
+
owns portable authority semantics and client contracts; the application host
|
|
33
|
+
supplies authenticated sessions, storage, secret resolution, Redis protocol
|
|
34
|
+
state, HTTP surfaces, and the user interface.
|
|
35
|
+
|
|
36
|
+
## Direct Protected-Service Admission
|
|
37
|
+
|
|
38
|
+
An external backend can accept a user's opaque delegated bearer and ask the
|
|
39
|
+
Connection Hub for a live decision about one concrete operation. The backend
|
|
40
|
+
also signs the request with its own registered workload secret; possession of
|
|
41
|
+
the user bearer alone is not service identity.
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import secrets
|
|
45
|
+
import time
|
|
46
|
+
|
|
47
|
+
from connection_hub.delegated_credentials.admission import (
|
|
48
|
+
AdmissionRequest,
|
|
49
|
+
sign_admission_request,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
request = AdmissionRequest(
|
|
53
|
+
resource="https://api.example.test/customers",
|
|
54
|
+
operation="customers.search",
|
|
55
|
+
)
|
|
56
|
+
timestamp = str(int(time.time()))
|
|
57
|
+
nonce = secrets.token_urlsafe(24)
|
|
58
|
+
signature = sign_admission_request(
|
|
59
|
+
secret=service_signing_secret,
|
|
60
|
+
service_id="crm-api",
|
|
61
|
+
timestamp=timestamp,
|
|
62
|
+
nonce=nonce,
|
|
63
|
+
delegated_token=user_delegated_bearer,
|
|
64
|
+
request=request,
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The service sends the semantic request body and the four `X-Connection-Hub-*` proof
|
|
69
|
+
headers to the configured Connection Hub admission endpoint, with the opaque
|
|
70
|
+
delegated bearer in `Authorization: Bearer ...`. An allow response contains a
|
|
71
|
+
service-scoped subject and only the bounded authority relevant to that
|
|
72
|
+
operation. It never returns provider credentials or the platform's internal
|
|
73
|
+
user id.
|
|
74
|
+
|
|
75
|
+
See the runnable
|
|
76
|
+
[`direct-admission-service`](../../examples/connection-hub/direct-admission-service/README.md)
|
|
77
|
+
and the [deployment recipe](../../docs/connection-hub/recipes/direct-protected-service.md).
|
|
78
|
+
|
|
79
|
+
## Integration Boundaries
|
|
80
|
+
|
|
81
|
+
- The service registry authenticates workloads and binds each service to
|
|
82
|
+
resource selectors. It does not duplicate the operation/grant catalog.
|
|
83
|
+
- Every decision intersects the delegated bearer with the current card and
|
|
84
|
+
active catalog. A cached allow is not an authority source.
|
|
85
|
+
- Connected-account credential resolution is a separate trusted operation;
|
|
86
|
+
direct admission does not export provider secrets.
|
|
87
|
+
- A protected backend still applies its own domain authorization after
|
|
88
|
+
Connection Hub admission.
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
- [Connection Hub architecture and semantic requirements](../../docs/connection-hub/connection-hub-architecture.md)
|
|
93
|
+
- [Delegated authority and admission](../../docs/connection-hub/package/delegated-authority-and-admission.md)
|
|
94
|
+
- [Delegated access cards](../../docs/connection-hub/package/delegated-cards.md)
|
|
95
|
+
- [OAuth delegated credential protocol](../../docs/connection-hub/package/oauth-delegated-credential-protocol.md)
|
|
96
|
+
- [Package extraction boundary](../../docs/connection-hub/package/extraction-architecture.md)
|
|
97
|
+
- [Package release procedure](../../docs/package-releases.md)
|
|
98
|
+
|
|
99
|
+
License: MIT. Source: https://github.com/elenaviter/app-ecosystem
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "connection-hub"
|
|
7
|
+
version = "0.0.3"
|
|
8
|
+
description = "Connection Hub delegated-access authority and client SDK."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Elena Viter" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Topic :: Security",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"aiohttp>=3.9,<4",
|
|
21
|
+
"httpx>=0.27,<1",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = [
|
|
26
|
+
"pytest>=8,<10",
|
|
27
|
+
"pytest-asyncio>=0.23,<2",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/elenaviter/app-ecosystem"
|
|
32
|
+
Repository = "https://github.com/elenaviter/app-ecosystem"
|
|
33
|
+
Documentation = "https://github.com/elenaviter/app-ecosystem/tree/main/docs/connection-hub"
|
|
34
|
+
Issues = "https://github.com/elenaviter/app-ecosystem/issues"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
connection_hub = ["py.typed"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Connection Hub delegated-access authority and client SDK.
|
|
2
|
+
|
|
3
|
+
Every caller has its own identity card, authority lives in one central record,
|
|
4
|
+
and a guarded boundary resolves current authority on every call.
|
|
5
|
+
|
|
6
|
+
Version 0.0.3 is a development release. It contains the host-neutral authority
|
|
7
|
+
core, delegated card/catalog contracts, OAuth and connected-account policy,
|
|
8
|
+
named-service admission, and direct protected-service admission. Host storage,
|
|
9
|
+
identity, and transport adapters remain explicit integration responsibilities.
|
|
10
|
+
See the repository for status: https://github.com/elenaviter/app-ecosystem
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from importlib import import_module
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
__version__ = "0.0.3"
|
|
17
|
+
|
|
18
|
+
_EXPORTS: dict[str, str] = {}
|
|
19
|
+
for name in (
|
|
20
|
+
"NAMESPACE",
|
|
21
|
+
"CONNECTION_CATALOG",
|
|
22
|
+
"CONNECTION_STATUS",
|
|
23
|
+
"CONNECTION_GET_TOKEN",
|
|
24
|
+
"CONNECTION_DISCONNECT",
|
|
25
|
+
"OAUTH_START",
|
|
26
|
+
"AGENT_GRANT_GET_TOKEN",
|
|
27
|
+
"AGENT_GRANT_CHECK",
|
|
28
|
+
"CONNECTION_OPERATIONS",
|
|
29
|
+
"ConnectionOperationSpec",
|
|
30
|
+
"build_connection_operations",
|
|
31
|
+
"Connection",
|
|
32
|
+
"ConnectionToken",
|
|
33
|
+
"CatalogEntry",
|
|
34
|
+
"ClientApp",
|
|
35
|
+
"AmbiguousConnectionAccount",
|
|
36
|
+
):
|
|
37
|
+
_EXPORTS[name] = "connection_hub.contract"
|
|
38
|
+
for name in ("ConnectionsClient", "ConnectionsError", "ConnectionsTransport"):
|
|
39
|
+
_EXPORTS[name] = "connection_hub.client"
|
|
40
|
+
for name in (
|
|
41
|
+
"AuthorityProviderSpec",
|
|
42
|
+
"AuthorityRegistry",
|
|
43
|
+
"AuthorityResolution",
|
|
44
|
+
"CredentialEnvelope",
|
|
45
|
+
"authority_provider_spec_from_declaration",
|
|
46
|
+
):
|
|
47
|
+
_EXPORTS[name] = "connection_hub.authority_registry"
|
|
48
|
+
|
|
49
|
+
__all__ = ["__version__", *_EXPORTS]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def __getattr__(name: str) -> Any:
|
|
53
|
+
module_name = _EXPORTS.get(name)
|
|
54
|
+
if not module_name:
|
|
55
|
+
raise AttributeError(name)
|
|
56
|
+
value = getattr(import_module(module_name), name)
|
|
57
|
+
globals()[name] = value
|
|
58
|
+
return value
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 Elena Viter
|
|
3
|
+
|
|
4
|
+
"""Request-scoped per-agent account binding for connected-account resolution.
|
|
5
|
+
|
|
6
|
+
The broker (`delegated_to_kdcube.broker.ensure_claim`) decides which connected
|
|
7
|
+
account satisfies a provider claim. The calling AGENT may be bound, per
|
|
8
|
+
provider, to specific account(s) AND — per account — to specific claims
|
|
9
|
+
(`account_scope: {provider_id: {account_id: [claims]}}` on its grant card). That
|
|
10
|
+
binding must reach the broker, but the shared resolver
|
|
11
|
+
(`integrations.connected_accounts.resolve_connected_account_claim`) runs
|
|
12
|
+
downstream of the door through a generic transport with no HTTP request.
|
|
13
|
+
|
|
14
|
+
Whoever HAS the agent's credential sets the binding into this contextvar at the
|
|
15
|
+
boundary — the door bridge from `request.state.delegated_credential`; a native
|
|
16
|
+
agent gate from the same view — and the resolver reads back the per-account
|
|
17
|
+
claim map for the provider it is resolving.
|
|
18
|
+
|
|
19
|
+
Default-closed for delegated callers: when an agent identity is bound for the
|
|
20
|
+
turn and the provider has NO binding entry, the resolver receives an EMPTY
|
|
21
|
+
mapping — "agent turn, nothing granted on this provider" — and the broker
|
|
22
|
+
denies with the agent-grant reason until the user binds an account. Only
|
|
23
|
+
non-agent turns (no delegated identity — the user's own trusted tools) resolve
|
|
24
|
+
to None, meaning no restriction. An agent never inherits the accounts the user
|
|
25
|
+
connected; the binding is the grant.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import contextvars
|
|
31
|
+
from contextlib import contextmanager
|
|
32
|
+
from typing import Any, Mapping
|
|
33
|
+
|
|
34
|
+
_ACCOUNT_SCOPE: contextvars.ContextVar[dict] = contextvars.ContextVar(
|
|
35
|
+
"kdcube_agent_account_scope", default={}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# The calling agent's delegated identity (client_id + delegated-resource id) for
|
|
39
|
+
# the current turn. The connected-account resolver reads it to route a
|
|
40
|
+
# per-account claim miss to THIS agent's grant card (Delegated by KDCube) instead
|
|
41
|
+
# of a connect-a-provider banner. Set alongside the account scope by the gate
|
|
42
|
+
# that holds the agent's credential; unset / non-agent turns leave it empty.
|
|
43
|
+
_AGENT_IDENTITY: contextvars.ContextVar[dict] = contextvars.ContextVar(
|
|
44
|
+
"kdcube_agent_identity", default={}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_account_scope(
|
|
49
|
+
scope: Mapping[str, Any] | None,
|
|
50
|
+
) -> dict[str, dict[str, tuple[str, ...]]]:
|
|
51
|
+
"""Normalize exact per-provider, per-account claim authority."""
|
|
52
|
+
normalized: dict[str, dict[str, tuple[str, ...]]] = {}
|
|
53
|
+
for provider, entry in dict(scope or {}).items():
|
|
54
|
+
pkey = str(provider or "").strip()
|
|
55
|
+
if not pkey:
|
|
56
|
+
continue
|
|
57
|
+
accounts: dict[str, tuple[str, ...]] = {}
|
|
58
|
+
if isinstance(entry, Mapping):
|
|
59
|
+
for account_id, claims in entry.items():
|
|
60
|
+
akey = str(account_id or "").strip()
|
|
61
|
+
if not akey:
|
|
62
|
+
continue
|
|
63
|
+
cl = tuple(str(c).strip() for c in (claims or ()) if str(c or "").strip())
|
|
64
|
+
accounts[akey] = cl or ("*",)
|
|
65
|
+
else:
|
|
66
|
+
for account_id in (entry or []):
|
|
67
|
+
akey = str(account_id or "").strip()
|
|
68
|
+
if akey:
|
|
69
|
+
accounts[akey] = ("*",)
|
|
70
|
+
if accounts:
|
|
71
|
+
normalized[pkey] = accounts
|
|
72
|
+
return normalized
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def set_agent_account_scope(scope: Mapping[str, Any] | None) -> None:
|
|
76
|
+
"""Bind the current agent's per-account claim scope
|
|
77
|
+
({provider_id: {account_id: [claims]}}). Accepts the legacy list form
|
|
78
|
+
({provider_id: [account_ids]}) and migrates it (account -> any claim)."""
|
|
79
|
+
_ACCOUNT_SCOPE.set(normalize_account_scope(scope))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def clear_agent_account_scope() -> None:
|
|
83
|
+
_ACCOUNT_SCOPE.set({})
|
|
84
|
+
_AGENT_IDENTITY.set({})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def set_agent_identity(*, client_id: str = "", resource: str = "") -> None:
|
|
88
|
+
"""Bind the current agent's delegated identity (its
|
|
89
|
+
``kdcube-agent:<app>:<agent>`` or ``dcr-…`` client id and the
|
|
90
|
+
delegated-resource id its grant is keyed under). The client id alone is
|
|
91
|
+
enough to mark the turn as a delegated (agent) turn — and with it the
|
|
92
|
+
default-closed account binding; the resource additionally lets a claim
|
|
93
|
+
miss deep-link the exact grant card. An empty client id clears the field."""
|
|
94
|
+
cid = str(client_id or "").strip()
|
|
95
|
+
res = str(resource or "").strip()
|
|
96
|
+
_AGENT_IDENTITY.set({"client_id": cid, "resource": res} if cid else {})
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@contextmanager
|
|
100
|
+
def bind_agent_account_scope(
|
|
101
|
+
scope: Mapping[str, Any] | None,
|
|
102
|
+
*,
|
|
103
|
+
client_id: str,
|
|
104
|
+
resource: str = "",
|
|
105
|
+
):
|
|
106
|
+
"""Bind one delegated provider invocation and restore prior task state."""
|
|
107
|
+
|
|
108
|
+
cid = str(client_id or "").strip()
|
|
109
|
+
res = str(resource or "").strip()
|
|
110
|
+
scope_token = _ACCOUNT_SCOPE.set(normalize_account_scope(scope))
|
|
111
|
+
identity_token = _AGENT_IDENTITY.set(
|
|
112
|
+
{"client_id": cid, "resource": res} if cid else {}
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
yield
|
|
116
|
+
finally:
|
|
117
|
+
_AGENT_IDENTITY.reset(identity_token)
|
|
118
|
+
_ACCOUNT_SCOPE.reset(scope_token)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def agent_identity() -> dict:
|
|
122
|
+
"""The current agent's delegated identity ``{client_id, resource}``, or an
|
|
123
|
+
empty dict on a non-agent turn / when it was not set."""
|
|
124
|
+
return dict(_AGENT_IDENTITY.get() or {})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def account_claim_scope_for(provider_id: str) -> dict[str, tuple[str, ...]] | None:
|
|
128
|
+
"""The current agent's per-account claim binding for ``provider_id`` —
|
|
129
|
+
``{account_id: (claims...)}`` (account "*" = any account, claim "*" = any
|
|
130
|
+
claim).
|
|
131
|
+
|
|
132
|
+
Returns None ONLY on a non-agent turn (no delegated identity bound): the
|
|
133
|
+
user's own trusted tools are unrestricted. On an agent turn with no binding
|
|
134
|
+
entry for this provider it returns an EMPTY mapping — default-closed: the
|
|
135
|
+
agent is bound to no account there, and the broker denies with the
|
|
136
|
+
agent-grant reason until the user grants one."""
|
|
137
|
+
entry = _ACCOUNT_SCOPE.get().get(str(provider_id or "").strip())
|
|
138
|
+
if not entry:
|
|
139
|
+
return {} if _AGENT_IDENTITY.get() else None
|
|
140
|
+
return {
|
|
141
|
+
str(account_id).strip(): tuple(claims)
|
|
142
|
+
for account_id, claims in dict(entry).items()
|
|
143
|
+
if str(account_id or "").strip()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__all__ = [
|
|
148
|
+
"set_agent_account_scope",
|
|
149
|
+
"clear_agent_account_scope",
|
|
150
|
+
"account_claim_scope_for",
|
|
151
|
+
"set_agent_identity",
|
|
152
|
+
"agent_identity",
|
|
153
|
+
"bind_agent_account_scope",
|
|
154
|
+
"normalize_account_scope",
|
|
155
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 Elena Viter
|
|
3
|
+
|
|
4
|
+
"""Host-neutral request-authenticator contracts."""
|
|
5
|
+
|
|
6
|
+
from connection_hub.authenticators.authority import (
|
|
7
|
+
AuthRequestHints,
|
|
8
|
+
AuthorityIdentity,
|
|
9
|
+
SurfaceGuardRequirement,
|
|
10
|
+
select_authenticator_candidates,
|
|
11
|
+
)
|
|
12
|
+
from connection_hub.authenticators.models import (
|
|
13
|
+
AuthenticatedRequest,
|
|
14
|
+
AuthenticatorRegistration,
|
|
15
|
+
RequestEnvelope,
|
|
16
|
+
)
|
|
17
|
+
from connection_hub.authenticators.client import (
|
|
18
|
+
DEFAULT_CONNECTION_HUB_BUNDLE_ID,
|
|
19
|
+
REQUEST_AUTHENTICATE_OPERATION,
|
|
20
|
+
ConnectionHubAuthenticatorsClient,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AuthRequestHints",
|
|
25
|
+
"AuthenticatedRequest",
|
|
26
|
+
"AuthenticatorRegistration",
|
|
27
|
+
"AuthorityIdentity",
|
|
28
|
+
"ConnectionHubAuthenticatorsClient",
|
|
29
|
+
"DEFAULT_CONNECTION_HUB_BUNDLE_ID",
|
|
30
|
+
"REQUEST_AUTHENTICATE_OPERATION",
|
|
31
|
+
"RequestEnvelope",
|
|
32
|
+
"SurfaceGuardRequirement",
|
|
33
|
+
"select_authenticator_candidates",
|
|
34
|
+
]
|