SCIM clients can filter list responses (GET /Users?filter=…) and search
endpoints (POST /Users/.search) using the SCIM 2.0 filter grammar
defined in
RFC 7644 §3.4.2.2.
AshScim parses these filters into the map-shaped input format consumed by
Ash.Query.filter_input/2, with the security-critical attribute
resolution done in the same pass.
| SCIM | Ash | Example |
|---|---|---|
eq |
eq |
userName eq "alice" |
ne |
not_eq |
active ne true |
co |
contains |
userName co "alice" |
sw |
string_starts_with? |
userName sw "alice" |
ew |
string_ends_with? |
userName ew "@example.com" |
pr |
is_nil: false |
externalId pr |
gt |
greater_than |
created gt "2024-01-01" |
ge |
greater_than_or_equal |
created ge "2024-01-01" |
lt |
less_than |
created lt "2024-12-31" |
le |
less_than_or_equal |
created le "2024-12-31" |
and, or, and parenthesized not (...) are supported with the standard
precedence (not > and > or):
userName eq "alice" or userName eq "bob"
active eq true and externalId pr
not (active eq true)
(userName co "alice" or userName co "bob") and active eq true
Chained ands and ors flatten into a single list:
a or b or cparses to%{or: [a, b, c]}a and b and cparses to%{and: [a, b, c]}
Sub-attributes of complex and multivalued mappings are addressable via
dotted paths:
name.familyName co "Anderson"
emails.value eq "alice@example.com"
For complex and single-attribute multivalueds, the dotted path resolves
through the sub-maps to the underlying scalar attribute and emits a flat
filter (%{last_name: %{contains: "Anderson"}}).
For relationship-backed multivalueds, the dotted path emits Ash's native nested filter form:
# members.value eq "u1" against a Group
%{memberships: %{user_id: %{eq: "u1"}}}This traverses the :memberships relationship, so a query like
?filter=members.value eq "u1" finds groups whose memberships include
the matching user.
The filter resolver is the security boundary between IdP-supplied input and your data layer. The rule it enforces:
Every attribute path in the filter must be declared in the resource's SCIM mappings. Anything else fails closed.
If an IdP sends ?filter=password eq "hunter2", the resolver sees that
password is not a declared SCIM attribute on this resource and returns
400 invalidFilter — even if the resource has a password Ash attribute.
The filter cannot reach the data layer.
This means filters can only target what the DSL exposes. If you have
attribute :department, :string on the resource but don't write
map :department, attribute: :department, IdP filters can't reference
it. For SCIM that's correct — you don't expose what you didn't declare.
SCIM ?sortBy= parameters go through the same resolution pipeline. A
sort by an unmapped attribute fails with 400 invalidValue.
GET /Users?sortBy=userName&sortOrder=descending
sortOrder accepts ascending (default) or descending.