You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1142 lines
						
					
					
						
							32 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							1142 lines
						
					
					
						
							32 KiB
						
					
					
				
								package policy
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"fmt"
							 | 
						|
									"net"
							 | 
						|
									"path/filepath"
							 | 
						|
									"regexp"
							 | 
						|
									"strconv"
							 | 
						|
									"strings"
							 | 
						|
									"sync"
							 | 
						|
									"time"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// Effect represents the policy evaluation result
							 | 
						|
								type Effect string
							 | 
						|
								
							 | 
						|
								const (
							 | 
						|
									EffectAllow Effect = "Allow"
							 | 
						|
									EffectDeny  Effect = "Deny"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// Package-level regex cache for performance optimization
							 | 
						|
								var (
							 | 
						|
									regexCache   = make(map[string]*regexp.Regexp)
							 | 
						|
									regexCacheMu sync.RWMutex
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// PolicyEngine evaluates policies against requests
							 | 
						|
								type PolicyEngine struct {
							 | 
						|
									config      *PolicyEngineConfig
							 | 
						|
									initialized bool
							 | 
						|
									store       PolicyStore
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// PolicyEngineConfig holds policy engine configuration
							 | 
						|
								type PolicyEngineConfig struct {
							 | 
						|
									// DefaultEffect when no policies match (Allow or Deny)
							 | 
						|
									DefaultEffect string `json:"defaultEffect"`
							 | 
						|
								
							 | 
						|
									// StoreType specifies the policy store backend (memory, filer, etc.)
							 | 
						|
									StoreType string `json:"storeType"`
							 | 
						|
								
							 | 
						|
									// StoreConfig contains store-specific configuration
							 | 
						|
									StoreConfig map[string]interface{} `json:"storeConfig,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// PolicyDocument represents an IAM policy document
							 | 
						|
								type PolicyDocument struct {
							 | 
						|
									// Version of the policy language (e.g., "2012-10-17")
							 | 
						|
									Version string `json:"Version"`
							 | 
						|
								
							 | 
						|
									// Id is an optional policy identifier
							 | 
						|
									Id string `json:"Id,omitempty"`
							 | 
						|
								
							 | 
						|
									// Statement contains the policy statements
							 | 
						|
									Statement []Statement `json:"Statement"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Statement represents a single policy statement
							 | 
						|
								type Statement struct {
							 | 
						|
									// Sid is an optional statement identifier
							 | 
						|
									Sid string `json:"Sid,omitempty"`
							 | 
						|
								
							 | 
						|
									// Effect specifies whether to Allow or Deny
							 | 
						|
									Effect string `json:"Effect"`
							 | 
						|
								
							 | 
						|
									// Principal specifies who the statement applies to (optional in role policies)
							 | 
						|
									Principal interface{} `json:"Principal,omitempty"`
							 | 
						|
								
							 | 
						|
									// NotPrincipal specifies who the statement does NOT apply to
							 | 
						|
									NotPrincipal interface{} `json:"NotPrincipal,omitempty"`
							 | 
						|
								
							 | 
						|
									// Action specifies the actions this statement applies to
							 | 
						|
									Action []string `json:"Action"`
							 | 
						|
								
							 | 
						|
									// NotAction specifies actions this statement does NOT apply to
							 | 
						|
									NotAction []string `json:"NotAction,omitempty"`
							 | 
						|
								
							 | 
						|
									// Resource specifies the resources this statement applies to
							 | 
						|
									Resource []string `json:"Resource"`
							 | 
						|
								
							 | 
						|
									// NotResource specifies resources this statement does NOT apply to
							 | 
						|
									NotResource []string `json:"NotResource,omitempty"`
							 | 
						|
								
							 | 
						|
									// Condition specifies conditions for when this statement applies
							 | 
						|
									Condition map[string]map[string]interface{} `json:"Condition,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// EvaluationContext provides context for policy evaluation
							 | 
						|
								type EvaluationContext struct {
							 | 
						|
									// Principal making the request (e.g., "user:alice", "role:admin")
							 | 
						|
									Principal string `json:"principal"`
							 | 
						|
								
							 | 
						|
									// Action being requested (e.g., "s3:GetObject")
							 | 
						|
									Action string `json:"action"`
							 | 
						|
								
							 | 
						|
									// Resource being accessed (e.g., "arn:seaweed:s3:::bucket/key")
							 | 
						|
									Resource string `json:"resource"`
							 | 
						|
								
							 | 
						|
									// RequestContext contains additional request information
							 | 
						|
									RequestContext map[string]interface{} `json:"requestContext,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// EvaluationResult contains the result of policy evaluation
							 | 
						|
								type EvaluationResult struct {
							 | 
						|
									// Effect is the final decision (Allow or Deny)
							 | 
						|
									Effect Effect `json:"effect"`
							 | 
						|
								
							 | 
						|
									// MatchingStatements contains statements that matched the request
							 | 
						|
									MatchingStatements []StatementMatch `json:"matchingStatements,omitempty"`
							 | 
						|
								
							 | 
						|
									// EvaluationDetails provides detailed evaluation information
							 | 
						|
									EvaluationDetails *EvaluationDetails `json:"evaluationDetails,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// StatementMatch represents a statement that matched during evaluation
							 | 
						|
								type StatementMatch struct {
							 | 
						|
									// PolicyName is the name of the policy containing this statement
							 | 
						|
									PolicyName string `json:"policyName"`
							 | 
						|
								
							 | 
						|
									// StatementSid is the statement identifier
							 | 
						|
									StatementSid string `json:"statementSid,omitempty"`
							 | 
						|
								
							 | 
						|
									// Effect is the effect of this statement
							 | 
						|
									Effect Effect `json:"effect"`
							 | 
						|
								
							 | 
						|
									// Reason explains why this statement matched
							 | 
						|
									Reason string `json:"reason,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// EvaluationDetails provides detailed information about policy evaluation
							 | 
						|
								type EvaluationDetails struct {
							 | 
						|
									// Principal that was evaluated
							 | 
						|
									Principal string `json:"principal"`
							 | 
						|
								
							 | 
						|
									// Action that was evaluated
							 | 
						|
									Action string `json:"action"`
							 | 
						|
								
							 | 
						|
									// Resource that was evaluated
							 | 
						|
									Resource string `json:"resource"`
							 | 
						|
								
							 | 
						|
									// PoliciesEvaluated lists all policies that were evaluated
							 | 
						|
									PoliciesEvaluated []string `json:"policiesEvaluated"`
							 | 
						|
								
							 | 
						|
									// ConditionsEvaluated lists all conditions that were evaluated
							 | 
						|
									ConditionsEvaluated []string `json:"conditionsEvaluated,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// PolicyStore defines the interface for storing and retrieving policies
							 | 
						|
								type PolicyStore interface {
							 | 
						|
									// StorePolicy stores a policy document (filerAddress ignored for memory stores)
							 | 
						|
									StorePolicy(ctx context.Context, filerAddress string, name string, policy *PolicyDocument) error
							 | 
						|
								
							 | 
						|
									// GetPolicy retrieves a policy document (filerAddress ignored for memory stores)
							 | 
						|
									GetPolicy(ctx context.Context, filerAddress string, name string) (*PolicyDocument, error)
							 | 
						|
								
							 | 
						|
									// DeletePolicy deletes a policy document (filerAddress ignored for memory stores)
							 | 
						|
									DeletePolicy(ctx context.Context, filerAddress string, name string) error
							 | 
						|
								
							 | 
						|
									// ListPolicies lists all policy names (filerAddress ignored for memory stores)
							 | 
						|
									ListPolicies(ctx context.Context, filerAddress string) ([]string, error)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewPolicyEngine creates a new policy engine
							 | 
						|
								func NewPolicyEngine() *PolicyEngine {
							 | 
						|
									return &PolicyEngine{}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Initialize initializes the policy engine with configuration
							 | 
						|
								func (e *PolicyEngine) Initialize(config *PolicyEngineConfig) error {
							 | 
						|
									if config == nil {
							 | 
						|
										return fmt.Errorf("config cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := e.validateConfig(config); err != nil {
							 | 
						|
										return fmt.Errorf("invalid configuration: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									e.config = config
							 | 
						|
								
							 | 
						|
									// Initialize policy store
							 | 
						|
									store, err := e.createPolicyStore(config)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("failed to create policy store: %w", err)
							 | 
						|
									}
							 | 
						|
									e.store = store
							 | 
						|
								
							 | 
						|
									e.initialized = true
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// InitializeWithProvider initializes the policy engine with configuration and a filer address provider
							 | 
						|
								func (e *PolicyEngine) InitializeWithProvider(config *PolicyEngineConfig, filerAddressProvider func() string) error {
							 | 
						|
									if config == nil {
							 | 
						|
										return fmt.Errorf("config cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := e.validateConfig(config); err != nil {
							 | 
						|
										return fmt.Errorf("invalid configuration: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									e.config = config
							 | 
						|
								
							 | 
						|
									// Initialize policy store with provider
							 | 
						|
									store, err := e.createPolicyStoreWithProvider(config, filerAddressProvider)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("failed to create policy store: %w", err)
							 | 
						|
									}
							 | 
						|
									e.store = store
							 | 
						|
								
							 | 
						|
									e.initialized = true
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateConfig validates the policy engine configuration
							 | 
						|
								func (e *PolicyEngine) validateConfig(config *PolicyEngineConfig) error {
							 | 
						|
									if config.DefaultEffect != "Allow" && config.DefaultEffect != "Deny" {
							 | 
						|
										return fmt.Errorf("invalid default effect: %s", config.DefaultEffect)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config.StoreType == "" {
							 | 
						|
										config.StoreType = "filer" // Default to filer store for persistence
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// createPolicyStore creates a policy store based on configuration
							 | 
						|
								func (e *PolicyEngine) createPolicyStore(config *PolicyEngineConfig) (PolicyStore, error) {
							 | 
						|
									switch config.StoreType {
							 | 
						|
									case "memory":
							 | 
						|
										return NewMemoryPolicyStore(), nil
							 | 
						|
									case "", "filer":
							 | 
						|
										// Check if caching is explicitly disabled
							 | 
						|
										if config.StoreConfig != nil {
							 | 
						|
											if noCache, ok := config.StoreConfig["noCache"].(bool); ok && noCache {
							 | 
						|
												return NewFilerPolicyStore(config.StoreConfig, nil)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										// Default to generic cached filer store for better performance
							 | 
						|
										return NewGenericCachedPolicyStore(config.StoreConfig, nil)
							 | 
						|
									case "cached-filer", "generic-cached":
							 | 
						|
										return NewGenericCachedPolicyStore(config.StoreConfig, nil)
							 | 
						|
									default:
							 | 
						|
										return nil, fmt.Errorf("unsupported store type: %s", config.StoreType)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// createPolicyStoreWithProvider creates a policy store with a filer address provider function
							 | 
						|
								func (e *PolicyEngine) createPolicyStoreWithProvider(config *PolicyEngineConfig, filerAddressProvider func() string) (PolicyStore, error) {
							 | 
						|
									switch config.StoreType {
							 | 
						|
									case "memory":
							 | 
						|
										return NewMemoryPolicyStore(), nil
							 | 
						|
									case "", "filer":
							 | 
						|
										// Check if caching is explicitly disabled
							 | 
						|
										if config.StoreConfig != nil {
							 | 
						|
											if noCache, ok := config.StoreConfig["noCache"].(bool); ok && noCache {
							 | 
						|
												return NewFilerPolicyStore(config.StoreConfig, filerAddressProvider)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										// Default to generic cached filer store for better performance
							 | 
						|
										return NewGenericCachedPolicyStore(config.StoreConfig, filerAddressProvider)
							 | 
						|
									case "cached-filer", "generic-cached":
							 | 
						|
										return NewGenericCachedPolicyStore(config.StoreConfig, filerAddressProvider)
							 | 
						|
									default:
							 | 
						|
										return nil, fmt.Errorf("unsupported store type: %s", config.StoreType)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// IsInitialized returns whether the engine is initialized
							 | 
						|
								func (e *PolicyEngine) IsInitialized() bool {
							 | 
						|
									return e.initialized
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AddPolicy adds a policy to the engine (filerAddress ignored for memory stores)
							 | 
						|
								func (e *PolicyEngine) AddPolicy(filerAddress string, name string, policy *PolicyDocument) error {
							 | 
						|
									if !e.initialized {
							 | 
						|
										return fmt.Errorf("policy engine not initialized")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if name == "" {
							 | 
						|
										return fmt.Errorf("policy name cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if policy == nil {
							 | 
						|
										return fmt.Errorf("policy cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := ValidatePolicyDocument(policy); err != nil {
							 | 
						|
										return fmt.Errorf("invalid policy document: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return e.store.StorePolicy(context.Background(), filerAddress, name, policy)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Evaluate evaluates policies against a request context (filerAddress ignored for memory stores)
							 | 
						|
								func (e *PolicyEngine) Evaluate(ctx context.Context, filerAddress string, evalCtx *EvaluationContext, policyNames []string) (*EvaluationResult, error) {
							 | 
						|
									if !e.initialized {
							 | 
						|
										return nil, fmt.Errorf("policy engine not initialized")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if evalCtx == nil {
							 | 
						|
										return nil, fmt.Errorf("evaluation context cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									result := &EvaluationResult{
							 | 
						|
										Effect: Effect(e.config.DefaultEffect),
							 | 
						|
										EvaluationDetails: &EvaluationDetails{
							 | 
						|
											Principal:         evalCtx.Principal,
							 | 
						|
											Action:            evalCtx.Action,
							 | 
						|
											Resource:          evalCtx.Resource,
							 | 
						|
											PoliciesEvaluated: policyNames,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var matchingStatements []StatementMatch
							 | 
						|
									explicitDeny := false
							 | 
						|
									hasAllow := false
							 | 
						|
								
							 | 
						|
									// Evaluate each policy
							 | 
						|
									for _, policyName := range policyNames {
							 | 
						|
										policy, err := e.store.GetPolicy(ctx, filerAddress, policyName)
							 | 
						|
										if err != nil {
							 | 
						|
											continue // Skip policies that can't be loaded
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Evaluate each statement in the policy
							 | 
						|
										for _, statement := range policy.Statement {
							 | 
						|
											if e.statementMatches(&statement, evalCtx) {
							 | 
						|
												match := StatementMatch{
							 | 
						|
													PolicyName:   policyName,
							 | 
						|
													StatementSid: statement.Sid,
							 | 
						|
													Effect:       Effect(statement.Effect),
							 | 
						|
													Reason:       "Action, Resource, and Condition matched",
							 | 
						|
												}
							 | 
						|
												matchingStatements = append(matchingStatements, match)
							 | 
						|
								
							 | 
						|
												if statement.Effect == "Deny" {
							 | 
						|
													explicitDeny = true
							 | 
						|
												} else if statement.Effect == "Allow" {
							 | 
						|
													hasAllow = true
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									result.MatchingStatements = matchingStatements
							 | 
						|
								
							 | 
						|
									// AWS IAM evaluation logic:
							 | 
						|
									// 1. If there's an explicit Deny, the result is Deny
							 | 
						|
									// 2. If there's an Allow and no Deny, the result is Allow
							 | 
						|
									// 3. Otherwise, use the default effect
							 | 
						|
									if explicitDeny {
							 | 
						|
										result.Effect = EffectDeny
							 | 
						|
									} else if hasAllow {
							 | 
						|
										result.Effect = EffectAllow
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return result, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// statementMatches checks if a statement matches the evaluation context
							 | 
						|
								func (e *PolicyEngine) statementMatches(statement *Statement, evalCtx *EvaluationContext) bool {
							 | 
						|
									// Check action match
							 | 
						|
									if !e.matchesActions(statement.Action, evalCtx.Action, evalCtx) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check resource match
							 | 
						|
									if !e.matchesResources(statement.Resource, evalCtx.Resource, evalCtx) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check conditions
							 | 
						|
									if !e.matchesConditions(statement.Condition, evalCtx) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchesActions checks if any action in the list matches the requested action
							 | 
						|
								func (e *PolicyEngine) matchesActions(actions []string, requestedAction string, evalCtx *EvaluationContext) bool {
							 | 
						|
									for _, action := range actions {
							 | 
						|
										if awsIAMMatch(action, requestedAction, evalCtx) {
							 | 
						|
											return true
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return false
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchesResources checks if any resource in the list matches the requested resource
							 | 
						|
								func (e *PolicyEngine) matchesResources(resources []string, requestedResource string, evalCtx *EvaluationContext) bool {
							 | 
						|
									for _, resource := range resources {
							 | 
						|
										if awsIAMMatch(resource, requestedResource, evalCtx) {
							 | 
						|
											return true
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return false
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchesConditions checks if all conditions are satisfied
							 | 
						|
								func (e *PolicyEngine) matchesConditions(conditions map[string]map[string]interface{}, evalCtx *EvaluationContext) bool {
							 | 
						|
									if len(conditions) == 0 {
							 | 
						|
										return true // No conditions means always match
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for conditionType, conditionBlock := range conditions {
							 | 
						|
										if !e.evaluateConditionBlock(conditionType, conditionBlock, evalCtx) {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateConditionBlock evaluates a single condition block
							 | 
						|
								func (e *PolicyEngine) evaluateConditionBlock(conditionType string, block map[string]interface{}, evalCtx *EvaluationContext) bool {
							 | 
						|
									switch conditionType {
							 | 
						|
									// IP Address conditions
							 | 
						|
									case "IpAddress":
							 | 
						|
										return e.evaluateIPCondition(block, evalCtx, true)
							 | 
						|
									case "NotIpAddress":
							 | 
						|
										return e.evaluateIPCondition(block, evalCtx, false)
							 | 
						|
								
							 | 
						|
									// String conditions
							 | 
						|
									case "StringEquals":
							 | 
						|
										return e.EvaluateStringCondition(block, evalCtx, true, false)
							 | 
						|
									case "StringNotEquals":
							 | 
						|
										return e.EvaluateStringCondition(block, evalCtx, false, false)
							 | 
						|
									case "StringLike":
							 | 
						|
										return e.EvaluateStringCondition(block, evalCtx, true, true)
							 | 
						|
									case "StringEqualsIgnoreCase":
							 | 
						|
										return e.evaluateStringConditionIgnoreCase(block, evalCtx, true, false)
							 | 
						|
									case "StringNotEqualsIgnoreCase":
							 | 
						|
										return e.evaluateStringConditionIgnoreCase(block, evalCtx, false, false)
							 | 
						|
									case "StringLikeIgnoreCase":
							 | 
						|
										return e.evaluateStringConditionIgnoreCase(block, evalCtx, true, true)
							 | 
						|
								
							 | 
						|
									// Numeric conditions
							 | 
						|
									case "NumericEquals":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, "==")
							 | 
						|
									case "NumericNotEquals":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, "!=")
							 | 
						|
									case "NumericLessThan":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, "<")
							 | 
						|
									case "NumericLessThanEquals":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, "<=")
							 | 
						|
									case "NumericGreaterThan":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, ">")
							 | 
						|
									case "NumericGreaterThanEquals":
							 | 
						|
										return e.evaluateNumericCondition(block, evalCtx, ">=")
							 | 
						|
								
							 | 
						|
									// Date conditions
							 | 
						|
									case "DateEquals":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, "==")
							 | 
						|
									case "DateNotEquals":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, "!=")
							 | 
						|
									case "DateLessThan":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, "<")
							 | 
						|
									case "DateLessThanEquals":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, "<=")
							 | 
						|
									case "DateGreaterThan":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, ">")
							 | 
						|
									case "DateGreaterThanEquals":
							 | 
						|
										return e.evaluateDateCondition(block, evalCtx, ">=")
							 | 
						|
								
							 | 
						|
									// Boolean conditions
							 | 
						|
									case "Bool":
							 | 
						|
										return e.evaluateBoolCondition(block, evalCtx)
							 | 
						|
								
							 | 
						|
									// Null conditions
							 | 
						|
									case "Null":
							 | 
						|
										return e.evaluateNullCondition(block, evalCtx)
							 | 
						|
								
							 | 
						|
									default:
							 | 
						|
										// Unknown condition types default to false (more secure)
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateIPCondition evaluates IP address conditions
							 | 
						|
								func (e *PolicyEngine) evaluateIPCondition(block map[string]interface{}, evalCtx *EvaluationContext, shouldMatch bool) bool {
							 | 
						|
									sourceIP, exists := evalCtx.RequestContext["sourceIP"]
							 | 
						|
									if !exists {
							 | 
						|
										return !shouldMatch // If no IP in context, condition fails for positive match
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									sourceIPStr, ok := sourceIP.(string)
							 | 
						|
									if !ok {
							 | 
						|
										return !shouldMatch
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									sourceIPAddr := net.ParseIP(sourceIPStr)
							 | 
						|
									if sourceIPAddr == nil {
							 | 
						|
										return !shouldMatch
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for key, value := range block {
							 | 
						|
										if key == "seaweed:SourceIP" {
							 | 
						|
											ranges, ok := value.([]string)
							 | 
						|
											if !ok {
							 | 
						|
												continue
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											for _, ipRange := range ranges {
							 | 
						|
												if strings.Contains(ipRange, "/") {
							 | 
						|
													// CIDR range
							 | 
						|
													_, cidr, err := net.ParseCIDR(ipRange)
							 | 
						|
													if err != nil {
							 | 
						|
														continue
							 | 
						|
													}
							 | 
						|
													if cidr.Contains(sourceIPAddr) {
							 | 
						|
														return shouldMatch
							 | 
						|
													}
							 | 
						|
												} else {
							 | 
						|
													// Single IP
							 | 
						|
													if sourceIPStr == ipRange {
							 | 
						|
														return shouldMatch
							 | 
						|
													}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return !shouldMatch
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// EvaluateStringCondition evaluates string-based conditions
							 | 
						|
								func (e *PolicyEngine) EvaluateStringCondition(block map[string]interface{}, evalCtx *EvaluationContext, shouldMatch bool, useWildcard bool) bool {
							 | 
						|
									// Iterate through all condition keys in the block
							 | 
						|
									for conditionKey, conditionValue := range block {
							 | 
						|
										// Get the context values for this condition key
							 | 
						|
										contextValues, exists := evalCtx.RequestContext[conditionKey]
							 | 
						|
										if !exists {
							 | 
						|
											// If the context key doesn't exist, condition fails for positive match
							 | 
						|
											if shouldMatch {
							 | 
						|
												return false
							 | 
						|
											}
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Convert context value to string slice
							 | 
						|
										var contextStrings []string
							 | 
						|
										switch v := contextValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											contextStrings = []string{v}
							 | 
						|
										case []string:
							 | 
						|
											contextStrings = v
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, item := range v {
							 | 
						|
												if str, ok := item.(string); ok {
							 | 
						|
													contextStrings = append(contextStrings, str)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										default:
							 | 
						|
											// Convert to string as fallback
							 | 
						|
											contextStrings = []string{fmt.Sprintf("%v", v)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Convert condition value to string slice
							 | 
						|
										var expectedStrings []string
							 | 
						|
										switch v := conditionValue.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedStrings = []string{v}
							 | 
						|
										case []string:
							 | 
						|
											expectedStrings = v
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, item := range v {
							 | 
						|
												if str, ok := item.(string); ok {
							 | 
						|
													expectedStrings = append(expectedStrings, str)
							 | 
						|
												} else {
							 | 
						|
													expectedStrings = append(expectedStrings, fmt.Sprintf("%v", item))
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										default:
							 | 
						|
											expectedStrings = []string{fmt.Sprintf("%v", v)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Evaluate the condition using AWS IAM-compliant matching
							 | 
						|
										conditionMet := false
							 | 
						|
										for _, expected := range expectedStrings {
							 | 
						|
											for _, contextValue := range contextStrings {
							 | 
						|
												if useWildcard {
							 | 
						|
													// Use AWS IAM-compliant wildcard matching for StringLike conditions
							 | 
						|
													// This handles case-insensitivity and policy variables
							 | 
						|
													if awsIAMMatch(expected, contextValue, evalCtx) {
							 | 
						|
														conditionMet = true
							 | 
						|
														break
							 | 
						|
													}
							 | 
						|
												} else {
							 | 
						|
													// For StringEquals/StringNotEquals, also support policy variables but be case-sensitive
							 | 
						|
													expandedExpected := expandPolicyVariables(expected, evalCtx)
							 | 
						|
													if expandedExpected == contextValue {
							 | 
						|
														conditionMet = true
							 | 
						|
														break
							 | 
						|
													}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
											if conditionMet {
							 | 
						|
												break
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// For shouldMatch=true (StringEquals, StringLike): condition must be met
							 | 
						|
										// For shouldMatch=false (StringNotEquals): condition must NOT be met
							 | 
						|
										if shouldMatch && !conditionMet {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
										if !shouldMatch && conditionMet {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidatePolicyDocument validates a policy document structure
							 | 
						|
								func ValidatePolicyDocument(policy *PolicyDocument) error {
							 | 
						|
									return ValidatePolicyDocumentWithType(policy, "resource")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateTrustPolicyDocument validates a trust policy document structure
							 | 
						|
								func ValidateTrustPolicyDocument(policy *PolicyDocument) error {
							 | 
						|
									return ValidatePolicyDocumentWithType(policy, "trust")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidatePolicyDocumentWithType validates a policy document for specific type
							 | 
						|
								func ValidatePolicyDocumentWithType(policy *PolicyDocument, policyType string) error {
							 | 
						|
									if policy == nil {
							 | 
						|
										return fmt.Errorf("policy document cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if policy.Version == "" {
							 | 
						|
										return fmt.Errorf("version is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(policy.Statement) == 0 {
							 | 
						|
										return fmt.Errorf("at least one statement is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for i, statement := range policy.Statement {
							 | 
						|
										if err := validateStatementWithType(&statement, policyType); err != nil {
							 | 
						|
											return fmt.Errorf("statement %d is invalid: %w", i, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateStatement validates a single statement (for backward compatibility)
							 | 
						|
								func validateStatement(statement *Statement) error {
							 | 
						|
									return validateStatementWithType(statement, "resource")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateStatementWithType validates a single statement based on policy type
							 | 
						|
								func validateStatementWithType(statement *Statement, policyType string) error {
							 | 
						|
									if statement.Effect != "Allow" && statement.Effect != "Deny" {
							 | 
						|
										return fmt.Errorf("invalid effect: %s (must be Allow or Deny)", statement.Effect)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(statement.Action) == 0 {
							 | 
						|
										return fmt.Errorf("at least one action is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Trust policies don't require Resource field, but resource policies do
							 | 
						|
									if policyType == "resource" {
							 | 
						|
										if len(statement.Resource) == 0 {
							 | 
						|
											return fmt.Errorf("at least one resource is required")
							 | 
						|
										}
							 | 
						|
									} else if policyType == "trust" {
							 | 
						|
										// Trust policies should have Principal field
							 | 
						|
										if statement.Principal == nil {
							 | 
						|
											return fmt.Errorf("trust policy statement must have Principal field")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Trust policies typically have specific actions
							 | 
						|
										validTrustActions := map[string]bool{
							 | 
						|
											"sts:AssumeRole":                true,
							 | 
						|
											"sts:AssumeRoleWithWebIdentity": true,
							 | 
						|
											"sts:AssumeRoleWithCredentials": true,
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										for _, action := range statement.Action {
							 | 
						|
											if !validTrustActions[action] {
							 | 
						|
												return fmt.Errorf("invalid action for trust policy: %s", action)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchResource checks if a resource pattern matches a requested resource
							 | 
						|
								// Uses hybrid approach: simple suffix wildcards for compatibility, filepath.Match for complex patterns
							 | 
						|
								func matchResource(pattern, resource string) bool {
							 | 
						|
									if pattern == resource {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Handle simple suffix wildcard (backward compatibility)
							 | 
						|
									if strings.HasSuffix(pattern, "*") {
							 | 
						|
										prefix := pattern[:len(pattern)-1]
							 | 
						|
										return strings.HasPrefix(resource, prefix)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// For complex patterns, use filepath.Match for advanced wildcard support (*, ?, [])
							 | 
						|
									matched, err := filepath.Match(pattern, resource)
							 | 
						|
									if err != nil {
							 | 
						|
										// Fallback to exact match if pattern is malformed
							 | 
						|
										return pattern == resource
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return matched
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// awsIAMMatch performs AWS IAM-compliant pattern matching with case-insensitivity and policy variable support
							 | 
						|
								func awsIAMMatch(pattern, value string, evalCtx *EvaluationContext) bool {
							 | 
						|
									// Step 1: Substitute policy variables (e.g., ${aws:username}, ${saml:username})
							 | 
						|
									expandedPattern := expandPolicyVariables(pattern, evalCtx)
							 | 
						|
								
							 | 
						|
									// Step 2: Handle special patterns
							 | 
						|
									if expandedPattern == "*" {
							 | 
						|
										return true // Universal wildcard
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Step 3: Case-insensitive exact match
							 | 
						|
									if strings.EqualFold(expandedPattern, value) {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Step 4: Handle AWS-style wildcards (case-insensitive)
							 | 
						|
									if strings.Contains(expandedPattern, "*") || strings.Contains(expandedPattern, "?") {
							 | 
						|
										return AwsWildcardMatch(expandedPattern, value)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return false
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// expandPolicyVariables substitutes AWS policy variables in the pattern
							 | 
						|
								func expandPolicyVariables(pattern string, evalCtx *EvaluationContext) string {
							 | 
						|
									if evalCtx == nil || evalCtx.RequestContext == nil {
							 | 
						|
										return pattern
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									expanded := pattern
							 | 
						|
								
							 | 
						|
									// Common AWS policy variables that might be used in SeaweedFS
							 | 
						|
									variableMap := map[string]string{
							 | 
						|
										"${aws:username}":      getContextValue(evalCtx, "aws:username", ""),
							 | 
						|
										"${saml:username}":     getContextValue(evalCtx, "saml:username", ""),
							 | 
						|
										"${oidc:sub}":          getContextValue(evalCtx, "oidc:sub", ""),
							 | 
						|
										"${aws:userid}":        getContextValue(evalCtx, "aws:userid", ""),
							 | 
						|
										"${aws:principaltype}": getContextValue(evalCtx, "aws:principaltype", ""),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for variable, value := range variableMap {
							 | 
						|
										if value != "" {
							 | 
						|
											expanded = strings.ReplaceAll(expanded, variable, value)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return expanded
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// getContextValue safely gets a value from the evaluation context
							 | 
						|
								func getContextValue(evalCtx *EvaluationContext, key, defaultValue string) string {
							 | 
						|
									if value, exists := evalCtx.RequestContext[key]; exists {
							 | 
						|
										if str, ok := value.(string); ok {
							 | 
						|
											return str
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return defaultValue
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AwsWildcardMatch performs case-insensitive wildcard matching like AWS IAM
							 | 
						|
								func AwsWildcardMatch(pattern, value string) bool {
							 | 
						|
									// Create regex pattern key for caching
							 | 
						|
									// First escape all regex metacharacters, then replace wildcards
							 | 
						|
									regexPattern := regexp.QuoteMeta(pattern)
							 | 
						|
									regexPattern = strings.ReplaceAll(regexPattern, "\\*", ".*")
							 | 
						|
									regexPattern = strings.ReplaceAll(regexPattern, "\\?", ".")
							 | 
						|
									regexPattern = "^" + regexPattern + "$"
							 | 
						|
									regexKey := "(?i)" + regexPattern
							 | 
						|
								
							 | 
						|
									// Try to get compiled regex from cache
							 | 
						|
									regexCacheMu.RLock()
							 | 
						|
									regex, found := regexCache[regexKey]
							 | 
						|
									regexCacheMu.RUnlock()
							 | 
						|
								
							 | 
						|
									if !found {
							 | 
						|
										// Compile and cache the regex
							 | 
						|
										compiledRegex, err := regexp.Compile(regexKey)
							 | 
						|
										if err != nil {
							 | 
						|
											// Fallback to simple case-insensitive comparison if regex fails
							 | 
						|
											return strings.EqualFold(pattern, value)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Store in cache with write lock
							 | 
						|
										regexCacheMu.Lock()
							 | 
						|
										// Double-check in case another goroutine added it
							 | 
						|
										if existingRegex, exists := regexCache[regexKey]; exists {
							 | 
						|
											regex = existingRegex
							 | 
						|
										} else {
							 | 
						|
											regexCache[regexKey] = compiledRegex
							 | 
						|
											regex = compiledRegex
							 | 
						|
										}
							 | 
						|
										regexCacheMu.Unlock()
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return regex.MatchString(value)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchAction checks if an action pattern matches a requested action
							 | 
						|
								// Uses hybrid approach: simple suffix wildcards for compatibility, filepath.Match for complex patterns
							 | 
						|
								func matchAction(pattern, action string) bool {
							 | 
						|
									if pattern == action {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Handle simple suffix wildcard (backward compatibility)
							 | 
						|
									if strings.HasSuffix(pattern, "*") {
							 | 
						|
										prefix := pattern[:len(pattern)-1]
							 | 
						|
										return strings.HasPrefix(action, prefix)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// For complex patterns, use filepath.Match for advanced wildcard support (*, ?, [])
							 | 
						|
									matched, err := filepath.Match(pattern, action)
							 | 
						|
									if err != nil {
							 | 
						|
										// Fallback to exact match if pattern is malformed
							 | 
						|
										return pattern == action
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return matched
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateStringConditionIgnoreCase evaluates string conditions with case insensitivity
							 | 
						|
								func (e *PolicyEngine) evaluateStringConditionIgnoreCase(block map[string]interface{}, evalCtx *EvaluationContext, shouldMatch bool, useWildcard bool) bool {
							 | 
						|
									for key, expectedValues := range block {
							 | 
						|
										contextValue, exists := evalCtx.RequestContext[key]
							 | 
						|
										if !exists {
							 | 
						|
											if !shouldMatch {
							 | 
						|
												continue // For NotEquals, missing key is OK
							 | 
						|
											}
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										contextStr, ok := contextValue.(string)
							 | 
						|
										if !ok {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										contextStr = strings.ToLower(contextStr)
							 | 
						|
										matched := false
							 | 
						|
								
							 | 
						|
										// Handle different value types
							 | 
						|
										switch v := expectedValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedStr := strings.ToLower(v)
							 | 
						|
											if useWildcard {
							 | 
						|
												matched, _ = filepath.Match(expectedStr, contextStr)
							 | 
						|
											} else {
							 | 
						|
												matched = expectedStr == contextStr
							 | 
						|
											}
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, val := range v {
							 | 
						|
												if valStr, ok := val.(string); ok {
							 | 
						|
													expectedStr := strings.ToLower(valStr)
							 | 
						|
													if useWildcard {
							 | 
						|
														if m, _ := filepath.Match(expectedStr, contextStr); m {
							 | 
						|
															matched = true
							 | 
						|
															break
							 | 
						|
														}
							 | 
						|
													} else {
							 | 
						|
														if expectedStr == contextStr {
							 | 
						|
															matched = true
							 | 
						|
															break
							 | 
						|
														}
							 | 
						|
													}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if shouldMatch && !matched {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
										if !shouldMatch && matched {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateNumericCondition evaluates numeric conditions
							 | 
						|
								func (e *PolicyEngine) evaluateNumericCondition(block map[string]interface{}, evalCtx *EvaluationContext, operator string) bool {
							 | 
						|
									for key, expectedValues := range block {
							 | 
						|
										contextValue, exists := evalCtx.RequestContext[key]
							 | 
						|
										if !exists {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										contextNum, err := parseNumeric(contextValue)
							 | 
						|
										if err != nil {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										matched := false
							 | 
						|
								
							 | 
						|
										// Handle different value types
							 | 
						|
										switch v := expectedValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedNum, err := parseNumeric(v)
							 | 
						|
											if err != nil {
							 | 
						|
												return false
							 | 
						|
											}
							 | 
						|
											matched = compareNumbers(contextNum, expectedNum, operator)
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, val := range v {
							 | 
						|
												expectedNum, err := parseNumeric(val)
							 | 
						|
												if err != nil {
							 | 
						|
													continue
							 | 
						|
												}
							 | 
						|
												if compareNumbers(contextNum, expectedNum, operator) {
							 | 
						|
													matched = true
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if !matched {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateDateCondition evaluates date conditions
							 | 
						|
								func (e *PolicyEngine) evaluateDateCondition(block map[string]interface{}, evalCtx *EvaluationContext, operator string) bool {
							 | 
						|
									for key, expectedValues := range block {
							 | 
						|
										contextValue, exists := evalCtx.RequestContext[key]
							 | 
						|
										if !exists {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										contextTime, err := parseDateTime(contextValue)
							 | 
						|
										if err != nil {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										matched := false
							 | 
						|
								
							 | 
						|
										// Handle different value types
							 | 
						|
										switch v := expectedValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedTime, err := parseDateTime(v)
							 | 
						|
											if err != nil {
							 | 
						|
												return false
							 | 
						|
											}
							 | 
						|
											matched = compareDates(contextTime, expectedTime, operator)
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, val := range v {
							 | 
						|
												expectedTime, err := parseDateTime(val)
							 | 
						|
												if err != nil {
							 | 
						|
													continue
							 | 
						|
												}
							 | 
						|
												if compareDates(contextTime, expectedTime, operator) {
							 | 
						|
													matched = true
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if !matched {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateBoolCondition evaluates boolean conditions
							 | 
						|
								func (e *PolicyEngine) evaluateBoolCondition(block map[string]interface{}, evalCtx *EvaluationContext) bool {
							 | 
						|
									for key, expectedValues := range block {
							 | 
						|
										contextValue, exists := evalCtx.RequestContext[key]
							 | 
						|
										if !exists {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										contextBool, err := parseBool(contextValue)
							 | 
						|
										if err != nil {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										matched := false
							 | 
						|
								
							 | 
						|
										// Handle different value types
							 | 
						|
										switch v := expectedValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedBool, err := parseBool(v)
							 | 
						|
											if err != nil {
							 | 
						|
												return false
							 | 
						|
											}
							 | 
						|
											matched = contextBool == expectedBool
							 | 
						|
										case bool:
							 | 
						|
											matched = contextBool == v
							 | 
						|
										case []interface{}:
							 | 
						|
											for _, val := range v {
							 | 
						|
												expectedBool, err := parseBool(val)
							 | 
						|
												if err != nil {
							 | 
						|
													continue
							 | 
						|
												}
							 | 
						|
												if contextBool == expectedBool {
							 | 
						|
													matched = true
							 | 
						|
													break
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if !matched {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateNullCondition evaluates null conditions
							 | 
						|
								func (e *PolicyEngine) evaluateNullCondition(block map[string]interface{}, evalCtx *EvaluationContext) bool {
							 | 
						|
									for key, expectedValues := range block {
							 | 
						|
										_, exists := evalCtx.RequestContext[key]
							 | 
						|
								
							 | 
						|
										expectedNull := false
							 | 
						|
										switch v := expectedValues.(type) {
							 | 
						|
										case string:
							 | 
						|
											expectedNull = v == "true"
							 | 
						|
										case bool:
							 | 
						|
											expectedNull = v
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// If we expect null (true) and key exists, or expect non-null (false) and key doesn't exist
							 | 
						|
										if expectedNull == exists {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Helper functions for parsing and comparing values
							 | 
						|
								
							 | 
						|
								// parseNumeric parses a value as a float64
							 | 
						|
								func parseNumeric(value interface{}) (float64, error) {
							 | 
						|
									switch v := value.(type) {
							 | 
						|
									case float64:
							 | 
						|
										return v, nil
							 | 
						|
									case float32:
							 | 
						|
										return float64(v), nil
							 | 
						|
									case int:
							 | 
						|
										return float64(v), nil
							 | 
						|
									case int64:
							 | 
						|
										return float64(v), nil
							 | 
						|
									case string:
							 | 
						|
										return strconv.ParseFloat(v, 64)
							 | 
						|
									default:
							 | 
						|
										return 0, fmt.Errorf("cannot parse %T as numeric", value)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// compareNumbers compares two numbers using the given operator
							 | 
						|
								func compareNumbers(a, b float64, operator string) bool {
							 | 
						|
									switch operator {
							 | 
						|
									case "==":
							 | 
						|
										return a == b
							 | 
						|
									case "!=":
							 | 
						|
										return a != b
							 | 
						|
									case "<":
							 | 
						|
										return a < b
							 | 
						|
									case "<=":
							 | 
						|
										return a <= b
							 | 
						|
									case ">":
							 | 
						|
										return a > b
							 | 
						|
									case ">=":
							 | 
						|
										return a >= b
							 | 
						|
									default:
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// parseDateTime parses a value as a time.Time
							 | 
						|
								func parseDateTime(value interface{}) (time.Time, error) {
							 | 
						|
									switch v := value.(type) {
							 | 
						|
									case string:
							 | 
						|
										// Try common date formats
							 | 
						|
										formats := []string{
							 | 
						|
											time.RFC3339,
							 | 
						|
											"2006-01-02T15:04:05Z",
							 | 
						|
											"2006-01-02T15:04:05",
							 | 
						|
											"2006-01-02 15:04:05",
							 | 
						|
											"2006-01-02",
							 | 
						|
										}
							 | 
						|
										for _, format := range formats {
							 | 
						|
											if t, err := time.Parse(format, v); err == nil {
							 | 
						|
												return t, nil
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										return time.Time{}, fmt.Errorf("cannot parse date: %s", v)
							 | 
						|
									case time.Time:
							 | 
						|
										return v, nil
							 | 
						|
									default:
							 | 
						|
										return time.Time{}, fmt.Errorf("cannot parse %T as date", value)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// compareDates compares two dates using the given operator
							 | 
						|
								func compareDates(a, b time.Time, operator string) bool {
							 | 
						|
									switch operator {
							 | 
						|
									case "==":
							 | 
						|
										return a.Equal(b)
							 | 
						|
									case "!=":
							 | 
						|
										return !a.Equal(b)
							 | 
						|
									case "<":
							 | 
						|
										return a.Before(b)
							 | 
						|
									case "<=":
							 | 
						|
										return a.Before(b) || a.Equal(b)
							 | 
						|
									case ">":
							 | 
						|
										return a.After(b)
							 | 
						|
									case ">=":
							 | 
						|
										return a.After(b) || a.Equal(b)
							 | 
						|
									default:
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// parseBool parses a value as a boolean
							 | 
						|
								func parseBool(value interface{}) (bool, error) {
							 | 
						|
									switch v := value.(type) {
							 | 
						|
									case bool:
							 | 
						|
										return v, nil
							 | 
						|
									case string:
							 | 
						|
										return strconv.ParseBool(v)
							 | 
						|
									default:
							 | 
						|
										return false, fmt.Errorf("cannot parse %T as boolean", value)
							 | 
						|
									}
							 | 
						|
								}
							 |