meta/lib/recycledcloud/ldap.ex
2021-01-13 17:02:55 +01:00

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