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 "" }