From 23f78c75738aac49a79de2834490048cde817669 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 14:27:11 -0400 Subject: [PATCH 1/6] Refactor password changes to go through Pleroma.Web.Auth so they can be supported by the different auth backends --- lib/pleroma/web/auth/authenticator.ex | 5 ++++ lib/pleroma/web/auth/pleroma_authenticator.ex | 20 +++++++++++++ lib/pleroma/web/auth/wrapper_authenticator.ex | 4 +++ .../controllers/util_controller.ex | 29 ++++++++++--------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index 01bf1575c..95be892cd 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -10,4 +10,9 @@ defmodule Pleroma.Web.Auth.Authenticator do @callback handle_error(Plug.Conn.t(), any()) :: any() @callback auth_template() :: String.t() | nil @callback oauth_consumer_template() :: String.t() | nil + + @callback change_password(Pleroma.User.t(), String.t(), String.t(), String.t()) :: + {:ok, Pleroma.User.t()} | {:error, term()} + + @optional_callbacks change_password: 4 end diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 09a58eb66..0da3f19fc 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.AuthenticationPlug import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1] @@ -101,4 +102,23 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do def auth_template, do: nil def oauth_consumer_template, do: nil + + @doc "Changes Pleroma.User password in the database" + def change_password(user, password, new_password, new_password) do + case CommonAPI.Utils.confirm_current_password(user, password) do + {:ok, user} -> + with {:ok, _user} <- + User.reset_password(user, %{ + password: new_password, + password_confirmation: new_password + }) do + {:ok, user} + end + + error -> + error + end + end + + def change_password(_, _, _, _), do: {:error, :password_confirmation} end diff --git a/lib/pleroma/web/auth/wrapper_authenticator.ex b/lib/pleroma/web/auth/wrapper_authenticator.ex index a077cfa41..97b901036 100644 --- a/lib/pleroma/web/auth/wrapper_authenticator.ex +++ b/lib/pleroma/web/auth/wrapper_authenticator.ex @@ -39,4 +39,8 @@ defmodule Pleroma.Web.Auth.WrapperAuthenticator do implementation().oauth_consumer_template() || Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html") end + + @impl true + def change_password(user, password, new_password, new_password_confirmation), + do: implementation().change_password(user, password, new_password, new_password_confirmation) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 6805233df..aeafa195d 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Healthcheck alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.WebFinger @@ -195,19 +196,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, _ ) do - case CommonAPI.Utils.confirm_current_password(user, body_params.password) do - {:ok, user} -> - with {:ok, _user} <- - User.reset_password(user, %{ - password: body_params.new_password, - password_confirmation: body_params.new_password_confirmation - }) do - json(conn, %{status: "success"}) - else - {:error, changeset} -> - {_, {error, _}} = Enum.at(changeset.errors, 0) - json(conn, %{error: "New password #{error}."}) - end + with {:ok, %User{}} <- + Authenticator.change_password( + user, + body_params.password, + body_params.new_password, + body_params.new_password_confirmation + ) do + json(conn, %{status: "success"}) + else + {:error, %Ecto.Changeset{} = changeset} -> + {_, {error, _}} = Enum.at(changeset.errors, 0) + json(conn, %{error: "New password #{error}."}) + + {:error, :password_confirmation} -> + json(conn, %{error: "New password does not match confirmation."}) {:error, msg} -> json(conn, %{error: msg}) From 67cc38b5ac0cb009a38de3b182f34bbcb97467da Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 15:39:38 -0400 Subject: [PATCH 2/6] Support password changes for LDAP auth backend --- lib/pleroma/ldap.ex | 30 +++++++++++++++++++--- lib/pleroma/web/auth/ldap_authenticator.ex | 9 +++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex index 46a2d0c17..9c1263fcf 100644 --- a/lib/pleroma/ldap.ex +++ b/lib/pleroma/ldap.ex @@ -83,6 +83,12 @@ defmodule Pleroma.LDAP do end end + def handle_call({:change_password, name, password, new_password}, _from, state) do + result = change_password(state[:handle], name, password, new_password) + + {:reply, result, state, :hibernate} + end + @impl true def terminate(_, state) do handle = Keyword.get(state, :handle) @@ -162,17 +168,16 @@ defmodule Pleroma.LDAP do end defp bind_user(handle, name, password) do - uid = Config.get([:ldap, :uid], "cn") - base = Config.get([:ldap, :base]) + dn = make_dn(name) - case :eldap.simple_bind(handle, "#{uid}=#{name},#{base}", password) do + case :eldap.simple_bind(handle, dn, password) do :ok -> case fetch_user(name) do %User{} = user -> user _ -> - register_user(handle, base, uid, name) + register_user(handle, ldap_base(), ldap_uid(), name) end # eldap does not inform us of socket closure @@ -231,6 +236,14 @@ defmodule Pleroma.LDAP do end end + defp change_password(handle, name, password, new_password) do + dn = make_dn(name) + + with :ok <- :eldap.simple_bind(handle, dn, password) do + :eldap.modify_password(handle, dn, to_charlist(new_password), to_charlist(password)) + end + end + defp decode_certfile(file) do with {:ok, data} <- File.read(file) do data @@ -242,4 +255,13 @@ defmodule Pleroma.LDAP do [] end end + + defp ldap_uid, do: to_charlist(Config.get([:ldap, :uid], "cn")) + defp ldap_base, do: to_charlist(Config.get([:ldap, :base])) + + defp make_dn(name) do + uid = ldap_uid() + base = ldap_base() + ~c"#{uid}=#{name},#{base}" + end end diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 7eb06183d..9bdf8447d 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -30,4 +30,13 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do error end end + + def change_password(user, password, new_password, new_password) do + case GenServer.call(LDAP, {:change_password, user.nickname, password, new_password}) do + :ok -> {:ok, user} + e -> e + end + end + + def change_password(_, _, _, _), do: {:error, :password_confirmation} end From ff039f953043d2c15f1eb44f794a77865ab5a775 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 15:41:08 -0400 Subject: [PATCH 3/6] Add example OpenLDAP ldif to enable users to change their own passwords --- installation/openldap/pw_self_service.ldif | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 installation/openldap/pw_self_service.ldif diff --git a/installation/openldap/pw_self_service.ldif b/installation/openldap/pw_self_service.ldif new file mode 100644 index 000000000..463dabbfb --- /dev/null +++ b/installation/openldap/pw_self_service.ldif @@ -0,0 +1,7 @@ +dn: olcDatabase={1}mdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {1}to attrs=userPassword + by self write + by anonymous auth + by * none From 6bc70b8b2a7c6942bfda01bfcc301a198cf3238b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 15:45:09 -0400 Subject: [PATCH 4/6] Add change_password/3 to LDAP module --- lib/pleroma/ldap.ex | 4 ++++ lib/pleroma/web/auth/ldap_authenticator.ex | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex index 9c1263fcf..2bc894bd8 100644 --- a/lib/pleroma/ldap.ex +++ b/lib/pleroma/ldap.ex @@ -104,6 +104,10 @@ defmodule Pleroma.LDAP do GenServer.call(__MODULE__, {:bind_user, name, password}) end + def change_password(name, password, new_password) do + GenServer.call(__MODULE__, {:change_password, name, password, new_password}) + end + defp connect do ldap = Config.get(:ldap, []) host = Keyword.get(ldap, :host, "localhost") diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 9bdf8447d..ec6601fb9 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -32,7 +32,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do end def change_password(user, password, new_password, new_password) do - case GenServer.call(LDAP, {:change_password, user.nickname, password, new_password}) do + case LDAP.change_password(user.nickname, password, new_password) do :ok -> {:ok, user} e -> e end From 1da057e6a4e4f8f7ddeb0ba286a3b996c1ba7710 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 15:51:56 -0400 Subject: [PATCH 5/6] Reorganize the LDAP module --- lib/pleroma/ldap.ex | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex index 2bc894bd8..b591c2918 100644 --- a/lib/pleroma/ldap.ex +++ b/lib/pleroma/ldap.ex @@ -15,6 +15,14 @@ defmodule Pleroma.LDAP do GenServer.start_link(__MODULE__, [], name: __MODULE__) end + def bind_user(name, password) do + GenServer.call(__MODULE__, {:bind_user, name, password}) + end + + def change_password(name, password, new_password) do + GenServer.call(__MODULE__, {:change_password, name, password, new_password}) + end + @impl true def init(state) do case {Config.get(Pleroma.Web.Auth.Authenticator), Config.get([:ldap, :enabled])} do @@ -47,33 +55,16 @@ defmodule Pleroma.LDAP do def handle_info(:connect, _state), do: do_handle_connect() def handle_info({:bind_after_reconnect, name, password, from}, state) do - result = bind_user(state[:handle], name, password) + result = do_bind_user(state[:handle], name, password) GenServer.reply(from, result) {:noreply, state} end - defp do_handle_connect do - state = - case connect() do - {:ok, handle} -> - :eldap.controlling_process(handle, self()) - Process.link(handle) - [handle: handle] - - _ -> - Logger.error("Failed to connect to LDAP. Retrying in 5000ms") - Process.send_after(self(), :connect, 5_000) - [] - end - - {:noreply, state} - end - @impl true def handle_call({:bind_user, name, password}, from, state) do - case bind_user(state[:handle], name, password) do + case do_bind_user(state[:handle], name, password) do :needs_reconnect -> Process.send(self(), {:bind_after_reconnect, name, password, from}, []) {:noreply, state, {:continue, :connect}} @@ -100,12 +91,21 @@ defmodule Pleroma.LDAP do :ok end - def bind_user(name, password) do - GenServer.call(__MODULE__, {:bind_user, name, password}) - end + defp do_handle_connect do + state = + case connect() do + {:ok, handle} -> + :eldap.controlling_process(handle, self()) + Process.link(handle) + [handle: handle] - def change_password(name, password, new_password) do - GenServer.call(__MODULE__, {:change_password, name, password, new_password}) + _ -> + Logger.error("Failed to connect to LDAP. Retrying in 5000ms") + Process.send_after(self(), :connect, 5_000) + [] + end + + {:noreply, state} end defp connect do @@ -171,7 +171,7 @@ defmodule Pleroma.LDAP do end end - defp bind_user(handle, name, password) do + defp do_bind_user(handle, name, password) do dn = make_dn(name) case :eldap.simple_bind(handle, dn, password) do From b6a951cfb5e277aa265436674055a4d0b993c5b9 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Oct 2024 16:20:38 -0400 Subject: [PATCH 6/6] LDAP password changing changelog --- changelog.d/ldap-password-change.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/ldap-password-change.add diff --git a/changelog.d/ldap-password-change.add b/changelog.d/ldap-password-change.add new file mode 100644 index 000000000..7ca555ee4 --- /dev/null +++ b/changelog.d/ldap-password-change.add @@ -0,0 +1 @@ +LDAP now supports users changing their passwords