Macaw.Auth.Ash.Strategy.Ldap (macaw_auth_ash v0.3.1)

Copy Markdown View Source

A directory sign-in as an AshAuthentication strategy: macaw_auth's Macaw.Auth.Native.Source.Ldap (its RFC 4514 and filter escaping, its TLS rules with allow_plaintext: false, its group checks and role mapping) on an Ash user resource, which it provisions at a user's first sign-in.

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshAuthentication, Macaw.Auth.Ash.Strategy.Ldap], ...

  authentication do
    strategies do
      ldap :nas do
        source {MyApp.Directory, :nas, []}
        identity_field :username
        roles_field :roles
        source_field :source
        display_name_field :display_name
        disabled_field :disabled_at
      end
    end
  end
end

source is the directory's configuration, exactly as a native LDAP source's (Macaw.Auth.Native.Source.Ldap.Config, its name defaulting to the strategy's): a map, or {module, function, args} returning one, called at each sign-in. Prefer the function: a map in the resource is fixed when it compiles, and a host's directory and its service password belong to the runtime (bind_password: {:env, "VAR"} is resolved at sign-in either way). A configuration fault raises at sign-in, naming the key: it is a deployment bug, not a refusal.

Each field option must name an attribute of the resource. One that doesn't is warned of at compile time (the verifier's error, which Spark reports as a compile warning), refused by Macaw.Auth.Ash.Config when it loads, and never read as unset at sign-in: a source or disabled field the record lacks raises KeyError.

The DSL entity builds (unless the resource defines them already):

  • sign_in_with_<name>: a generic action taking username and password, returning the user. Through AshAuthentication (its plug, or AshAuthentication.Strategy.action/4) it signs a user token as the password strategy does; Macaw.Auth.Ash calls sign_in/4 itself and makes its own session.
  • provision_with_<name>: a create accepting the identity, roles, source and display name fields.
  • refresh_with_<name>: an update accepting the roles and display name.

Provisioning (as Macaw.Auth.Native.Provision): a directory user signing in for the first time gets a row, with source_field the directory's name and no password; at every later sign-in the directory's roles and display name replace the row's, so a user removed from an admin group loses the role at their next sign-in. A row with the username that another source owns (a local user, or another directory's) is never taken over (:username_taken), and a disabled row stays refused (:disabled), left as it is. The user resource needs a unique identity on identity_field: two first sign-ins at once then make one row.

What the directory says beyond these (its DN, entry id, email) is not stored: a user resource has no standard place for it.

Summary

Functions

A sentence explaining a refusal's reason, for logs and diagnostics.

Checks a password against the directory and provisions the user: {:ok, record}, or {:error, reason, knows?}, where knows? says whether the refusal is final (Macaw.Auth.Native.Source.Ldap.knows?/2; a provisioning refusal always is). reason is the source's (explain_refusal/1), or :username_taken or :disabled: for logs, never for the visitor.

The directory's configuration, parsed: raises ArgumentError naming a fault.

Types

t()

@type t() :: %Macaw.Auth.Ash.Strategy.Ldap{
  __spark_metadata__: term(),
  disabled_field: term(),
  display_name_field: term(),
  identity_field: term(),
  name: term(),
  provision_action_name: term(),
  refresh_action_name: term(),
  resource: term(),
  roles_field: term(),
  sign_in_action_name: term(),
  source: term(),
  source_field: term(),
  strategy_module: term()
}

Functions

explain_refusal(reason)

@spec explain_refusal(atom()) :: String.t()

A sentence explaining a refusal's reason, for logs and diagnostics.

sign_in(strategy, username, password, opts)

@spec sign_in(t(), String.t(), String.t(), keyword()) ::
  {:ok, Ash.Resource.record()} | {:error, atom(), boolean()}

Checks a password against the directory and provisions the user: {:ok, record}, or {:error, reason, knows?}, where knows? says whether the refusal is final (Macaw.Auth.Native.Source.Ldap.knows?/2; a provisioning refusal always is). reason is the source's (explain_refusal/1), or :username_taken or :disabled: for logs, never for the visitor.

source!(strategy)

@spec source!(t()) :: Macaw.Auth.Native.Source.Ldap.t()

The directory's configuration, parsed: raises ArgumentError naming a fault.