Skip to content

Commit a1b27a3

Browse files
committed
Add permissions for agents
1 parent 62ef560 commit a1b27a3

File tree

7 files changed

+909
-1
lines changed

7 files changed

+909
-1
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Django AI Core provides core functionality for interacting with LLMs in your Dja
1010
- Logging for LLM requests
1111
- Async/streaming support
1212
- Agents:
13-
- Auth wrappers
1413
- Rate limiting
1514
- Streaming responses
1615
- Index:

docs/modules/agents/permissions.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Agent Permissions
2+
3+
The Agent Permissions system allows you to control who can execute specific agents in your application. You can use the built-in permissions or create your own custom permission logic.
4+
5+
## Overview
6+
7+
By default, agents are accessible to all users. To restrict access, you can add a `permission` to your agent class.
8+
9+
This permission will be checked before the agent can be accessed through the automatically generated view. The permission is not checked when executing `execute` on the agent manually in code.
10+
11+
## Built-in Permissions
12+
13+
### AllowAny
14+
15+
Allows all requests (this is the default behavior if no permission is specified).
16+
17+
```python
18+
from django_ai_core.contrib.agents import Agent
19+
from django_ai_core.contrib.agents.permissions import AllowAny
20+
21+
class MyAgent(Agent):
22+
slug = "my-agent"
23+
permission = AllowAny()
24+
25+
def execute(self):
26+
return "Hello!"
27+
```
28+
29+
### IsAuthenticated
30+
31+
Requires the user to be authenticated.
32+
33+
```python
34+
from django_ai_core.contrib.agents import Agent
35+
from django_ai_core.contrib.agents.permissions import IsAuthenticated
36+
37+
class SecureAgent(Agent):
38+
slug = "secure-agent"
39+
description = "Agent that requires authentication"
40+
permission = IsAuthenticated()
41+
parameters = []
42+
43+
def execute(self):
44+
return "You are authenticated!"
45+
```
46+
47+
### DjangoPermission
48+
49+
Uses Django's built-in permission system to check for a specific permission. The permission should be in the format `'app_label.permission_codename'`.
50+
51+
```python
52+
from django_ai_core.contrib.agents import Agent
53+
from django_ai_core.contrib.agents.permissions import DjangoPermission
54+
55+
class AdminAgent(Agent):
56+
slug = "admin-agent"
57+
description = "Agent requiring admin permission"
58+
permission = DjangoPermission("myapp.can_use_admin_agent")
59+
parameters = []
60+
61+
def execute(self):
62+
return "Admin access granted!"
63+
```
64+
65+
### CompositePermission
66+
67+
Combines multiple permissions with AND or OR logic.
68+
69+
```python
70+
from django_ai_core.contrib.agents import Agent
71+
from django_ai_core.contrib.agents.permissions import (
72+
CompositePermission,
73+
IsAuthenticated,
74+
DjangoPermission,
75+
)
76+
77+
class SuperSecureAgent(Agent):
78+
slug = "super-secure-agent"
79+
description = "Agent with multiple permission requirements"
80+
permission = CompositePermission(
81+
[
82+
IsAuthenticated(),
83+
DjangoPermission("myapp.can_use_secure_features")
84+
],
85+
require_all=True # All permissions must pass (logical AND)
86+
)
87+
parameters = []
88+
89+
def execute(self):
90+
return "Super secure data!"
91+
92+
class FlexibleAgent(Agent):
93+
slug = "flexible-agent"
94+
description = "Agent with flexible permission requirements"
95+
permission = CompositePermission(
96+
[
97+
DjangoPermission("myapp.admin_access"),
98+
DjangoPermission("myapp.power_user")
99+
],
100+
require_all=False # Any permission can pass (logical OR)
101+
)
102+
parameters = []
103+
104+
def execute(self):
105+
return "Flexible access!"
106+
```
107+
108+
## Creating Custom Permissions
109+
110+
You can create your own permissions by subclassing `BasePermission`:
111+
112+
```python
113+
from django_ai_core.contrib.agents import Agent
114+
from django_ai_core.contrib.agents.permissions import BasePermission
115+
116+
class IPAllowlist(BasePermission):
117+
"""Only allow requests from specific IP addresses."""
118+
119+
def __init__(self, allowed_ips):
120+
self.allowed_ips = allowed_ips
121+
122+
def has_permission(self, request, agent_slug, **kwargs):
123+
client_ip = request.META.get('REMOTE_ADDR')
124+
return client_ip in self.allowed_ips
125+
126+
def get_permission_denied_message(self, request, agent_slug):
127+
return f"Your IP address is not authorized to execute '{agent_slug}'"
128+
129+
class RestrictedAgent(Agent):
130+
slug = "restricted-agent"
131+
description = "Agent only accessible from specific IPs"
132+
permission = IPAllowlist(['192.168.1.100', '10.0.0.5'])
133+
parameters = []
134+
135+
def execute(self):
136+
return "Access granted from allowlisted IP!"
137+
```
138+
139+
## Permission Denied Responses
140+
141+
When a permission check fails, the agent execution view returns a 403 Forbidden response:
142+
143+
```json
144+
{
145+
"error": "You do not have permission to execute agent 'my-agent'",
146+
"code": "permission_denied"
147+
}
148+
```
149+
150+
You can customize this message by overriding the `get_permission_denied_message()` method in your permission class.

src/django_ai_core/contrib/agents/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from django.core.validators import validate_slug
77
from django.core.exceptions import ValidationError
88

