paddle v0.1.1 Paddle

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 :paddle, Paddle,
  host: "ldap.my-organisation.org",
  base: "dc=myorganisation,dc=org",
  ssl: true,
  port: 636,
  account_subdn: "ou=People",
  group_subdn: "ou=Group",
  account_class: "inetOrgPerson",
  group_class: "posixGroup"
  • :host - The host of the LDAP server. Mandatory
  • :base - The base DN.
  • :ssl - When set to true, use SSL to connect to the LDAP server. Defaults to false.
  • :port - The port the LDAP server listen to. Defaults to 389.
  • :account_subdn - The DN (without the base) where the accounts are located. Defaults to "ou=People".
  • :group_subdn - The DN (without the base) where the groups are located. Defaults to "ou=Group".
  • :account_class - The class (objectClass) of all your user entries. Defaults to "posixAccount"
  • :group_class - The class (objectClass) of all your group entries. Defaults to "posixGroup"

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 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). Some are already defined:

Paddle.get %Paddle.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.

You are also provided with some “user” functions that will automatically get the information from the right subDN and check that the entry have the right objectClass, see Configuration.

Example:

Paddle.users(filter: [givenName: "User"])

Class objects

A class object is simply a struct implementing the Paddle.Class protocol. Some “classes” are already defined and implemented (see Paddle.PosixAccount, and Paddle.PosixGroup)

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

Filters

A filter in Paddle is a Keyword list and the atom corresponding to the key must have a value strictly equal to, well the given value. When multiple keywords are provided, the result must match all criteria.

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

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

Get the environment 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 keyword list

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 a keyword list

Init the LDAP connection

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

Start the LDAP process

Terminate the LDAP connection

Types

add_ldap_error()
add_ldap_error ::
  :undefinedAttributeType |
  :objectClassViolation |
  :invalidAttributeSyntax |
  :noSuchObject |
  :insufficientAccessRights |
  :entryAlreadyExists
attributes()
attributes ::
  keyword |
  %{required(binary) => binary} |
  [{binary, binary}]
auth_status()
auth_status() :: :ok | {:error, atom}
delete_ldap_error()
delete_ldap_error ::
  :noSuchObject |
  :notAllowedOnNonLeaf |
  :insufficientAccessRights
eldap_dn()
eldap_dn() :: charlist
eldap_entry()
eldap_entry() :: {:eldap_entry, eldap_dn, [{charlist, [charlist]}]}
ldap_conn()
ldap_conn() :: :eldap.handle
ldap_entry()
ldap_entry() :: %{required(binary) => binary}
mod()
mod ::
  {:add, {binary | atom, binary | [binary]}} |
  {:delete, binary} |
  {:replace, {binary | atom, binary | [binary]}}
modify_ldap_error()
modify_ldap_error ::
  :noSuchObject |
  :undefinedAttributeType |
  :namingViolation |
  :attributeOrValueExists |
  :invalidAttributeSyntax |
  :notAllowedOnRDN |
  :objectClassViolation |
  :objectClassModsProhibited |
  :insufficientAccessRights
reason()
reason() :: :normal | :shutdown | {:shutdown, term} | term
search_ldap_error()
search_ldap_error ::
  :noSuchObject |
  :sizeLimitExceeded |
  :timeLimitExceeded |
  :undefinedAttributeType |
  :insufficientAccessRights

Functions

add(class_object)
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(%Paddle.PosixAccount{uid: "myUser", cn: "My User", gidNumber: "501", homeDirectory: "/home/myUser"})
add(kwdn, attributes)
add(keyword, 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 as usual. 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']

authenticate(kwdn, password)
authenticate(keyword | binary, binary) :: boolean

Check the given credentials.

The user id can be given through a binary, which will expand to uid=<id>,<group subdn>,<base>, or through a keyword list if you want to specify the whole DN (still without the base).

Example:

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

Get the environment whole configuration of the Paddle application.

config(atom)
config(atom) :: any

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

config(key, default)
config(atom, any) :: any

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

delete(kwdn)
delete(Paddle.Class.t | keyword) ::
  :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(%Paddle.PosixAccount{uid: "testuser"})

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

get(kwdn)
get(keyword) ::
  {:ok, [ldap_entry]} |
  {:error, search_ldap_error}

Get one or more LDAP entries given a keyword list.

Example:

iex> Paddle.get(base: [uid: "testuser", ou: "People"])
{:ok,
 [%{"cn" => ["Test User"],
   "dn" => "uid=testuser,ou=People,dc=test,dc=com",
   "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,dc=test,dc=com",
   "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,dc=test,dc=com",
   "gecos" => ["Test User,,,,"], "gidNumber" => ["120"],
   "homeDirectory" => ["/home/testuser"],
   "loginShell" => ["/bin/bash"],
   "objectClass" => ["account", "posixAccount", "top"],
   "uid" => ["testuser"], "uidNumber" => ["500"],
   "userPassword" => ["{SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP"]}]}
get(object, additional_filter \\ nil)
get(Paddle.Class.t, [tuple]) ::
  {: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(%Paddle.PosixAccount{})
{:ok,
 [%Paddle.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(%Paddle.PosixGroup{cn: "users"})
{:ok,
 [%Paddle.PosixGroup{cn: ["users"], description: nil, gidNumber: ["2"],
   memberUid: ["testuser"], userPassword: nil}]}

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

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

get!(object, additional_filter \\ [])
get!(Paddle.Class.t, [tuple]) :: [Paddle.Class.t]

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

get_dn(object)
get_dn(struct) ::
  {:ok, binary} |
  {:error, :missing_unique_identifier}

Get the DN of an entry.

Example:

iex> Paddle.get_dn(%Paddle.PosixAccount{uid: "testuser"})
{:ok, "uid=testuser,ou=People"}
get_single(kwdn)
get_single(keyword) ::
  {:ok, ldap_entry} |
  {:error, search_ldap_error}

Get a single LDAP entry given a keyword list.

Example:

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

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

Init the LDAP connection.

This is called by the GenServer.start_link/3 function. GenServer will then handle and keep the state, which is in this case the ldap connection, and pass it we we need it.

modify(kwdn, mods)
modify(Paddle.Class.t | keyword, 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"]})
start(type, args)
start_link()
start_link() :: Genserver.on_start

Start the LDAP process.

This function is called by the supervisor handled by the main application in MyApplication.start/2.

terminate(reason, ldap_conn)
terminate(reason, ldap_conn) :: :ok

Terminate the LDAP connection.

Called by GenServer when the process is stopped.