Skip to content

Condition Matchers

Condition matchers add fine-grained control to policy and statement evaluation. While action matchers determine what request is being made, condition matchers determine when a rule should apply based on request properties, security context, rate limits, or custom expressions.

ConditionMatcher Interface

ConditionMatcher extends RequestMatcher:

kotlin
interface ConditionMatcher : RequestMatcher

AbstractConditionMatcher

AbstractConditionMatcher provides a negate option and template method pattern:

kotlin
abstract class AbstractConditionMatcher(
    final override val type: String,
    final override val configuration: Configuration
) : ConditionMatcher {
    private val negate: Boolean = configuration.get("negate")?.asBoolean() ?: false

    override fun match(request, securityContext): Boolean {
        val match = internalMatch(request, securityContext)
        return if (negate) !match else match
    }
}

Any condition can be inverted by setting "negate": true in its configuration.

PartExtractor Framework

Many condition matchers share a common pattern: extract a value from the request or security context, then compare it. The PartExtractor framework formalizes this:

kotlin
fun interface PartExtractor {
    fun extract(request: Request, securityContext: SecurityContext): String
}

Part Identifiers

DefaultPartExtractor supports these part identifiers:

Request Parts (prefix request.):

PartDescription
request.pathRequest URL path
request.methodHTTP method
request.appIdApplication ID
request.spaceIdSpace ID
request.deviceIdDevice ID
request.remoteIpClient IP address
request.originOrigin header URI
request.origin.hostOrigin host
request.refererReferer header URI
request.referer.hostReferer host
request.header.{key}Request header value
request.attributes.{key}Request attribute
request.path.var.{key}Path variable (set by PathActionMatcher)

Security Context Parts (prefix context.):

PartDescription
context.tenantIdCurrent tenant ID
context.principal.idPrincipal user ID
context.principal.attributes.{key}Principal attribute value

Condition Matcher Types

Path-Based Matchers

All path-based matchers extend PartConditionMatcher and operate on an extracted string value:

MatcherType KeyDescription
EqConditionMatchereqEquality check (supports template expressions and ignoreCase)
ContainsConditionMatchercontainsSubstring containment check
StartsWithConditionMatcherstartsWithPrefix check
EndsWithConditionMatcherendsWithSuffix check
InConditionMatcherinSet membership check
RegularConditionMatcherregularRegex pattern match
PathConditionMatcherpathSpring PathPattern match

Context-Based Matchers

These inspect the security context directly:

MatcherType KeyDescription
AuthenticatedConditionMatcherauthenticatedChecks securityContext.principal.authenticated
InRoleConditionMatcherinRoleChecks if principal has a specific role
InTenantConditionMatcherinTenantChecks tenant type (DEFAULT, USER, PLATFORM)

Rate Limiters

MatcherType KeyDescription
RateLimiterConditionMatcherrateLimiterGlobal rate limiting using Guava RateLimiter
GroupedRateLimiterConditionMatchergroupedRateLimiterPer-group rate limiting (e.g., per-user, per-IP)

RateLimiterConditionMatcher creates a single rate limiter shared across all requests. GroupedRateLimiterConditionMatcher creates a LoadingCache of rate limiters keyed by the extracted part value, with automatic expiration after access.

When a rate limit is exceeded, TooManyRequestsException is thrown, which the authorization layer catches and converts to AuthorizeResult.TOO_MANY_REQUESTS.

Expression-Based Matchers

MatcherType KeyDescription
OgnlConditionMatcherognlOGNL expression evaluation
SpelConditionMatcherspelSpring Expression Language evaluation

Both provide access to request and context objects in their expression root, enabling arbitrarily complex conditions.

Boolean Combinators

BoolConditionMatcher combines multiple conditions with AND/OR logic:

kotlin
class BoolConditionMatcher(configuration: Configuration) {
    val and: List<ConditionMatcher>
    val or: List<ConditionMatcher>
}
  • AND: All conditions in the and list must match. If any fails, the result is false.
  • OR: At least one condition in the or list must match. If none match, the result is false.
  • When only and is present (no or), returns true after all AND conditions pass.

