131 lines
3.8 KiB
Elixir
131 lines
3.8 KiB
Elixir
defmodule RecycledCloud.LDAP do
|
|
require Logger
|
|
use GenServer
|
|
|
|
# Every request to the LDAP backend go through this GenServer.
|
|
|
|
def start_link(_) do
|
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
|
end
|
|
|
|
def init(_) do
|
|
{:ok, connect()}
|
|
end
|
|
|
|
def handle_call({:pool, query}, _from, state) do
|
|
case state do
|
|
%{status: :ok, conn: ldap_conn} ->
|
|
result = internal_execute_query(query, ldap_conn)
|
|
if (result == {:error, :ldap_closed}) do
|
|
{:reply, result, connect()}
|
|
else
|
|
{:reply, result, state}
|
|
end
|
|
%{status: :error, conn: nil} ->
|
|
state = connect()
|
|
if (state.status == :ok) do
|
|
result = internal_execute_query(query, state.conn)
|
|
{:reply, result, state}
|
|
else
|
|
error = {:error, "Unable to contact backend"}
|
|
{:reply, error, state}
|
|
end
|
|
end
|
|
end
|
|
|
|
def handle_call({:single, query}, _from, state) do
|
|
single_state = connect()
|
|
case single_state do
|
|
%{status: :ok, conn: ldap_conn} ->
|
|
result = internal_execute_query(query, ldap_conn)
|
|
close(ldap_conn)
|
|
{:reply, result, state}
|
|
%{status: :error, conn: nil} ->
|
|
error = {:error, "Unable to contact backend"}
|
|
{:reply, error, state}
|
|
end
|
|
end
|
|
|
|
def terminate(reason, state) do
|
|
if (state.status == :ok), do: close(state.conn)
|
|
Logger.info "Terminating LDAP backend: #{inspect(reason)}."
|
|
end
|
|
|
|
###
|
|
|
|
def search(ldap_conn, field, value, search_timeout \\ 1000) do
|
|
base_dn = Application.get_env(:recycledcloud, :ldap) |> Keyword.get(:base_dn)
|
|
opts = [
|
|
{:base, to_charlist(base_dn)},
|
|
{:scope, :eldap.wholeSubtree()},
|
|
{:filter, :eldap.equalityMatch(to_charlist(field), value)},
|
|
{:timeout, search_timeout}
|
|
]
|
|
|
|
case :eldap.search(ldap_conn, opts) do
|
|
{:ok, {:eldap_search_result, eldap_result, _}} ->
|
|
entries = Enum.map(
|
|
eldap_result,
|
|
fn entry ->
|
|
{:eldap_entry, dn, attrs} = entry
|
|
Enum.reduce(attrs, %{:dn => dn},
|
|
fn pair, acc ->
|
|
{key, value} = pair
|
|
%{List.to_atom(key) => value} |> Map.merge(acc)
|
|
end
|
|
)
|
|
end
|
|
)
|
|
|
|
{:ok, entries}
|
|
{:error, err} -> {:error, err}
|
|
end
|
|
end
|
|
def execute(query), do: GenServer.call(__MODULE__, {:pool, query})
|
|
def execute_single(query), do: GenServer.call(__MODULE__, {:single, query})
|
|
|
|
###
|
|
|
|
defp internal_execute_query(query, ldap_conn), do: query.(ldap_conn)
|
|
|
|
defp bind(ldap_conn) do
|
|
conf = Application.get_env(:recycledcloud, :ldap, [])
|
|
dn = conf |> Keyword.get(:bind_dn)
|
|
pw = conf |> Keyword.get(:bind_pw)
|
|
|
|
:eldap.simple_bind(ldap_conn, dn, pw)
|
|
end
|
|
|
|
# The method is used by the RC.LDAPTestEnvironment module.
|
|
def connect do
|
|
conf = Application.get_env(:recycledcloud, :ldap, [])
|
|
|
|
host = Keyword.get(conf, :server, "localhost") |> String.to_charlist
|
|
opts = [
|
|
{:port, Keyword.get(conf, :port, 389)},
|
|
{:ssl, Keyword.get(conf, :ssl, false)},
|
|
{:tcpopts, Keyword.get(conf, :tcpopts, [])}
|
|
]
|
|
case :eldap.open([host], opts) do
|
|
{:ok, ldap_conn} ->
|
|
Logger.info "Successfuly connected to LDAP server."
|
|
case bind(ldap_conn) do
|
|
{:error, err} ->
|
|
Logger.warning("Could not bind to LDAP server: #{inspect(err)}")
|
|
_ -> :noop
|
|
end
|
|
|
|
%{status: :ok, conn: ldap_conn}
|
|
{:error, err} ->
|
|
Logger.warning "Failed to connect to LDAP server: #{inspect(err)}. Authentication will not be possible."
|
|
%{status: :error, conn: nil}
|
|
end
|
|
end
|
|
|
|
# The method is used by the RC.LDAPTestEnvironment module.
|
|
def close(ldap_conn) do
|
|
ldap_conn |> :eldap.close
|
|
Logger.debug("An LDAP connection was closed.")
|
|
end
|
|
end
|
|
|