Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/rbac_role_handlers.go
Sharang Parnerkar 13f57c4519 refactor(go): split obligations, portfolio, rbac, whistleblower handlers and stores, roadmap parser
Split 7 files exceeding the 500 LOC hard cap into 16 files, all under 500 LOC.
No exported symbols renamed; zero behavior changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:00:15 +02:00

393 lines
10 KiB
Go

package handlers
import (
"net/http"
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// ============================================================================
// Role Endpoints
// ============================================================================
// ListRoles returns roles for a tenant (including system roles)
func (h *RBACHandlers) ListRoles(c *gin.Context) {
tenantID := rbac.GetTenantID(c)
var tenantIDPtr *uuid.UUID
if tenantID != uuid.Nil {
tenantIDPtr = &tenantID
}
roles, err := h.store.ListRoles(c.Request.Context(), tenantIDPtr)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"roles": roles})
}
// ListSystemRoles returns all system roles
func (h *RBACHandlers) ListSystemRoles(c *gin.Context) {
roles, err := h.store.ListSystemRoles(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"roles": roles})
}
// GetRole returns a role by ID
func (h *RBACHandlers) GetRole(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role ID"})
return
}
role, err := h.store.GetRole(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "role not found"})
return
}
c.JSON(http.StatusOK, role)
}
// CreateRole creates a new role
func (h *RBACHandlers) CreateRole(c *gin.Context) {
var role rbac.Role
if err := c.ShouldBindJSON(&role); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tenantID := rbac.GetTenantID(c)
if tenantID != uuid.Nil {
role.TenantID = &tenantID
}
if err := h.store.CreateRole(c.Request.Context(), &role); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, role)
}
// ============================================================================
// User Role Endpoints
// ============================================================================
// AssignRoleRequest represents a role assignment request
type AssignRoleRequest struct {
UserID string `json:"user_id" binding:"required"`
RoleID string `json:"role_id" binding:"required"`
NamespaceID *string `json:"namespace_id"`
ExpiresAt *string `json:"expires_at"` // RFC3339 format
}
// AssignRole assigns a role to a user
func (h *RBACHandlers) AssignRole(c *gin.Context) {
var req AssignRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID, err := uuid.Parse(req.UserID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})
return
}
roleID, err := uuid.Parse(req.RoleID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role ID"})
return
}
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
return
}
grantorID := rbac.GetUserID(c)
if grantorID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
userRole := &rbac.UserRole{
UserID: userID,
RoleID: roleID,
TenantID: tenantID,
}
if req.NamespaceID != nil {
nsID, err := uuid.Parse(*req.NamespaceID)
if err == nil {
userRole.NamespaceID = &nsID
}
}
if err := h.service.AssignRoleToUser(c.Request.Context(), userRole, grantorID); err != nil {
if err == rbac.ErrPermissionDenied {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "role assigned successfully"})
}
// RevokeRole revokes a role from a user
func (h *RBACHandlers) RevokeRole(c *gin.Context) {
userIDStr := c.Param("userId")
roleIDStr := c.Param("roleId")
userID, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})
return
}
roleID, err := uuid.Parse(roleIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role ID"})
return
}
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
return
}
revokerID := rbac.GetUserID(c)
if revokerID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
var namespaceID *uuid.UUID
if nsIDStr := c.Query("namespace_id"); nsIDStr != "" {
if nsID, err := uuid.Parse(nsIDStr); err == nil {
namespaceID = &nsID
}
}
if err := h.service.RevokeRoleFromUser(c.Request.Context(), userID, roleID, tenantID, namespaceID, revokerID); err != nil {
if err == rbac.ErrPermissionDenied {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "role revoked successfully"})
}
// GetUserRoles returns all roles for a user
func (h *RBACHandlers) GetUserRoles(c *gin.Context) {
userIDStr := c.Param("userId")
userID, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})
return
}
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
return
}
roles, err := h.store.GetUserRoles(c.Request.Context(), userID, tenantID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"roles": roles})
}
// ============================================================================
// Permission Endpoints
// ============================================================================
// GetEffectivePermissions returns effective permissions for the current user
func (h *RBACHandlers) GetEffectivePermissions(c *gin.Context) {
userID := rbac.GetUserID(c)
tenantID := rbac.GetTenantID(c)
namespaceID := rbac.GetNamespaceID(c)
if userID == uuid.Nil || tenantID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
perms, err := h.service.GetEffectivePermissions(c.Request.Context(), userID, tenantID, namespaceID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, perms)
}
// GetUserContext returns complete context for the current user
func (h *RBACHandlers) GetUserContext(c *gin.Context) {
userID := rbac.GetUserID(c)
tenantID := rbac.GetTenantID(c)
if userID == uuid.Nil || tenantID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
ctx, err := h.policyEngine.GetUserContext(c.Request.Context(), userID, tenantID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, ctx)
}
// CheckPermission checks if user has a specific permission
func (h *RBACHandlers) CheckPermission(c *gin.Context) {
permission := c.Query("permission")
if permission == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "permission parameter required"})
return
}
userID := rbac.GetUserID(c)
tenantID := rbac.GetTenantID(c)
namespaceID := rbac.GetNamespaceID(c)
if userID == uuid.Nil || tenantID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
return
}
hasPermission, err := h.service.HasPermission(c.Request.Context(), userID, tenantID, namespaceID, permission)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"permission": permission,
"has_permission": hasPermission,
})
}
// ============================================================================
// LLM Policy Endpoints
// ============================================================================
// ListLLMPolicies returns LLM policies for a tenant
func (h *RBACHandlers) ListLLMPolicies(c *gin.Context) {
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
return
}
policies, err := h.store.ListLLMPolicies(c.Request.Context(), tenantID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"policies": policies})
}
// GetLLMPolicy returns an LLM policy by ID
func (h *RBACHandlers) GetLLMPolicy(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid policy ID"})
return
}
policy, err := h.store.GetLLMPolicy(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "policy not found"})
return
}
c.JSON(http.StatusOK, policy)
}
// CreateLLMPolicy creates a new LLM policy
func (h *RBACHandlers) CreateLLMPolicy(c *gin.Context) {
var policy rbac.LLMPolicy
if err := c.ShouldBindJSON(&policy); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
return
}
policy.TenantID = tenantID
if err := h.store.CreateLLMPolicy(c.Request.Context(), &policy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, policy)
}
// UpdateLLMPolicy updates an LLM policy
func (h *RBACHandlers) UpdateLLMPolicy(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid policy ID"})
return
}
var policy rbac.LLMPolicy
if err := c.ShouldBindJSON(&policy); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
policy.ID = id
if err := h.store.UpdateLLMPolicy(c.Request.Context(), &policy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, policy)
}
// DeleteLLMPolicy deletes an LLM policy
func (h *RBACHandlers) DeleteLLMPolicy(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid policy ID"})
return
}
if err := h.store.DeleteLLMPolicy(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "policy deleted"})
}