All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
213 lines
5.9 KiB
Go
213 lines
5.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"armature/models"
|
|
"armature/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
|
|
}
|