[#923] Support for multiple (external) registrations per user via Registration.

This commit is contained in:
Ivan Tashkinov 2019-03-18 17:23:38 +03:00
parent 2a96283efb
commit 26b6354095
9 changed files with 93 additions and 50 deletions

View File

@ -381,7 +381,7 @@ config :pleroma, :ldap,
base: System.get_env("LDAP_BASE") || "dc=example,dc=com", base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn" 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, config :ueberauth,
Ueberauth, Ueberauth,

View File

@ -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

View File

@ -13,6 +13,7 @@ defmodule Pleroma.User do
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Registration
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
@ -41,8 +42,6 @@ defmodule Pleroma.User do
field(:email, :string) field(:email, :string)
field(:name, :string) field(:name, :string)
field(:nickname, :string) field(:nickname, :string)
field(:auth_provider, :string)
field(:auth_provider_uid, :string)
field(:password_hash, :string) field(:password_hash, :string)
field(:password, :string, virtual: true) field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true) field(:password_confirmation, :string, virtual: true)
@ -56,6 +55,7 @@ defmodule Pleroma.User do
field(:bookmarks, {:array, :string}, default: []) field(:bookmarks, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime) field(:last_refreshed_at, :naive_datetime)
has_many(:notifications, Notification) has_many(:notifications, Notification)
has_many(:registrations, Registration)
embeds_one(:info, Pleroma.User.Info) embeds_one(:info, Pleroma.User.Info)
timestamps() timestamps()
@ -210,13 +210,12 @@ defmodule Pleroma.User do
end end
# TODO: FIXME (WIP): # 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) info_change = User.Info.confirmation_changeset(%User.Info{}, :confirmed)
changeset = changeset =
struct struct
|> cast(params, [:email, :nickname, :name, :bio, :auth_provider, :auth_provider_uid]) |> cast(params, [:email, :nickname, :name, :bio])
|> validate_required([:auth_provider, :auth_provider_uid])
|> unique_constraint(:email) |> unique_constraint(:email)
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) |> 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) get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
end 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 def get_cached_user_info(user) do
key = "user_info:#{user.id}" key = "user_info:#{user.id}"
Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)

View File

@ -15,10 +15,10 @@ defmodule Pleroma.Web.Auth.Authenticator do
@callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
def get_user(plug, params), do: implementation().get_user(plug, params) 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()} {:ok, User.t()} | {:error, any()}
def get_or_create_user_by_oauth(plug, params), def get_by_external_registration(plug, params),
do: implementation().get_or_create_user_by_oauth(plug, params) do: implementation().get_by_external_registration(plug, params)
@callback handle_error(Plug.Conn.t(), any()) :: any() @callback handle_error(Plug.Conn.t(), any()) :: any()
def handle_error(plug, error), do: implementation().handle_error(plug, error) def handle_error(plug, error), do: implementation().handle_error(plug, error)

View File

@ -40,7 +40,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
end end
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 def handle_error(%Plug.Conn{} = _conn, error) do
error error

View File

@ -5,6 +5,8 @@
defmodule Pleroma.Web.Auth.PleromaAuthenticator do defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.User alias Pleroma.User
alias Pleroma.Registration
alias Pleroma.Repo
@behaviour Pleroma.Web.Auth.Authenticator @behaviour Pleroma.Web.Auth.Authenticator
@ -27,20 +29,21 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
end end
end end
def get_or_create_user_by_oauth( def get_by_external_registration(
%Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}}, %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
_params _params
) do ) 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} {:ok, user}
else else
info = auth.info info = auth.info
email = info.email email = info.email
nickname = info.nickname 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 = email =
if email && User.get_by_email(email) do if email && User.get_by_email(email) do
nil nil
@ -48,31 +51,39 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
email email
end end
# Note: generating a random numeric suffix to nickname in case this nickname is already taken
nickname = nickname =
if nickname && User.get_by_nickname(nickname) do if nickname && User.get_by_nickname(nickname) do
nil "#{nickname}_#{:os.system_time()}"
else else
nickname nickname
end end
new_user = with {:ok, new_user} <-
User.oauth_register_changeset( User.external_registration_changeset(
%User{}, %User{},
%{ %{
auth_provider: to_string(provider), name: info.name,
auth_provider_uid: to_string(uid), bio: info.description,
name: info.name, email: email,
bio: info.description, nickname: nickname
email: email, }
nickname: nickname )
} |> Repo.insert(),
) {:ok, _} <-
Registration.changeset(%Registration{}, %{
Pleroma.Repo.insert(new_user) 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
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} do: {:error, :missing_credentials}
def handle_error(%Plug.Conn{} = _conn, error) do def handle_error(%Plug.Conn{} = _conn, error) do

View File

@ -47,7 +47,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
conn, conn,
%{"client_id" => client_id, "redirect_uri" => redirect_uri} = params %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params
) do ) 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( do_create_authorization(
conn, conn,
%{ %{

View File

@ -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

View File

@ -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