Paddle (penguin_paddle v0.1.6) View Source

Module handling ldap requests and translate them to the :eldap syntax.

Configuration

The configuration should be in the dev.secret.exs or prod.secret.exs depending on the environment you're working on. Here's an example config:

config :penguin_paddle, Paddle,
  host: "ldap.my-organisation.org",
  base: "dc=myorganisation,dc=org",
  ssl: true,
  port: 636,
  ipv6: true,
  tcpopts: [],
  sslopts: [certfile: '/path/to/certificate.crt'],
  timeout: 3000,
  account_subdn: "ou=People",
  schema_files: Path.wildcard("/etc/openldap/schema/*.schema"),
  filter_passwords: true
OptionDescriptionDefault
:hostThe host(s) containing the LDAP server(s). Can be a bitstring for a single host, or a list of bitstrings, which will make Paddle try to connect to each host in the specified order. See also the :timeout option.Mandatory
:baseThe base DN.""
:sslWhen set to true, use SSL to connect to the LDAP server.false
:portThe port the LDAP server listen to.389
:ipv6When set to true, connect to the LDAP server using IPv6.false
:tcpoptsAdditionnal :gen_tcp.connect/4 / :ssl.connect/4 options. Must not have the :active, :binary, :deliver, :list, :mode or :packet options. See :gen_tcp's option documentation.[]
:ssloptsAdditionnal :ssl.connect/4 options. Ineffective if the :ssl option is set to false. See :ssl's option documentation.[]
:timeoutThe timeout in milliseconds, or nil for the default TCP stack timeout value (which may be very long), for each request to the LDAP server.nil
:account_subdnThe DN (without the base) where the accounts are located. Used by the Paddle.authenticate/2 function."ou=People"
:account_identifierThe identifier by which users are identified. Used by the Paddle.authenticate/2 function.:uid
:schema_filesFiles which are to be parsed to help generate classes using Paddle.Class.Helper.[]
:filter_passwordsFilter passwords from appearing in the logstrue

Usage

To check a user's credentials and/or authenticate the connection, simply do:

Paddle.authenticate("username", "password")

You can also specify the partial DN like so:

Paddle.authenticate([cn: "admin"], "adminpassword")

Many functions support passing both a base and a filter via a keyword list or a map like so:

Paddle.get(filter: [uid: "testuser"], base: [ou: "People"])

But you can also use structs which implements the Paddle.Class protocol (called class objects). If we take as example the classes defined in test/support/classes.ex, we could do:

Paddle.get %MyApp.PosixAccount{uid: "user"}

The previous example will return every accounts which are in a given subDN (defined in the Paddle.Class protocol), which have the right objectClass (also defined in the same protocol), and have an uid of "user".

You can also specify an additional filter as second argument.

Class objects

A class object is simply a struct implementing the Paddle.Class protocol.

If you're in need of some examples, you can see the test/support/classes.ex file which defines MyApp.PosixAccount, and MyApp.PosixGroup (but only in test mode, so you would have to define your own).

For more informations, see the Paddle.Class module documentation.

Filters

A filter in Paddle is a keyword list or a map.

This is equivalent to a filter where each attribute name (key from the map / keyword list) must have a corresponding value (value from the map / keyword list).

Example:

[uid: "user", cn: "User", homeDirectory: "/home/user"]

If you are missing some filtering capabilities, you can always pass as argument an :eldap filter like so:

Paddle.get(filter: :eldap.substrings('uid', initial: 'b'))

For more informations and examples, see Paddle.Filters.construct_filter/1

Bases

A base in Paddle can be a Keyword list that will be converted to a charlist to be passed on to the :eldap module. A direct string can also be passed.

For more informations and examples, see Paddle.Filters.construct_dn/2

Link to this section Summary

Functions

Add an entry to the LDAP given a class object.

Add an entry to the LDAP given a DN and a list of attributes.

Check the given credentials and authenticate the current connection.

Returns a specification to start this module under a supervisor.

Get the whole configuration of the Paddle application.

Get the environment configuration of the Paddle application under a certain key.

Same as config/1 but allows you to specify a default value.

Delete a LDAP entry given a DN or a class object.

Get one or more LDAP entries given a partial DN and a filter.

Get an entry in the LDAP given a class object. You can specify an optional additional filter as second argument.

Same as get/1 but throws in case of an error.

Same as get/2 but throws in case of an error.

Get the DN of an entry.

Get a single LDAP entry given an optional partial DN and an optional filter.

Modify an LDAP entry given a DN or a class object and a list of modifications.

Closes the current connection and opens a new one.

Link to this section Types

Specs

add_ldap_error() ::
  :undefinedAttributeType
  | :objectClassViolation
  | :invalidAttributeSyntax
  | :noSuchObject
  | :insufficientAccessRights
  | :entryAlreadyExists

Specs

