agentauthlayer 0.1.8__tar.gz → 0.1.10__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.8 → agentauthlayer-0.1.10}/MANIFEST.in +0 -1
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/PKG-INFO +32 -1
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/README.md +31 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/__init__.py +3 -1
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/cli.py +118 -16
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/client.py +45 -0
- agentauthlayer-0.1.10/agent_auth/context.py +85 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/policy.py +18 -6
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/server_runtime.py +10 -2
- agentauthlayer-0.1.10/agent_auth/web_dist/assets/index-CcMhzoW_.css +1 -0
- agentauthlayer-0.1.10/agent_auth/web_dist/assets/index-Coc7Vjc1.js +306 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/web_dist/index.html +2 -2
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/PKG-INFO +32 -1
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/SOURCES.txt +3 -3
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/agents.py +9 -1
- agentauthlayer-0.1.10/auth_app/api/routes/tokens.py +114 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/main.py +16 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/contracts.py +4 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_token_repo.py +24 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_user_repo.py +16 -4
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/token_repo.py +6 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/agent.py +7 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/token.py +25 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/agent_service.py +15 -1
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/token_service.py +16 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/pyproject.toml +1 -1
- agentauthlayer-0.1.10/tests/test_runtime_binding.py +56 -0
- agentauthlayer-0.1.8/agent_auth/context.py +0 -21
- agentauthlayer-0.1.8/agent_auth/web_dist/assets/index-BBJ7rinV.css +0 -1
- agentauthlayer-0.1.8/agent_auth/web_dist/assets/index-DXUoW2DG.js +0 -429
- agentauthlayer-0.1.8/agent_auth_definitive_guide.pdf +0 -0
- agentauthlayer-0.1.8/auth_app/api/routes/tokens.py +0 -50
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/__main__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/agents.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/audit.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/auth.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/core.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/credentials.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/delegation.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/exceptions.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/models.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/policy_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/principals.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/registry.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/runtime.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/session.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/storage.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/tokens.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/users.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/web_dist/favicon.ico +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/web_dist/grid.svg +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/web_dist/placeholder.svg +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agent_auth/web_dist/robots.txt +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/dependency_links.txt +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/entry_points.txt +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/requires.txt +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/agentauthlayer.egg-info/top_level.txt +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/audit.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/auth.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/bootstrap.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/health.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/policy.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/projects.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/api/routes/users.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/core/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/core/config.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/core/db.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/core/errors.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/core/logging.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/dependencies/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/dependencies/auth.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/dependencies/security.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/dependencies/user.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/domain/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/domain/enums.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/domain/models.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/middleware/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/middleware/correlation.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/agent_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/audit_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/constraint_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/delegation_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/project_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/role_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_agent_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_audit_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_constraint_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_delegation_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_permission_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_project_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/sqlite_role_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/repositories/user_repo.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/audit.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/auth.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/bootstrap.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/device_agents.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/policy.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/project.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/schemas/user.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/__init__.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/audit_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/device_agent_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/policy_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/project_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/auth_app/services/user_service.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/setup.cfg +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_agent_auth_library.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_auth_flow.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_core_first_boundary.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_health.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_iam_policy.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_project_flow.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_sqlite_repos.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_storage.py +0 -0
- {agentauthlayer-0.1.8 → agentauthlayer-0.1.10}/tests/test_tool_registry.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.10
|
|
4
4
|
Summary: Library-first authentication and authorization SDK for AI agents
|
|
5
5
|
Author: Vaibhav Ahluwalia
|
|
6
6
|
License: MIT
|
|
@@ -32,6 +32,37 @@ Requires-Dist: email-validator
|
|
|
32
32
|
|
|
33
33
|
Unified SDK and local control plane package for Agent Auth.
|
|
34
34
|
|
|
35
|
+
## Token-first developer flow
|
|
36
|
+
|
|
37
|
+
You can now create project-scoped tokens from:
|
|
38
|
+
- the Project Settings page in the UI
|
|
39
|
+
- the Tokens page in the UI
|
|
40
|
+
- the CLI with `agentauth token create`
|
|
41
|
+
|
|
42
|
+
## Runtime binding
|
|
43
|
+
|
|
44
|
+
Runtime authorization now enforces stronger execution binding checks for project-scoped flows:
|
|
45
|
+
- project scope mismatches are denied before policy evaluation
|
|
46
|
+
- agent/principal mismatches are denied before policy evaluation
|
|
47
|
+
- execution context automatically carries project, agent, and principal context into authorization checks
|
|
48
|
+
|
|
49
|
+
Use the resulting token in code directly:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from agent_auth.client import AuthAPIClient
|
|
53
|
+
|
|
54
|
+
client = AuthAPIClient(
|
|
55
|
+
base_url="http://127.0.0.1:8002",
|
|
56
|
+
token="YOUR_PROJECT_TOKEN",
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or store it locally for CLI + SDK reuse:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
agentauth login --base-url http://127.0.0.1:8002 --token YOUR_PROJECT_TOKEN --email admin@agentauth.dev
|
|
64
|
+
```
|
|
65
|
+
|
|
35
66
|
`agentauthlayer` helps you:
|
|
36
67
|
- start a local Agent Auth backend and UI with one command
|
|
37
68
|
- authenticate once and reuse local credentials
|
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
Unified SDK and local control plane package for Agent Auth.
|
|
4
4
|
|
|
5
|
+
## Token-first developer flow
|
|
6
|
+
|
|
7
|
+
You can now create project-scoped tokens from:
|
|
8
|
+
- the Project Settings page in the UI
|
|
9
|
+
- the Tokens page in the UI
|
|
10
|
+
- the CLI with `agentauth token create`
|
|
11
|
+
|
|
12
|
+
## Runtime binding
|
|
13
|
+
|
|
14
|
+
Runtime authorization now enforces stronger execution binding checks for project-scoped flows:
|
|
15
|
+
- project scope mismatches are denied before policy evaluation
|
|
16
|
+
- agent/principal mismatches are denied before policy evaluation
|
|
17
|
+
- execution context automatically carries project, agent, and principal context into authorization checks
|
|
18
|
+
|
|
19
|
+
Use the resulting token in code directly:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from agent_auth.client import AuthAPIClient
|
|
23
|
+
|
|
24
|
+
client = AuthAPIClient(
|
|
25
|
+
base_url="http://127.0.0.1:8002",
|
|
26
|
+
token="YOUR_PROJECT_TOKEN",
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or store it locally for CLI + SDK reuse:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agentauth login --base-url http://127.0.0.1:8002 --token YOUR_PROJECT_TOKEN --email admin@agentauth.dev
|
|
34
|
+
```
|
|
35
|
+
|
|
5
36
|
`agentauthlayer` helps you:
|
|
6
37
|
- start a local Agent Auth backend and UI with one command
|
|
7
38
|
- 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
|
|
5
|
+
from agent_auth.context import AuthContext, ExecutionContext, execution_context_from_args
|
|
6
6
|
from agent_auth.delegation import DelegationGrant
|
|
7
7
|
from agent_auth.exceptions import (
|
|
8
8
|
AgentAuthError,
|
|
@@ -30,6 +30,7 @@ __all__ = [
|
|
|
30
30
|
"AgentPrincipal",
|
|
31
31
|
"AuthAPIClient",
|
|
32
32
|
"AuthContext",
|
|
33
|
+
"ExecutionContext",
|
|
33
34
|
"AuthServiceError",
|
|
34
35
|
"DelegationGrant",
|
|
35
36
|
"InvalidTokenError",
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
"clear_registries",
|
|
51
52
|
"list_registered_agents",
|
|
52
53
|
"list_registered_tools",
|
|
54
|
+
"execution_context_from_args",
|
|
53
55
|
"principal_fields",
|
|
54
56
|
"principal_from_agent",
|
|
55
57
|
"principal_from_user",
|
|
@@ -6,6 +6,7 @@ import importlib
|
|
|
6
6
|
import json
|
|
7
7
|
import sys
|
|
8
8
|
import webbrowser
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import requests
|
|
11
12
|
|
|
@@ -125,33 +126,69 @@ def logout_command(args) -> int:
|
|
|
125
126
|
|
|
126
127
|
def sync_command(args) -> int:
|
|
127
128
|
clear_registries()
|
|
129
|
+
|
|
130
|
+
cwd = str(Path.cwd())
|
|
131
|
+
if cwd not in sys.path:
|
|
132
|
+
sys.path.insert(0, cwd)
|
|
133
|
+
|
|
128
134
|
importlib.import_module(args.module)
|
|
129
135
|
|
|
130
136
|
client = AuthAPIClient()
|
|
131
137
|
tools = list_registered_tools()
|
|
132
138
|
agents = list_registered_agents()
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
existing_permissions = {permission['action']: permission for permission in client.list_permissions()}
|
|
141
|
+
tool_payload = [{'action': tool.action, 'description': tool.description} for tool in tools]
|
|
142
|
+
tools_to_sync = [tool for tool in tool_payload if existing_permissions.get(tool['action'], {}).get('description') != tool['description']]
|
|
143
|
+
if tools_to_sync:
|
|
144
|
+
client.sync_tools(tools_to_sync)
|
|
139
145
|
|
|
140
|
-
|
|
146
|
+
agent_results = []
|
|
141
147
|
for agent in agents:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
target_project = args.project or agent.project_id
|
|
149
|
+
try:
|
|
150
|
+
existing = client.get_agent(agent.agent_id)
|
|
151
|
+
except AgentNotFoundError:
|
|
152
|
+
existing = None
|
|
153
|
+
|
|
154
|
+
desired = {
|
|
155
|
+
'name': agent.name,
|
|
156
|
+
'role': agent.role,
|
|
157
|
+
'scopes': list(agent.scopes),
|
|
158
|
+
'project_id': target_project,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if existing is None:
|
|
162
|
+
created = client.create_agent(
|
|
163
|
+
agent_id=agent.agent_id,
|
|
164
|
+
name=agent.name,
|
|
165
|
+
owner=agent.owner,
|
|
166
|
+
role=agent.role,
|
|
167
|
+
scopes=agent.scopes,
|
|
168
|
+
project_id=target_project,
|
|
169
|
+
)
|
|
170
|
+
agent_results.append({'agent_id': created['agent_id'], 'status': 'created'})
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
changed_fields = {}
|
|
174
|
+
for field in ['name', 'role', 'project_id', 'scopes']:
|
|
175
|
+
if existing.get(field) != desired[field]:
|
|
176
|
+
changed_fields[field] = desired[field]
|
|
177
|
+
|
|
178
|
+
if changed_fields:
|
|
179
|
+
updated = client.update_agent(agent.agent_id, **changed_fields)
|
|
180
|
+
agent_results.append({'agent_id': updated['agent_id'], 'status': 'updated', 'changed_fields': sorted(changed_fields.keys())})
|
|
181
|
+
else:
|
|
182
|
+
agent_results.append({'agent_id': agent.agent_id, 'status': 'unchanged'})
|
|
150
183
|
|
|
151
184
|
print(json.dumps({
|
|
152
185
|
'module': args.module,
|
|
153
|
-
'
|
|
154
|
-
'
|
|
186
|
+
'project': args.project,
|
|
187
|
+
'tools': {
|
|
188
|
+
'added_or_updated': [tool['action'] for tool in tools_to_sync],
|
|
189
|
+
'unchanged': [tool.action for tool in tools if tool.action not in {item['action'] for item in tools_to_sync}],
|
|
190
|
+
},
|
|
191
|
+
'agents': agent_results,
|
|
155
192
|
}, indent=2))
|
|
156
193
|
return 0
|
|
157
194
|
|
|
@@ -163,6 +200,41 @@ def delete_agent_command(args) -> int:
|
|
|
163
200
|
return 0
|
|
164
201
|
|
|
165
202
|
|
|
203
|
+
def project_list_command(args) -> int:
|
|
204
|
+
client = AuthAPIClient()
|
|
205
|
+
projects = client.list_projects()
|
|
206
|
+
print(json.dumps(projects, indent=2))
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def project_create_command(args) -> int:
|
|
211
|
+
client = AuthAPIClient()
|
|
212
|
+
project = client.create_project(args.project_id, args.name, args.description or "")
|
|
213
|
+
print(json.dumps(project, indent=2))
|
|
214
|
+
return 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def token_create_command(args) -> int:
|
|
218
|
+
client = AuthAPIClient()
|
|
219
|
+
token = client.create_project_token(args.project, args.name, args.scopes or [])
|
|
220
|
+
print(json.dumps(token, indent=2))
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def token_list_command(args) -> int:
|
|
225
|
+
client = AuthAPIClient()
|
|
226
|
+
tokens = client.list_tokens(args.subject_type)
|
|
227
|
+
print(json.dumps(tokens, indent=2))
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def token_revoke_command(args) -> int:
|
|
232
|
+
client = AuthAPIClient()
|
|
233
|
+
result = client.revoke_token_by_jti(args.jti)
|
|
234
|
+
print(json.dumps(result, indent=2))
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
166
238
|
def ui_command(args) -> int:
|
|
167
239
|
creds = load_credentials() or {}
|
|
168
240
|
base_url = (args.base_url or creds.get('base_url') or DEFAULT_BASE_URL).rstrip('/')
|
|
@@ -230,8 +302,38 @@ def main():
|
|
|
230
302
|
|
|
231
303
|
sync_parser = subparsers.add_parser('sync', help='Import a module and sync its registered tools and agents')
|
|
232
304
|
sync_parser.add_argument('--module', required=True)
|
|
305
|
+
sync_parser.add_argument('--project', help='Override the project_id used when creating synced agents')
|
|
233
306
|
sync_parser.set_defaults(func=sync_command)
|
|
234
307
|
|
|
308
|
+
project_parser = subparsers.add_parser('project', help='Manage projects from the CLI')
|
|
309
|
+
project_subparsers = project_parser.add_subparsers(dest='project_command')
|
|
310
|
+
|
|
311
|
+
project_list_parser = project_subparsers.add_parser('list', help='List available projects')
|
|
312
|
+
project_list_parser.set_defaults(func=project_list_command)
|
|
313
|
+
|
|
314
|
+
project_create_parser = project_subparsers.add_parser('create', help='Create a project')
|
|
315
|
+
project_create_parser.add_argument('--project-id', required=True)
|
|
316
|
+
project_create_parser.add_argument('--name', required=True)
|
|
317
|
+
project_create_parser.add_argument('--description')
|
|
318
|
+
project_create_parser.set_defaults(func=project_create_command)
|
|
319
|
+
|
|
320
|
+
token_parser = subparsers.add_parser('token', help='Manage project tokens from the CLI')
|
|
321
|
+
token_subparsers = token_parser.add_subparsers(dest='token_command')
|
|
322
|
+
|
|
323
|
+
token_create_parser = token_subparsers.add_parser('create', help='Create a project token')
|
|
324
|
+
token_create_parser.add_argument('--project', required=True)
|
|
325
|
+
token_create_parser.add_argument('--name', required=True)
|
|
326
|
+
token_create_parser.add_argument('--scope', dest='scopes', action='append', help='Scope to include. Repeat for multiple scopes.')
|
|
327
|
+
token_create_parser.set_defaults(func=token_create_command)
|
|
328
|
+
|
|
329
|
+
token_list_parser = token_subparsers.add_parser('list', help='List tokens')
|
|
330
|
+
token_list_parser.add_argument('--subject-type', choices=['user', 'agent', 'system'])
|
|
331
|
+
token_list_parser.set_defaults(func=token_list_command)
|
|
332
|
+
|
|
333
|
+
token_revoke_parser = token_subparsers.add_parser('revoke', help='Revoke a token by JTI')
|
|
334
|
+
token_revoke_parser.add_argument('--jti', required=True)
|
|
335
|
+
token_revoke_parser.set_defaults(func=token_revoke_command)
|
|
336
|
+
|
|
235
337
|
delete_agent_parser = subparsers.add_parser('delete-agent', help='Delete an agent by ID')
|
|
236
338
|
delete_agent_parser.add_argument('agent_id')
|
|
237
339
|
delete_agent_parser.set_defaults(func=delete_agent_command)
|
|
@@ -68,6 +68,10 @@ class AuthAPIClient:
|
|
|
68
68
|
raise PermissionDeniedError(detail or "Permission denied")
|
|
69
69
|
if response.status_code == 404 and path.startswith("/agents/"):
|
|
70
70
|
raise AgentNotFoundError(detail or "Agent not found")
|
|
71
|
+
if response.status_code == 404 and path == "/agents" and detail and "Project not found" in detail:
|
|
72
|
+
raise AuthServiceError(
|
|
73
|
+
"Project not found. Create the project in the Agent Auth UI first, or update your agent's project_id to match an existing project."
|
|
74
|
+
)
|
|
71
75
|
raise AuthServiceError(detail or f"Request failed with status {response.status_code}")
|
|
72
76
|
|
|
73
77
|
def health(self) -> dict[str, Any]:
|
|
@@ -88,6 +92,44 @@ class AuthAPIClient:
|
|
|
88
92
|
payload["project_id"] = project_id
|
|
89
93
|
return self._request("POST", "/agents", json=payload)
|
|
90
94
|
|
|
95
|
+
def update_agent(self, agent_id: str, *, name: str | None = None, role: str | None = None, scopes: list[str] | None = None, project_id: str | None = None) -> dict[str, Any]:
|
|
96
|
+
payload: dict[str, Any] = {}
|
|
97
|
+
if name is not None:
|
|
98
|
+
payload["name"] = name
|
|
99
|
+
if role is not None:
|
|
100
|
+
payload["role"] = role
|
|
101
|
+
if scopes is not None:
|
|
102
|
+
payload["scopes"] = scopes
|
|
103
|
+
if project_id is not None:
|
|
104
|
+
payload["project_id"] = project_id
|
|
105
|
+
return self._request("PATCH", f"/agents/{agent_id}", json=payload)
|
|
106
|
+
|
|
107
|
+
def list_projects(self) -> list[dict[str, Any]]:
|
|
108
|
+
return self._request("GET", "/projects")
|
|
109
|
+
|
|
110
|
+
def create_project(self, project_id: str, name: str, description: str = "") -> dict[str, Any]:
|
|
111
|
+
return self._request(
|
|
112
|
+
"POST",
|
|
113
|
+
"/projects",
|
|
114
|
+
json={"project_id": project_id, "name": name, "description": description},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def create_project_token(self, project_id: str, name: str, scopes: list[str] | None = None) -> dict[str, Any]:
|
|
118
|
+
return self._request(
|
|
119
|
+
"POST",
|
|
120
|
+
"/tokens/project",
|
|
121
|
+
json={"project_id": project_id, "name": name, "scopes": scopes or []},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def list_tokens(self, subject_type: str | None = None) -> list[dict[str, Any]]:
|
|
125
|
+
path = "/tokens"
|
|
126
|
+
if subject_type:
|
|
127
|
+
path = f"/tokens?subject_type={subject_type}"
|
|
128
|
+
return self._request("GET", path)
|
|
129
|
+
|
|
130
|
+
def revoke_token_by_jti(self, jti: str) -> dict[str, Any]:
|
|
131
|
+
return self._request("POST", "/tokens/revoke", json={"jti": jti})
|
|
132
|
+
|
|
91
133
|
def delete_agent(self, agent_id: str) -> dict[str, Any]:
|
|
92
134
|
return self._request("DELETE", f"/agents/{agent_id}")
|
|
93
135
|
|
|
@@ -100,6 +142,9 @@ class AuthAPIClient:
|
|
|
100
142
|
def sync_tools(self, permissions: list[dict[str, str]]) -> dict[str, Any]:
|
|
101
143
|
return self._request("POST", "/policy/permissions/sync", json={"permissions": permissions})
|
|
102
144
|
|
|
145
|
+
def list_permissions(self) -> list[dict[str, Any]]:
|
|
146
|
+
return self._request("GET", "/policy/permissions")
|
|
147
|
+
|
|
103
148
|
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]:
|
|
104
149
|
return self._request(
|
|
105
150
|
"POST",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class AuthContext:
|
|
9
|
+
principal_id: str
|
|
10
|
+
principal_type: str
|
|
11
|
+
jti: str
|
|
12
|
+
scopes: list[str] = field(default_factory=list)
|
|
13
|
+
role: str | None = None
|
|
14
|
+
email: str | None = None
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def subject_id(self) -> str:
|
|
18
|
+
return self.principal_id
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def subject_type(self) -> str:
|
|
22
|
+
return self.principal_type
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class ExecutionContext:
|
|
27
|
+
principal_id: str | None = None
|
|
28
|
+
principal_type: str | None = None
|
|
29
|
+
agent_id: str | None = None
|
|
30
|
+
project_id: str | None = None
|
|
31
|
+
role: str | None = None
|
|
32
|
+
scopes: list[str] = field(default_factory=list)
|
|
33
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
email: str | None = None
|
|
35
|
+
|
|
36
|
+
def merged_context(self, extra: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
37
|
+
merged = dict(self.context)
|
|
38
|
+
if self.project_id and 'project_id' not in merged:
|
|
39
|
+
merged['project_id'] = self.project_id
|
|
40
|
+
if self.agent_id and 'agent_id' not in merged:
|
|
41
|
+
merged['agent_id'] = self.agent_id
|
|
42
|
+
if self.principal_id and 'principal_id' not in merged:
|
|
43
|
+
merged['principal_id'] = self.principal_id
|
|
44
|
+
if self.principal_type and 'principal_type' not in merged:
|
|
45
|
+
merged['principal_type'] = self.principal_type
|
|
46
|
+
if extra:
|
|
47
|
+
merged.update(extra)
|
|
48
|
+
return merged
|
|
49
|
+
|
|
50
|
+
def project_scope(self) -> str | None:
|
|
51
|
+
for scope in self.scopes:
|
|
52
|
+
if scope.startswith('project:'):
|
|
53
|
+
return scope.split(':', 1)[1]
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def binding_mismatch_reason(self) -> str | None:
|
|
57
|
+
scoped_project = self.project_scope()
|
|
58
|
+
if self.project_id and scoped_project and self.project_id != scoped_project:
|
|
59
|
+
return f"project_scope_mismatch:{self.project_id}!={scoped_project}"
|
|
60
|
+
if self.principal_type == 'agent' and self.agent_id and self.principal_id and self.agent_id != self.principal_id:
|
|
61
|
+
return f"agent_principal_mismatch:{self.agent_id}!={self.principal_id}"
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def execution_context_from_args(
|
|
66
|
+
*,
|
|
67
|
+
principal_id: str | None = None,
|
|
68
|
+
principal_type: str | None = None,
|
|
69
|
+
agent_id: str | None = None,
|
|
70
|
+
project_id: str | None = None,
|
|
71
|
+
role: str | None = None,
|
|
72
|
+
scopes: list[str] | None = None,
|
|
73
|
+
context: dict[str, Any] | None = None,
|
|
74
|
+
email: str | None = None,
|
|
75
|
+
) -> ExecutionContext:
|
|
76
|
+
return ExecutionContext(
|
|
77
|
+
principal_id=principal_id,
|
|
78
|
+
principal_type=principal_type,
|
|
79
|
+
agent_id=agent_id,
|
|
80
|
+
project_id=project_id,
|
|
81
|
+
role=role,
|
|
82
|
+
scopes=scopes or [],
|
|
83
|
+
context=context or {},
|
|
84
|
+
email=email,
|
|
85
|
+
)
|
|
@@ -270,16 +270,28 @@ def require_permission(action: str, resource: str | None = None):
|
|
|
270
270
|
def decorator(func: Callable):
|
|
271
271
|
@wraps(func)
|
|
272
272
|
def wrapper(*args, **kwargs):
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
execution_context = kwargs.get('execution_context')
|
|
274
|
+
|
|
275
|
+
if execution_context is not None:
|
|
276
|
+
mismatch = execution_context.binding_mismatch_reason()
|
|
277
|
+
if mismatch:
|
|
278
|
+
raise PermissionError(f'Access Denied: {mismatch}')
|
|
279
|
+
principal_id = execution_context.principal_id or execution_context.agent_id
|
|
280
|
+
role = execution_context.role
|
|
281
|
+
context = execution_context.merged_context()
|
|
282
|
+
granted_scopes = execution_context.scopes or []
|
|
283
|
+
resource_value = kwargs.get('resource') or resource or '*'
|
|
284
|
+
else:
|
|
285
|
+
principal_id = kwargs.get('agent_id') or (args[0] if args else None)
|
|
286
|
+
role = kwargs.get('role')
|
|
287
|
+
context = kwargs.get('context', {}) or {}
|
|
288
|
+
granted_scopes = kwargs.get('granted_scopes', []) or []
|
|
289
|
+
resource_value = kwargs.get('resource') or resource or '*'
|
|
278
290
|
|
|
279
291
|
evaluator = PolicyEvaluator()
|
|
280
292
|
decision = evaluator.evaluate(
|
|
281
293
|
PolicyRequest(
|
|
282
|
-
principal_id=
|
|
294
|
+
principal_id=principal_id or 'unknown',
|
|
283
295
|
action=action,
|
|
284
296
|
resource=resource_value,
|
|
285
297
|
granted_scopes=granted_scopes,
|
|
@@ -21,8 +21,15 @@ def fallback_data_dir() -> Path:
|
|
|
21
21
|
return Path.home() / ".agentauth"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def project_dir() -> Path:
|
|
25
|
+
explicit_project_dir = os.getenv("AGENTAUTH_PROJECT_DIR")
|
|
26
|
+
if explicit_project_dir:
|
|
27
|
+
return Path(explicit_project_dir).expanduser().resolve()
|
|
28
|
+
return Path.cwd().resolve()
|
|
29
|
+
|
|
30
|
+
|
|
24
31
|
def project_state_dir() -> Path:
|
|
25
|
-
return
|
|
32
|
+
return project_dir() / ".agentauth"
|
|
26
33
|
|
|
27
34
|
|
|
28
35
|
def local_data_dir() -> Path:
|
|
@@ -30,7 +37,7 @@ def local_data_dir() -> Path:
|
|
|
30
37
|
if explicit_state_dir:
|
|
31
38
|
return Path(explicit_state_dir).expanduser().resolve()
|
|
32
39
|
|
|
33
|
-
cwd =
|
|
40
|
+
cwd = project_dir()
|
|
34
41
|
if cwd.exists() and cwd.is_dir():
|
|
35
42
|
return project_state_dir()
|
|
36
43
|
|
|
@@ -59,6 +66,7 @@ def ensure_local_env(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> dict
|
|
|
59
66
|
env.setdefault("JWT_SECRET_KEY", f"agentauth_local_{secrets.token_urlsafe(24)}")
|
|
60
67
|
env.setdefault("CORS_ORIGINS", "*")
|
|
61
68
|
env.setdefault("AGENTAUTH_STATE_DIR", str(data_dir))
|
|
69
|
+
env.setdefault("AGENTAUTH_PROJECT_DIR", str(project_dir()))
|
|
62
70
|
return env
|
|
63
71
|
|
|
64
72
|
|