Files
dbis_core/gateway/go/middleware/capability-check.go
2026-03-02 12:14:07 -08:00

153 lines
4.0 KiB
Go

package middleware
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"solacenet-gateway/cache"
"solacenet-gateway/config"
"time"
"github.com/gin-gonic/gin"
)
type PolicyDecisionRequest struct {
TenantID string `json:"tenantId"`
ProgramID string `json:"programId,omitempty"`
CapabilityID string `json:"capabilityId"`
Region string `json:"region,omitempty"`
Channel string `json:"channel,omitempty"`
Actor string `json:"actor,omitempty"`
Context map[string]interface{} `json:"context,omitempty"`
}
type PolicyDecisionResponse struct {
Allowed bool `json:"allowed"`
Mode string `json:"mode"`
Limits map[string]interface{} `json:"limits,omitempty"`
ReasonCode string `json:"reasonCode,omitempty"`
DecisionID string `json:"decisionId"`
}
// CapabilityCheckMiddleware checks if a capability is enabled before routing
func CapabilityCheckMiddleware(cfg *config.Config, cache *cache.Cache) gin.HandlerFunc {
return func(c *gin.Context) {
// Extract capability ID from request path or header
capabilityID := c.GetHeader("X-Capability-ID")
if capabilityID == "" {
// Try to extract from path pattern
// This is a simplified version - adjust based on your routing
capabilityID = extractCapabilityFromPath(c.Request.URL.Path)
}
if capabilityID == "" {
c.Next()
return
}
// Extract context from request
tenantID := c.GetHeader("X-Tenant-ID")
programID := c.GetHeader("X-Program-ID")
region := c.GetHeader("X-Region")
channel := c.GetHeader("X-Channel")
actor := c.GetHeader("X-Actor")
// Check cache first
cacheKey := fmt.Sprintf("policy:decision:%s:%s:%s:%s:%s:%s",
tenantID, programID, capabilityID, region, channel, actor)
if cached, err := cache.Get(cacheKey); err == nil && cached != nil {
var decision PolicyDecisionResponse
if json.Unmarshal(cached, &decision) == nil {
if !decision.Allowed {
c.JSON(http.StatusForbidden, gin.H{
"error": "Capability not available",
"reasonCode": decision.ReasonCode,
"mode": decision.Mode,
})
c.Abort()
return
}
c.Set("policyDecision", decision)
c.Next()
return
}
}
// Call policy engine
decisionReq := PolicyDecisionRequest{
TenantID: tenantID,
ProgramID: programID,
CapabilityID: capabilityID,
Region: region,
Channel: channel,
Actor: actor,
}
decision, err := callPolicyEngine(cfg, decisionReq)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to check capability",
})
c.Abort()
return
}
// Cache the decision
if decisionJSON, err := json.Marshal(decision); err == nil {
cache.Set(cacheKey, decisionJSON, time.Duration(cfg.CacheTTL)*time.Second)
}
if !decision.Allowed {
c.JSON(http.StatusForbidden, gin.H{
"error": "Capability not available",
"reasonCode": decision.ReasonCode,
"mode": decision.Mode,
})
c.Abort()
return
}
c.Set("policyDecision", decision)
c.Next()
}
}
func callPolicyEngine(cfg *config.Config, req PolicyDecisionRequest) (*PolicyDecisionResponse, error) {
reqBody, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(
fmt.Sprintf("%s/api/v1/solacenet/policy/decide", cfg.PolicyEngineURL),
"application/json",
bytes.NewBuffer(reqBody),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var decision PolicyDecisionResponse
if err := json.Unmarshal(body, &decision); err != nil {
return nil, err
}
return &decision, nil
}
func extractCapabilityFromPath(path string) string {
// Simplified extraction - adjust based on your routing patterns
// Example: /api/v1/payments/... -> "payment-gateway"
// This should be configured based on your actual routing
return ""
}