kobil-agent-sdk 1.2.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.
- kobil_agent_sdk-1.2.0/.gitignore +9 -0
- kobil_agent_sdk-1.2.0/LICENSE +128 -0
- kobil_agent_sdk-1.2.0/PKG-INFO +64 -0
- kobil_agent_sdk-1.2.0/README.md +32 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/__init__.py +120 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/__init__.py +23 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/ciba.py +207 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/oauth.py +450 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/server_approval.py +264 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/vault_compat.py +213 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/config.py +221 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/easy.py +303 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/fga.py +178 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/identity/__init__.py +3 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/identity/agent_identity.py +211 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/__init__.py +81 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/anthropic.py +90 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/claude_agent.py +157 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/google_adk.py +142 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/google_genai.py +88 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/openai.py +100 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/openai_agents.py +157 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/kobil_ai.py +281 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/registry.py +146 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/types.py +204 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/web/__init__.py +21 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/web/identity.py +277 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/__init__.py +13 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/anthropic.py +70 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/google_adk.py +45 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/google_genai.py +95 -0
- kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/openai.py +90 -0
- kobil_agent_sdk-1.2.0/pyproject.toml +52 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
KOBIL Agent Trust SDK License
|
|
2
|
+
Version 1.0
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 KOBIL GmbH. All rights reserved.
|
|
5
|
+
The Software is developed by the KOBIL AI Team.
|
|
6
|
+
|
|
7
|
+
In short: you may install and use the Software free of charge, also in
|
|
8
|
+
commercial products, and ship it inside your own application. You may not
|
|
9
|
+
change it, copy it into other software, or pass it on or sell it on its
|
|
10
|
+
own. The numbered terms below are the license; this summary is not.
|
|
11
|
+
|
|
12
|
+
1. Definitions
|
|
13
|
+
|
|
14
|
+
"The Software" is the code and documentation in this package.
|
|
15
|
+
|
|
16
|
+
"You" is the person or organization that accepts these terms.
|
|
17
|
+
|
|
18
|
+
"Your company" is the organization you work for, and the organizations
|
|
19
|
+
that control it, that it controls, or that are under common control
|
|
20
|
+
with it.
|
|
21
|
+
|
|
22
|
+
"Your application" is software or a service that you build and that
|
|
23
|
+
does more than give access to the Software. Your application is not a
|
|
24
|
+
work based on the Software just because it uses the Software.
|
|
25
|
+
|
|
26
|
+
2. Acceptance
|
|
27
|
+
|
|
28
|
+
By installing, copying or using the Software you accept these terms.
|
|
29
|
+
If you do not accept them, do not use the Software.
|
|
30
|
+
|
|
31
|
+
3. What you may do
|
|
32
|
+
|
|
33
|
+
KOBIL grants you and your company a non-exclusive, worldwide,
|
|
34
|
+
non-transferable license, free of charge, to:
|
|
35
|
+
|
|
36
|
+
a. install and use the Software, unchanged, to build, test and run your
|
|
37
|
+
applications, including commercial ones that you charge for;
|
|
38
|
+
b. make the copies of the Software that this needs, for example on
|
|
39
|
+
developer machines, on build servers and in your application's
|
|
40
|
+
environments;
|
|
41
|
+
c. ship the Software, unchanged, as part of your application, for
|
|
42
|
+
example inside its container image, package or bundle.
|
|
43
|
+
|
|
44
|
+
Your company's employees and contractors may do the same on your
|
|
45
|
+
company's behalf.
|
|
46
|
+
|
|
47
|
+
Building your application with tools such as bundlers, minifiers,
|
|
48
|
+
transpilers or compilers is not a change to the Software. Using AI
|
|
49
|
+
coding tools to write applications that use the Software is allowed.
|
|
50
|
+
|
|
51
|
+
4. Shipping the Software inside your application
|
|
52
|
+
|
|
53
|
+
When you ship the Software as part of your application:
|
|
54
|
+
|
|
55
|
+
a. the Software must be needed for your application to work, and must
|
|
56
|
+
not be offered as a library, SDK or tool of its own;
|
|
57
|
+
b. the people who receive your application get no right to use the
|
|
58
|
+
Software apart from your application;
|
|
59
|
+
c. keep this license and the copyright notice with the Software, or in
|
|
60
|
+
your application's third-party notices.
|
|
61
|
+
|
|
62
|
+
5. What you may not do
|
|
63
|
+
|
|
64
|
+
You may not:
|
|
65
|
+
|
|
66
|
+
a. change, adapt or translate the Software, or make works based on it;
|
|
67
|
+
b. copy the Software's code, or substantial parts of it, into other
|
|
68
|
+
software, whether by hand or with automated or AI tools, or use the
|
|
69
|
+
Software to train AI models;
|
|
70
|
+
c. publish, share, sublicense, sell, rent or lease the Software on its
|
|
71
|
+
own, or as part of another SDK, library, framework or developer
|
|
72
|
+
tool;
|
|
73
|
+
d. remove or change this license or any copyright notice in the
|
|
74
|
+
Software;
|
|
75
|
+
e. use the Software to break the law, or to suggest that KOBIL endorses
|
|
76
|
+
your application.
|
|
77
|
+
|
|
78
|
+
These limits apply to the Software itself. You may sell and
|
|
79
|
+
commercially distribute your application.
|
|
80
|
+
|
|
81
|
+
6. KOBIL services
|
|
82
|
+
|
|
83
|
+
The Software can connect to KOBIL services. Using those services is
|
|
84
|
+
governed by their own terms, not by this license. This license does not
|
|
85
|
+
give you access to any KOBIL service.
|
|
86
|
+
|
|
87
|
+
7. Ownership
|
|
88
|
+
|
|
89
|
+
KOBIL keeps all rights in the Software that this license does not
|
|
90
|
+
expressly grant. This license gives you no right to KOBIL's names,
|
|
91
|
+
logos or trademarks, except to say truthfully that your application
|
|
92
|
+
uses the Software.
|
|
93
|
+
|
|
94
|
+
8. Other software
|
|
95
|
+
|
|
96
|
+
The Software uses third-party open-source packages. Each of them is
|
|
97
|
+
covered by its own license, not by this one.
|
|
98
|
+
|
|
99
|
+
9. New versions
|
|
100
|
+
|
|
101
|
+
KOBIL may release new versions of the Software under a new version of
|
|
102
|
+
this license. Each copy of the Software is covered by the license that
|
|
103
|
+
came with it.
|
|
104
|
+
|
|
105
|
+
10. Violations
|
|
106
|
+
|
|
107
|
+
If you break these terms, KOBIL may tell you so in writing. The first
|
|
108
|
+
time this happens, your license continues if, within 30 days of that
|
|
109
|
+
notice, you fully comply again and take practical steps to correct past
|
|
110
|
+
violations. Otherwise, and after any later violation, your license
|
|
111
|
+
ends. You must then stop using and shipping the Software.
|
|
112
|
+
|
|
113
|
+
11. No warranty
|
|
114
|
+
|
|
115
|
+
As far as the law allows, the Software comes "as is", without any
|
|
116
|
+
warranty or condition, express or implied, including fitness for a
|
|
117
|
+
particular purpose and non-infringement.
|
|
118
|
+
|
|
119
|
+
12. Limitation of liability
|
|
120
|
+
|
|
121
|
+
As far as the law allows, KOBIL is not liable for any damages that come
|
|
122
|
+
from these terms or from using, or not being able to use, the Software,
|
|
123
|
+
under any kind of legal claim.
|
|
124
|
+
|
|
125
|
+
13. Other permissions
|
|
126
|
+
|
|
127
|
+
For any use these terms do not allow, ask KOBIL for written permission
|
|
128
|
+
at www.kobil.com.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kobil-agent-sdk
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: KOBIL AI Trust Platform Agent SDK — OAuth 2.1, CIBA, DPoP, agent identity, and framework integrations
|
|
5
|
+
License-Expression: LicenseRef-KOBIL-Agent-Trust-SDK-1.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: agent,ai-trust,ciba,dpop,kobil,oauth,sdk
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: cryptography>=42.0
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Provides-Extra: all
|
|
12
|
+
Requires-Dist: anthropic>=0.20; extra == 'all'
|
|
13
|
+
Requires-Dist: claude-agent-sdk>=0.1; extra == 'all'
|
|
14
|
+
Requires-Dist: google-adk>=2.10; extra == 'all'
|
|
15
|
+
Requires-Dist: google-genai>=0.1; extra == 'all'
|
|
16
|
+
Requires-Dist: openai-agents>=0.1; extra == 'all'
|
|
17
|
+
Requires-Dist: openai>=1.0; extra == 'all'
|
|
18
|
+
Provides-Extra: anthropic
|
|
19
|
+
Requires-Dist: anthropic>=0.20; extra == 'anthropic'
|
|
20
|
+
Requires-Dist: claude-agent-sdk>=0.1; extra == 'anthropic'
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
25
|
+
Provides-Extra: google
|
|
26
|
+
Requires-Dist: google-adk>=2.10; extra == 'google'
|
|
27
|
+
Requires-Dist: google-genai>=0.1; extra == 'google'
|
|
28
|
+
Provides-Extra: openai
|
|
29
|
+
Requires-Dist: openai-agents>=0.1; extra == 'openai'
|
|
30
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# kobil-agent-sdk (Python)
|
|
34
|
+
|
|
35
|
+
Python mirror of the `@uveysservetoglu/kobil-agent-sdk` TypeScript SDK.
|
|
36
|
+
|
|
37
|
+
This is a behavioral 1:1 port of the TypeScript package in `../src`, delivered
|
|
38
|
+
in layers given its size (~11k LOC). The **current layer** is the self-contained
|
|
39
|
+
auth foundation:
|
|
40
|
+
|
|
41
|
+
- **config** — `KobilSDKConfig`, `create_config` (endpoint derivation, defaults)
|
|
42
|
+
- **OAuth 2.1** — PKCE (`generate_code_verifier`/`generate_code_challenge`),
|
|
43
|
+
authorization-code exchange, client-credentials via the Management API proxy
|
|
44
|
+
(mTLS), RFC 8693 token exchange, refresh; automatic retry with backoff
|
|
45
|
+
- **CIBA** — initiate / poll / status
|
|
46
|
+
- **DPoP** — ECDSA P-256 (ES256), emitted in IEEE-P1363 raw `R||S` form to match
|
|
47
|
+
WebCrypto so proofs are byte-compatible with the TypeScript SDK
|
|
48
|
+
|
|
49
|
+
Subsequent layers add `KobilAI`, the tool registry, agent identity, platform,
|
|
50
|
+
the isolated key-signer subprocess, and the vendor framework adapters
|
|
51
|
+
(OpenAI / Anthropic / Google).
|
|
52
|
+
|
|
53
|
+
## Install (dev)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m venv .venv && source .venv/bin/activate
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Test
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pytest
|
|
64
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# kobil-agent-sdk (Python)
|
|
2
|
+
|
|
3
|
+
Python mirror of the `@uveysservetoglu/kobil-agent-sdk` TypeScript SDK.
|
|
4
|
+
|
|
5
|
+
This is a behavioral 1:1 port of the TypeScript package in `../src`, delivered
|
|
6
|
+
in layers given its size (~11k LOC). The **current layer** is the self-contained
|
|
7
|
+
auth foundation:
|
|
8
|
+
|
|
9
|
+
- **config** — `KobilSDKConfig`, `create_config` (endpoint derivation, defaults)
|
|
10
|
+
- **OAuth 2.1** — PKCE (`generate_code_verifier`/`generate_code_challenge`),
|
|
11
|
+
authorization-code exchange, client-credentials via the Management API proxy
|
|
12
|
+
(mTLS), RFC 8693 token exchange, refresh; automatic retry with backoff
|
|
13
|
+
- **CIBA** — initiate / poll / status
|
|
14
|
+
- **DPoP** — ECDSA P-256 (ES256), emitted in IEEE-P1363 raw `R||S` form to match
|
|
15
|
+
WebCrypto so proofs are byte-compatible with the TypeScript SDK
|
|
16
|
+
|
|
17
|
+
Subsequent layers add `KobilAI`, the tool registry, agent identity, platform,
|
|
18
|
+
the isolated key-signer subprocess, and the vendor framework adapters
|
|
19
|
+
(OpenAI / Anthropic / Google).
|
|
20
|
+
|
|
21
|
+
## Install (dev)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python -m venv .venv && source .venv/bin/activate
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Test
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pytest
|
|
32
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""kobil-agent-sdk — KOBIL AI Trust Platform Agent SDK (Python).
|
|
2
|
+
|
|
3
|
+
Python mirror of the ``@uveysservetoglu/kobil-agent-sdk`` TypeScript package.
|
|
4
|
+
|
|
5
|
+
This package is being ported in layers. The current layer covers the
|
|
6
|
+
self-contained **auth foundation**: configuration, OAuth 2.1 (PKCE, client
|
|
7
|
+
credentials via the Management API proxy/mTLS, token exchange, refresh),
|
|
8
|
+
CIBA, FGA, and DPoP (ECDSA P-256 / ES256). The ``KobilAI`` facade, registry,
|
|
9
|
+
identity, platform, key-signer isolation, and vendor adapters follow in
|
|
10
|
+
subsequent layers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .auth import (
|
|
16
|
+
CIBAClient,
|
|
17
|
+
DPoPManager,
|
|
18
|
+
OAuthClient,
|
|
19
|
+
TokenExchangeClient,
|
|
20
|
+
TokenExchangeError,
|
|
21
|
+
TokenExchangeRequest,
|
|
22
|
+
TokenExchangeResponse,
|
|
23
|
+
generate_code_challenge,
|
|
24
|
+
generate_code_verifier,
|
|
25
|
+
)
|
|
26
|
+
from .config import KobilSDKConfig, create_config
|
|
27
|
+
from .easy import (
|
|
28
|
+
KobilToolContext,
|
|
29
|
+
KobilToolOptions,
|
|
30
|
+
SecureOptions,
|
|
31
|
+
UnifiedConfig,
|
|
32
|
+
create_kobil,
|
|
33
|
+
extract_tool_context,
|
|
34
|
+
kobil_tool,
|
|
35
|
+
secure,
|
|
36
|
+
)
|
|
37
|
+
from .fga import FGAClient, FGAClientConfig
|
|
38
|
+
from .identity.agent_identity import AgentIdentityManager
|
|
39
|
+
from .kobil_ai import KobilAI
|
|
40
|
+
from .registry import ToolRegistry
|
|
41
|
+
from .types import (
|
|
42
|
+
AgentIdentity,
|
|
43
|
+
AuthorizationDeniedError,
|
|
44
|
+
AuthorizationTimeoutError,
|
|
45
|
+
AuthResult,
|
|
46
|
+
CIBAAuthRequest,
|
|
47
|
+
CIBAAuthResponse,
|
|
48
|
+
ConfigurationError,
|
|
49
|
+
FGAError,
|
|
50
|
+
FGACheckRequest,
|
|
51
|
+
FGACheckResponse,
|
|
52
|
+
KobilConfig,
|
|
53
|
+
KobilSDKError,
|
|
54
|
+
OAuthError,
|
|
55
|
+
ToolDescriptor,
|
|
56
|
+
)
|
|
57
|
+
from .web import (
|
|
58
|
+
KobilIdentity,
|
|
59
|
+
KobilIdentityOptions,
|
|
60
|
+
KobilUser,
|
|
61
|
+
create_kobil_identity,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# The release pipeline stamps the version into pyproject.toml only, so read it back from the installed metadata.
|
|
65
|
+
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError, version as _version
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
__version__ = _version("kobil-agent-sdk")
|
|
69
|
+
except _PackageNotFoundError: # running from a source checkout that is not installed
|
|
70
|
+
__version__ = "0.0.0"
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
# facade + core
|
|
74
|
+
"KobilAI",
|
|
75
|
+
"ToolRegistry",
|
|
76
|
+
"AgentIdentityManager",
|
|
77
|
+
"KobilToolContext",
|
|
78
|
+
"KobilToolOptions",
|
|
79
|
+
"SecureOptions",
|
|
80
|
+
"UnifiedConfig",
|
|
81
|
+
"kobil_tool",
|
|
82
|
+
"secure",
|
|
83
|
+
"create_kobil",
|
|
84
|
+
"extract_tool_context",
|
|
85
|
+
"FGAClient",
|
|
86
|
+
"FGAClientConfig",
|
|
87
|
+
# config
|
|
88
|
+
"KobilSDKConfig",
|
|
89
|
+
"create_config",
|
|
90
|
+
# auth
|
|
91
|
+
"OAuthClient",
|
|
92
|
+
"CIBAClient",
|
|
93
|
+
"DPoPManager",
|
|
94
|
+
"TokenExchangeClient",
|
|
95
|
+
"TokenExchangeError",
|
|
96
|
+
"TokenExchangeRequest",
|
|
97
|
+
"TokenExchangeResponse",
|
|
98
|
+
"generate_code_verifier",
|
|
99
|
+
"generate_code_challenge",
|
|
100
|
+
# types
|
|
101
|
+
"KobilConfig",
|
|
102
|
+
"AuthResult",
|
|
103
|
+
"AgentIdentity",
|
|
104
|
+
"CIBAAuthRequest",
|
|
105
|
+
"CIBAAuthResponse",
|
|
106
|
+
"FGACheckRequest",
|
|
107
|
+
"FGACheckResponse",
|
|
108
|
+
"ToolDescriptor",
|
|
109
|
+
"KobilSDKError",
|
|
110
|
+
"OAuthError",
|
|
111
|
+
"ConfigurationError",
|
|
112
|
+
"FGAError",
|
|
113
|
+
"AuthorizationTimeoutError",
|
|
114
|
+
"AuthorizationDeniedError",
|
|
115
|
+
# web identity (OIDC login for MiniApp / web agents)
|
|
116
|
+
"create_kobil_identity",
|
|
117
|
+
"KobilIdentity",
|
|
118
|
+
"KobilIdentityOptions",
|
|
119
|
+
"KobilUser",
|
|
120
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Auth subpackage — OAuth, CIBA, DPoP, token exchange."""
|
|
2
|
+
|
|
3
|
+
from .ciba import CIBAClient
|
|
4
|
+
from .oauth import OAuthClient, generate_code_challenge, generate_code_verifier
|
|
5
|
+
from .vault_compat import (
|
|
6
|
+
DPoPManager,
|
|
7
|
+
TokenExchangeClient,
|
|
8
|
+
TokenExchangeError,
|
|
9
|
+
TokenExchangeRequest,
|
|
10
|
+
TokenExchangeResponse,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CIBAClient",
|
|
15
|
+
"OAuthClient",
|
|
16
|
+
"generate_code_verifier",
|
|
17
|
+
"generate_code_challenge",
|
|
18
|
+
"DPoPManager",
|
|
19
|
+
"TokenExchangeClient",
|
|
20
|
+
"TokenExchangeError",
|
|
21
|
+
"TokenExchangeRequest",
|
|
22
|
+
"TokenExchangeResponse",
|
|
23
|
+
]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""CIBA client (mirror of src/auth/ciba.ts)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from ..config import KobilSDKConfig
|
|
11
|
+
from ..types import (
|
|
12
|
+
AuthorizationDeniedError,
|
|
13
|
+
AuthorizationTimeoutError,
|
|
14
|
+
AuthResult,
|
|
15
|
+
CIBAAuthRequest,
|
|
16
|
+
CIBAAuthResponse,
|
|
17
|
+
KobilSDKError,
|
|
18
|
+
OAuthError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
DEFAULT_POLL_INTERVAL_SECONDS = 5
|
|
22
|
+
MAX_POLL_DURATION_SECONDS = 300
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _now_ms() -> int:
|
|
26
|
+
import time
|
|
27
|
+
|
|
28
|
+
return int(time.time() * 1000)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CIBAClient:
|
|
32
|
+
def __init__(self, config) -> None:
|
|
33
|
+
self._cfg = KobilSDKConfig(config)
|
|
34
|
+
|
|
35
|
+
async def initiate_auth(self, request: CIBAAuthRequest) -> CIBAAuthResponse:
|
|
36
|
+
# Accept either a CIBAAuthRequest or a plain dict (some callers pass a
|
|
37
|
+
# raw dict, e.g. kobil_aitrust.trust.require_approval). Coercing here
|
|
38
|
+
# fixes every caller without breaking object-style callers.
|
|
39
|
+
if isinstance(request, dict):
|
|
40
|
+
request = CIBAAuthRequest(
|
|
41
|
+
login_hint=request["login_hint"],
|
|
42
|
+
binding_message=request.get("binding_message") or "Approval required",
|
|
43
|
+
scope=request.get("scope"),
|
|
44
|
+
requested_expiry=request.get("requested_expiry"),
|
|
45
|
+
tool_name=request.get("tool_name"),
|
|
46
|
+
)
|
|
47
|
+
if not self._cfg.client_secret:
|
|
48
|
+
raise OAuthError("invalid_client", "clientSecret is required for CIBA authentication")
|
|
49
|
+
|
|
50
|
+
body = {
|
|
51
|
+
"client_id": self._cfg.client_id,
|
|
52
|
+
"client_secret": self._cfg.client_secret,
|
|
53
|
+
"login_hint": request.login_hint,
|
|
54
|
+
"binding_message": request.binding_message,
|
|
55
|
+
}
|
|
56
|
+
if request.tool_name:
|
|
57
|
+
body["tool_name"] = request.tool_name
|
|
58
|
+
if request.scope:
|
|
59
|
+
body["scope"] = request.scope
|
|
60
|
+
elif self._cfg.scopes:
|
|
61
|
+
body["scope"] = " ".join(self._cfg.scopes)
|
|
62
|
+
if request.requested_expiry is not None:
|
|
63
|
+
body["requested_expiry"] = str(request.requested_expiry)
|
|
64
|
+
if self._cfg.audience:
|
|
65
|
+
body["audience"] = self._cfg.audience
|
|
66
|
+
|
|
67
|
+
headers = {
|
|
68
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
69
|
+
"Accept": "application/json",
|
|
70
|
+
**(self._cfg.config.get("custom_headers") or {}),
|
|
71
|
+
}
|
|
72
|
+
timeout = self._cfg.timeout_ms / 1000
|
|
73
|
+
try:
|
|
74
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
75
|
+
response = await http.post(self._cfg.backchannel_auth_endpoint, headers=headers, data=body)
|
|
76
|
+
except Exception as error: # noqa: BLE001
|
|
77
|
+
raise KobilSDKError(f"CIBA initiation request failed: {error}", "NETWORK_ERROR")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
data = response.json()
|
|
81
|
+
except Exception:
|
|
82
|
+
raise KobilSDKError(
|
|
83
|
+
f"CIBA initiation endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
|
|
84
|
+
"INVALID_RESPONSE",
|
|
85
|
+
)
|
|
86
|
+
if not response.is_success:
|
|
87
|
+
raise OAuthError(data.get("error") or "unknown_error", data.get("error_description"), response.status_code)
|
|
88
|
+
|
|
89
|
+
return CIBAAuthResponse(
|
|
90
|
+
auth_req_id=data["auth_req_id"],
|
|
91
|
+
expires_in=data.get("expires_in", 120),
|
|
92
|
+
interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
|
|
93
|
+
status="pending",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def poll_for_token(self, auth_req_id: str, interval: Optional[int] = None) -> AuthResult:
|
|
97
|
+
current_interval_ms = (interval if interval is not None else DEFAULT_POLL_INTERVAL_SECONDS) * 1000
|
|
98
|
+
deadline = _now_ms() + MAX_POLL_DURATION_SECONDS * 1000
|
|
99
|
+
|
|
100
|
+
while _now_ms() < deadline:
|
|
101
|
+
await asyncio.sleep(current_interval_ms / 1000)
|
|
102
|
+
|
|
103
|
+
body = {
|
|
104
|
+
"grant_type": "urn:openid:params:grant-type:ciba",
|
|
105
|
+
"client_id": self._cfg.client_id,
|
|
106
|
+
"auth_req_id": auth_req_id,
|
|
107
|
+
}
|
|
108
|
+
if self._cfg.client_secret:
|
|
109
|
+
body["client_secret"] = self._cfg.client_secret
|
|
110
|
+
headers = {
|
|
111
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
112
|
+
"Accept": "application/json",
|
|
113
|
+
**(self._cfg.config.get("custom_headers") or {}),
|
|
114
|
+
}
|
|
115
|
+
timeout = self._cfg.timeout_ms / 1000
|
|
116
|
+
try:
|
|
117
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
118
|
+
response = await http.post(self._cfg.token_endpoint, headers=headers, data=body)
|
|
119
|
+
except Exception as error: # noqa: BLE001
|
|
120
|
+
raise KobilSDKError(f"CIBA poll request failed: {error}", "NETWORK_ERROR")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
data = response.json()
|
|
124
|
+
except Exception:
|
|
125
|
+
raise KobilSDKError(
|
|
126
|
+
f"CIBA poll endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
|
|
127
|
+
"INVALID_RESPONSE",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if response.is_success:
|
|
131
|
+
return AuthResult(
|
|
132
|
+
access_token=data["access_token"],
|
|
133
|
+
token_type=data.get("token_type", "Bearer"),
|
|
134
|
+
expires_in=data.get("expires_in", 3600),
|
|
135
|
+
refresh_token=data.get("refresh_token"),
|
|
136
|
+
id_token=data.get("id_token"),
|
|
137
|
+
dpop_nonce=response.headers.get("DPoP-Nonce"),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
error_type = data.get("error")
|
|
141
|
+
if error_type == "slow_down":
|
|
142
|
+
current_interval_ms += 5000
|
|
143
|
+
continue
|
|
144
|
+
if error_type == "authorization_pending":
|
|
145
|
+
continue
|
|
146
|
+
if error_type == "access_denied":
|
|
147
|
+
raise AuthorizationDeniedError()
|
|
148
|
+
if error_type == "expired_token":
|
|
149
|
+
raise AuthorizationTimeoutError("CIBA authentication request expired")
|
|
150
|
+
raise OAuthError(error_type or "unknown_error", data.get("error_description"), response.status_code)
|
|
151
|
+
|
|
152
|
+
raise AuthorizationTimeoutError(
|
|
153
|
+
f"CIBA polling exceeded maximum duration of {MAX_POLL_DURATION_SECONDS}s"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
async def get_status(self, auth_req_id: str) -> CIBAAuthResponse:
|
|
157
|
+
body = {
|
|
158
|
+
"grant_type": "urn:openid:params:grant-type:ciba",
|
|
159
|
+
"client_id": self._cfg.client_id,
|
|
160
|
+
"auth_req_id": auth_req_id,
|
|
161
|
+
}
|
|
162
|
+
if self._cfg.client_secret:
|
|
163
|
+
body["client_secret"] = self._cfg.client_secret
|
|
164
|
+
headers = {
|
|
165
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
166
|
+
"Accept": "application/json",
|
|
167
|
+
**(self._cfg.config.get("custom_headers") or {}),
|
|
168
|
+
}
|
|
169
|
+
timeout = self._cfg.timeout_ms / 1000
|
|
170
|
+
try:
|
|
171
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
172
|
+
response = await http.post(self._cfg.token_endpoint, headers=headers, data=body)
|
|
173
|
+
except Exception as error: # noqa: BLE001
|
|
174
|
+
raise KobilSDKError(f"CIBA status request failed: {error}", "NETWORK_ERROR")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
data = response.json()
|
|
178
|
+
except Exception:
|
|
179
|
+
raise KobilSDKError(
|
|
180
|
+
f"CIBA status endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
|
|
181
|
+
"INVALID_RESPONSE",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if response.is_success:
|
|
185
|
+
return CIBAAuthResponse(
|
|
186
|
+
auth_req_id=auth_req_id,
|
|
187
|
+
expires_in=data.get("expires_in", 0),
|
|
188
|
+
interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
|
|
189
|
+
status="approved",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
error_type = data.get("error")
|
|
193
|
+
if error_type in ("authorization_pending", "slow_down"):
|
|
194
|
+
return CIBAAuthResponse(
|
|
195
|
+
auth_req_id=auth_req_id,
|
|
196
|
+
expires_in=data.get("expires_in", 0),
|
|
197
|
+
interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
|
|
198
|
+
status="pending",
|
|
199
|
+
)
|
|
200
|
+
if error_type == "access_denied":
|
|
201
|
+
return CIBAAuthResponse(auth_req_id=auth_req_id, expires_in=0, interval=0, status="denied")
|
|
202
|
+
if error_type == "expired_token":
|
|
203
|
+
return CIBAAuthResponse(auth_req_id=auth_req_id, expires_in=0, interval=0, status="expired")
|
|
204
|
+
raise OAuthError(error_type or "unknown_error", data.get("error_description"), response.status_code)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
__all__ = ["CIBAClient", "DEFAULT_POLL_INTERVAL_SECONDS", "MAX_POLL_DURATION_SECONDS"]
|