153 lines
4.0 KiB
Go
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 ""
|
|
}
|