defmodule Meta.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(:meta, :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(:meta, :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(:meta, :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