agentauthlayer 0.1.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.
- agentauthlayer-0.1.0/PKG-INFO +131 -0
- agentauthlayer-0.1.0/README.md +110 -0
- agentauthlayer-0.1.0/agent_auth/__init__.py +48 -0
- agentauthlayer-0.1.0/agent_auth/__main__.py +4 -0
- agentauthlayer-0.1.0/agent_auth/agents.py +40 -0
- agentauthlayer-0.1.0/agent_auth/audit.py +51 -0
- agentauthlayer-0.1.0/agent_auth/auth.py +36 -0
- agentauthlayer-0.1.0/agent_auth/cli.py +28 -0
- agentauthlayer-0.1.0/agent_auth/client.py +107 -0
- agentauthlayer-0.1.0/agent_auth/context.py +21 -0
- agentauthlayer-0.1.0/agent_auth/core.py +638 -0
- agentauthlayer-0.1.0/agent_auth/delegation.py +15 -0
- agentauthlayer-0.1.0/agent_auth/exceptions.py +72 -0
- agentauthlayer-0.1.0/agent_auth/models.py +72 -0
- agentauthlayer-0.1.0/agent_auth/policy.py +296 -0
- agentauthlayer-0.1.0/agent_auth/policy_service.py +176 -0
- agentauthlayer-0.1.0/agent_auth/principals.py +44 -0
- agentauthlayer-0.1.0/agent_auth/registry.py +90 -0
- agentauthlayer-0.1.0/agent_auth/session.py +135 -0
- agentauthlayer-0.1.0/agent_auth/storage.py +536 -0
- agentauthlayer-0.1.0/agent_auth/tokens.py +92 -0
- agentauthlayer-0.1.0/agent_auth/users.py +173 -0
- agentauthlayer-0.1.0/agentauthlayer.egg-info/PKG-INFO +131 -0
- agentauthlayer-0.1.0/agentauthlayer.egg-info/SOURCES.txt +36 -0
- agentauthlayer-0.1.0/agentauthlayer.egg-info/dependency_links.txt +1 -0
- agentauthlayer-0.1.0/agentauthlayer.egg-info/requires.txt +2 -0
- agentauthlayer-0.1.0/agentauthlayer.egg-info/top_level.txt +1 -0
- agentauthlayer-0.1.0/pyproject.toml +39 -0
- agentauthlayer-0.1.0/setup.cfg +4 -0
- agentauthlayer-0.1.0/tests/test_agent_auth_library.py +267 -0
- agentauthlayer-0.1.0/tests/test_auth_flow.py +123 -0
- agentauthlayer-0.1.0/tests/test_core_first_boundary.py +111 -0
- agentauthlayer-0.1.0/tests/test_health.py +16 -0
- agentauthlayer-0.1.0/tests/test_iam_policy.py +34 -0
- agentauthlayer-0.1.0/tests/test_project_flow.py +38 -0
- agentauthlayer-0.1.0/tests/test_sqlite_repos.py +167 -0
- agentauthlayer-0.1.0/tests/test_storage.py +237 -0
- agentauthlayer-0.1.0/tests/test_tool_registry.py +197 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentauthlayer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library-first authentication and authorization SDK for AI agents
|
|
5
|
+
Author: Vaibhav Ahluwalia
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/VaibhavAhluwalia/Agent-Auth-
|
|
8
|
+
Project-URL: Repository, https://github.com/VaibhavAhluwalia/Agent-Auth-
|
|
9
|
+
Keywords: agents,auth,authorization,iam,security,sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: requests>=2.31.0
|
|
20
|
+
Requires-Dist: python-jose>=3.3.0
|
|
21
|
+
|
|
22
|
+
# Agent Auth SDK
|
|
23
|
+
|
|
24
|
+
A library-first authentication and authorization SDK for AI agents.
|
|
25
|
+
|
|
26
|
+
## What this package is
|
|
27
|
+
|
|
28
|
+
`agent-auth-sdk` is the reusable Python SDK layer of the Agent Auth platform. It is designed for developers who want to:
|
|
29
|
+
- register agents
|
|
30
|
+
- issue and verify access with the Agent Auth control plane
|
|
31
|
+
- sync tool definitions
|
|
32
|
+
- evaluate permissions
|
|
33
|
+
- embed policy enforcement into agent runtimes
|
|
34
|
+
|
|
35
|
+
This package is the **developer-facing library**, not the full FastAPI server or React admin UI.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install agent-auth-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For local development:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from agent_auth import AuthAPIClient
|
|
53
|
+
|
|
54
|
+
client = AuthAPIClient(
|
|
55
|
+
base_url="http://127.0.0.1:8002",
|
|
56
|
+
token="YOUR_ADMIN_OR_SERVICE_TOKEN",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
agent = client.create_agent(
|
|
60
|
+
agent_id="research-bot-01",
|
|
61
|
+
name="Research Bot 01",
|
|
62
|
+
owner="vaibhav@company.com",
|
|
63
|
+
role="research_agent",
|
|
64
|
+
scopes=[],
|
|
65
|
+
project_id="ai-platform",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
print(agent)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Environment variables
|
|
72
|
+
|
|
73
|
+
If you prefer, the SDK reads these automatically:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export AGENT_AUTH_URL=http://127.0.0.1:8002
|
|
77
|
+
export AGENT_AUTH_TOKEN=YOUR_ADMIN_OR_SERVICE_TOKEN
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from agent_auth import AuthAPIClient
|
|
84
|
+
|
|
85
|
+
client = AuthAPIClient()
|
|
86
|
+
print(client.health())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Common capabilities
|
|
90
|
+
|
|
91
|
+
### Sync tools from code
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
client.sync_tools([
|
|
95
|
+
{"action": "tool.search_web", "description": "Search the web"},
|
|
96
|
+
{"action": "docs.read", "description": "Read protected docs"},
|
|
97
|
+
])
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Evaluate access
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
decision = client.evaluate(
|
|
104
|
+
principal_id="research-bot-01",
|
|
105
|
+
action="tool.search_web",
|
|
106
|
+
resource="web/*",
|
|
107
|
+
role="research_agent",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
print(decision)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Package scope
|
|
114
|
+
|
|
115
|
+
This package currently exposes the reusable SDK and policy primitives under `agent_auth/`.
|
|
116
|
+
The full control plane server, admin UI, and demos remain in the same repository but are not part of the SDK package build.
|
|
117
|
+
|
|
118
|
+
## Dev-ready validation checklist
|
|
119
|
+
|
|
120
|
+
Before publishing, verify:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e .
|
|
124
|
+
python -c "import agent_auth; print(agent_auth.__all__)"
|
|
125
|
+
python -m build
|
|
126
|
+
pip install dist/*.whl
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Repository
|
|
130
|
+
|
|
131
|
+
- Source: <https://github.com/VaibhavAhluwalia/Agent-Auth->
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Agent Auth SDK
|
|
2
|
+
|
|
3
|
+
A library-first authentication and authorization SDK for AI agents.
|
|
4
|
+
|
|
5
|
+
## What this package is
|
|
6
|
+
|
|
7
|
+
`agent-auth-sdk` is the reusable Python SDK layer of the Agent Auth platform. It is designed for developers who want to:
|
|
8
|
+
- register agents
|
|
9
|
+
- issue and verify access with the Agent Auth control plane
|
|
10
|
+
- sync tool definitions
|
|
11
|
+
- evaluate permissions
|
|
12
|
+
- embed policy enforcement into agent runtimes
|
|
13
|
+
|
|
14
|
+
This package is the **developer-facing library**, not the full FastAPI server or React admin UI.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install agent-auth-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For local development:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from agent_auth import AuthAPIClient
|
|
32
|
+
|
|
33
|
+
client = AuthAPIClient(
|
|
34
|
+
base_url="http://127.0.0.1:8002",
|
|
35
|
+
token="YOUR_ADMIN_OR_SERVICE_TOKEN",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
agent = client.create_agent(
|
|
39
|
+
agent_id="research-bot-01",
|
|
40
|
+
name="Research Bot 01",
|
|
41
|
+
owner="vaibhav@company.com",
|
|
42
|
+
role="research_agent",
|
|
43
|
+
scopes=[],
|
|
44
|
+
project_id="ai-platform",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(agent)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Environment variables
|
|
51
|
+
|
|
52
|
+
If you prefer, the SDK reads these automatically:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export AGENT_AUTH_URL=http://127.0.0.1:8002
|
|
56
|
+
export AGENT_AUTH_TOKEN=YOUR_ADMIN_OR_SERVICE_TOKEN
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from agent_auth import AuthAPIClient
|
|
63
|
+
|
|
64
|
+
client = AuthAPIClient()
|
|
65
|
+
print(client.health())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Common capabilities
|
|
69
|
+
|
|
70
|
+
### Sync tools from code
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
client.sync_tools([
|
|
74
|
+
{"action": "tool.search_web", "description": "Search the web"},
|
|
75
|
+
{"action": "docs.read", "description": "Read protected docs"},
|
|
76
|
+
])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Evaluate access
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
decision = client.evaluate(
|
|
83
|
+
principal_id="research-bot-01",
|
|
84
|
+
action="tool.search_web",
|
|
85
|
+
resource="web/*",
|
|
86
|
+
role="research_agent",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
print(decision)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Package scope
|
|
93
|
+
|
|
94
|
+
This package currently exposes the reusable SDK and policy primitives under `agent_auth/`.
|
|
95
|
+
The full control plane server, admin UI, and demos remain in the same repository but are not part of the SDK package build.
|
|
96
|
+
|
|
97
|
+
## Dev-ready validation checklist
|
|
98
|
+
|
|
99
|
+
Before publishing, verify:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install -e .
|
|
103
|
+
python -c "import agent_auth; print(agent_auth.__all__)"
|
|
104
|
+
python -m build
|
|
105
|
+
pip install dist/*.whl
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Repository
|
|
109
|
+
|
|
110
|
+
- Source: <https://github.com/VaibhavAhluwalia/Agent-Auth->
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""agent_auth — reusable authentication and authorization primitives for Agent Auth."""
|
|
2
|
+
|
|
3
|
+
from agent_auth.auth import principal_fields, principal_from_agent, principal_from_user
|
|
4
|
+
from agent_auth.client import AuthAPIClient
|
|
5
|
+
from agent_auth.context import AuthContext
|
|
6
|
+
from agent_auth.delegation import DelegationGrant
|
|
7
|
+
from agent_auth.exceptions import (
|
|
8
|
+
AgentAuthError,
|
|
9
|
+
AgentNotFoundError,
|
|
10
|
+
AuthServiceError,
|
|
11
|
+
InvalidTokenError,
|
|
12
|
+
PermissionDeniedError,
|
|
13
|
+
RevokedTokenError,
|
|
14
|
+
)
|
|
15
|
+
from agent_auth.models import Agent, TokenClaims, TokenRecord, User, UserClaims
|
|
16
|
+
from agent_auth.policy import PolicyDecision, PolicyEvaluator, PolicyRequest, PolicyStatement, RoleDefinition, require_permission
|
|
17
|
+
from agent_auth.principals import AgentPrincipal, Principal, SystemPrincipal, UserPrincipal, user_scopes_for_role
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Agent",
|
|
21
|
+
"AgentAuthError",
|
|
22
|
+
"AgentNotFoundError",
|
|
23
|
+
"AgentPrincipal",
|
|
24
|
+
"AuthAPIClient",
|
|
25
|
+
"AuthContext",
|
|
26
|
+
"AuthServiceError",
|
|
27
|
+
"DelegationGrant",
|
|
28
|
+
"InvalidTokenError",
|
|
29
|
+
"PermissionDeniedError",
|
|
30
|
+
"PolicyDecision",
|
|
31
|
+
"PolicyEvaluator",
|
|
32
|
+
"PolicyRequest",
|
|
33
|
+
"PolicyStatement",
|
|
34
|
+
"Principal",
|
|
35
|
+
"RevokedTokenError",
|
|
36
|
+
"RoleDefinition",
|
|
37
|
+
"SystemPrincipal",
|
|
38
|
+
"TokenClaims",
|
|
39
|
+
"TokenRecord",
|
|
40
|
+
"User",
|
|
41
|
+
"UserClaims",
|
|
42
|
+
"UserPrincipal",
|
|
43
|
+
"principal_fields",
|
|
44
|
+
"principal_from_agent",
|
|
45
|
+
"principal_from_user",
|
|
46
|
+
"require_permission",
|
|
47
|
+
"user_scopes_for_role",
|
|
48
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, TypeVar
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
AgentT = TypeVar("AgentT")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentStore(Protocol[AgentT]):
|
|
10
|
+
def save(self, agent: AgentT) -> AgentT:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
def get(self, agent_id: str) -> AgentT | None:
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
def list_all(self, project_id: str | None = None) -> list[AgentT]:
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentIdentityService:
|
|
21
|
+
"""Core agent lifecycle orchestration over a configured agent store."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, store: AgentStore[AgentT]):
|
|
24
|
+
self.store = store
|
|
25
|
+
|
|
26
|
+
def save(self, agent: AgentT) -> AgentT:
|
|
27
|
+
return self.store.save(agent)
|
|
28
|
+
|
|
29
|
+
def get(self, agent_id: str) -> AgentT | None:
|
|
30
|
+
return self.store.get(agent_id)
|
|
31
|
+
|
|
32
|
+
def list_agents(self, project_id: str | None = None) -> list[AgentT]:
|
|
33
|
+
return self.store.list_all(project_id=project_id)
|
|
34
|
+
|
|
35
|
+
def update_scopes(self, agent_id: str, scopes: list[str]) -> AgentT | None:
|
|
36
|
+
agent = self.store.get(agent_id)
|
|
37
|
+
if not agent:
|
|
38
|
+
return None
|
|
39
|
+
agent.scopes = scopes
|
|
40
|
+
return self.store.save(agent)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""agent_auth audit logger — pluggable, default prints to stderr."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Any, Protocol
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("agent_auth.audit")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuditBackend(Protocol):
|
|
15
|
+
"""Interface that any custom audit backend must implement."""
|
|
16
|
+
|
|
17
|
+
def log(self, event: dict[str, Any]) -> None: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StderrAuditBackend:
|
|
21
|
+
"""Default backend — writes JSON lines to stderr."""
|
|
22
|
+
|
|
23
|
+
def log(self, event: dict[str, Any]) -> None:
|
|
24
|
+
line = json.dumps(event, default=str)
|
|
25
|
+
print(line, file=sys.stderr)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InMemoryAuditBackend:
|
|
29
|
+
"""Keeps events in a list — useful for testing."""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self.events: list[dict[str, Any]] = []
|
|
33
|
+
|
|
34
|
+
def log(self, event: dict[str, Any]) -> None:
|
|
35
|
+
self.events.append(event)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AuditLogger:
|
|
39
|
+
"""Thin wrapper around a backend that stamps every event."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, backend: AuditBackend | None = None) -> None:
|
|
42
|
+
self._backend: AuditBackend = backend or StderrAuditBackend()
|
|
43
|
+
|
|
44
|
+
def record(self, event_type: str, **details: Any) -> None:
|
|
45
|
+
event = {
|
|
46
|
+
"event": event_type,
|
|
47
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
48
|
+
**details,
|
|
49
|
+
}
|
|
50
|
+
self._backend.log(event)
|
|
51
|
+
logger.debug("audit: %s", event)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from agent_auth.principals import AgentPrincipal, Principal, UserPrincipal, user_scopes_for_role
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def principal_from_agent(*, agent_id: str, role: str | None = None, scopes: list[str] | None = None, owner: str | None = None, status: str | None = None) -> AgentPrincipal:
|
|
7
|
+
return AgentPrincipal(
|
|
8
|
+
principal_id=agent_id,
|
|
9
|
+
principal_type='agent',
|
|
10
|
+
role=role,
|
|
11
|
+
scopes=list(scopes or []),
|
|
12
|
+
owner=owner,
|
|
13
|
+
status=status,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def principal_from_user(*, user_id: str, email: str, role: str, scopes: list[str] | None = None, status: str | None = None) -> UserPrincipal:
|
|
18
|
+
effective_scopes = list(scopes) if scopes is not None else user_scopes_for_role(role)
|
|
19
|
+
return UserPrincipal(
|
|
20
|
+
principal_id=user_id,
|
|
21
|
+
principal_type='user',
|
|
22
|
+
role=role,
|
|
23
|
+
scopes=effective_scopes,
|
|
24
|
+
email=email,
|
|
25
|
+
status=status,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def principal_fields(principal: Principal) -> tuple[str, str, list[str], str | None, str | None]:
|
|
30
|
+
return (
|
|
31
|
+
principal.principal_id,
|
|
32
|
+
principal.principal_type,
|
|
33
|
+
list(principal.scopes),
|
|
34
|
+
principal.role,
|
|
35
|
+
principal.email,
|
|
36
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import uvicorn
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(
|
|
7
|
+
description="Agent Auth Platform CLI - Manage and run the control plane.",
|
|
8
|
+
prog="agent-auth"
|
|
9
|
+
)
|
|
10
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
11
|
+
|
|
12
|
+
# The "ui" command (like "mlflow ui")
|
|
13
|
+
ui_parser = subparsers.add_parser("ui", help="Launch the integrated Auth API and React Dashboard")
|
|
14
|
+
ui_parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)")
|
|
15
|
+
ui_parser.add_argument("--port", type=int, default=8002, help="Port to bind to (default: 8002)")
|
|
16
|
+
ui_parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
|
17
|
+
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
if args.command == "ui":
|
|
21
|
+
print(f"Starting Agent Auth UI & Control Plane on http://{args.host}:{args.port}")
|
|
22
|
+
uvicorn.run("auth_app.main:app", host=args.host, port=args.port, reload=args.reload)
|
|
23
|
+
else:
|
|
24
|
+
parser.print_help()
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from agent_auth.context import AuthContext
|
|
9
|
+
from agent_auth.exceptions import (
|
|
10
|
+
AgentNotFoundError,
|
|
11
|
+
AuthServiceError,
|
|
12
|
+
InvalidTokenError,
|
|
13
|
+
PermissionDeniedError,
|
|
14
|
+
RevokedTokenError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AuthAPIClient:
|
|
19
|
+
"""Thin SDK client for talking to the Agent Auth control plane."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, base_url: str | None = None, token: str | None = None, timeout: int = 30) -> None:
|
|
22
|
+
self.base_url = (base_url or os.getenv("AGENT_AUTH_URL") or "http://127.0.0.1:8002").rstrip("/")
|
|
23
|
+
self.token = token or os.getenv("AGENT_AUTH_TOKEN")
|
|
24
|
+
self.timeout = timeout
|
|
25
|
+
|
|
26
|
+
def _headers(self) -> dict[str, str]:
|
|
27
|
+
headers = {"Content-Type": "application/json"}
|
|
28
|
+
if self.token:
|
|
29
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
30
|
+
return headers
|
|
31
|
+
|
|
32
|
+
def _request(self, method: str, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
33
|
+
response = requests.request(
|
|
34
|
+
method,
|
|
35
|
+
f"{self.base_url}{path}",
|
|
36
|
+
json=json,
|
|
37
|
+
headers=self._headers(),
|
|
38
|
+
timeout=self.timeout,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if response.ok:
|
|
42
|
+
if not response.content:
|
|
43
|
+
return None
|
|
44
|
+
return response.json()
|
|
45
|
+
|
|
46
|
+
detail = None
|
|
47
|
+
try:
|
|
48
|
+
detail = response.json().get("detail")
|
|
49
|
+
except Exception:
|
|
50
|
+
detail = response.text or "Request failed"
|
|
51
|
+
|
|
52
|
+
if response.status_code == 401:
|
|
53
|
+
if detail and ("revoked" in detail.lower() or "inactive" in detail.lower()):
|
|
54
|
+
raise RevokedTokenError(detail)
|
|
55
|
+
raise InvalidTokenError(detail or "Invalid token")
|
|
56
|
+
if response.status_code == 403:
|
|
57
|
+
raise PermissionDeniedError(detail or "Permission denied")
|
|
58
|
+
if response.status_code == 404 and path.startswith("/agents/"):
|
|
59
|
+
raise AgentNotFoundError(detail or "Agent not found")
|
|
60
|
+
raise AuthServiceError(detail or f"Request failed with status {response.status_code}")
|
|
61
|
+
|
|
62
|
+
def health(self) -> dict[str, Any]:
|
|
63
|
+
return self._request("GET", "/health")
|
|
64
|
+
|
|
65
|
+
def create_agent(self, agent_id: str, name: str, owner: str, role: str, scopes: list[str], project_id: str | None = None) -> dict[str, Any]:
|
|
66
|
+
payload = {
|
|
67
|
+
"agent_id": agent_id,
|
|
68
|
+
"name": name,
|
|
69
|
+
"owner": owner,
|
|
70
|
+
"role": role,
|
|
71
|
+
"scopes": scopes,
|
|
72
|
+
}
|
|
73
|
+
if project_id:
|
|
74
|
+
payload["project_id"] = project_id
|
|
75
|
+
return self._request("POST", "/agents", json=payload)
|
|
76
|
+
|
|
77
|
+
def get_agent(self, agent_id: str) -> dict[str, Any]:
|
|
78
|
+
return self._request("GET", f"/agents/{agent_id}")
|
|
79
|
+
|
|
80
|
+
def issue_token(self, agent_id: str) -> dict[str, Any]:
|
|
81
|
+
return self._request("POST", "/auth/token", json={"agent_id": agent_id})
|
|
82
|
+
|
|
83
|
+
def sync_tools(self, permissions: list[dict[str, str]]) -> dict[str, Any]:
|
|
84
|
+
return self._request("POST", "/policy/permissions/sync", json={"permissions": permissions})
|
|
85
|
+
|
|
86
|
+
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]:
|
|
87
|
+
return self._request(
|
|
88
|
+
"POST",
|
|
89
|
+
"/policy/evaluate",
|
|
90
|
+
json={
|
|
91
|
+
"principal_id": principal_id,
|
|
92
|
+
"action": action,
|
|
93
|
+
"resource": resource,
|
|
94
|
+
"granted_scopes": granted_scopes or [],
|
|
95
|
+
"role": role,
|
|
96
|
+
"context": context or {},
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def auth_context(self, principal_id: str, scopes: list[str], role: str | None = None, principal_type: str = "agent", email: str | None = None) -> AuthContext:
|
|
101
|
+
return AuthContext(
|
|
102
|
+
principal_id=principal_id,
|
|
103
|
+
principal_type=principal_type,
|
|
104
|
+
scopes=scopes,
|
|
105
|
+
role=role,
|
|
106
|
+
email=email,
|
|
107
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class AuthContext:
|
|
8
|
+
principal_id: str
|
|
9
|
+
principal_type: str
|
|
10
|
+
jti: str
|
|
11
|
+
scopes: list[str] = field(default_factory=list)
|
|
12
|
+
role: str | None = None
|
|
13
|
+
email: str | None = None
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def subject_id(self) -> str:
|
|
17
|
+
return self.principal_id
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def subject_type(self) -> str:
|
|
21
|
+
return self.principal_type
|