SPI: ConditionMatcherFactory

ConditionMatcherFactory is the SPI interface:

kotlin
interface ConditionMatcherFactory {
    val type: String
    fun create(configuration: Configuration): ConditionMatcher
}

Custom condition matchers are registered via META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory.

Architecture Diagrams

Condition Matcher Class Hierarchy

mermaid
classDiagram
    direction TB
    class ConditionMatcher {
        <<interface>>
        +match(request, context): Boolean
    }
    class AbstractConditionMatcher {
        <<abstract>>
        +type: String
        -negate: Boolean
        +match(request, context): Boolean
        #internalMatch(request, context): Boolean
    }
    class PartConditionMatcher {
        <<abstract>>
        -partExtractor: PartExtractor
        #internalMatch(request, context): Boolean
        #matchPart(partValue, context): Boolean
    }

    ConditionMatcher <|.. AbstractConditionMatcher
    ConditionMatcher <|.. RateLimiterConditionMatcher
    ConditionMatcher <|.. BoolConditionMatcher
    AbstractConditionMatcher <|-- PartConditionMatcher
    AbstractConditionMatcher <|-- AuthenticatedConditionMatcher
    AbstractConditionMatcher <|-- InRoleConditionMatcher
    AbstractConditionMatcher <|-- InTenantConditionMatcher
    AbstractConditionMatcher <|-- OgnlConditionMatcher
    AbstractConditionMatcher <|-- SpelConditionMatcher
    PartConditionMatcher <|-- EqConditionMatcher
    PartConditionMatcher <|-- ContainsConditionMatcher
    PartConditionMatcher <|-- StartsWithConditionMatcher
    PartConditionMatcher <|-- EndsWithConditionMatcher
    PartConditionMatcher <|-- InConditionMatcher
    PartConditionMatcher <|-- RegularConditionMatcher
    PartConditionMatcher <|-- PathConditionMatcher
    PartConditionMatcher <|-- GroupedRateLimiterConditionMatcher

Part-Based Condition Evaluation Flow

mermaid
flowchart TD
    A["PartConditionMatcher.match(request, context)"] --> B["negate check"]
    B --> C["partExtractor.extract(request, context)"]
    C --> D["Extract value from request or context"]
    D --> E["matchPart(partValue, context)"]
    E --> F{"Result"}
    F -->|"true"| G["return true (or false if negate)"]
    F -->|"false"| H["return false (or true if negate)"]

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style H fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Boolean Combinator Evaluation

mermaid
sequenceDiagram
    autonumber
    participant Caller
    participant Bool as BoolConditionMatcher
    participant AndC as "AND Conditions"
    participant OrC as "OR Conditions"

    Caller->>Bool: match(request, context)
    loop each AND condition
        Bool->>AndC: condition.match(request, context)
        alt AND fails
            AndC-->>Bool: false
            Bool-->>Caller: false
        end
    end
    Note over Bool: All AND conditions passed
    alt OR list is empty
        Bool-->>Caller: true
    else OR list has items
        loop each OR condition
            Bool->>OrC: condition.match(request, context)
            alt OR matches
                OrC-->>Bool: true
                Bool-->>Caller: true
            end
        end
        Note over Bool: No OR condition matched
        Bool-->>Caller: false
    end

Policy JSON Examples

Path-based condition

json
{
  "condition": {
    "eq": {
      "part": "request.path",
      "value": "/api/admin"
    }
  }
}

Rate limiter (per-user)

json
{
  "condition": {
    "groupedRateLimiter": {
      "part": "context.principal.id",
      "permitsPerSecond": 10.0,
      "expireAfterAccessSecond": 60
    }
  }
}

Boolean combination

json
{
  "condition": {
    "bool": {
      "and": [
        { "authenticated": {} },
        { "inRole": { "value": "admin" } }
      ],
      "or": [
        { "eq": { "part": "request.method", "value": "GET" } },
        { "startsWith": { "part": "request.path", "value": "/api/public" } }
      ]
    }
  }
}

References

Licensed under the Apache License, Version 2.0.