This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/extension_permissions.go
Jeffrey Smith 3d4228f868
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-sqlite (push) Successful in 2m46s
CI/CD / test-go-pg (push) Successful in 2m47s
CI/CD / build-and-deploy (push) Successful in 26s
Feat v0.6.3 dead code sweep (#38)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 12:37:47 +00:00

213 lines
5.9 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"switchboard-core/models"
"switchboard-core/store"
)
// ExtPermHandler serves extension permission management endpoints.
type ExtPermHandler struct {
stores store.Stores
}
func NewExtPermHandler(stores store.Stores) *ExtPermHandler {
return &ExtPermHandler{stores: stores}
}
// ListPackagePermissions returns all declared permissions for a package.
// GET /api/v1/admin/extensions/:id/permissions
func (h *ExtPermHandler) ListPackagePermissions(c *gin.Context) {
pkgID := c.Param("id")
perms, err := h.stores.ExtPermissions.ListForPackage(c.Request.Context(), pkgID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list permissions"})
return
}
c.JSON(http.StatusOK, gin.H{"data": perms})
}
// GrantPermission grants a single declared permission for a package.
// POST /api/v1/admin/extensions/:id/permissions/:perm/grant
func (h *ExtPermHandler) GrantPermission(c *gin.Context) {
pkgID := c.Param("id")
perm := c.Param("perm")
userID := c.GetString("user_id")
if !models.ValidExtensionPermissions[perm] {
c.JSON(http.StatusBadRequest, gin.H{"error": "unrecognized permission: " + perm})
return
}
if err := h.stores.ExtPermissions.Grant(c.Request.Context(), pkgID, perm, userID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to grant permission"})
return
}
// Check if all permissions are now granted → activate package
h.maybeActivate(c, pkgID)
c.JSON(http.StatusOK, gin.H{"status": "granted", "package_id": pkgID, "permission": perm})
}
// RevokePermission revokes a single permission for a package.
// POST /api/v1/admin/extensions/:id/permissions/:perm/revoke
func (h *ExtPermHandler) RevokePermission(c *gin.Context) {
pkgID := c.Param("id")
perm := c.Param("perm")
if err := h.stores.ExtPermissions.Revoke(c.Request.Context(), pkgID, perm); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to revoke permission"})
return
}
// Suspend if a previously-active package loses a grant
h.maybeSuspend(c, pkgID)
c.JSON(http.StatusOK, gin.H{"status": "revoked", "package_id": pkgID, "permission": perm})
}
// GrantAllPermissions grants all declared permissions for a package.
// POST /api/v1/admin/extensions/:id/permissions/grant-all
func (h *ExtPermHandler) GrantAllPermissions(c *gin.Context) {
pkgID := c.Param("id")
userID := c.GetString("user_id")
if err := h.stores.ExtPermissions.GrantAll(c.Request.Context(), pkgID, userID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to grant all permissions"})
return
}
// Activate the package now that all permissions are granted
h.maybeActivate(c, pkgID)
c.JSON(http.StatusOK, gin.H{"status": "granted_all", "package_id": pkgID})
}
// ReviewPackage returns the package with its declared permissions.
// GET /api/v1/admin/extensions/:id/review
func (h *ExtPermHandler) ReviewPackage(c *gin.Context) {
pkgID := c.Param("id")
pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
if err != nil || pkg == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "package not found"})
return
}
perms, err := h.stores.ExtPermissions.ListForPackage(c.Request.Context(), pkgID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list permissions"})
return
}
c.JSON(http.StatusOK, gin.H{
"package": pkg,
"permissions": perms,
})
}
// ── Lifecycle helpers ────────────────────────
// maybeActivate transitions a pending_review package to active
// if all declared permissions have been granted.
func (h *ExtPermHandler) maybeActivate(c *gin.Context, pkgID string) {
pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
if err != nil || pkg == nil {
return
}
if pkg.Status != models.PackageStatusPendingReview {
return
}
perms, err := h.stores.ExtPermissions.ListForPackage(c.Request.Context(), pkgID)
if err != nil {
return
}
allGranted := true
for _, p := range perms {
if !p.Granted {
allGranted = false
break
}
}
if allGranted {
_ = h.stores.Packages.SetStatus(c.Request.Context(), pkgID, models.PackageStatusActive)
}
}
// maybeSuspend transitions an active package to suspended
// if a required permission was revoked.
func (h *ExtPermHandler) maybeSuspend(c *gin.Context, pkgID string) {
pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
if err != nil || pkg == nil {
return
}
if pkg.Status != models.PackageStatusActive {
return
}
perms, err := h.stores.ExtPermissions.ListForPackage(c.Request.Context(), pkgID)
if err != nil || len(perms) == 0 {
return
}
for _, p := range perms {
if !p.Granted {
_ = h.stores.Packages.SetStatus(c.Request.Context(), pkgID, models.PackageStatusSuspended)
return
}
}
}
// ── Manifest Permission Parsing ──────────────
// SyncManifestPermissions extracts the "permissions" array from a
// package manifest and declares them in the store. Returns the list
// of declared permissions. If the manifest declares permissions,
// the package status is set to pending_review.
//
// Called by AdminInstallExtension and InstallPackage handlers.
func SyncManifestPermissions(c *gin.Context, stores store.Stores, pkgID string, manifest map[string]any) []string {
if stores.ExtPermissions == nil {
return nil
}
raw, ok := manifest["permissions"]
if !ok {
return nil
}
arr, ok := raw.([]interface{})
if !ok {
return nil
}
var perms []string
for _, v := range arr {
if s, ok := v.(string); ok && models.ValidExtensionPermissions[s] {
perms = append(perms, s)
}
}
if len(perms) == 0 {
return nil
}
if err := stores.ExtPermissions.DeclareForPackage(c.Request.Context(), pkgID, perms); err != nil {
return nil
}
// Package needs review before activation
_ = stores.Packages.SetStatus(c.Request.Context(), pkgID, models.PackageStatusPendingReview)
return perms
}