[#923] Support for multiple (external) registrations per user via Registration.
authorIvan Tashkinov <ivant.business@gmail.com>
Mon, 18 Mar 2019 14:23:38 +0000 (17:23 +0300)
committerIvan Tashkinov <ivant.business@gmail.com>
Mon, 18 Mar 2019 14:23:38 +0000 (17:23 +0300)
config/config.exs
lib/pleroma/registration.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/web/auth/authenticator.ex
lib/pleroma/web/auth/ldap_authenticator.ex
lib/pleroma/web/auth/pleroma_authenticator.ex
lib/pleroma/web/oauth/oauth_controller.ex
priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs [deleted file]
priv/repo/migrations/20190315101315_create_registrations.exs [new file with mode: 0644]

index 6839b489bb4d7f582e1de2b3c98e6903c72c81dc..03baf894dc439504b831318643f4fa309ce3a445 100644 (file)
@@ -381,7 +381,7 @@ config :pleroma, :ldap,
   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 (file)
index 0000000..1bd91a3
--- /dev/null
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# 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
index 7f8b282e07c5d8026aa7922bb8f5a13f763b1fc5..bd742b2fd552d4243e08529ba1c6f407b609ef0b 100644 (file)
@@ -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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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)
index fa439d562669e685a4e995626e09e0bcb1a726e4..11f45eec3a8baf39332d66886c16e8939792e49c 100644 (file)
@@ -15,10 +15,10 @@ defmodule Pleroma.Web.Auth.Authenticator 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)
index 6c65cff27baab5248e756d500ee03bcdf11ec2eb..51a0f0fa2f78122eeec9df85c55cf7347662ae9f 100644 (file)
@@ -40,7 +40,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator 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
index 2e2bcfb702063ad0d32e1ab24acb3c72040f20b3..2d4399490abe2889ce68fd45f603b530f2719691 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
           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
index 588933d31f2f9d8b9f52da37cbf1d561a35de3b9..8c864cb1d473b85b599ae4a535f5b53945cdcdf0 100644 (file)
@@ -47,7 +47,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
         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 (file)
index 90947f8..0000000
+++ /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 (file)
index 0000000..dac86b7
--- /dev/null
@@ -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