# Pleroma: A lightweight social networking server # Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.LDAPAuthenticator do alias Pleroma.User require Logger @behaviour Pleroma.Web.Auth.Authenticator @connection_timeout 10_000 @search_timeout 10_000 def get_user(%Plug.Conn{} = conn) do if Pleroma.Config.get([:ldap, :enabled]) do {name, password} = case conn.params do %{"authorization" => %{"name" => name, "password" => password}} -> {name, password} %{"grant_type" => "password", "username" => name, "password" => password} -> {name, password} end case ldap_user(name, password) do %User{} = user -> {:ok, user} {:error, {:ldap_connection_error, _}} -> # When LDAP is unavailable, try default authenticator Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) error -> error end else # Fall back to default authenticator Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) end end def handle_error(%Plug.Conn{} = _conn, error) do error end def auth_template, do: nil defp ldap_user(name, password) do ldap = Pleroma.Config.get(:ldap, []) host = Keyword.get(ldap, :host, "localhost") port = Keyword.get(ldap, :port, 389) ssl = Keyword.get(ldap, :ssl, false) sslopts = Keyword.get(ldap, :sslopts, []) options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ if sslopts != [], do: [{:sslopts, sslopts}], else: [] case :eldap.open([to_charlist(host)], options) do {:ok, connection} -> try do if Keyword.get(ldap, :tls, false) do :application.ensure_all_started(:ssl) case :eldap.start_tls( connection, Keyword.get(ldap, :tlsopts, []), @connection_timeout ) do :ok -> :ok error -> Logger.error("Could not start TLS: #{inspect(error)}") end end bind_user(connection, ldap, name, password) after :eldap.close(connection) end {:error, error} -> Logger.error("Could not open LDAP connection: #{inspect(error)}") {:error, {:ldap_connection_error, error}} end end defp bind_user(connection, ldap, name, password) do uid = Keyword.get(ldap, :uid, "cn") base = Keyword.get(ldap, :base) case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do :ok -> case User.get_by_nickname_or_email(name) do %User{} = user -> user _ -> register_user(connection, base, uid, name, password) end error -> error end end defp register_user(connection, base, uid, name, password) do case :eldap.search(connection, [ {:base, to_charlist(base)}, {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, {:scope, :eldap.wholeSubtree()}, {:attributes, ['mail', 'email']}, {:timeout, @search_timeout} ]) do {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do params = %{ email: :erlang.list_to_binary(mail), name: name, nickname: name, password: password, password_confirmation: password } changeset = User.register_changeset(%User{}, params) case User.register(changeset) do {:ok, user} -> user error -> error end else _ -> Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}") {:error, :ldap_registration_missing_attributes} end error -> error end end end