coding-client 1.21.2__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.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Identity headers for outbound service-to-service calls.
|
|
2
|
+
|
|
3
|
+
Promoted into the SDK from ``coding-containers/src/clients/_tenant.py``, whose
|
|
4
|
+
docstring already made the argument for it: *"this module exists so the 'attach
|
|
5
|
+
the tenant' step is one function with one failure mode, rather than N copies of a
|
|
6
|
+
header dict that can each be forgotten independently"*. That reasoning applies to
|
|
7
|
+
all nine services, and only one of them had the module.
|
|
8
|
+
|
|
9
|
+
Every client in the fleet talks to a sibling over plain HTTP inside the mesh. The
|
|
10
|
+
mesh does **not** inject a tenant identity service-to-service, so each caller has
|
|
11
|
+
to send ``X-Tenant-Id`` itself. Several didn't, and were reaching their peer only
|
|
12
|
+
because that peer's ``TRUSTED_CIDRS`` covered the pod CIDR — a network position,
|
|
13
|
+
not a credential.
|
|
14
|
+
|
|
15
|
+
The rule that has not changed: **a missing tenant is an error, never a default.**
|
|
16
|
+
Substituting ``DEFAULT_TENANT_ID``, ``"anonymous"`` or any other literal makes the
|
|
17
|
+
code compile and the call succeed while attributing it to a tenant that does not
|
|
18
|
+
own the resource. coding-client removed its ``"n8n-dev"`` fallback for exactly
|
|
19
|
+
this reason — that literal was the stale pre-uuid7 label of a *real* tenant, so
|
|
20
|
+
substituting it wrote into an unaddressable silo.
|
|
21
|
+
|
|
22
|
+
What is new: ``service_token``
|
|
23
|
+
------------------------------
|
|
24
|
+
The caller's own service license, sent as ``Authorization: Bearer <token>``
|
|
25
|
+
**without touching ``X-Tenant-Id``**. That separation is the whole point. Putting
|
|
26
|
+
the credential in ``X-Tenant-Id`` — as some fleet clients do, overloading the
|
|
27
|
+
header — collapses every customer into the calling service's own tenant, because
|
|
28
|
+
the callee then resolves the tenant from the token. Keeping them apart is what
|
|
29
|
+
lets a caller say "I am the containers service, acting for customer T", which the
|
|
30
|
+
callee honours iff the service's subject is allowlisted (see :mod:`._onbehalf`).
|
|
31
|
+
|
|
32
|
+
``Authorization`` here is the **caller's own** identity. It is never a forwarded
|
|
33
|
+
copy of an inbound one: most of these calls are detached (fire-and-forget scans,
|
|
34
|
+
``asyncio.create_task`` deep builds, background bulk jobs) and have no inbound
|
|
35
|
+
request to borrow a credential from.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from .tenant import TENANT_ID_HEADER
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"LICENSE_ID_HEADER",
|
|
44
|
+
"TENANT_ID_HEADER",
|
|
45
|
+
"TenantMissingError",
|
|
46
|
+
"optional_tenant_headers",
|
|
47
|
+
"tenant_headers",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
#: The customer's license, forwarded so a vouched call is attributable to the
|
|
51
|
+
#: license that should be *billed* rather than to the vouching service's own.
|
|
52
|
+
LICENSE_ID_HEADER = "X-License-Id"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TenantMissingError(Exception):
|
|
56
|
+
"""An outbound service call had no tenant to attribute itself to.
|
|
57
|
+
|
|
58
|
+
Deliberately NOT a subclass of ``ValueError`` / ``RuntimeError`` / ``OSError``
|
|
59
|
+
/ ``httpx.HTTPError``, so none of the broad ``except`` clauses that wrap these
|
|
60
|
+
call sites — most of which degrade to a plausible-looking fallback result —
|
|
61
|
+
can silently absorb it. A caller that cannot name its tenant is a bug, and it
|
|
62
|
+
fails loudly.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _credential_headers(service_token: str | None, license_id: str | None) -> dict[str, str]:
|
|
67
|
+
"""The credential half, shared by both builders so they cannot drift."""
|
|
68
|
+
headers: dict[str, str] = {}
|
|
69
|
+
if service_token:
|
|
70
|
+
headers["Authorization"] = f"Bearer {service_token}"
|
|
71
|
+
if license_id:
|
|
72
|
+
headers[LICENSE_ID_HEADER] = license_id
|
|
73
|
+
return headers
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def tenant_headers(
|
|
77
|
+
tenant_id: str | None,
|
|
78
|
+
*,
|
|
79
|
+
operation: str,
|
|
80
|
+
service_token: str | None = None,
|
|
81
|
+
license_id: str | None = None,
|
|
82
|
+
) -> dict[str, str]:
|
|
83
|
+
"""Build the identity headers for an outbound call that is tenant-scoped.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
tenant_id: The tenant the call is made on behalf of. Required.
|
|
87
|
+
operation: Human-readable name of the call, for the error message.
|
|
88
|
+
service_token: The caller's own service license. Sent as
|
|
89
|
+
``Authorization: Bearer``; does not affect ``X-Tenant-Id``.
|
|
90
|
+
license_id: The customer's license id, for attribution.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
``{"X-Tenant-Id": ...}`` plus any credential headers supplied.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
TenantMissingError: ``tenant_id`` is None or empty.
|
|
97
|
+
"""
|
|
98
|
+
if not tenant_id:
|
|
99
|
+
raise TenantMissingError(
|
|
100
|
+
f"Refusing to call {operation} with no tenant. The tenant must be "
|
|
101
|
+
"threaded from the authenticated caller; it is never defaulted, "
|
|
102
|
+
"because attributing a call to a guessed tenant is a cross-tenant "
|
|
103
|
+
"authorisation bug."
|
|
104
|
+
)
|
|
105
|
+
return {TENANT_ID_HEADER: tenant_id, **_credential_headers(service_token, license_id)}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def optional_tenant_headers(
|
|
109
|
+
tenant_id: str | None,
|
|
110
|
+
*,
|
|
111
|
+
service_token: str | None = None,
|
|
112
|
+
license_id: str | None = None,
|
|
113
|
+
) -> dict[str, str]:
|
|
114
|
+
"""Attach the tenant when there is one, and attach *nothing* when there is not.
|
|
115
|
+
|
|
116
|
+
For calls whose authorisation does NOT come from the tenant — coding-github's
|
|
117
|
+
``/api/internal/github/clone-token`` is guarded by the ``X-Internal-Token``
|
|
118
|
+
shared secret and never reads ``X-Tenant-Id`` at all — refusing the call for a
|
|
119
|
+
missing tenant (:func:`tenant_headers`) would be a regression, because the
|
|
120
|
+
tenant is genuinely optional there and the peer does not want it.
|
|
121
|
+
|
|
122
|
+
The rule this preserves is the one that matters: **absent is absent.** A
|
|
123
|
+
``tenant_id or "anonymous"`` makes every tenantless call claim to come from a
|
|
124
|
+
tenant literally named ``anonymous`` — an identity no registry issues and no
|
|
125
|
+
audit trail can attribute, and one that is indistinguishable downstream from a
|
|
126
|
+
real tenant that happens to be called that.
|
|
127
|
+
|
|
128
|
+
Use :func:`tenant_headers` whenever the tenant *scopes the resource* — a
|
|
129
|
+
guessed tenant there is a cross-tenant authorisation bug, not a lost log line.
|
|
130
|
+
|
|
131
|
+
Note the credential is still attached when supplied: a call can legitimately
|
|
132
|
+
have no tenant to name while still needing to prove who is making it.
|
|
133
|
+
"""
|
|
134
|
+
headers = _credential_headers(service_token, license_id)
|
|
135
|
+
if tenant_id:
|
|
136
|
+
headers[TENANT_ID_HEADER] = tenant_id
|
|
137
|
+
return headers
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Role-based access control helpers."""
|
|
2
|
+
|
|
3
|
+
from fastapi import Depends, HTTPException
|
|
4
|
+
|
|
5
|
+
from .dependencies import get_current_user
|
|
6
|
+
from .models import User
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def require_role(role: str):
|
|
10
|
+
"""Require user has specific role."""
|
|
11
|
+
|
|
12
|
+
async def checker(user: User = Depends(get_current_user)):
|
|
13
|
+
if not user.has_realm_role(role):
|
|
14
|
+
raise HTTPException(status_code=403, detail=f"Role '{role}' required")
|
|
15
|
+
return user
|
|
16
|
+
|
|
17
|
+
return checker
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Backwards compatibility - was require_access_coding in rbac_coding.py
|
|
21
|
+
require_access_coding = require_role("coding-access")
|