defmodule Paddle.Filters do
@moduledoc ~S"""
Module used internally by Paddle to manipulate LDAP filters.
"""
@type easy_filter :: keyword | %{optional(atom | binary) => binary}
@type eldap_filter :: tuple
@type filter :: easy_filter | eldap_filter | nil
@type t :: filter
@spec construct_filter(filter) :: eldap_filter
@doc ~S"""
Construct a eldap filter from the given keyword list or map.
If given an `:eldap` filter (a tuple), it is returned as is.
If given `nil`, it will return an empty filter (`:eldap.and([])`).
Examples:
iex> Paddle.Filters.construct_filter(uid: "testuser")
{:equalityMatch, {:AttributeValueAssertion, 'uid', 'testuser'}}
iex> Paddle.Filters.construct_filter(%{uid: "testuser"})
{:equalityMatch, {:AttributeValueAssertion, 'uid', 'testuser'}}
iex> Paddle.Filters.construct_filter(%{"uid" => "testuser"})
{:equalityMatch, {:AttributeValueAssertion, 'uid', 'testuser'}}
iex> Paddle.Filters.construct_filter(:eldap.substrings('uid', initial: 'b'))
{:substrings, {:SubstringFilter, 'uid', [initial: 'b']}}
iex> Paddle.Filters.construct_filter(nil)
{:and, []}
iex> Paddle.Filters.construct_filter([])
{:and, []}
iex> Paddle.Filters.construct_filter(uid: "testuser", cn: "Test User")
{:and,
[equalityMatch: {:AttributeValueAssertion, 'uid', 'testuser'},
equalityMatch: {:AttributeValueAssertion, 'cn', 'Test User'}]}
"""
def construct_filter(filter) when is_tuple(filter), do: filter
def construct_filter(nil), do: :eldap.and([])
def construct_filter(filter) when is_map(filter),
do:
filter
|> Enum.into([])
|> construct_filter
def construct_filter([{key, value}]) when is_binary(value) do
:eldap.equalityMatch('#{key}', '#{value}')
end
def construct_filter(kwdn) when is_list(kwdn) do
criteria =
kwdn
|> Enum.map(fn {key, value} -> :eldap.equalityMatch('#{key}', '#{value}') end)
:eldap.and(criteria)
end
@spec merge_filter(filter, filter) :: filter
@doc ~S"""
Merge two filters with an "and" operation.
Examples:
iex> Paddle.Filters.merge_filter([uid: "testuser"], [cn: "Test User"])
{:and,
[equalityMatch: {:AttributeValueAssertion, 'uid', 'testuser'},
equalityMatch: {:AttributeValueAssertion, 'cn', 'Test User'}]}
iex> Paddle.Filters.merge_filter([uid: "testuser"], :eldap.substrings('cn', [initial: 'Tes']))
{:and,
[equalityMatch: {:AttributeValueAssertion, 'uid', 'testuser'},
substrings: {:SubstringFilter, 'cn', [initial: 'Tes']}]}
iex> Paddle.Filters.merge_filter([uid: "testuser"], [])
[uid: "testuser"]
iex> Paddle.Filters.merge_filter([], [cn: "Test User"])
[cn: "Test User"]
iex> Paddle.Filters.merge_filter([], nil)
{:and, []}
"""
def merge_filter(lhs, rhs)
for lhs <- [[], nil], rhs <- [[], nil] do
def merge_filter(unquote(lhs), unquote(rhs)), do: :eldap.and([])
end
for null_filter <- [[], nil] do
def merge_filter(filter, unquote(null_filter)), do: filter
def merge_filter(unquote(null_filter), filter), do: filter
end
def merge_filter({:and, lcond}, {:and, rcond}), do: {:and, lcond ++ rcond}
def merge_filter({:and, lcond}, rhs), do: {:and, [construct_filter(rhs) | lcond]}
def merge_filter(lhs, {:and, rcond}), do: {:and, [construct_filter(lhs) | rcond]}
def merge_filter(lhs, rhs) do
:eldap.and([construct_filter(lhs), construct_filter(rhs)])
end
@spec class_filter([binary]) :: eldap_filter
@doc ~S"""
Construct a filter that matches a list of objectClasses.
Examples:
iex> Paddle.Filters.class_filter ["posixAccount", "account"]
{:and,
[equalityMatch: {:AttributeValueAssertion, 'objectClass', 'posixAccount'},
equalityMatch: {:AttributeValueAssertion, 'objectClass', 'account'}]}
"""
def class_filter(classes) when is_list(classes) do
classes
|> Enum.map(&:eldap.equalityMatch('objectClass', '#{&1}'))
|> :eldap.and()
end
def class_filter(class), do: :eldap.equalityMatch('objectClass', '#{class}')
end