181 lines
5.9 KiB
Python
181 lines
5.9 KiB
Python
"""
|
|
Policy Matcher - Matches requests against policy attachments.
|
|
|
|
Uses existing wildcard pattern matching helpers to determine which policies
|
|
apply to a given request based on team alias, key alias, and model.
|
|
|
|
Policies are matched via policy_attachments which define WHERE each policy applies.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional
|
|
|
|
from litellm._logging import verbose_proxy_logger
|
|
from litellm.proxy.auth.route_checks import RouteChecks
|
|
from litellm.types.proxy.policy_engine import Policy, PolicyMatchContext, PolicyScope
|
|
|
|
|
|
class PolicyMatcher:
|
|
"""
|
|
Matches incoming requests against policy attachments.
|
|
|
|
Supports wildcard patterns:
|
|
- "*" matches everything
|
|
- "prefix-*" matches anything starting with "prefix-"
|
|
|
|
Uses policy_attachments to determine which policies apply to a request.
|
|
"""
|
|
|
|
@staticmethod
|
|
def matches_pattern(value: Optional[str], patterns: List[str]) -> bool:
|
|
"""
|
|
Check if a value matches any of the given patterns.
|
|
|
|
Uses the existing RouteChecks._route_matches_wildcard_pattern helper.
|
|
|
|
Args:
|
|
value: The value to check (e.g., team alias, key alias, model)
|
|
patterns: List of patterns to match against
|
|
|
|
Returns:
|
|
True if value matches any pattern, False otherwise
|
|
"""
|
|
# If no value provided, only match if patterns include "*"
|
|
if value is None:
|
|
return "*" in patterns
|
|
|
|
for pattern in patterns:
|
|
# Use existing wildcard pattern matching helper
|
|
if RouteChecks._route_matches_wildcard_pattern(
|
|
route=value, pattern=pattern
|
|
):
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def scope_matches(scope: PolicyScope, context: PolicyMatchContext) -> bool:
|
|
"""
|
|
Check if a policy scope matches the given context.
|
|
|
|
A scope matches if ALL of its fields match:
|
|
- teams matches context.team_alias
|
|
- keys matches context.key_alias
|
|
- models matches context.model
|
|
|
|
Args:
|
|
scope: The policy scope to check
|
|
context: The request context
|
|
|
|
Returns:
|
|
True if scope matches context, False otherwise
|
|
"""
|
|
# Check teams
|
|
if not PolicyMatcher.matches_pattern(context.team_alias, scope.get_teams()):
|
|
return False
|
|
|
|
# Check keys
|
|
if not PolicyMatcher.matches_pattern(context.key_alias, scope.get_keys()):
|
|
return False
|
|
|
|
# Check models
|
|
if not PolicyMatcher.matches_pattern(context.model, scope.get_models()):
|
|
return False
|
|
|
|
# Check tags (only if scope specifies tags)
|
|
# Unlike teams/keys/models, empty tags means "do not check" rather than "match all"
|
|
scope_tags = scope.get_tags()
|
|
if scope_tags:
|
|
if not context.tags:
|
|
return False
|
|
# Match if ANY context tag matches ANY scope tag pattern
|
|
if not any(
|
|
PolicyMatcher.matches_pattern(tag, scope_tags) for tag in context.tags
|
|
):
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def get_matching_policies(
|
|
context: PolicyMatchContext,
|
|
) -> List[str]:
|
|
"""
|
|
Get list of policy names that match the given context via attachments.
|
|
|
|
Args:
|
|
context: The request context to match against
|
|
|
|
Returns:
|
|
List of policy names that match the context
|
|
"""
|
|
from litellm.proxy.policy_engine.attachment_registry import (
|
|
get_attachment_registry,
|
|
)
|
|
|
|
registry = get_attachment_registry()
|
|
if not registry.is_initialized():
|
|
verbose_proxy_logger.debug(
|
|
"AttachmentRegistry not initialized, returning empty list"
|
|
)
|
|
return []
|
|
|
|
return registry.get_attached_policies(context)
|
|
|
|
@staticmethod
|
|
def get_matching_policies_from_registry(
|
|
context: PolicyMatchContext,
|
|
) -> List[str]:
|
|
"""
|
|
Get list of policy names that match the given context from the global registry.
|
|
|
|
Args:
|
|
context: The request context to match against
|
|
|
|
Returns:
|
|
List of policy names that match the context
|
|
"""
|
|
return PolicyMatcher.get_matching_policies(context=context)
|
|
|
|
@staticmethod
|
|
def get_policies_with_matching_conditions(
|
|
policy_names: List[str],
|
|
context: PolicyMatchContext,
|
|
policies: Optional[Dict[str, Policy]] = None,
|
|
) -> List[str]:
|
|
"""
|
|
Filter policies to only those whose conditions match the context.
|
|
|
|
A policy's condition matches if:
|
|
- The policy has no condition (condition is None), OR
|
|
- The policy's condition evaluates to True for the given context
|
|
|
|
Args:
|
|
policy_names: List of policy names to filter
|
|
context: The request context to evaluate conditions against
|
|
policies: Dictionary of all policies (if None, uses global registry)
|
|
|
|
Returns:
|
|
List of policy names whose conditions match the context
|
|
"""
|
|
from litellm.proxy.policy_engine.condition_evaluator import ConditionEvaluator
|
|
from litellm.proxy.policy_engine.policy_registry import get_policy_registry
|
|
|
|
if policies is None:
|
|
registry = get_policy_registry()
|
|
if not registry.is_initialized():
|
|
return []
|
|
policies = registry.get_all_policies()
|
|
|
|
matching_policies = []
|
|
for policy_name in policy_names:
|
|
policy = policies.get(policy_name)
|
|
if policy is None:
|
|
continue
|
|
# Policy matches if it has no condition OR condition evaluates to True
|
|
if policy.condition is None or ConditionEvaluator.evaluate(
|
|
policy.condition, context
|
|
):
|
|
matching_policies.append(policy_name)
|
|
|
|
return matching_policies
|