diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fe07f290..8546370e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `/api/v1/notifications/dismiss` - `/api/v1/search` - `/api/v1/statuses/{id}/card` +- LDAP authenticator ## 2022.07 diff --git a/config/config.exs b/config/config.exs index 9186b011d..61b5a0317 100644 --- a/config/config.exs +++ b/config/config.exs @@ -511,6 +511,7 @@ "~", "about", "activities", + "akkoma", "api", "auth", "check_password", @@ -591,17 +592,6 @@ extra: true, validate_tld: :no_scheme -config :pleroma, :ldap, - enabled: System.get_env("LDAP_ENABLED") == "true", - host: System.get_env("LDAP_HOST") || "localhost", - port: String.to_integer(System.get_env("LDAP_PORT") || "389"), - ssl: System.get_env("LDAP_SSL") == "true", - sslopts: [], - tls: System.get_env("LDAP_TLS") == "true", - tlsopts: [], - base: System.get_env("LDAP_BASE") || "dc=example,dc=com", - uid: System.get_env("LDAP_UID") || "cn" - oauth_consumer_strategies = "OAUTH_CONSUMER_STRATEGIES" |> System.get_env() diff --git a/config/description.exs b/config/description.exs index 098a6f5b7..c67da64b5 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2158,104 +2158,6 @@ } ] }, - %{ - group: :pleroma, - key: :ldap, - label: "LDAP", - type: :group, - description: - "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <> - " will be verified by trying to authenticate (bind) to a LDAP server." <> - " If a user exists in the LDAP directory but there is no account with the same name yet on the" <> - " Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.", - children: [ - %{ - key: :enabled, - type: :boolean, - description: "Enables LDAP authentication" - }, - %{ - key: :host, - type: :string, - description: "LDAP server hostname", - suggestions: ["localhosts"] - }, - %{ - key: :port, - type: :integer, - description: "LDAP port, e.g. 389 or 636", - suggestions: [389, 636] - }, - %{ - key: :ssl, - label: "SSL", - type: :boolean, - description: "Enable to use SSL, usually implies the port 636" - }, - %{ - key: :sslopts, - label: "SSL options", - type: :keyword, - description: "Additional SSL options", - suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], - children: [ - %{ - key: :cacertfile, - type: :string, - description: "Path to file with PEM encoded cacerts", - suggestions: ["path/to/file/with/PEM/cacerts"] - }, - %{ - key: :verify, - type: :atom, - description: "Type of cert verification", - suggestions: [:verify_peer] - } - ] - }, - %{ - key: :tls, - label: "TLS", - type: :boolean, - description: "Enable to use STARTTLS, usually implies the port 389" - }, - %{ - key: :tlsopts, - label: "TLS options", - type: :keyword, - description: "Additional TLS options", - suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], - children: [ - %{ - key: :cacertfile, - type: :string, - description: "Path to file with PEM encoded cacerts", - suggestions: ["path/to/file/with/PEM/cacerts"] - }, - %{ - key: :verify, - type: :atom, - description: "Type of cert verification", - suggestions: [:verify_peer] - } - ] - }, - %{ - key: :base, - type: :string, - description: "LDAP base, e.g. \"dc=example,dc=com\"", - suggestions: ["dc=example,dc=com"] - }, - %{ - key: :uid, - label: "UID", - type: :string, - description: - "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"", - suggestions: ["cn"] - } - ] - }, %{ group: :pleroma, key: :auth, diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index c7df00190..85381d67c 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -900,28 +900,6 @@ Authentication / authorization settings. ### Pleroma.Web.Auth.Authenticator * `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator. -* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication. - -### :ldap - -Use LDAP for user authentication. When a user logs in to the Akkoma -instance, the name and password will be verified by trying to authenticate -(bind) to an LDAP server. If a user exists in the LDAP directory but there -is no account with the same name yet on the Akkoma instance then a new -Akkoma account will be created with the same name as the LDAP user name. - -* `enabled`: enables LDAP authentication -* `host`: LDAP server hostname -* `port`: LDAP port, e.g. 389 or 636 -* `ssl`: true to use SSL, usually implies the port 636 -* `sslopts`: additional SSL options -* `tls`: true to start TLS, usually implies the port 389 -* `tlsopts`: additional TLS options -* `base`: LDAP base, e.g. "dc=example,dc=com" -* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base" - -Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an -OpenLDAP server the value may be `uid: "uid"`. ### :oauth2 (Akkoma as OAuth 2.0 provider settings) diff --git a/docs/docs/installation/alpine_linux_en.md b/docs/docs/installation/alpine_linux_en.md index f98998fb8..3be69af6e 100644 --- a/docs/docs/installation/alpine_linux_en.md +++ b/docs/docs/installation/alpine_linux_en.md @@ -41,12 +41,6 @@ doas apk add git build-base cmake file-dev doas apk add erlang elixir ``` -* Install `erlang-eldap` if you want to enable ldap authenticator - -```shell -doas apk add erlang-eldap -``` - ### Install PostgreSQL * Install Postgresql server: diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a9caec552..9ff52c94f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -663,34 +663,6 @@ def force_password_reset_async(user) do @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def force_password_reset(user), do: update_password_reset_pending(user, true) - # Used to auto-register LDAP accounts which won't have a password hash stored locally - def register_changeset_ldap(struct, params = %{password: password}) - when is_nil(password) do - params = Map.put_new(params, :accepts_chat_messages, true) - - params = - if Map.has_key?(params, :email) do - Map.put_new(params, :email, params[:email]) - else - params - end - - struct - |> cast(params, [ - :name, - :nickname, - :email, - :accepts_chat_messages - ]) - |> validate_required([:name, :nickname]) - |> unique_constraint(:nickname) - |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames])) - |> validate_format(:nickname, local_nickname_regex()) - |> put_ap_id() - |> unique_constraint(:ap_id) - |> put_following_and_follower_and_featured_address() - end - def register_changeset(struct, params \\ %{}, opts \\ []) do bio_limit = Config.get([:instance, :user_bio_length], 5000) name_limit = Config.get([:instance, :user_name_length], 100) diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex deleted file mode 100644 index f77e8d203..000000000 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ /dev/null @@ -1,129 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Auth.LDAPAuthenticator do - alias Pleroma.User - - require Logger - - import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1] - - @behaviour Pleroma.Web.Auth.Authenticator - @base Pleroma.Web.Auth.PleromaAuthenticator - - @connection_timeout 10_000 - @search_timeout 10_000 - - defdelegate get_registration(conn), to: @base - defdelegate create_from_registration(conn, registration), to: @base - defdelegate handle_error(conn, error), to: @base - defdelegate auth_template, to: @base - defdelegate oauth_consumer_template, to: @base - - def get_user(%Plug.Conn{} = conn) do - with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])}, - {:ok, {name, password}} <- fetch_credentials(conn), - %User{} = user <- ldap_user(name, password) do - {:ok, user} - else - {:ldap, _} -> - @base.get_user(conn) - - error -> - error - end - end - - 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 fetch_user(name) do - %User{} = user -> - user - - _ -> - register_user(connection, base, uid, name) - end - - error -> - error - end - end - - defp register_user(connection, base, uid, name) do - case :eldap.search(connection, [ - {:base, to_charlist(base)}, - {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, - {:scope, :eldap.wholeSubtree()}, - {:timeout, @search_timeout} - ]) do - {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> - params = %{ - name: name, - nickname: name, - password: nil - } - - params = - case List.keyfind(attributes, 'mail', 0) do - {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail)) - _ -> params - end - - changeset = User.register_changeset_ldap(%User{}, params) - - case User.register(changeset) do - {:ok, user} -> user - error -> error - end - - error -> - error - end - end -end diff --git a/mix.exs b/mix.exs index 420ea2e24..a0eef4f1d 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,6 @@ def project do elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], - xref: [exclude: [:eldap]], start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), @@ -35,7 +34,7 @@ def project do releases: [ pleroma: [ include_executables_for: [:unix], - applications: [ex_syslogger: :load, syslog: :load, eldap: :transient], + applications: [ex_syslogger: :load, syslog: :load], steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1], config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}] ] diff --git a/test/pleroma/web/o_auth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs deleted file mode 100644 index 61b9ce6b7..000000000 --- a/test/pleroma/web/o_auth/ldap_authorization_test.exs +++ /dev/null @@ -1,135 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do - use Pleroma.Web.ConnCase - alias Pleroma.Repo - alias Pleroma.Web.OAuth.Token - import Pleroma.Factory - import Mock - - @skip if !Code.ensure_loaded?(:eldap), do: :skip - - setup_all do: clear_config([:ldap, :enabled], true) - - setup_all do: clear_config(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) - - @tag @skip - test "authorizes the existing user using LDAP credentials" do - password = "testpassword" - user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)) - app = insert(:oauth_app, scopes: ["read", "write"]) - - host = Pleroma.Config.get([:ldap, :host]) |> to_charlist - port = Pleroma.Config.get([:ldap, :port]) - - with_mocks [ - {:eldap, [], - [ - open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, - simple_bind: fn _connection, _dn, ^password -> :ok end, - close: fn _connection -> - send(self(), :close_connection) - :ok - end - ]} - ] do - conn = - build_conn() - |> post("/oauth/token", %{ - "grant_type" => "password", - "username" => user.nickname, - "password" => password, - "client_id" => app.client_id, - "client_secret" => app.client_secret - }) - - assert %{"access_token" => token} = json_response(conn, 200) - - token = Repo.get_by(Token, token: token) - - assert token.user_id == user.id - assert_received :close_connection - end - end - - @tag @skip - test "creates a new user after successful LDAP authorization" do - password = "testpassword" - user = build(:user) - app = insert(:oauth_app, scopes: ["read", "write"]) - - host = Pleroma.Config.get([:ldap, :host]) |> to_charlist - port = Pleroma.Config.get([:ldap, :port]) - - with_mocks [ - {:eldap, [], - [ - open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, - simple_bind: fn _connection, _dn, ^password -> :ok end, - equalityMatch: fn _type, _value -> :ok end, - wholeSubtree: fn -> :ok end, - search: fn _connection, _options -> - {:ok, {:eldap_search_result, [{:eldap_entry, '', []}], []}} - end, - close: fn _connection -> - send(self(), :close_connection) - :ok - end - ]} - ] do - conn = - build_conn() - |> post("/oauth/token", %{ - "grant_type" => "password", - "username" => user.nickname, - "password" => password, - "client_id" => app.client_id, - "client_secret" => app.client_secret - }) - - assert %{"access_token" => token} = json_response(conn, 200) - - token = Repo.get_by(Token, token: token) |> Repo.preload(:user) - - assert token.user.nickname == user.nickname - assert_received :close_connection - end - end - - @tag @skip - test "disallow authorization for wrong LDAP credentials" do - password = "testpassword" - user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)) - app = insert(:oauth_app, scopes: ["read", "write"]) - - host = Pleroma.Config.get([:ldap, :host]) |> to_charlist - port = Pleroma.Config.get([:ldap, :port]) - - with_mocks [ - {:eldap, [], - [ - open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, - simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end, - close: fn _connection -> - send(self(), :close_connection) - :ok - end - ]} - ] do - conn = - build_conn() - |> post("/oauth/token", %{ - "grant_type" => "password", - "username" => user.nickname, - "password" => password, - "client_id" => app.client_id, - "client_secret" => app.client_secret - }) - - assert %{"error" => "Invalid credentials"} = json_response(conn, 400) - assert_received :close_connection - end - end -end