View Source LetMe (LetMe v1.2.4)

LetMe is library for defining and evaluating authorization rules and handling query scopes and field redactions.

This module only defines auxiliary functions. The main functionality lies in the LetMe.Policy module.

Summary

Functions

Takes a list of rules and only returns the rules that would evaluate to true for the given subject and object.

Takes a list of rules and a list of filter options and returns a filtered list of rules.

Takes a struct or a list of structs and redacts fields depending on the subject (user).

Removes redacted fields from a given list of fields.

Functions

Link to this function

filter_allowed_actions(rules, subject, object, policy)

View Source
@spec filter_allowed_actions([LetMe.Rule.t()], subject, object, module()) :: [
  LetMe.Rule.t()
]
when subject: any(), object: {atom(), any()} | struct()

Takes a list of rules and only returns the rules that would evaluate to true for the given subject and object.

The object needs to be passed as a tuple, where the first element is the object name, and the second element is the actual object, e.g. {:article, %Article{}}.

If you registered the schema module with LetMe.Policy.object/3, you can pass the struct without tagging it with the object name, e.g. %Article{}.

This function is used internally by LetMe.Policy.filter_allowed_actions/3.

Example

rules = MyApp.Policy.list_rules()

filter_allowed_actions(
  rules,
  %User{},
  {:article, %Article{}},
  MyApp.Policy
)
Link to this function

filter_rules(rules, opts)

View Source
@spec filter_rules(
  [LetMe.Rule.t()],
  keyword()
) :: [LetMe.Rule.t()]

Takes a list of rules and a list of filter options and returns a filtered list of rules.

This function is used by LetMe.Policy.list_rules/1.

Filter options

  • :object - Matches an object exactly.
  • :action - Matches an action exactly.
  • :allow - Either a check name as an atom or a 2-tuple with the check name and the options.
  • :metadata - Either a metadata name as an atom or a 2-tuple with the metadata name and the metadata value.
  • :deny - Either a check name as an atom or a 2-tuple with the check name and the options.

If an atom is passed as allow or deny, the atom is interpreted as a check name and all rules using the given check name are returned, regardless of whether additional options are passed to the check. If a 2-tuple is passed, the first tuple element must be the check name as an atom and the second tuple element must be the check options. In this case, all rules are returned that use the given check with exactly the same options. In either case, rules that have more checks in addition to the given one will also be returned.

Examples

iex> rules = [
...>   %LetMe.Rule{action: :create, name: :article_create, object: :article},
...>   %LetMe.Rule{action: :create, name: :category_create, object: :category}
...> ]
iex> filter_rules(rules, object: :article)
[%LetMe.Rule{action: :create, name: :article_create, object: :article}]

iex> rules = [
...>   %LetMe.Rule{
...>     action: :create,
...>     name: :article_create,
...>     object: :article,
...>     allow: [[role: :editor]]
...>   },
...>   %LetMe.Rule{
...>     action: :update,
...>     name: :article_update,
...>     object: :article,
...>     allow: [:own_resource, [role: :writer]]
...>   }
...> ]
iex> filter_rules(rules, allow: :own_resource)
[%LetMe.Rule{action: :update, name: :article_update, object: :article, allow: [:own_resource, [role: :writer]]}]
iex> match?([_, _], filter_rules(rules, allow: :role))
true
iex> filter_rules(rules, allow: {:role, :editor})
[%LetMe.Rule{action: :create, name: :article_create, object: :article, allow: [[role: :editor]]}]
iex> filter_rules(rules, allow: {:role, :writer})
[%LetMe.Rule{action: :update, name: :article_update, object: :article, allow: [:own_resource, [role: :writer]]}]
Link to this function

redact(struct, subject, opts \\ [])

View Source
@spec redact(struct(), any(), keyword()) :: struct()
@spec redact([struct()], any(), keyword()) :: [struct()]
@spec redact(nil, any(), keyword()) :: nil

Takes a struct or a list of structs and redacts fields depending on the subject (user).

Uses the callback implementation for LetMe.Schema.redacted_fields/3 in the struct module.

Options

  • :redact_value - The value to be used for redacted fields. Defaults to :redacted.

Any additional options will be passed to LetMe.Schema.redacted_fields/3.

Example

iex> article = %MyApp.Blog.Article{}
iex> user = %{id: 2, role: :user}
iex> redact(article, user)
%MyApp.Blog.Article{like_count: :redacted, title: "Give us back our moon dust and cockroaches", user_id: 1, view_count: :redacted}

iex> article = %MyApp.Blog.Article{}
iex> user = %{id: 2, role: :user}
iex> redact(article, user, redact_value: nil)
%MyApp.Blog.Article{like_count: nil, title: "Give us back our moon dust and cockroaches", user_id: 1, view_count: nil}

iex> articles = [
...>   %MyApp.Blog.Article{},
...>   %MyApp.Blog.Article{user_id: 2, title: "Joey Chestnut is chomp champ"}
...> ]
iex> user = %{id: 2, role: :user}
iex> redact(articles, user)
[%MyApp.Blog.Article{like_count: :redacted, title: "Give us back our moon dust and cockroaches", user_id: 1, view_count: :redacted}, %MyApp.Blog.Article{like_count: 25, title: "Joey Chestnut is chomp champ", user_id: 2, view_count: :redacted}]
Link to this function

reject_redacted_fields(fields, object, subject, opts \\ [])

View Source
@spec reject_redacted_fields([atom()], struct(), any(), keyword()) :: [atom()]

Removes redacted fields from a given list of fields.

Uses the LetMe.Schema.redacted_fields/3 callback implementation of the struct module to determine the fields to remove.

Examples

iex> fields = [:like_count, :title, :user_id, :view_count]
iex> user = %{id: 1, role: :user}
iex> article = %MyApp.Blog.Article{}
iex> result = reject_redacted_fields(fields, article, user)
iex> Enum.sort(result)
[:like_count, :title, :user_id]

This can be useful as a safeguard to prevent accidentally casting fields the user is not allowed to see and thereby nilifying or replacing them.

def update_changeset(%Article{} = article, attrs, %User{} = user) do
  fields = LetMe.reject_redacted_fields(
    [:title, :body, :internal_reference],
    article,
    user
  )

  article
  |> cast(attrs, fields)
  |> validate_required([:title, :body])
end

If a keyword list is given as a fourth argument, it is passed to LetMe.Schema.redacted_fields/3.