From 26b63540953f6a65bb52531b434fd6ab85aaedfe Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 18 Mar 2019 17:23:38 +0300 Subject: [PATCH] [#923] Support for multiple (external) registrations per user via Registration. --- config/config.exs | 2 +- lib/pleroma/registration.ex | 36 +++++++++++++ lib/pleroma/user.ex | 16 ++---- lib/pleroma/web/auth/authenticator.ex | 6 +-- lib/pleroma/web/auth/ldap_authenticator.ex | 2 +- lib/pleroma/web/auth/pleroma_authenticator.ex | 51 +++++++++++-------- lib/pleroma/web/oauth/oauth_controller.ex | 2 +- ...rovider_and_auth_provider_uid_to_users.exs | 12 ----- .../20190315101315_create_registrations.exs | 16 ++++++ 9 files changed, 93 insertions(+), 50 deletions(-) create mode 100644 lib/pleroma/registration.ex delete mode 100644 priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs create mode 100644 priv/repo/migrations/20190315101315_create_registrations.exs diff --git a/config/config.exs b/config/config.exs index 6839b489b..03baf894d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -381,7 +381,7 @@ base: System.get_env("LDAP_BASE") || "dc=example,dc=com", uid: System.get_env("LDAP_UID") || "cn" -config :pleroma, :auth, oauth_consumer_enabled: false +config :pleroma, :auth, oauth_consumer_enabled: System.get_env("OAUTH_CONSUMER_ENABLED") == "true" config :ueberauth, Ueberauth, diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex new file mode 100644 index 000000000..1bd91a316 --- /dev/null +++ b/lib/pleroma/registration.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Registration do + use Ecto.Schema + + import Ecto.Changeset + + alias Pleroma.Registration + alias Pleroma.Repo + alias Pleroma.User + + schema "registrations" do + belongs_to(:user, User, type: Pleroma.FlakeId) + field(:provider, :string) + field(:uid, :string) + field(:info, :map, default: %{}) + + timestamps() + end + + def changeset(registration, params \\ %{}) do + registration + |> cast(params, [:user_id, :provider, :uid, :info]) + |> foreign_key_constraint(:user_id) + |> unique_constraint(:uid, name: :registrations_provider_uid_index) + end + + def get_by_provider_uid(provider, uid) do + Repo.get_by(Registration, + provider: to_string(provider), + uid: to_string(uid) + ) + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7f8b282e0..bd742b2fd 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -13,6 +13,7 @@ defmodule Pleroma.User do alias Pleroma.Formatter alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web @@ -41,8 +42,6 @@ defmodule Pleroma.User do field(:email, :string) field(:name, :string) field(:nickname, :string) - field(:auth_provider, :string) - field(:auth_provider_uid, :string) field(:password_hash, :string) field(:password, :string, virtual: true) field(:password_confirmation, :string, virtual: true) @@ -56,6 +55,7 @@ defmodule Pleroma.User do field(:bookmarks, {:array, :string}, default: []) field(:last_refreshed_at, :naive_datetime) has_many(:notifications, Notification) + has_many(:registrations, Registration) embeds_one(:info, Pleroma.User.Info) timestamps() @@ -210,13 +210,12 @@ def reset_password(user, data) do end # TODO: FIXME (WIP): - def oauth_register_changeset(struct, params \\ %{}) do + def external_registration_changeset(struct, params \\ %{}) do info_change = User.Info.confirmation_changeset(%User.Info{}, :confirmed) changeset = struct - |> cast(params, [:email, :nickname, :name, :bio, :auth_provider, :auth_provider_uid]) - |> validate_required([:auth_provider, :auth_provider_uid]) + |> cast(params, [:email, :nickname, :name, :bio]) |> unique_constraint(:email) |> unique_constraint(:nickname) |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) @@ -544,13 +543,6 @@ def get_by_nickname_or_email(nickname_or_email) do get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email) end - def get_by_auth_provider_uid(auth_provider, auth_provider_uid), - do: - Repo.get_by(User, - auth_provider: to_string(auth_provider), - auth_provider_uid: to_string(auth_provider_uid) - ) - def get_cached_user_info(user) do key = "user_info:#{user.id}" Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index fa439d562..11f45eec3 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -15,10 +15,10 @@ def implementation do @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} def get_user(plug, params), do: implementation().get_user(plug, params) - @callback get_or_create_user_by_oauth(Plug.Conn.t(), Map.t()) :: + @callback get_by_external_registration(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} - def get_or_create_user_by_oauth(plug, params), - do: implementation().get_or_create_user_by_oauth(plug, params) + def get_by_external_registration(plug, params), + do: implementation().get_by_external_registration(plug, params) @callback handle_error(Plug.Conn.t(), any()) :: any() def handle_error(plug, error), do: implementation().handle_error(plug, error) diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 6c65cff27..51a0f0fa2 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -40,7 +40,7 @@ def get_user(%Plug.Conn{} = conn, params) do end end - def get_or_create_user_by_oauth(conn, params), do: get_user(conn, params) + def get_by_external_registration(conn, params), do: get_user(conn, params) def handle_error(%Plug.Conn{} = _conn, error) do error diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 2e2bcfb70..2d4399490 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do alias Comeonin.Pbkdf2 alias Pleroma.User + alias Pleroma.Registration + alias Pleroma.Repo @behaviour Pleroma.Web.Auth.Authenticator @@ -27,20 +29,21 @@ def get_user(%Plug.Conn{} = _conn, params) do end end - def get_or_create_user_by_oauth( + def get_by_external_registration( %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}}, _params ) do - user = User.get_by_auth_provider_uid(provider, uid) + registration = Registration.get_by_provider_uid(provider, uid) - if user do + if registration do + user = Repo.preload(registration, :user).user {:ok, user} else info = auth.info email = info.email nickname = info.nickname - # TODO: FIXME: connect to existing (non-oauth) account (need a UI flow for that) / generate a random nickname? + # Note: nullifying email in case this email is already taken email = if email && User.get_by_email(email) do nil @@ -48,31 +51,39 @@ def get_or_create_user_by_oauth( email end + # Note: generating a random numeric suffix to nickname in case this nickname is already taken nickname = if nickname && User.get_by_nickname(nickname) do - nil + "#{nickname}_#{:os.system_time()}" else nickname end - new_user = - User.oauth_register_changeset( - %User{}, - %{ - auth_provider: to_string(provider), - auth_provider_uid: to_string(uid), - name: info.name, - bio: info.description, - email: email, - nickname: nickname - } - ) - - Pleroma.Repo.insert(new_user) + with {:ok, new_user} <- + User.external_registration_changeset( + %User{}, + %{ + name: info.name, + bio: info.description, + email: email, + nickname: nickname + } + ) + |> Repo.insert(), + {:ok, _} <- + Registration.changeset(%Registration{}, %{ + user_id: new_user.id, + provider: to_string(provider), + uid: to_string(uid), + info: %{nickname: info.nickname, email: info.email} + }) + |> Repo.insert() do + {:ok, new_user} + end end end - def get_or_create_user_by_oauth(%Plug.Conn{} = _conn, _params), + def get_by_external_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials} def handle_error(%Plug.Conn{} = _conn, error) do diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 588933d31..8c864cb1d 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -47,7 +47,7 @@ def callback( conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params ) do - with {:ok, user} <- Authenticator.get_or_create_user_by_oauth(conn, params) do + with {:ok, user} <- Authenticator.get_by_external_registration(conn, params) do do_create_authorization( conn, %{ diff --git a/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs b/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs deleted file mode 100644 index 90947f85a..000000000 --- a/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Pleroma.Repo.Migrations.AddAuthProviderAndAuthProviderUidToUsers do - use Ecto.Migration - - def change do - alter table(:users) do - add :auth_provider, :string - add :auth_provider_uid, :string - end - - create unique_index(:users, [:auth_provider, :auth_provider_uid]) - end -end diff --git a/priv/repo/migrations/20190315101315_create_registrations.exs b/priv/repo/migrations/20190315101315_create_registrations.exs new file mode 100644 index 000000000..dac86b780 --- /dev/null +++ b/priv/repo/migrations/20190315101315_create_registrations.exs @@ -0,0 +1,16 @@ +defmodule Pleroma.Repo.Migrations.CreateRegistrations do + use Ecto.Migration + + def change do + create table(:registrations) do + add :user_id, references(:users, type: :uuid, on_delete: :delete_all) + add :provider, :string + add :uid, :string + add :info, :map, default: %{} + + timestamps() + end + + create unique_index(:registrations, [:provider, :uid]) + end +end