agentauthlayer 0.1.10__tar.gz → 0.1.12__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.
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/PKG-INFO +34 -2
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/README.md +32 -1
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/__init__.py +2 -1
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/cli.py +9 -1
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/client.py +91 -7
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/context.py +41 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/principals.py +3 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/session.py +6 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/users.py +3 -0
- agentauthlayer-0.1.12/agent_auth/web_dist/assets/index-BOER6keK.js +21 -0
- agentauthlayer-0.1.12/agent_auth/web_dist/assets/index-t7ZqV4V6.css +1 -0
- agentauthlayer-0.1.12/agent_auth/web_dist/assets/ui-C1LFd_jm.js +242 -0
- agentauthlayer-0.1.12/agent_auth/web_dist/assets/vendor-BNEucebo.js +59 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/web_dist/index.html +4 -2
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/PKG-INFO +34 -2
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/SOURCES.txt +15 -8
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/requires.txt +1 -0
- agentauthlayer-0.1.12/auth_app/api/routes/projects.py +145 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/tokens.py +6 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/users.py +6 -2
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/core/config.py +1 -1
- agentauthlayer-0.1.12/auth_app/core/pg_shim.py +67 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/dependencies/auth.py +27 -2
- agentauthlayer-0.1.12/auth_app/dependencies/user.py +16 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/domain/models.py +10 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/contracts.py +13 -1
- agentauthlayer-0.1.12/auth_app/repositories/postgres_agent_repo.py +114 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_audit_repo.py +76 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_constraint_repo.py +47 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_delegation_repo.py +85 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_permission_repo.py +41 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_project_repo.py +175 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_role_repo.py +68 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_token_repo.py +117 -0
- agentauthlayer-0.1.12/auth_app/repositories/postgres_user_repo.py +233 -0
- agentauthlayer-0.1.12/auth_app/repositories/project_repo.py +64 -0
- agentauthlayer-0.1.12/auth_app/repositories/sqlite_project_repo.py +160 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/project.py +17 -6
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/token.py +4 -0
- agentauthlayer-0.1.12/auth_app/services/project_service.py +52 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/token_service.py +8 -2
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/user_service.py +3 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/pyproject.toml +2 -1
- agentauthlayer-0.1.12/tests/test_runtime_binding.py +109 -0
- agentauthlayer-0.1.10/agent_auth/web_dist/assets/index-CcMhzoW_.css +0 -1
- agentauthlayer-0.1.10/agent_auth/web_dist/assets/index-Coc7Vjc1.js +0 -306
- agentauthlayer-0.1.10/auth_app/api/routes/projects.py +0 -84
- agentauthlayer-0.1.10/auth_app/dependencies/user.py +0 -38
- agentauthlayer-0.1.10/auth_app/repositories/project_repo.py +0 -37
- agentauthlayer-0.1.10/auth_app/repositories/sqlite_project_repo.py +0 -104
- agentauthlayer-0.1.10/auth_app/services/project_service.py +0 -38
- agentauthlayer-0.1.10/tests/test_agent_auth_library.py +0 -267
- agentauthlayer-0.1.10/tests/test_core_first_boundary.py +0 -111
- agentauthlayer-0.1.10/tests/test_runtime_binding.py +0 -56
- agentauthlayer-0.1.10/tests/test_sqlite_repos.py +0 -167
- agentauthlayer-0.1.10/tests/test_storage.py +0 -237
- agentauthlayer-0.1.10/tests/test_tool_registry.py +0 -197
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/MANIFEST.in +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/__main__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/agents.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/audit.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/auth.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/core.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/credentials.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/delegation.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/exceptions.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/models.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/policy.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/policy_service.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/registry.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/runtime.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/server_runtime.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/storage.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/tokens.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/web_dist/favicon.ico +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/web_dist/grid.svg +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/web_dist/placeholder.svg +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agent_auth/web_dist/robots.txt +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/dependency_links.txt +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/entry_points.txt +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/agentauthlayer.egg-info/top_level.txt +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/agents.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/audit.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/auth.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/bootstrap.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/health.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/api/routes/policy.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/core/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/core/db.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/core/errors.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/core/logging.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/dependencies/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/dependencies/security.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/domain/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/domain/enums.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/main.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/middleware/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/middleware/correlation.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/agent_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/audit_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/constraint_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/delegation_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/role_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_agent_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_audit_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_constraint_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_delegation_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_permission_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_role_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_token_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/sqlite_user_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/token_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/repositories/user_repo.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/agent.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/audit.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/auth.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/bootstrap.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/device_agents.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/policy.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/schemas/user.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/__init__.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/agent_service.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/audit_service.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/device_agent_service.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/auth_app/services/policy_service.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/setup.cfg +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/tests/test_auth_flow.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/tests/test_health.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/tests/test_iam_policy.py +0 -0
- {agentauthlayer-0.1.10 → agentauthlayer-0.1.12}/tests/test_project_flow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentauthlayer
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.12
|
|
4
4
|
Summary: Library-first authentication and authorization SDK for AI agents
|
|
5
5
|
Author: Vaibhav Ahluwalia
|
|
6
6
|
License: MIT
|
|
@@ -26,6 +26,7 @@ Requires-Dist: bcrypt==4.0.1
|
|
|
26
26
|
Requires-Dist: python-multipart
|
|
27
27
|
Requires-Dist: httpx
|
|
28
28
|
Requires-Dist: sqlalchemy
|
|
29
|
+
Requires-Dist: psycopg2-binary
|
|
29
30
|
Requires-Dist: email-validator
|
|
30
31
|
|
|
31
32
|
# agentauthlayer
|
|
@@ -39,12 +40,19 @@ You can now create project-scoped tokens from:
|
|
|
39
40
|
- the Tokens page in the UI
|
|
40
41
|
- the CLI with `agentauth token create`
|
|
41
42
|
|
|
43
|
+
These tokens support one visible token model with richer internal semantics:
|
|
44
|
+
- `purpose=sync` for registration and sync flows
|
|
45
|
+
- `purpose=runtime` for runtime execution flows
|
|
46
|
+
- `purpose=automation` for service or CI style automation
|
|
47
|
+
- optional `agent_id` binding for runtime-oriented tokens
|
|
48
|
+
|
|
42
49
|
## Runtime binding
|
|
43
50
|
|
|
44
51
|
Runtime authorization now enforces stronger execution binding checks for project-scoped flows:
|
|
45
52
|
- project scope mismatches are denied before policy evaluation
|
|
46
53
|
- agent/principal mismatches are denied before policy evaluation
|
|
47
|
-
- execution context automatically carries project, agent, and
|
|
54
|
+
- execution context automatically carries project, agent, principal, and token-purpose context into authorization checks
|
|
55
|
+
- runtime-bound tokens now carry first-class `project_id`, `agent_id`, and `purpose` fields, with scope-based fallback for older tokens
|
|
48
56
|
|
|
49
57
|
Use the resulting token in code directly:
|
|
50
58
|
|
|
@@ -63,6 +71,30 @@ Or store it locally for CLI + SDK reuse:
|
|
|
63
71
|
agentauth login --base-url http://127.0.0.1:8002 --token YOUR_PROJECT_TOKEN --email admin@agentauth.dev
|
|
64
72
|
```
|
|
65
73
|
|
|
74
|
+
### Create a bound runtime token from the CLI
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
agentauth token create \
|
|
78
|
+
--project ai-platform \
|
|
79
|
+
--name research-runtime-token \
|
|
80
|
+
--purpose runtime \
|
|
81
|
+
--agent-id research-agent-01 \
|
|
82
|
+
--scope docs.read
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then use it directly in code:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from agent_auth import AuthAPIClient
|
|
89
|
+
|
|
90
|
+
client = AuthAPIClient(token="YOUR_RUNTIME_TOKEN")
|
|
91
|
+
ctx = client.execution_context()
|
|
92
|
+
|
|
93
|
+
print(ctx.project_id)
|
|
94
|
+
print(ctx.agent_id)
|
|
95
|
+
print(ctx.context.get("token_purpose"))
|
|
96
|
+
```
|
|
97
|
+
|
|
66
98
|
`agentauthlayer` helps you:
|
|
67
99
|
- start a local Agent Auth backend and UI with one command
|
|
68
100
|
- authenticate once and reuse local credentials
|
|
@@ -9,12 +9,19 @@ You can now create project-scoped tokens from:
|
|
|
9
9
|
- the Tokens page in the UI
|
|
10
10
|
- the CLI with `agentauth token create`
|
|
11
11
|
|
|
12
|
+
These tokens support one visible token model with richer internal semantics:
|
|
13
|
+
- `purpose=sync` for registration and sync flows
|
|
14
|
+
- `purpose=runtime` for runtime execution flows
|
|
15
|
+
- `purpose=automation` for service or CI style automation
|
|
16
|
+
- optional `agent_id` binding for runtime-oriented tokens
|
|
17
|
+
|
|
12
18
|
## Runtime binding
|
|
13
19
|
|
|
14
20
|
Runtime authorization now enforces stronger execution binding checks for project-scoped flows:
|
|
15
21
|
- project scope mismatches are denied before policy evaluation
|
|
16
22
|
- agent/principal mismatches are denied before policy evaluation
|
|
17
|
-
- execution context automatically carries project, agent, and
|
|
23
|
+
- execution context automatically carries project, agent, principal, and token-purpose context into authorization checks
|
|
24
|
+
- runtime-bound tokens now carry first-class `project_id`, `agent_id`, and `purpose` fields, with scope-based fallback for older tokens
|
|
18
25
|
|
|
19
26
|
Use the resulting token in code directly:
|
|
20
27
|
|
|
@@ -33,6 +40,30 @@ Or store it locally for CLI + SDK reuse:
|
|
|
33
40
|
agentauth login --base-url http://127.0.0.1:8002 --token YOUR_PROJECT_TOKEN --email admin@agentauth.dev
|
|
34
41
|
```
|
|
35
42
|
|
|
43
|
+
### Create a bound runtime token from the CLI
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
agentauth token create \
|
|
47
|
+
--project ai-platform \
|
|
48
|
+
--name research-runtime-token \
|
|
49
|
+
--purpose runtime \
|
|
50
|
+
--agent-id research-agent-01 \
|
|
51
|
+
--scope docs.read
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then use it directly in code:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from agent_auth import AuthAPIClient
|
|
58
|
+
|
|
59
|
+
client = AuthAPIClient(token="YOUR_RUNTIME_TOKEN")
|
|
60
|
+
ctx = client.execution_context()
|
|
61
|
+
|
|
62
|
+
print(ctx.project_id)
|
|
63
|
+
print(ctx.agent_id)
|
|
64
|
+
print(ctx.context.get("token_purpose"))
|
|
65
|
+
```
|
|
66
|
+
|
|
36
67
|
`agentauthlayer` helps you:
|
|
37
68
|
- start a local Agent Auth backend and UI with one command
|
|
38
69
|
- authenticate once and reuse local credentials
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from agent_auth.auth import principal_fields, principal_from_agent, principal_from_user
|
|
4
4
|
from agent_auth.client import AuthAPIClient
|
|
5
|
-
from agent_auth.context import AuthContext, ExecutionContext, execution_context_from_args
|
|
5
|
+
from agent_auth.context import AuthContext, ExecutionContext, execution_context_from_args, execution_context_from_token_payload
|
|
6
6
|
from agent_auth.delegation import DelegationGrant
|
|
7
7
|
from agent_auth.exceptions import (
|
|
8
8
|
AgentAuthError,
|
|
@@ -52,6 +52,7 @@ __all__ = [
|
|
|
52
52
|
"list_registered_agents",
|
|
53
53
|
"list_registered_tools",
|
|
54
54
|
"execution_context_from_args",
|
|
55
|
+
"execution_context_from_token_payload",
|
|
55
56
|
"principal_fields",
|
|
56
57
|
"principal_from_agent",
|
|
57
58
|
"principal_from_user",
|
|
@@ -216,7 +216,13 @@ def project_create_command(args) -> int:
|
|
|
216
216
|
|
|
217
217
|
def token_create_command(args) -> int:
|
|
218
218
|
client = AuthAPIClient()
|
|
219
|
-
token = client.create_project_token(
|
|
219
|
+
token = client.create_project_token(
|
|
220
|
+
args.project,
|
|
221
|
+
args.name,
|
|
222
|
+
args.scopes or [],
|
|
223
|
+
purpose=args.purpose,
|
|
224
|
+
agent_id=args.agent_id,
|
|
225
|
+
)
|
|
220
226
|
print(json.dumps(token, indent=2))
|
|
221
227
|
return 0
|
|
222
228
|
|
|
@@ -324,6 +330,8 @@ def main():
|
|
|
324
330
|
token_create_parser.add_argument('--project', required=True)
|
|
325
331
|
token_create_parser.add_argument('--name', required=True)
|
|
326
332
|
token_create_parser.add_argument('--scope', dest='scopes', action='append', help='Scope to include. Repeat for multiple scopes.')
|
|
333
|
+
token_create_parser.add_argument('--purpose', default='sync', choices=['sync', 'runtime', 'automation'], help='Intended token purpose.')
|
|
334
|
+
token_create_parser.add_argument('--agent-id', help='Optional runtime agent binding for runtime-oriented tokens.')
|
|
327
335
|
token_create_parser.set_defaults(func=token_create_command)
|
|
328
336
|
|
|
329
337
|
token_list_parser = token_subparsers.add_parser('list', help='List tokens')
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
3
5
|
import os
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
import requests
|
|
7
9
|
|
|
8
|
-
from agent_auth.context import AuthContext
|
|
10
|
+
from agent_auth.context import AuthContext, ExecutionContext, execution_context_from_token_payload
|
|
9
11
|
from agent_auth.credentials import load_credentials
|
|
10
12
|
from agent_auth.exceptions import (
|
|
11
13
|
AgentNotFoundError,
|
|
@@ -40,6 +42,21 @@ class AuthAPIClient:
|
|
|
40
42
|
headers["Authorization"] = f"Bearer {self.token}"
|
|
41
43
|
return headers
|
|
42
44
|
|
|
45
|
+
def decode_token(self, token: str | None = None) -> dict[str, Any] | None:
|
|
46
|
+
raw = token or self.token
|
|
47
|
+
if not raw:
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
parts = raw.split('.')
|
|
51
|
+
if len(parts) != 3:
|
|
52
|
+
return None
|
|
53
|
+
payload = parts[1]
|
|
54
|
+
padding = '=' * (-len(payload) % 4)
|
|
55
|
+
decoded = base64.urlsafe_b64decode(payload + padding)
|
|
56
|
+
return json.loads(decoded.decode('utf-8'))
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
43
60
|
def _request(self, method: str, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
44
61
|
response = requests.request(
|
|
45
62
|
method,
|
|
@@ -114,12 +131,33 @@ class AuthAPIClient:
|
|
|
114
131
|
json={"project_id": project_id, "name": name, "description": description},
|
|
115
132
|
)
|
|
116
133
|
|
|
117
|
-
def create_project_token(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
def create_project_token(
|
|
135
|
+
self,
|
|
136
|
+
project_id: str,
|
|
137
|
+
name: str,
|
|
138
|
+
scopes: list[str] | None = None,
|
|
139
|
+
*,
|
|
140
|
+
purpose: str = "sync",
|
|
141
|
+
agent_id: str | None = None,
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
payload: dict[str, Any] = {
|
|
144
|
+
"project_id": project_id,
|
|
145
|
+
"name": name,
|
|
146
|
+
"scopes": scopes or [],
|
|
147
|
+
"purpose": purpose,
|
|
148
|
+
}
|
|
149
|
+
if agent_id:
|
|
150
|
+
payload["agent_id"] = agent_id
|
|
151
|
+
return self._request("POST", "/tokens/project", json=payload)
|
|
152
|
+
|
|
153
|
+
def create_sync_token(self, project_id: str, name: str = "project-sync-token", scopes: list[str] | None = None) -> dict[str, Any]:
|
|
154
|
+
return self.create_project_token(project_id, name, scopes or ["admin_agents", "admin_tokens"], purpose="sync")
|
|
155
|
+
|
|
156
|
+
def create_runtime_token(self, project_id: str, agent_id: str, name: str = "project-runtime-token", scopes: list[str] | None = None) -> dict[str, Any]:
|
|
157
|
+
return self.create_project_token(project_id, name, scopes or ["docs.read"], purpose="runtime", agent_id=agent_id)
|
|
158
|
+
|
|
159
|
+
def create_automation_token(self, project_id: str, name: str = "project-automation-token", scopes: list[str] | None = None) -> dict[str, Any]:
|
|
160
|
+
return self.create_project_token(project_id, name, scopes or ["admin_agents"], purpose="automation")
|
|
123
161
|
|
|
124
162
|
def list_tokens(self, subject_type: str | None = None) -> list[dict[str, Any]]:
|
|
125
163
|
path = "/tokens"
|
|
@@ -145,6 +183,52 @@ class AuthAPIClient:
|
|
|
145
183
|
def list_permissions(self) -> list[dict[str, Any]]:
|
|
146
184
|
return self._request("GET", "/policy/permissions")
|
|
147
185
|
|
|
186
|
+
def explain_token(self, token: str | None = None) -> dict[str, Any]:
|
|
187
|
+
payload = self.decode_token(token)
|
|
188
|
+
if not payload:
|
|
189
|
+
raise InvalidTokenError("Unable to decode token")
|
|
190
|
+
scopes = list(payload.get("scopes") or [])
|
|
191
|
+
project_id = payload.get("project_id")
|
|
192
|
+
agent_id = payload.get("agent_id")
|
|
193
|
+
purpose = payload.get("purpose")
|
|
194
|
+
if not project_id:
|
|
195
|
+
project_id = next((scope.split(":", 1)[1] for scope in scopes if isinstance(scope, str) and scope.startswith("project:")), None)
|
|
196
|
+
if not agent_id:
|
|
197
|
+
agent_id = next((scope.split(":", 1)[1] for scope in scopes if isinstance(scope, str) and scope.startswith("agent:")), None)
|
|
198
|
+
if not purpose:
|
|
199
|
+
purpose = next((scope.split(":", 1)[1] for scope in scopes if isinstance(scope, str) and scope.startswith("purpose:")), None)
|
|
200
|
+
return {
|
|
201
|
+
"principal_id": payload.get("sub") or payload.get("principal_id"),
|
|
202
|
+
"principal_type": payload.get("principal_type") or payload.get("subject_type") or payload.get("sub_type"),
|
|
203
|
+
"role": payload.get("role"),
|
|
204
|
+
"email": payload.get("email"),
|
|
205
|
+
"purpose": purpose,
|
|
206
|
+
"project_id": project_id,
|
|
207
|
+
"agent_id": agent_id,
|
|
208
|
+
"scopes": scopes,
|
|
209
|
+
"auth_source": self.auth_source,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def execution_context(self, *, agent_id: str | None = None, role: str | None = None, context: dict[str, Any] | None = None, require_runtime_binding: bool = False) -> ExecutionContext:
|
|
213
|
+
if not self.token:
|
|
214
|
+
raise InvalidTokenError("No token available to derive execution context")
|
|
215
|
+
payload = self.decode_token(self.token)
|
|
216
|
+
if not payload:
|
|
217
|
+
raise InvalidTokenError("Unable to decode token for execution context")
|
|
218
|
+
ctx = execution_context_from_token_payload(payload, agent_id=agent_id, role=role, context=context)
|
|
219
|
+
if require_runtime_binding:
|
|
220
|
+
token_info = self.explain_token()
|
|
221
|
+
if token_info.get("purpose") != "runtime":
|
|
222
|
+
raise InvalidTokenError("Token is not bound for runtime usage")
|
|
223
|
+
if not ctx.project_id:
|
|
224
|
+
raise InvalidTokenError("Runtime token is missing project binding")
|
|
225
|
+
if not ctx.agent_id:
|
|
226
|
+
raise InvalidTokenError("Runtime token is missing agent binding")
|
|
227
|
+
return ctx
|
|
228
|
+
|
|
229
|
+
def runtime_context(self, *, context: dict[str, Any] | None = None, role: str | None = None) -> ExecutionContext:
|
|
230
|
+
return self.execution_context(context=context, role=role, require_runtime_binding=True)
|
|
231
|
+
|
|
148
232
|
def evaluate(self, principal_id: str, action: str, resource: str | None = None, granted_scopes: list[str] | None = None, role: str | None = None, context: dict[str, str] | None = None) -> dict[str, Any]:
|
|
149
233
|
return self._request(
|
|
150
234
|
"POST",
|
|
@@ -83,3 +83,44 @@ def execution_context_from_args(
|
|
|
83
83
|
context=context or {},
|
|
84
84
|
email=email,
|
|
85
85
|
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def execution_context_from_token_payload(payload: dict[str, Any], *, agent_id: str | None = None, role: str | None = None, context: dict[str, Any] | None = None) -> ExecutionContext:
|
|
89
|
+
scopes = list(payload.get('scopes') or [])
|
|
90
|
+
|
|
91
|
+
project_id = payload.get('project_id') or payload.get('bound_project_id')
|
|
92
|
+
if project_id is None:
|
|
93
|
+
for scope in scopes:
|
|
94
|
+
if isinstance(scope, str) and scope.startswith('project:'):
|
|
95
|
+
project_id = scope.split(':', 1)[1]
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
principal_id = payload.get('sub') or payload.get('principal_id')
|
|
99
|
+
principal_type = payload.get('principal_type') or payload.get('subject_type') or payload.get('sub_type')
|
|
100
|
+
|
|
101
|
+
derived_agent_id = agent_id
|
|
102
|
+
if derived_agent_id is None and principal_type == 'agent':
|
|
103
|
+
derived_agent_id = principal_id
|
|
104
|
+
if derived_agent_id is None:
|
|
105
|
+
derived_agent_id = payload.get('agent_id') or payload.get('bound_agent_id')
|
|
106
|
+
if derived_agent_id is None:
|
|
107
|
+
for scope in scopes:
|
|
108
|
+
if isinstance(scope, str) and scope.startswith('agent:'):
|
|
109
|
+
derived_agent_id = scope.split(':', 1)[1]
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
base_context = dict(context or {})
|
|
113
|
+
token_purpose = payload.get('purpose') or payload.get('token_purpose')
|
|
114
|
+
if token_purpose and 'token_purpose' not in base_context:
|
|
115
|
+
base_context['token_purpose'] = token_purpose
|
|
116
|
+
|
|
117
|
+
return ExecutionContext(
|
|
118
|
+
principal_id=principal_id,
|
|
119
|
+
principal_type=principal_type,
|
|
120
|
+
agent_id=derived_agent_id,
|
|
121
|
+
project_id=project_id,
|
|
122
|
+
role=role or payload.get('role'),
|
|
123
|
+
scopes=scopes,
|
|
124
|
+
context=base_context,
|
|
125
|
+
email=payload.get('email'),
|
|
126
|
+
)
|
|
@@ -121,6 +121,12 @@ class PrincipalTokenService:
|
|
|
121
121
|
"email": principal.email,
|
|
122
122
|
"exp": expires_at,
|
|
123
123
|
}
|
|
124
|
+
if principal.token_purpose:
|
|
125
|
+
payload["purpose"] = principal.token_purpose
|
|
126
|
+
if principal.bound_project_id:
|
|
127
|
+
payload["project_id"] = principal.bound_project_id
|
|
128
|
+
if principal.bound_agent_id:
|
|
129
|
+
payload["agent_id"] = principal.bound_agent_id
|
|
124
130
|
if token_type:
|
|
125
131
|
payload["type"] = token_type
|
|
126
132
|
token = jwt.encode(payload, self.settings.secret_key, algorithm=self.settings.algorithm)
|
|
@@ -129,6 +129,9 @@ class CoreUserService:
|
|
|
129
129
|
def list_invites(self) -> list[InviteT]:
|
|
130
130
|
return self.store.list_invites()
|
|
131
131
|
|
|
132
|
+
def get_user_by_email(self, email: str) -> UserT | None:
|
|
133
|
+
return self.store.get_user_by_email(email)
|
|
134
|
+
|
|
132
135
|
def get_user_by_id(self, user_id: str) -> UserT | None:
|
|
133
136
|
return self.store.get_user_by_id(user_id)
|
|
134
137
|
|