attributes() ::
  keyword() | %{required(binary()) => binary()} | [{binary(), binary()}]

Specs

auth_status() :: :ok | {:error, atom()}
Link to this type

authenticate_ldap_error()

View Source

Specs

authenticate_ldap_error() ::
  :operationsError
  | :protocolError
  | :authMethodNotSupported
  | :strongAuthRequired
  | :referral
  | :saslBindInProgress
  | :inappropriateAuthentication
  | :invalidCredentials
  | :unavailable
  | :anonymous_auth

Specs

delete_ldap_error() ::
  :noSuchObject | :notAllowedOnNonLeaf | :insufficientAccessRights

Specs

dn() :: keyword() | binary()

Specs

ldap_entry() :: %{required(binary()) => binary()}

Specs

mod() ::
  {:add, {binary() | atom(), binary() | [binary()]}}
  | {:delete, binary()}
  | {:replace, {binary() | atom(), binary() | [binary()]}}

Specs

modify_ldap_error() ::
  :noSuchObject
  | :undefinedAttributeType
  | :namingViolation
  | :attributeOrValueExists
  | :invalidAttributeSyntax
  | :notAllowedOnRDN
  | :objectClassViolation
  | :objectClassModsProhibited
  | :insufficientAccessRights

Specs

reason() :: :normal | :shutdown | {:shutdown, term()} | term()

Specs

search_ldap_error() ::
  :noSuchObject
  | :sizeLimitExceeded
  | :timeLimitExceeded
  | :undefinedAttributeType
  | :insufficientAccessRights

Link to this section Functions

Specs

add(Paddle.Class.t()) ::
  :ok
  | {:error, :missing_unique_identifier}
  | {:error, :missing_req_attributes, [atom()]}
  | {:error, add_ldap_error()}

Add an entry to the LDAP given a class object.

Example:

Paddle.add(%MyApp.PosixAccount{uid: "myUser", cn: "My User", gidNumber: "501", homeDirectory: "/home/myUser"})

Specs

add(dn(), attributes()) :: :ok | {:error, add_ldap_error()}

Add an entry to the LDAP given a DN and a list of attributes.

The first argument is the DN given as a string or keyword list. The second argument is the list of attributes in the new entry as a keyword list like so:

[objectClass: ["account", "posixAccount"],
 cn: "User",
 loginShell: "/bin/bash",
 homeDirectory: "/home/user",
 uidNumber: 501,
 gidNumber: 100]

Please note that due to the limitation of char lists you cannot pass directly a char list as an attribute value. But, you can wrap it in an array like this: homeDirectory: ['/home/user']

Link to this function

authenticate(kwdn, password)

View Source

Specs

authenticate(dn(), binary()) :: :ok | {:error, authenticate_ldap_error()}

Check the given credentials and authenticate the current connection.

When given the wrong credentials, returns {:error, :invalidCredentials}

The user id can be passed as a binary, which will expand to <account_identifier>=<id>,<account subdn>,<base>, or with a keyword list if you want to specify the whole DN (but still without the base DN).

Example:

iex> Paddle.authenticate("testuser", "test")
:ok
iex> Paddle.authenticate("testuser", "wrong password")
{:error, :invalidCredentials}
iex> Paddle.authenticate([cn: "admin"], "test")
:ok

Returns a specification to start this module under a supervisor.

See Supervisor.

Specs

config() :: keyword()

Get the whole configuration of the Paddle application.

Specs

config(atom()) :: any()

Get the environment configuration of the Paddle application under a certain key.

Specs

config(atom(), any()) :: any()

Same as config/1 but allows you to specify a default value.

Specs

delete(Paddle.Class.t() | dn()) :: :ok | {:error, delete_ldap_error()}

Delete a LDAP entry given a DN or a class object.

Examples:

Paddle.delete("uid=testuser,ou=People")
Paddle.delete([uid: "testuser", ou: "People"])
Paddle.delete(%MyApp.PosixAccount{uid: "testuser"})

The three examples above do exactly the same thing (provided that the MyApp.PosixAccount is configured appropriately).

Specs

get(dn()) :: {:ok, [ldap_entry()]} | {:error, search_ldap_error()}
get(Paddle.Class.t()) ::
  {:ok, [Paddle.Class.t()]} | {:error, search_ldap_error()}

Get one or more LDAP entries given a partial DN and a filter.

Example:

iex> Paddle.get(base: [uid: "testuser", ou: "People"])
{:ok,
 [%{"cn" => ["Test User"],
   "dn" => "uid=testuser,ou=People",
   "gecos" => ["Test User,,,,"], "gidNumber" => ["120"],
   "homeDirectory" => ["/home/testuser"],
   "loginShell" => ["/bin/bash"],
   "objectClass" => ["account", "posixAccount", "top"],
   "uid" => ["testuser"], "uidNumber" => ["500"],
   "userPassword" => ["{SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP"]}]}

