SEC - TASKER: enforce workspace-scoped agent access
This commit is contained in:
@@ -12,7 +12,7 @@ from rest_framework.response import Response
|
||||
from plane.app.permissions import ROLE, allow_permission
|
||||
from plane.app.views.base import BaseAPIView
|
||||
from plane.authentication.nodedc_workspace_policy import get_nodedc_workspace_creation_policy
|
||||
from plane.db.models import Project, Workspace
|
||||
from plane.db.models import Project, ProjectMember, Workspace, WorkspaceMember
|
||||
|
||||
|
||||
def get_gateway_config():
|
||||
@@ -80,16 +80,35 @@ def require_workspace(slug):
|
||||
)
|
||||
|
||||
|
||||
def validate_project_in_workspace(workspace, project_id):
|
||||
def is_workspace_admin(user, workspace):
|
||||
return WorkspaceMember.objects.filter(
|
||||
workspace=workspace,
|
||||
member=user,
|
||||
role=ROLE.ADMIN.value,
|
||||
is_active=True,
|
||||
).exists()
|
||||
|
||||
|
||||
def validate_project_in_workspace(workspace, project_id, user):
|
||||
workspace_admin = is_workspace_admin(user, workspace)
|
||||
if not project_id:
|
||||
if not workspace_admin:
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": "project_required",
|
||||
"message": "Workspace members must select a concrete project for Codex Agent grants.",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
exists = Project.objects.filter(id=project_id, workspace=workspace, archived_at__isnull=True).exists()
|
||||
project = Project.objects.filter(id=project_id, workspace=workspace, archived_at__isnull=True).first()
|
||||
except ValidationError:
|
||||
exists = False
|
||||
project = None
|
||||
|
||||
if not exists:
|
||||
if project is None:
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
@@ -98,6 +117,25 @@ def validate_project_in_workspace(workspace, project_id):
|
||||
},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if workspace_admin:
|
||||
return None
|
||||
|
||||
if not ProjectMember.objects.filter(
|
||||
project=project,
|
||||
member=user,
|
||||
role__gte=ROLE.MEMBER.value,
|
||||
is_active=True,
|
||||
).exists():
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": "project_access_denied",
|
||||
"message": "Codex Agent grants are limited to projects where this user is an active project member.",
|
||||
},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -147,7 +185,7 @@ class CodexAgentEntitledEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class CodexAgentListEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -155,7 +193,7 @@ class CodexAgentListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
return gateway_request("GET", f"/api/internal/v1/owners/{owner_path(request.user)}/agents")
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -177,7 +215,7 @@ class CodexAgentListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentDetailEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -185,7 +223,7 @@ class CodexAgentDetailEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
return gateway_request("GET", f"/api/internal/v1/owners/{owner_path(request.user)}/agents/{agent_path(agent_id)}")
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def patch(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -200,7 +238,7 @@ class CodexAgentDetailEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentRevokeEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -214,7 +252,7 @@ class CodexAgentRevokeEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentGrantListEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -222,7 +260,7 @@ class CodexAgentGrantListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
return gateway_request("GET", f"/api/internal/v1/owners/{owner_path(request.user)}/agents/{agent_path(agent_id)}/grants")
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -233,7 +271,7 @@ class CodexAgentGrantListEndpoint(CodexAgentEntitledEndpoint):
|
||||
return workspace_error
|
||||
|
||||
project_id = request.data.get("project_id")
|
||||
project_error = validate_project_in_workspace(workspace, project_id)
|
||||
project_error = validate_project_in_workspace(workspace, project_id, request.user)
|
||||
if project_error is not None:
|
||||
return project_error
|
||||
|
||||
@@ -247,7 +285,7 @@ class CodexAgentGrantListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentTokenListEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -255,7 +293,7 @@ class CodexAgentTokenListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
return gateway_request("GET", f"/api/internal/v1/owners/{owner_path(request.user)}/agents/{agent_path(agent_id)}/tokens")
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -269,7 +307,7 @@ class CodexAgentTokenListEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentTokenRevokeEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug, agent_id, token_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
@@ -283,7 +321,7 @@ class CodexAgentTokenRevokeEndpoint(CodexAgentEntitledEndpoint):
|
||||
|
||||
|
||||
class CodexAgentSetupEndpoint(CodexAgentEntitledEndpoint):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, agent_id):
|
||||
entitlement_error = self.require_entitlement(request, slug)
|
||||
if entitlement_error is not None:
|
||||
|
||||
Reference in New Issue
Block a user