move to 2.5.5
[anni] / lib / pleroma / web / auth / ldap_authenticator.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.Auth.LDAPAuthenticator do
6   alias Pleroma.User
7
8   require Logger
9
10   import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
11
12   @behaviour Pleroma.Web.Auth.Authenticator
13   @base Pleroma.Web.Auth.PleromaAuthenticator
14
15   @connection_timeout 10_000
16   @search_timeout 10_000
17
18   defdelegate get_registration(conn), to: @base
19   defdelegate create_from_registration(conn, registration), to: @base
20   defdelegate handle_error(conn, error), to: @base
21   defdelegate auth_template, to: @base
22   defdelegate oauth_consumer_template, to: @base
23
24   def get_user(%Plug.Conn{} = conn) do
25     with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
26          {:ok, {name, password}} <- fetch_credentials(conn),
27          %User{} = user <- ldap_user(name, password) do
28       {:ok, user}
29     else
30       {:ldap, _} ->
31         @base.get_user(conn)
32
33       error ->
34         error
35     end
36   end
37
38   defp ldap_user(name, password) do
39     ldap = Pleroma.Config.get(:ldap, [])
40     host = Keyword.get(ldap, :host, "localhost")
41     port = Keyword.get(ldap, :port, 389)
42     ssl = Keyword.get(ldap, :ssl, false)
43     sslopts = Keyword.get(ldap, :sslopts, [])
44
45     options =
46       [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
47         if sslopts != [], do: [{:sslopts, sslopts}], else: []
48
49     case :eldap.open([to_charlist(host)], options) do
50       {:ok, connection} ->
51         try do
52           if Keyword.get(ldap, :tls, false) do
53             :application.ensure_all_started(:ssl)
54
55             case :eldap.start_tls(
56                    connection,
57                    Keyword.get(ldap, :tlsopts, []),
58                    @connection_timeout
59                  ) do
60               :ok ->
61                 :ok
62
63               error ->
64                 Logger.error("Could not start TLS: #{inspect(error)}")
65             end
66           end
67
68           bind_user(connection, ldap, name, password)
69         after
70           :eldap.close(connection)
71         end
72
73       {:error, error} ->
74         Logger.error("Could not open LDAP connection: #{inspect(error)}")
75         {:error, {:ldap_connection_error, error}}
76     end
77   end
78
79   defp bind_user(connection, ldap, name, password) do
80     uid = Keyword.get(ldap, :uid, "cn")
81     base = Keyword.get(ldap, :base)
82
83     case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
84       :ok ->
85         case fetch_user(name) do
86           %User{} = user ->
87             user
88
89           _ ->
90             register_user(connection, base, uid, name)
91         end
92
93       error ->
94         error
95     end
96   end
97
98   defp register_user(connection, base, uid, name) do
99     case :eldap.search(connection, [
100            {:base, to_charlist(base)},
101            {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
102            {:scope, :eldap.wholeSubtree()},
103            {:timeout, @search_timeout}
104          ]) do
105       {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
106         params = %{
107           name: name,
108           nickname: name,
109           password: nil
110         }
111
112         params =
113           case List.keyfind(attributes, 'mail', 0) do
114             {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
115             _ -> params
116           end
117
118         changeset = User.register_changeset_ldap(%User{}, params)
119
120         case User.register(changeset) do
121           {:ok, user} -> user
122           error -> error
123         end
124
125       error ->
126         error
127     end
128   end
129 end