iex> Paddle.get(base: "uid=testuser,ou=People")
{:ok,
 [%{"cn" => ["Test User"],
   "dn" => "uid=testuser,ou=People",
   "gecos" => ["Test User,,,,"], "gidNumber" => ["120"],
   "homeDirectory" => ["/home/testuser"],
   "loginShell" => ["/bin/bash"],
   "objectClass" => ["account", "posixAccount", "top"],
   "uid" => ["testuser"], "uidNumber" => ["500"],
   "userPassword" => ["{SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP"]}]}

iex> Paddle.get(base: [uid: "nothing"])
{:error, :noSuchObject}

iex> Paddle.get(filter: [uid: "testuser"], base: [ou: "People"])
{:ok,
 [%{"cn" => ["Test User"],
   "dn" => "uid=testuser,ou=People",
   "gecos" => ["Test User,,,,"], "gidNumber" => ["120"],
   "homeDirectory" => ["/home/testuser"],
   "loginShell" => ["/bin/bash"],
   "objectClass" => ["account", "posixAccount", "top"],
   "uid" => ["testuser"], "uidNumber" => ["500"],
   "userPassword" => ["{SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP"]}]}
Link to this function

get(object, additional_filter \\ nil)

View Source

Specs

get(Paddle.Class.t(), Paddle.Filters.t()) ::
  {:ok, [Paddle.Class.t()]} | {:error, search_ldap_error()}

Get an entry in the LDAP given a class object. You can specify an optional additional filter as second argument.

Example:

iex> Paddle.get(%MyApp.PosixAccount{})
{:ok,
 [%MyApp.PosixAccount{cn: ["Test User"], description: nil,
   gecos: ["Test User,,,,"], gidNumber: ["120"],
   homeDirectory: ["/home/testuser"], host: nil, l: nil,
   loginShell: ["/bin/bash"], o: nil,
   ou: nil, seeAlso: nil, uid: ["testuser"],
   uidNumber: ["500"],
   userPassword: ["{SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP"]}]}

iex> Paddle.get(%MyApp.PosixGroup{cn: "users"})
{:ok,
 [%MyApp.PosixGroup{cn: ["users"], description: nil, gidNumber: ["2"],
   memberUid: ["testuser"], userPassword: nil}]}

iex> Paddle.get(%MyApp.PosixGroup{}, :eldap.substrings('cn', initial: 'a'))
{:ok,
 [%MyApp.PosixGroup{cn: ["adm"], description: nil, gidNumber: ["3"],
   memberUid: nil, userPassword: nil}]}

Specs

get!(dn()) :: [ldap_entry()]
get!(Paddle.Class.t()) :: [Paddle.Class.t()]

Same as get/1 but throws in case of an error.

Link to this function

get!(object, additional_filter \\ [])

View Source

Specs

Same as get/2 but throws in case of an error.

Specs

get_dn(Paddle.Class.t()) ::
  {:ok, binary()} | {:error, :missing_unique_identifier}

Get the DN of an entry.

Example:

iex> Paddle.get_dn(%MyApp.PosixAccount{uid: "testuser"})
{:ok, "uid=testuser,ou=People"}

Specs

get_single(dn()) :: {:ok, ldap_entry()} | {:error, search_ldap_error()}

Get a single LDAP entry given an optional partial DN and an optional filter.

Example:

iex> Paddle.get_single(base: [ou: "People"])
{:ok,
 %{"dn" => "ou=People",
  "objectClass" => ["top", "organizationalUnit"], "ou" => ["People"]}}

iex> Paddle.get_single(filter: [uid: "nothing"])
{:error, :noSuchObject}

Specs

modify(Paddle.Class.t() | dn(), [mod()]) :: :ok | {:error, modify_ldap_error()}

Modify an LDAP entry given a DN or a class object and a list of modifications.

A modification is specified like so:

{action, {parameters...}}

Available modifications:

  • {:add, {field, value}}
  • {:delete, field}
  • {:replace, {field, value}}

For example, adding a "description" field:

{:add, {"description", "This is a description"}}

This allows you to do things like this:

Paddle.modify([uid: "testuser", ou: "People"],
              add: {"description", "This is a description"},
              delete: "gecos",
              replace: {"o", ["Club *Nix", "Linux Foundation"]})

Or, using class objects:

Paddle.modify(%MyApp.PosixAccount{uid: "testuser"},
              add: {"description", "This is a description"},
              delete: "gecos",
              replace: {"o", ["Club *Nix", "Linux Foundation"]})

Closes the current connection and opens a new one.

Accepts connection information as arguments. Not specified values will be fetched from the config.

Example:

iex> Paddle.reconnect(host: ['example.com'])
{:error, {:not_connected, "connect failed"}}
iex> Paddle.reconnect()
{:ok, :connected}