package handlers import ( "net/http" "github.com/breakpilot/ai-compliance-sdk/internal/rbac" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // RBACHandlers handles RBAC-related API endpoints type RBACHandlers struct { store *rbac.Store service *rbac.Service policyEngine *rbac.PolicyEngine } // NewRBACHandlers creates new RBAC handlers func NewRBACHandlers(store *rbac.Store, service *rbac.Service, policyEngine *rbac.PolicyEngine) *RBACHandlers { return &RBACHandlers{ store: store, service: service, policyEngine: policyEngine, } } // ============================================================================ // Tenant Endpoints // ============================================================================ // ListTenants returns all tenants func (h *RBACHandlers) ListTenants(c *gin.Context) { tenants, err := h.store.ListTenants(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"tenants": tenants}) } // GetTenant returns a tenant by ID func (h *RBACHandlers) GetTenant(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tenant ID"}) return } tenant, err := h.store.GetTenant(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "tenant not found"}) return } c.JSON(http.StatusOK, tenant) } // CreateTenant creates a new tenant func (h *RBACHandlers) CreateTenant(c *gin.Context) { var tenant rbac.Tenant if err := c.ShouldBindJSON(&tenant); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.store.CreateTenant(c.Request.Context(), &tenant); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, tenant) } // UpdateTenant updates a tenant func (h *RBACHandlers) UpdateTenant(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tenant ID"}) return } var tenant rbac.Tenant if err := c.ShouldBindJSON(&tenant); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenant.ID = id if err := h.store.UpdateTenant(c.Request.Context(), &tenant); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, tenant) } // ============================================================================ // Namespace Endpoints // ============================================================================ // ListNamespaces returns namespaces for a tenant func (h *RBACHandlers) ListNamespaces(c *gin.Context) { tenantID, err := uuid.Parse(c.Param("id")) if err != nil { tenantID = rbac.GetTenantID(c) } if tenantID == uuid.Nil { c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"}) return } namespaces, err := h.store.ListNamespaces(c.Request.Context(), tenantID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"namespaces": namespaces}) } // GetNamespace returns a namespace by ID func (h *RBACHandlers) GetNamespace(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid namespace ID"}) return } namespace, err := h.store.GetNamespace(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"}) return } c.JSON(http.StatusOK, namespace) } // CreateNamespace creates a new namespace func (h *RBACHandlers) CreateNamespace(c *gin.Context) { tenantID, err := uuid.Parse(c.Param("id")) if err != nil { tenantID = rbac.GetTenantID(c) } if tenantID == uuid.Nil { c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"}) return } var namespace rbac.Namespace if err := c.ShouldBindJSON(&namespace); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } namespace.TenantID = tenantID if err := h.store.CreateNamespace(c.Request.Context(), &namespace); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, namespace) } // ============================================================================ // 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"}) }