9+
from .permissions import BasePermission
10+
911

1012
@dataclass
1113
class AgentParameter:
1214
name: str
1315
type: type
1416
description: str
17+
permission = BasePermission | None
1518

1619
def as_dict(self):
1720
return {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from abc import ABC, abstractmethod
2+
3+
from django.contrib.auth.models import AnonymousUser
4+
from django.http import HttpRequest
5+
6+
7+
class BasePermission(ABC):
8+
"""
9+
Base class for implementing custom permission checks on agents.
10+
11+
Subclass this to create your own permission logic for controlling
12+
who can execute agents in your application.
13+
"""
14+
15+
@abstractmethod
16+
def has_permission(self, request: HttpRequest, agent_slug: str, **kwargs) -> bool:
17+
"""
18+
Check if the request has permission to execute the agent.
19+
20+
Args:
21+
request: The Django HTTP request object
22+
agent_slug: The slug of the agent being executed
23+
**kwargs: Additional context that may be useful for permission checks
24+
25+
Returns:
26+
bool: True if permission is granted, False otherwise
27+
"""
28+
pass
29+
30+
def get_permission_denied_message(
31+
self, request: HttpRequest, agent_slug: str
32+
) -> str:
33+
"""
34+
Return a custom message when permission is denied.
35+
36+
Override this method to provide specific error messages.
37+
38+
Args:
39+
request: The Django HTTP request object
40+
agent_slug: The slug of the agent being executed
41+
42+
Returns:
43+
str: The permission denied message
44+
"""
45+
return f"You do not have permission to execute agent '{agent_slug}'"
46+
47+
48+
class AllowAny(BasePermission):
49+
"""
50+
Permission that allows all requests.
51+
52+
This is the default behavior if no permission is specified.
53+
"""
54+
55+
def has_permission(self, request: HttpRequest, agent_slug: str, **kwargs) -> bool:
56+
return True
57+
58+
59+
class IsAuthenticated(BasePermission):
60+
"""
61+
Permission that requires the user to be authenticated.
62+
"""
63+
64+
def has_permission(self, request: HttpRequest, agent_slug: str, **kwargs) -> bool:
65+
return bool(request.user and request.user.is_authenticated)
66+
67+
def get_permission_denied_message(
68+
self, request: HttpRequest, agent_slug: str
69+
) -> str:
70+
return "Authentication required to execute this agent"
71+
72+
73+
class DjangoPermission(BasePermission):
74+
"""
75+
Permission that uses Django's built-in permission system.
76+
77+
This checks for a specific Django permission before allowing agent execution.
78+
The permission should be in the format 'app_label.permission_codename'.
79+
80+
Args:
81+
permission: The Django permission string (e.g., 'myapp.can_use_agent')
82+
"""
83+
84+
def __init__(self, permission: str):
85+
if "." not in permission:
86+
raise ValueError(
87+
"Permission must be in format 'app_label.permission_codename', "
88+
f"got '{permission}'"
89+
)
90+
self.permission = permission
91+
92+
def has_permission(self, request: HttpRequest, agent_slug: str, **kwargs) -> bool:
93+
if not request.user or isinstance(request.user, AnonymousUser):
94+
return False
95+
return request.user.has_perm(self.permission)
96+
97+
def get_permission_denied_message(
98+
self, request: HttpRequest, agent_slug: str
99+
) -> str:
100+
return f"You do not have the required permission '{self.permission}' to execute agent '{agent_slug}'"
101+
102+
103+
class CompositePermission(BasePermission):
104+
"""
105+
Permission that combines multiple permissions
106+
107+
Args:
108+
permissions: List of permissions
109+
require_all: If True, all permissions must pass. If False, any permission can pass.
110+
"""
111+
112+
def __init__(self, permissions: list[BasePermission], require_all: bool = True):
113+
if not permissions:
114+
raise ValueError("At least one permission must be provided")
115+
self.permissions = permissions
116+
self.require_all = require_all
117+
118+
def has_permission(self, request: HttpRequest, agent_slug: str, **kwargs) -> bool:
119+
if self.require_all:
120+
return all(
121+
permission.has_permission(request, agent_slug, **kwargs)
122+
for permission in self.permissions
123+
)
124+
else:
125+
return any(
126+
permission.has_permission(request, agent_slug, **kwargs)
127+
for permission in self.permissions
128+
)
129+
130+
def get_permission_denied_message(
131+
self, request: HttpRequest, agent_slug: str
132+
) -> str:
133+
for permission in self.permissions:
134+
if not permission.has_permission(request, agent_slug):
135+
return permission.get_permission_denied_message(request, agent_slug)
136+
return f"You do not have permission to execute agent '{agent_slug}'"

src/django_ai_core/contrib/agents/views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.utils.decorators import method_decorator
88

99
from . import registry, Agent
10+
from .permissions import AllowAny
1011

1112

1213
class AgentExecutionException(Exception):
@@ -73,6 +74,18 @@ def post(self, request):
7374
status=404,
7475
)
7576

77+
permission = getattr(agent, "permission", None) or AllowAny()
78+
if not permission.has_permission(request, self.agent_slug):
79+
return JsonResponse(
80+
{
81+
"error": permission.get_permission_denied_message(
82+
request, self.agent_slug
83+
),
84+
"code": "permission_denied",
85+
},
86+
status=403,
87+
)
88+
7689
result = self._execute_agent(agent, data["arguments"])
7790

7891
return JsonResponse({"status": "completed", "data": result})

0 commit comments

Comments
 (0)