From 63ab61ed3f4988bfaf9080bcdc4fc8d5046fa57e Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 11 Mar 2019 20:37:26 +0300
Subject: [PATCH 01/17] Sign in via Twitter (WIP).
---
config/config.exs | 11 +++++++++++
config/dev.exs | 1 -
lib/pleroma/web/endpoint.ex | 10 ++++++----
lib/pleroma/web/oauth/oauth_controller.ex | 11 +++++++++++
lib/pleroma/web/oauth/oauth_view.ex | 1 +
lib/pleroma/web/router.ex | 12 ++++++++++++
.../web/templates/o_auth/o_auth/show.html.eex | 7 +++++++
mix.exs | 9 +++++++--
mix.lock | 11 ++++++++---
9 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index cd4c8e562..8c754cef3 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -369,6 +369,17 @@
rel: false
]
+config :ueberauth,
+ Ueberauth,
+ base_path: "/oauth",
+ providers: [
+ twitter: {Ueberauth.Strategy.Twitter, []}
+ ]
+
+config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
+ consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
+ consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
index f77bb9976..a7eb4b644 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -12,7 +12,6 @@
protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
],
protocol: "http",
- secure_cookie_flag: false,
debug_errors: true,
code_reloader: true,
check_origin: false,
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 3eed047ca..d906db67d 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -50,23 +50,25 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.MethodOverride)
plug(Plug.Head)
+ secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
+
cookie_name =
- if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
+ if secure_cookies,
do: "__Host-pleroma_key",
else: "pleroma_key"
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
+ # Note: "SameSite=Strict" would cause issues with Twitter OAuth
plug(
Plug.Session,
store: :cookie,
key: cookie_name,
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
http_only: true,
- secure:
- Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
- extra: "SameSite=Strict"
+ secure: secure_cookies,
+ extra: "SameSite=Lax"
)
plug(Pleroma.Web.Router)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 36318d69b..7b052cb36 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -15,11 +15,22 @@ defmodule Pleroma.Web.OAuth.OAuthController do
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
+ plug(Ueberauth)
plug(:fetch_session)
plug(:fetch_flash)
action_fallback(Pleroma.Web.OAuth.FallbackController)
+ def callback(%{assigns: %{ueberauth_failure: _failure}} = conn, _params) do
+ conn
+ |> put_flash(:error, "Failed to authenticate.")
+ |> redirect(to: "/")
+ end
+
+ def callback(%{assigns: %{ueberauth_auth: _auth}} = _conn, _params) do
+ raise "Authenticated successfully. Sign up via OAuth is not yet implemented."
+ end
+
def authorize(conn, params) do
app = Repo.get_by(App, client_id: params["client_id"])
available_scopes = (app && app.scopes) || []
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex
index 9b37a91c5..1450b5a8d 100644
--- a/lib/pleroma/web/oauth/oauth_view.ex
+++ b/lib/pleroma/web/oauth/oauth_view.ex
@@ -5,4 +5,5 @@
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ import Phoenix.HTML.Link
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 65a90e31e..7cf7794b3 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -5,6 +5,11 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
+ pipeline :browser do
+ plug(:accepts, ["html"])
+ plug(:fetch_session)
+ end
+
pipeline :api do
plug(:accepts, ["json"])
plug(:fetch_session)
@@ -197,6 +202,13 @@ defmodule Pleroma.Web.Router do
post("/authorize", OAuthController, :create_authorization)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
+
+ scope [] do
+ pipe_through(:browser)
+
+ get("/:provider", OAuthController, :request)
+ get("/:provider/callback", OAuthController, :callback)
+ end
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 161333847..d465f06b1 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -4,7 +4,9 @@
<%= if get_flash(@conn, :error) do %>
<%= get_flash(@conn, :error) %>
<% end %>
+
OAuth Authorization
+
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<%= label f, :name, "Name or email" %>
@@ -33,3 +35,8 @@
<%= hidden_input f, :state, value: @state%>
<%= submit "Authorize" %>
<% end %>
+
+
+<%= link to: "/oauth/twitter", class: "alert alert-info" do %>
+ Sign in with Twitter
+<% end %>
\ No newline at end of file
diff --git a/mix.exs b/mix.exs
index 70b5e4bd6..dcd273d72 100644
--- a/mix.exs
+++ b/mix.exs
@@ -41,7 +41,7 @@ def project do
def application do
[
mod: {Pleroma.Application, []},
- extra_applications: [:logger, :runtime_tools, :comeonin],
+ extra_applications: [:logger, :runtime_tools, :comeonin, :ueberauth_twitter],
included_applications: [:ex_syslogger]
]
end
@@ -69,7 +69,8 @@ defp deps do
{:phoenix_html, "~> 2.10"},
{:calendar, "~> 0.17.4"},
{:cachex, "~> 3.0.2"},
- {:httpoison, "~> 1.2.0"},
+ {:httpoison, "~> 1.2.0", override: true},
+ {:poison, "~> 3.0", override: true},
{:tesla, "~> 1.2"},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
@@ -90,6 +91,10 @@ defp deps do
{:floki, "~> 0.20.0"},
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
{:timex, "~> 3.5"},
+ {:oauth, github: "tim/erlang-oauth"},
+ # {:oauth2, "~> 0.8", override: true},
+ {:ueberauth, "~> 0.4"},
+ {:ueberauth_twitter, "~> 0.2"},
{:auto_linker,
git: "https://git.pleroma.social/pleroma/auto_linker.git",
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"}
diff --git a/mix.lock b/mix.lock
index f43a18564..92660b70a 100644
--- a/mix.lock
+++ b/mix.lock
@@ -4,7 +4,7 @@
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
- "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
+ "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
@@ -26,7 +26,7 @@
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
- "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
+ "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
@@ -38,11 +38,14 @@
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
- "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
+ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
+ "oauth": {:git, "https://github.com/tim/erlang-oauth.git", "bd19896e31125f99ff45bb5850b1c0e74b996743", []},
+ "oauth2": {:hex, :oauth2, "0.9.4", "632e8e8826a45e33ac2ea5ac66dcc019ba6bb5a0d2ba77e342d33e3b7b252c6e", [:mix], [{:hackney, "~> 1.7", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+ "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.1", "801f9d632808657f1f7c657c8bbe624caaf2ba91429123ebe3801598aea4c3d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
@@ -63,6 +66,8 @@
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+ "ueberauth": {:hex, :ueberauth, "0.5.0", "4570ec94d7f784dc4c4aa94c83391dbd9b9bd7b66baa30e95a666c5ec1b168b1", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+ "ueberauth_twitter": {:hex, :ueberauth_twitter, "0.2.4", "770ac273cc696cde986582e7a36df0923deb39fa3deff0152fbf150343809f81", [:mix], [{:httpoison, "~> 0.7", [hex: :httpoison, repo: "hexpm", optional: false]}, {:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 1.3 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.2", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
From aacbf0f57053786533df045125dee93ace0daa93 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Fri, 15 Mar 2019 17:08:03 +0300
Subject: [PATCH 02/17] [#923] OAuth: prototype of sign in / sign up with
Twitter.
---
config/config.exs | 6 +-
lib/pleroma/user.ex | 46 +++++++++-
lib/pleroma/web/auth/authenticator.ex | 9 +-
lib/pleroma/web/auth/pleroma_authenticator.ex | 56 ++++++++++++-
lib/pleroma/web/endpoint.ex | 11 ++-
lib/pleroma/web/oauth/oauth_controller.ex | 83 ++++++++++++++-----
lib/pleroma/web/oauth/oauth_view.ex | 1 -
.../templates/o_auth/o_auth/consumer.html.eex | 14 ++++
.../web/templates/o_auth/o_auth/show.html.eex | 8 +-
...rovider_and_auth_provider_uid_to_users.exs | 12 +++
10 files changed, 209 insertions(+), 37 deletions(-)
create mode 100644 lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
create mode 100644 priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs
diff --git a/config/config.exs b/config/config.exs
index 8c754cef3..1ddc1bad1 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -369,11 +369,15 @@
rel: false
]
+config :pleroma, :auth, oauth_consumer_enabled: false
+
config :ueberauth,
Ueberauth,
base_path: "/oauth",
providers: [
- twitter: {Ueberauth.Strategy.Twitter, []}
+ twitter:
+ {Ueberauth.Strategy.Twitter,
+ [callback_params: ~w[client_id redirect_uri scope scopes]]}
]
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index f49ede149..e17df8e34 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -40,6 +40,8 @@ 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)
@@ -206,6 +208,36 @@ def reset_password(user, data) do
update_and_set_cache(password_update_changeset(user, data))
end
+ # TODO: FIXME (WIP):
+ def oauth_register_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])
+ |> unique_constraint(:email)
+ |> unique_constraint(:nickname)
+ |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+ |> validate_format(:email, @email_regex)
+ |> validate_length(:bio, max: 1000)
+ |> put_change(:info, info_change)
+
+ if changeset.valid? do
+ nickname = changeset.changes[:nickname]
+ ap_id = (nickname && User.ap_id(%User{nickname: nickname})) || nil
+ followers = User.ap_followers(%User{nickname: ap_id})
+
+ changeset
+ |> put_change(:ap_id, ap_id)
+ |> unique_constraint(:ap_id)
+ |> put_change(:following, [followers])
+ |> put_change(:follower_address, followers)
+ else
+ changeset
+ end
+ end
+
def register_changeset(struct, params \\ %{}, opts \\ []) do
confirmation_status =
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
@@ -504,13 +536,19 @@ def get_by_nickname(nickname) do
end
end
+ def get_by_email(email), do: Repo.get_by(User, email: email)
+
def get_by_nickname_or_email(nickname_or_email) do
- case user = Repo.get_by(User, nickname: nickname_or_email) do
- %User{} -> user
- nil -> Repo.get_by(User, email: nickname_or_email)
- end
+ 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 82267c595..fa439d562 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -12,8 +12,13 @@ def implementation do
)
end
- @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
- def get_user(plug), do: implementation().get_user(plug)
+ @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()) ::
+ {:ok, User.t()} | {:error, any()}
+ def get_or_create_user_by_oauth(plug, params),
+ do: implementation().get_or_create_user_by_oauth(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/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 3cc19af01..fb04ef8da 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -8,9 +8,9 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
@behaviour Pleroma.Web.Auth.Authenticator
- def get_user(%Plug.Conn{} = conn) do
- %{"authorization" => %{"name" => name, "password" => password}} = conn.params
-
+ def get_user(%Plug.Conn{} = _conn, %{
+ "authorization" => %{"name" => name, "password" => password}
+ }) do
with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
{:ok, user}
@@ -20,6 +20,56 @@ def get_user(%Plug.Conn{} = conn) do
end
end
+ def get_user(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
+
+ def get_or_create_user_by_oauth(
+ %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
+ _params
+ ) do
+ user = User.get_by_auth_provider_uid(provider, uid)
+
+ if user do
+ {: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?
+ email =
+ if email && User.get_by_email(email) do
+ nil
+ else
+ email
+ end
+
+ nickname =
+ if nickname && User.get_by_nickname(nickname) do
+ nil
+ 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)
+ end
+ end
+
+ def get_or_create_user_by_oauth(%Plug.Conn{} = _conn, _params),
+ do: {:error, :missing_credentials}
+
def handle_error(%Plug.Conn{} = _conn, error) do
error
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index d906db67d..31ffdecc0 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -57,10 +57,17 @@ defmodule Pleroma.Web.Endpoint do
do: "__Host-pleroma_key",
else: "pleroma_key"
+ same_site =
+ if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do
+ # Note: "SameSite=Strict" prevents sign in with external OAuth provider (no cookies during callback request)
+ "SameSite=Lax"
+ else
+ "SameSite=Strict"
+ end
+
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
- # Note: "SameSite=Strict" would cause issues with Twitter OAuth
plug(
Plug.Session,
store: :cookie,
@@ -68,7 +75,7 @@ defmodule Pleroma.Web.Endpoint do
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
http_only: true,
secure: secure_cookies,
- extra: "SameSite=Lax"
+ extra: same_site
)
plug(Pleroma.Web.Router)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 7b052cb36..366085a57 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -15,20 +15,57 @@ defmodule Pleroma.Web.OAuth.OAuthController do
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
- plug(Ueberauth)
+ if Pleroma.Config.get([:auth, :oauth_consumer_enabled]), do: plug(Ueberauth)
+
plug(:fetch_session)
plug(:fetch_flash)
action_fallback(Pleroma.Web.OAuth.FallbackController)
- def callback(%{assigns: %{ueberauth_failure: _failure}} = conn, _params) do
+ def request(conn, params) do
+ message =
+ if params["provider"] do
+ "Unsupported OAuth provider: #{params["provider"]}."
+ else
+ "Bad OAuth request."
+ end
+
conn
- |> put_flash(:error, "Failed to authenticate.")
+ |> put_flash(:error, message)
|> redirect(to: "/")
end
- def callback(%{assigns: %{ueberauth_auth: _auth}} = _conn, _params) do
- raise "Authenticated successfully. Sign up via OAuth is not yet implemented."
+ def callback(%{assigns: %{ueberauth_failure: failure}} = conn, %{"redirect_uri" => redirect_uri}) do
+ messages = for e <- Map.get(failure, :errors, []), do: e.message
+ message = Enum.join(messages, "; ")
+
+ conn
+ |> put_flash(:error, "Failed to authenticate: #{message}.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+
+ 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
+ do_create_authorization(
+ conn,
+ %{
+ "authorization" => %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri,
+ "scope" => oauth_scopes(params, nil)
+ }
+ },
+ user
+ )
+ else
+ _ ->
+ conn
+ |> put_flash(:error, "Failed to set up user account.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
end
def authorize(conn, params) do
@@ -47,14 +84,21 @@ def authorize(conn, params) do
})
end
- def create_authorization(conn, %{
- "authorization" =>
- %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri
- } = auth_params
- }) do
- with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
+ def create_authorization(conn, params), do: do_create_authorization(conn, params, nil)
+
+ defp do_create_authorization(
+ conn,
+ %{
+ "authorization" =>
+ %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri
+ } = auth_params
+ } = params,
+ user
+ ) do
+ with {_, {:ok, %User{} = user}} <-
+ {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
%App{} = app <- Repo.get_by(App, client_id: client_id),
true <- redirect_uri in String.split(app.redirect_uris),
scopes <- oauth_scopes(auth_params, []),
@@ -63,13 +107,7 @@ def create_authorization(conn, %{
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
- redirect_uri =
- if redirect_uri == "." do
- # Special case: Local MastodonFE
- mastodon_api_url(conn, :login)
- else
- redirect_uri
- end
+ redirect_uri = redirect_uri(conn, redirect_uri)
cond do
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
@@ -225,4 +263,9 @@ defp get_app_from_request(conn, params) do
nil
end
end
+
+ # Special case: Local MastodonFE
+ defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
+
+ defp redirect_uri(_conn, redirect_uri), do: redirect_uri
end
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex
index 1450b5a8d..9b37a91c5 100644
--- a/lib/pleroma/web/oauth/oauth_view.ex
+++ b/lib/pleroma/web/oauth/oauth_view.ex
@@ -5,5 +5,4 @@
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
- import Phoenix.HTML.Link
end
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
new file mode 100644
index 000000000..e7251bce8
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -0,0 +1,14 @@
+External OAuth Authorization
+<%= form_for @conn, o_auth_path(@conn, :request, :twitter), [method: "get"], fn f -> %>
+
+
+ <%= hidden_input f, :client_id, value: @client_id %>
+ <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+ <%= hidden_input f, :state, value: @state%>
+ <%= submit "Sign in with Twitter" %>
+<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index d465f06b1..2fa7837fc 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -36,7 +36,7 @@
<%= submit "Authorize" %>
<% end %>
-
-<%= link to: "/oauth/twitter", class: "alert alert-info" do %>
- Sign in with Twitter
-<% end %>
\ No newline at end of file
+<%= if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do %>
+
+ <%= render @view_module, "consumer.html", assigns %>
+<% end %>
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
new file mode 100644
index 000000000..90947f85a
--- /dev/null
+++ b/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs
@@ -0,0 +1,12 @@
+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
From 26b63540953f6a65bb52531b434fd6ab85aaedfe Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 18 Mar 2019 17:23:38 +0300
Subject: [PATCH 03/17] [#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
From 8d21859717a75e01128f50b0b51efdd0a4748670 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 18 Mar 2019 18:09:53 +0300
Subject: [PATCH 04/17] [#923] External User registration refactoring, password
randomization.
---
lib/pleroma/user.ex | 38 ++++---------------
lib/pleroma/web/auth/pleroma_authenticator.ex | 14 +++++--
2 files changed, 18 insertions(+), 34 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index bd742b2fd..558216894 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -209,35 +209,6 @@ def reset_password(user, data) do
update_and_set_cache(password_update_changeset(user, data))
end
- # TODO: FIXME (WIP):
- def external_registration_changeset(struct, params \\ %{}) do
- info_change = User.Info.confirmation_changeset(%User.Info{}, :confirmed)
-
- changeset =
- struct
- |> cast(params, [:email, :nickname, :name, :bio])
- |> unique_constraint(:email)
- |> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
- |> validate_format(:email, @email_regex)
- |> validate_length(:bio, max: 1000)
- |> put_change(:info, info_change)
-
- if changeset.valid? do
- nickname = changeset.changes[:nickname]
- ap_id = (nickname && User.ap_id(%User{nickname: nickname})) || nil
- followers = User.ap_followers(%User{nickname: ap_id})
-
- changeset
- |> put_change(:ap_id, ap_id)
- |> unique_constraint(:ap_id)
- |> put_change(:following, [followers])
- |> put_change(:follower_address, followers)
- else
- changeset
- end
- end
-
def register_changeset(struct, params \\ %{}, opts \\ []) do
confirmation_status =
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
@@ -251,7 +222,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
changeset =
struct
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
- |> validate_required([:email, :name, :nickname, :password, :password_confirmation])
+ |> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
@@ -262,6 +233,13 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_length(:name, min: 1, max: 100)
|> put_change(:info, info_change)
+ changeset =
+ if opts[:external] do
+ changeset
+ else
+ validate_required(changeset, [:email])
+ end
+
if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 2d4399490..36ecd0560 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -54,20 +54,26 @@ def get_by_external_registration(
# Note: generating a random numeric suffix to nickname in case this nickname is already taken
nickname =
if nickname && User.get_by_nickname(nickname) do
- "#{nickname}_#{:os.system_time()}"
+ "#{nickname}#{:os.system_time()}"
else
nickname
end
+ random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
+
with {:ok, new_user} <-
- User.external_registration_changeset(
+ User.register_changeset(
%User{},
%{
name: info.name,
bio: info.description,
email: email,
- nickname: nickname
- }
+ nickname: nickname,
+ password: random_password,
+ password_confirmation: random_password
+ },
+ external: true,
+ confirmed: true
)
|> Repo.insert(),
{:ok, _} <-
From 40e9a04c31a9965dee92cb8f07ed6db28f8ccd75 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 18 Mar 2019 20:31:24 +0300
Subject: [PATCH 05/17] [#923] Registration validations & unique index on
[:user_id, :provider].
---
lib/pleroma/registration.ex | 1 +
priv/repo/migrations/20190315101315_create_registrations.exs | 1 +
2 files changed, 2 insertions(+)
diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex
index 1bd91a316..773e25fa6 100644
--- a/lib/pleroma/registration.ex
+++ b/lib/pleroma/registration.ex
@@ -23,6 +23,7 @@ defmodule Pleroma.Registration do
def changeset(registration, params \\ %{}) do
registration
|> cast(params, [:user_id, :provider, :uid, :info])
+ |> validate_required([:provider, :uid])
|> foreign_key_constraint(:user_id)
|> unique_constraint(:uid, name: :registrations_provider_uid_index)
end
diff --git a/priv/repo/migrations/20190315101315_create_registrations.exs b/priv/repo/migrations/20190315101315_create_registrations.exs
index dac86b780..c566912f5 100644
--- a/priv/repo/migrations/20190315101315_create_registrations.exs
+++ b/priv/repo/migrations/20190315101315_create_registrations.exs
@@ -12,5 +12,6 @@ def change do
end
create unique_index(:registrations, [:provider, :uid])
+ create unique_index(:registrations, [:user_id, :provider])
end
end
From e17a9a1f6680bfc464a1433fcff37b6d61cc5340 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 20 Mar 2019 10:35:31 +0300
Subject: [PATCH 06/17] [#923] Nickname & email selection for external
registrations, option to connect to existing account.
---
lib/pleroma/registration.ex | 20 ++
lib/pleroma/web/auth/authenticator.ex | 12 +-
lib/pleroma/web/auth/ldap_authenticator.ex | 11 +-
lib/pleroma/web/auth/pleroma_authenticator.ex | 95 ++++---
lib/pleroma/web/oauth/oauth_controller.ex | 245 +++++++++++++-----
lib/pleroma/web/router.ex | 2 +
.../templates/o_auth/o_auth/register.html.eex | 48 ++++
.../20190315101315_create_registrations.exs | 3 +-
8 files changed, 309 insertions(+), 127 deletions(-)
create mode 100644 lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex
index 773e25fa6..21fd1fc3f 100644
--- a/lib/pleroma/registration.ex
+++ b/lib/pleroma/registration.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Registration do
alias Pleroma.Repo
alias Pleroma.User
+ @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
+
schema "registrations" do
belongs_to(:user, User, type: Pleroma.FlakeId)
field(:provider, :string)
@@ -20,6 +22,18 @@ defmodule Pleroma.Registration do
timestamps()
end
+ def nickname(registration, default \\ nil),
+ do: Map.get(registration.info, "nickname", default)
+
+ def email(registration, default \\ nil),
+ do: Map.get(registration.info, "email", default)
+
+ def name(registration, default \\ nil),
+ do: Map.get(registration.info, "name", default)
+
+ def description(registration, default \\ nil),
+ do: Map.get(registration.info, "description", default)
+
def changeset(registration, params \\ %{}) do
registration
|> cast(params, [:user_id, :provider, :uid, :info])
@@ -28,6 +42,12 @@ def changeset(registration, params \\ %{}) do
|> unique_constraint(:uid, name: :registrations_provider_uid_index)
end
+ def bind_to_user(registration, user) do
+ registration
+ |> changeset(%{user_id: (user && user.id) || nil})
+ |> Repo.update()
+ end
+
def get_by_provider_uid(provider, uid) do
Repo.get_by(Registration,
provider: to_string(provider),
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 11f45eec3..1f614668c 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.Auth.Authenticator do
alias Pleroma.User
+ alias Pleroma.Registration
def implementation do
Pleroma.Config.get(
@@ -15,10 +16,15 @@ 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_by_external_registration(Plug.Conn.t(), Map.t()) ::
+ @callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
{:ok, User.t()} | {:error, any()}
- def get_by_external_registration(plug, params),
- do: implementation().get_by_external_registration(plug, params)
+ def create_from_registration(plug, params, registration),
+ do: implementation().create_from_registration(plug, params, registration)
+
+ @callback get_registration(Plug.Conn.t(), Map.t()) ::
+ {:ok, Registration.t()} | {:error, any()}
+ def get_registration(plug, params),
+ do: implementation().get_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 51a0f0fa2..65abd7f38 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -8,10 +8,15 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
require Logger
@behaviour Pleroma.Web.Auth.Authenticator
+ @base Pleroma.Web.Auth.PleromaAuthenticator
@connection_timeout 10_000
@search_timeout 10_000
+ defdelegate get_registration(conn, params), to: @base
+
+ defdelegate create_from_registration(conn, params, registration), to: @base
+
def get_user(%Plug.Conn{} = conn, params) do
if Pleroma.Config.get([:ldap, :enabled]) do
{name, password} =
@@ -29,19 +34,17 @@ def get_user(%Plug.Conn{} = conn, params) do
{:error, {:ldap_connection_error, _}} ->
# When LDAP is unavailable, try default authenticator
- Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params)
+ @base.get_user(conn, params)
error ->
error
end
else
# Fall back to default authenticator
- Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params)
+ @base.get_user(conn, params)
end
end
- def get_by_external_registration(conn, params), do: get_user(conn, params)
-
def handle_error(%Plug.Conn{} = _conn, error) do
error
end
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 36ecd0560..60847ce6a 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -29,68 +29,63 @@ def get_user(%Plug.Conn{} = _conn, params) do
end
end
- def get_by_external_registration(
+ def get_registration(
%Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
_params
) do
registration = Registration.get_by_provider_uid(provider, uid)
if registration do
- user = Repo.preload(registration, :user).user
- {:ok, user}
+ {:ok, registration}
else
info = auth.info
- email = info.email
- nickname = info.nickname
- # Note: nullifying email in case this email is already taken
- email =
- if email && User.get_by_email(email) do
- nil
- else
- 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
- "#{nickname}#{:os.system_time()}"
- else
- nickname
- end
-
- random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
-
- with {:ok, new_user} <-
- User.register_changeset(
- %User{},
- %{
- name: info.name,
- bio: info.description,
- email: email,
- nickname: nickname,
- password: random_password,
- password_confirmation: random_password
- },
- external: true,
- confirmed: true
- )
- |> 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
+ Registration.changeset(%Registration{}, %{
+ provider: to_string(provider),
+ uid: to_string(uid),
+ info: %{
+ "nickname" => info.nickname,
+ "email" => info.email,
+ "name" => info.name,
+ "description" => info.description
+ }
+ })
+ |> Repo.insert()
end
end
- def get_by_external_registration(%Plug.Conn{} = _conn, _params),
- do: {:error, :missing_credentials}
+ def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
+
+ def create_from_registration(_conn, params, registration) do
+ nickname = value([params["nickname"], Registration.nickname(registration)])
+ email = value([params["email"], Registration.email(registration)])
+ name = value([params["name"], Registration.name(registration)]) || nickname
+ bio = value([params["bio"], Registration.description(registration)])
+
+ random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
+
+ with {:ok, new_user} <-
+ User.register_changeset(
+ %User{},
+ %{
+ email: email,
+ nickname: nickname,
+ name: name,
+ bio: bio,
+ password: random_password,
+ password_confirmation: random_password
+ },
+ external: true,
+ confirmed: true
+ )
+ |> Repo.insert(),
+ {:ok, _} <-
+ Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
+ {:ok, new_user}
+ end
+ end
+
+ defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
def handle_error(%Plug.Conn{} = _conn, error) do
error
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 8c864cb1d..a2c62ae68 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Registration
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
@@ -21,52 +22,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
action_fallback(Pleroma.Web.OAuth.FallbackController)
- def request(conn, params) do
- message =
- if params["provider"] do
- "Unsupported OAuth provider: #{params["provider"]}."
- else
- "Bad OAuth request."
- end
-
- conn
- |> put_flash(:error, message)
- |> redirect(to: "/")
- end
-
- def callback(%{assigns: %{ueberauth_failure: failure}} = conn, %{"redirect_uri" => redirect_uri}) do
- messages = for e <- Map.get(failure, :errors, []), do: e.message
- message = Enum.join(messages, "; ")
-
- conn
- |> put_flash(:error, "Failed to authenticate: #{message}.")
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
-
- def callback(
- conn,
- %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params
- ) do
- with {:ok, user} <- Authenticator.get_by_external_registration(conn, params) do
- do_create_authorization(
- conn,
- %{
- "authorization" => %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri,
- "scope" => oauth_scopes(params, nil)
- }
- },
- user
- )
- else
- _ ->
- conn
- |> put_flash(:error, "Failed to set up user account.")
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
- end
-
def authorize(conn, params) do
app = Repo.get_by(App, client_id: params["client_id"])
available_scopes = (app && app.scopes) || []
@@ -83,29 +38,16 @@ def authorize(conn, params) do
})
end
- def create_authorization(conn, params), do: do_create_authorization(conn, params, nil)
-
- defp do_create_authorization(
- conn,
- %{
- "authorization" =>
- %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri
- } = auth_params
- } = params,
- user
- ) do
- with {_, {:ok, %User{} = user}} <-
- {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
- %App{} = app <- Repo.get_by(App, client_id: client_id),
- true <- redirect_uri in String.split(app.redirect_uris),
- scopes <- oauth_scopes(auth_params, []),
- {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
- # Note: `scope` param is intentionally not optional in this context
- {:missing_scopes, false} <- {:missing_scopes, scopes == []},
- {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
+ def create_authorization(
+ conn,
+ %{
+ "authorization" => %{"redirect_uri" => redirect_uri} = auth_params
+ } = params,
+ opts \\ []
+ ) do
+ with {:ok, auth} <-
+ (opts[:auth] && {:ok, opts[:auth]}) ||
+ do_create_authorization(conn, params, opts[:user]) do
redirect_uri = redirect_uri(conn, redirect_uri)
cond do
@@ -232,6 +174,166 @@ def token_revoke(conn, %{"token" => token} = params) do
end
end
+ def request(conn, params) do
+ message =
+ if params["provider"] do
+ "Unsupported OAuth provider: #{params["provider"]}."
+ else
+ "Bad OAuth request."
+ end
+
+ conn
+ |> put_flash(:error, message)
+ |> redirect(to: "/")
+ end
+
+ def callback(%{assigns: %{ueberauth_failure: failure}} = conn, %{"redirect_uri" => redirect_uri}) do
+ messages = for e <- Map.get(failure, :errors, []), do: e.message
+ message = Enum.join(messages, "; ")
+
+ conn
+ |> put_flash(:error, "Failed to authenticate: #{message}.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+
+ def callback(
+ conn,
+ %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params
+ ) do
+ with {:ok, registration} <- Authenticator.get_registration(conn, params) do
+ user = Repo.preload(registration, :user).user
+
+ auth_params = %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri,
+ "scopes" => oauth_scopes(params, nil)
+ }
+
+ if user do
+ create_authorization(
+ conn,
+ %{"authorization" => auth_params},
+ user: user
+ )
+ else
+ registration_params =
+ Map.merge(auth_params, %{
+ "nickname" => Registration.nickname(registration),
+ "email" => Registration.email(registration)
+ })
+
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> redirect(to: o_auth_path(conn, :registration_details, registration_params))
+ end
+ else
+ _ ->
+ conn
+ |> put_flash(:error, "Failed to set up user account.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+ end
+
+ def registration_details(conn, params) do
+ render(conn, "register.html", %{
+ client_id: params["client_id"],
+ redirect_uri: params["redirect_uri"],
+ scopes: oauth_scopes(params, []),
+ nickname: params["nickname"],
+ email: params["email"]
+ })
+ end
+
+ def register(conn, %{"op" => "connect"} = params) do
+ create_authorization_params = %{
+ "authorization" => Map.merge(params, %{"name" => params["auth_name"]})
+ }
+
+ with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
+ %Registration{} = registration <- Repo.get(Registration, registration_id),
+ {:ok, auth} <- do_create_authorization(conn, create_authorization_params),
+ %User{} = user <- Repo.preload(auth, :user).user,
+ {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
+ conn
+ |> put_session_registration_id(nil)
+ |> create_authorization(
+ create_authorization_params,
+ auth: auth
+ )
+ else
+ _ ->
+ conn
+ |> put_flash(:error, "Unknown error, please try again.")
+ |> redirect(to: o_auth_path(conn, :registration_details, params))
+ end
+ end
+
+ def register(conn, params) do
+ with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
+ %Registration{} = registration <- Repo.get(Registration, registration_id),
+ {:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
+ conn
+ |> put_session_registration_id(nil)
+ |> create_authorization(
+ %{
+ "authorization" => %{
+ "client_id" => params["client_id"],
+ "redirect_uri" => params["redirect_uri"],
+ "scopes" => oauth_scopes(params, nil)
+ }
+ },
+ user: user
+ )
+ else
+ {:error, changeset} ->
+ message =
+ Enum.map(changeset.errors, fn {field, {error, _}} ->
+ "#{field} #{error}"
+ end)
+ |> Enum.join("; ")
+
+ message =
+ String.replace(
+ message,
+ "ap_id has already been taken",
+ "nickname has already been taken"
+ )
+
+ conn
+ |> put_flash(:error, "Error: #{message}.")
+ |> redirect(to: o_auth_path(conn, :registration_details, params))
+
+ _ ->
+ conn
+ |> put_flash(:error, "Unknown error, please try again.")
+ |> redirect(to: o_auth_path(conn, :registration_details, params))
+ end
+ end
+
+ defp do_create_authorization(
+ conn,
+ %{
+ "authorization" =>
+ %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri
+ } = auth_params
+ } = params,
+ user \\ nil
+ ) do
+ with {_, {:ok, %User{} = user}} <-
+ {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
+ %App{} = app <- Repo.get_by(App, client_id: client_id),
+ true <- redirect_uri in String.split(app.redirect_uris),
+ scopes <- oauth_scopes(auth_params, []),
+ {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
+ # Note: `scope` param is intentionally not optional in this context
+ {:missing_scopes, false} <- {:missing_scopes, scopes == []},
+ {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
+ Authorization.create_authorization(app, user, scopes)
+ end
+ end
+
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
# decoding it. Investigate sometime.
defp fix_padding(token) do
@@ -269,4 +371,9 @@ defp get_app_from_request(conn, params) do
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
+
+ defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
+
+ defp put_session_registration_id(conn, registration_id),
+ do: put_session(conn, :registration_id, registration_id)
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 9b6784120..f2cec574b 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -208,12 +208,14 @@ defmodule Pleroma.Web.Router do
post("/authorize", OAuthController, :create_authorization)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
+ get("/registration_details", OAuthController, :registration_details)
scope [] do
pipe_through(:browser)
get("/:provider", OAuthController, :request)
get("/:provider/callback", OAuthController, :callback)
+ post("/register", OAuthController, :register)
end
end
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
new file mode 100644
index 000000000..f4547170c
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -0,0 +1,48 @@
+<%= if get_flash(@conn, :info) do %>
+ <%= get_flash(@conn, :info) %>
+<% end %>
+<%= if get_flash(@conn, :error) do %>
+ <%= get_flash(@conn, :error) %>
+<% end %>
+
+Registration Details
+
+If you'd like to register a new account,
+
+please provide the details below.
+
+
+<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
+
+
+ <%= label f, :nickname, "Nickname" %>
+ <%= text_input f, :nickname, value: @nickname %>
+
+
+ <%= label f, :email, "Email" %>
+ <%= text_input f, :email, value: @email %>
+
+
+<%= submit "Proceed as new user", name: "op", value: "register" %>
+
+
+
+
+Alternatively, sign in to connect to existing account.
+
+
+ <%= label f, :auth_name, "Name or email" %>
+ <%= text_input f, :auth_name %>
+
+
+ <%= label f, :password, "Password" %>
+ <%= password_input f, :password %>
+
+
+<%= submit "Proceed as existing user", name: "op", value: "connect" %>
+
+<%= hidden_input f, :client_id, value: @client_id %>
+<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
+
+<% end %>
diff --git a/priv/repo/migrations/20190315101315_create_registrations.exs b/priv/repo/migrations/20190315101315_create_registrations.exs
index c566912f5..fbb22ec7c 100644
--- a/priv/repo/migrations/20190315101315_create_registrations.exs
+++ b/priv/repo/migrations/20190315101315_create_registrations.exs
@@ -2,7 +2,8 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations do
use Ecto.Migration
def change do
- create table(:registrations) do
+ create table(:registrations, primary_key: false) do
+ add :id, :uuid, primary_key: true
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
add :provider, :string
add :uid, :string
From af68a42ef7841013476831e92d3841088fa875df Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 20 Mar 2019 20:25:48 +0300
Subject: [PATCH 07/17] [#923] Support for multiple OAuth consumer strategies.
---
config/config.exs | 24 +++++++++------
lib/pleroma/web/oauth/oauth_controller.ex | 29 +++++++++++++------
.../templates/o_auth/o_auth/consumer.html.eex | 20 +++++--------
.../web/templates/o_auth/o_auth/show.html.eex | 1 -
mix.exs | 13 +++++----
mix.lock | 1 +
6 files changed, 52 insertions(+), 36 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 03baf894d..7d8de5af6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -381,20 +381,26 @@
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
-config :pleroma, :auth, oauth_consumer_enabled: System.get_env("OAUTH_CONSUMER_ENABLED") == "true"
+oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES" || ""))
+
+ueberauth_providers =
+ for strategy <- oauth_consumer_strategies do
+ strategy_module_name =
+ System.get_env("UEBERAUTH_#{String.upcase(strategy)}_STRATEGY_MODULE") ||
+ "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
+
+ strategy_module = String.to_atom(strategy_module_name)
+ {String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
+ end
config :ueberauth,
Ueberauth,
base_path: "/oauth",
- providers: [
- twitter:
- {Ueberauth.Strategy.Twitter,
- [callback_params: ~w[client_id redirect_uri scope scopes]]}
- ]
+ providers: ueberauth_providers
-config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
- consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
- consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
+config :pleroma, :auth,
+ oauth_consumer_strategies: oauth_consumer_strategies,
+ oauth_consumer_enabled: oauth_consumer_strategies != []
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index a2c62ae68..b300c96df 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -187,25 +187,25 @@ def request(conn, params) do
|> redirect(to: "/")
end
- def callback(%{assigns: %{ueberauth_failure: failure}} = conn, %{"redirect_uri" => redirect_uri}) do
+ def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
+ params = callback_params(params)
messages = for e <- Map.get(failure, :errors, []), do: e.message
message = Enum.join(messages, "; ")
conn
|> put_flash(:error, "Failed to authenticate: #{message}.")
- |> redirect(external: redirect_uri(conn, redirect_uri))
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
- def callback(
- conn,
- %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params
- ) do
+ def callback(conn, params) do
+ params = callback_params(params)
+
with {:ok, registration} <- Authenticator.get_registration(conn, params) do
user = Repo.preload(registration, :user).user
auth_params = %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri,
+ "client_id" => params["client_id"],
+ "redirect_uri" => params["redirect_uri"],
"scopes" => oauth_scopes(params, nil)
}
@@ -230,10 +230,21 @@ def callback(
_ ->
conn
|> put_flash(:error, "Failed to set up user account.")
- |> redirect(external: redirect_uri(conn, redirect_uri))
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
end
+ defp callback_params(%{"state" => state} = params) do
+ [client_id, redirect_uri, scope, state] = String.split(state, "|")
+
+ Map.merge(params, %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri,
+ "scope" => scope,
+ "state" => state
+ })
+ end
+
def registration_details(conn, params) do
render(conn, "register.html", %{
client_id: params["client_id"],
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index e7251bce8..a64859a49 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,14 +1,10 @@
-External OAuth Authorization
-<%= form_for @conn, o_auth_path(@conn, :request, :twitter), [method: "get"], fn f -> %>
-
+
+
+Sign in with external provider
- <%= hidden_input f, :client_id, value: @client_id %>
- <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
- <%= hidden_input f, :state, value: @state%>
- <%= submit "Sign in with Twitter" %>
+<%= for strategy <- Pleroma.Config.get([:auth, :oauth_consumer_strategies], []) do %>
+ <%= form_for @conn, o_auth_path(@conn, :request, strategy), [method: "get"], fn f -> %>
+ <%= hidden_input f, :state, value: Enum.join([@client_id, @redirect_uri, Enum.join(@available_scopes, " "), @state], "|") %>
+ <%= submit "Sign in with #{String.capitalize(strategy)}" %>
+ <% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 2fa7837fc..b2381869a 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -37,6 +37,5 @@
<% end %>
<%= if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do %>
-
<%= render @view_module, "consumer.html", assigns %>
<% end %>
diff --git a/mix.exs b/mix.exs
index 25711bc26..f7ab008ac 100644
--- a/mix.exs
+++ b/mix.exs
@@ -44,7 +44,7 @@ def project do
def application do
[
mod: {Pleroma.Application, []},
- extra_applications: [:logger, :runtime_tools, :comeonin, :ueberauth_twitter],
+ extra_applications: [:logger, :runtime_tools, :comeonin],
included_applications: [:ex_syslogger]
]
end
@@ -57,6 +57,12 @@ defp elixirc_paths(_), do: ["lib"]
#
# Type `mix help deps` for examples and options.
defp deps do
+ oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
+
+ oauth_deps =
+ for s <- oauth_strategies,
+ do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
+
[
{:phoenix, "~> 1.4.1"},
{:plug_cowboy, "~> 2.0"},
@@ -94,14 +100,11 @@ defp deps do
{:floki, "~> 0.20.0"},
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
{:timex, "~> 3.5"},
- {:oauth, github: "tim/erlang-oauth"},
- # {:oauth2, "~> 0.8", override: true},
{:ueberauth, "~> 0.4"},
- {:ueberauth_twitter, "~> 0.2"},
{:auto_linker,
git: "https://git.pleroma.social/pleroma/auto_linker.git",
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"}
- ]
+ ] ++ oauth_deps
end
# Aliases are shortcuts or tasks specific to the current project.
diff --git a/mix.lock b/mix.lock
index 92660b70a..6a6cee1a9 100644
--- a/mix.lock
+++ b/mix.lock
@@ -67,6 +67,7 @@
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"ueberauth": {:hex, :ueberauth, "0.5.0", "4570ec94d7f784dc4c4aa94c83391dbd9b9bd7b66baa30e95a666c5ec1b168b1", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+ "ueberauth_facebook": {:hex, :ueberauth_facebook, "0.8.0", "9ec8571f804dd5c06f4e305d70606b39fc0ac8a8f43ed56ebb76012a97d14729", [:mix], [{:oauth2, "~> 0.9", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.4", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm"},
"ueberauth_twitter": {:hex, :ueberauth_twitter, "0.2.4", "770ac273cc696cde986582e7a36df0923deb39fa3deff0152fbf150343809f81", [:mix], [{:httpoison, "~> 0.7", [hex: :httpoison, repo: "hexpm", optional: false]}, {:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 1.3 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.2", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
From 81bf6d9e6a92b4af00b3351b043193a3c299ede5 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 20 Mar 2019 20:29:08 +0300
Subject: [PATCH 08/17] [#923] Typo fix.
---
config/config.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 7d8de5af6..586844516 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"
-oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES" || ""))
+oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
From 2a95014b9d7142aa2549e70f428293af78fae8eb Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 27 Mar 2019 15:39:35 +0300
Subject: [PATCH 09/17] [#923] OAuth consumer improvements, fixes, refactoring.
---
config/config.exs | 5 +---
lib/pleroma/web/auth/authenticator.ex | 6 ++++
lib/pleroma/web/auth/ldap_authenticator.ex | 2 ++
lib/pleroma/web/auth/pleroma_authenticator.ex | 2 ++
lib/pleroma/web/oauth/oauth_controller.ex | 28 +++++++++++++------
lib/pleroma/web/router.ex | 1 +
.../templates/o_auth/o_auth/_scopes.html.eex | 13 +++++++++
.../templates/o_auth/o_auth/consumer.html.eex | 15 ++++++----
.../web/templates/o_auth/o_auth/show.html.eex | 16 ++---------
mix.lock | 7 +----
10 files changed, 59 insertions(+), 36 deletions(-)
create mode 100644 lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
diff --git a/config/config.exs b/config/config.exs
index 586844516..bdaf5205a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -385,10 +385,7 @@
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
- strategy_module_name =
- System.get_env("UEBERAUTH_#{String.upcase(strategy)}_STRATEGY_MODULE") ||
- "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
-
+ strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
strategy_module = String.to_atom(strategy_module_name)
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
end
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 1f614668c..bb87b323c 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -33,4 +33,10 @@ def handle_error(plug, error), do: implementation().handle_error(plug, error)
def auth_template do
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
end
+
+ @callback oauth_consumer_template() :: String.t() | nil
+ def oauth_consumer_template do
+ implementation().oauth_consumer_template() ||
+ Pleroma.Config.get(:oauth_consumer_template, "consumer.html")
+ end
end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index 65abd7f38..8b6d5a77f 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -51,6 +51,8 @@ def handle_error(%Plug.Conn{} = _conn, error) do
def auth_template, do: nil
+ def oauth_consumer_template, do: nil
+
defp ldap_user(name, password) do
ldap = Pleroma.Config.get(:ldap, [])
host = Keyword.get(ldap, :host, "localhost")
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 60847ce6a..8b190f97f 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -92,4 +92,6 @@ def handle_error(%Plug.Conn{} = _conn, error) do
end
def auth_template, do: nil
+
+ def oauth_consumer_template, do: nil
end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index b300c96df..078839d5c 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -174,6 +174,25 @@ def token_revoke(conn, %{"token" => token} = params) do
end
end
+ def prepare_request(conn, %{"provider" => provider} = params) do
+ scope =
+ oauth_scopes(params, [])
+ |> Enum.join(" ")
+
+ state =
+ params
+ |> Map.delete("scopes")
+ |> Map.put("scope", scope)
+ |> Poison.encode!()
+
+ params =
+ params
+ |> Map.drop(~w(scope scopes client_id redirect_uri))
+ |> Map.put("state", state)
+
+ redirect(conn, to: o_auth_path(conn, :request, provider, params))
+ end
+
def request(conn, params) do
message =
if params["provider"] do
@@ -235,14 +254,7 @@ def callback(conn, params) do
end
defp callback_params(%{"state" => state} = params) do
- [client_id, redirect_uri, scope, state] = String.split(state, "|")
-
- Map.merge(params, %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri,
- "scope" => scope,
- "state" => state
- })
+ Map.merge(params, Poison.decode!(state))
end
def registration_details(conn, params) do
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index f2cec574b..4d0e04d9f 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -213,6 +213,7 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:browser)
+ get("/prepare_request", OAuthController, :prepare_request)
get("/:provider", OAuthController, :request)
get("/:provider/callback", OAuthController, :callback)
post("/register", OAuthController, :register)
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
new file mode 100644
index 000000000..4b8fb5dae
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
@@ -0,0 +1,13 @@
+
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index a64859a49..002f014e6 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -2,9 +2,14 @@
Sign in with external provider
-<%= for strategy <- Pleroma.Config.get([:auth, :oauth_consumer_strategies], []) do %>
- <%= form_for @conn, o_auth_path(@conn, :request, strategy), [method: "get"], fn f -> %>
- <%= hidden_input f, :state, value: Enum.join([@client_id, @redirect_uri, Enum.join(@available_scopes, " "), @state], "|") %>
- <%= submit "Sign in with #{String.capitalize(strategy)}" %>
- <% end %>
+<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
+ <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
+
+ <%= hidden_input f, :client_id, value: @client_id %>
+ <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+ <%= hidden_input f, :state, value: @state %>
+
+ <%= for strategy <- Pleroma.Config.get([:auth, :oauth_consumer_strategies], []) do %>
+ <%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
+ <% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index b2381869a..e6cf1db45 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -16,18 +16,8 @@
<%= label f, :password, "Password" %>
<%= password_input f, :password %>
-
+
+<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f, scope_param: "authorization[scope][]"}) %>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :response_type, value: @response_type %>
@@ -37,5 +27,5 @@
<% end %>
<%= if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do %>
- <%= render @view_module, "consumer.html", assigns %>
+ <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
<% end %>
diff --git a/mix.lock b/mix.lock
index 6a6cee1a9..ee8617124 100644
--- a/mix.lock
+++ b/mix.lock
@@ -43,9 +43,6 @@
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
- "oauth": {:git, "https://github.com/tim/erlang-oauth.git", "bd19896e31125f99ff45bb5850b1c0e74b996743", []},
- "oauth2": {:hex, :oauth2, "0.9.4", "632e8e8826a45e33ac2ea5ac66dcc019ba6bb5a0d2ba77e342d33e3b7b252c6e", [:mix], [{:hackney, "~> 1.7", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
- "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.1", "801f9d632808657f1f7c657c8bbe624caaf2ba91429123ebe3801598aea4c3d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
@@ -66,9 +63,7 @@
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
- "ueberauth": {:hex, :ueberauth, "0.5.0", "4570ec94d7f784dc4c4aa94c83391dbd9b9bd7b66baa30e95a666c5ec1b168b1", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
- "ueberauth_facebook": {:hex, :ueberauth_facebook, "0.8.0", "9ec8571f804dd5c06f4e305d70606b39fc0ac8a8f43ed56ebb76012a97d14729", [:mix], [{:oauth2, "~> 0.9", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.4", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm"},
- "ueberauth_twitter": {:hex, :ueberauth_twitter, "0.2.4", "770ac273cc696cde986582e7a36df0923deb39fa3deff0152fbf150343809f81", [:mix], [{:httpoison, "~> 0.7", [hex: :httpoison, repo: "hexpm", optional: false]}, {:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 1.3 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.2", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm"},
+ "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
From 642075b1a935c42181a10ea695b2289883126136 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 27 Mar 2019 16:20:50 +0300
Subject: [PATCH 10/17] [#923] Enabled binding of multiple OAuth provider
accounts to single user.
---
priv/repo/migrations/20190315101315_create_registrations.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/priv/repo/migrations/20190315101315_create_registrations.exs b/priv/repo/migrations/20190315101315_create_registrations.exs
index fbb22ec7c..6b28cbdd3 100644
--- a/priv/repo/migrations/20190315101315_create_registrations.exs
+++ b/priv/repo/migrations/20190315101315_create_registrations.exs
@@ -13,6 +13,6 @@ def change do
end
create unique_index(:registrations, [:provider, :uid])
- create unique_index(:registrations, [:user_id, :provider])
+ create unique_index(:registrations, [:user_id, :provider, :uid])
end
end
From eadafc88b898879eb50545b700ea13c8596e908b Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 1 Apr 2019 09:28:56 +0300
Subject: [PATCH 11/17] [#923] Deps config adjustment (no `override` for
`httpoison`), code analysis issues fixes.
---
lib/pleroma/web/auth/pleroma_authenticator.ex | 2 +-
lib/pleroma/web/endpoint.ex | 3 ++-
lib/pleroma/web/oauth/oauth_controller.ex | 2 +-
mix.exs | 2 +-
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 8b190f97f..c826adb4c 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -4,9 +4,9 @@
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Comeonin.Pbkdf2
- alias Pleroma.User
alias Pleroma.Registration
alias Pleroma.Repo
+ alias Pleroma.User
@behaviour Pleroma.Web.Auth.Authenticator
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index f92724d8b..b85b95bf9 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -60,7 +60,8 @@ defmodule Pleroma.Web.Endpoint do
same_site =
if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do
- # Note: "SameSite=Strict" prevents sign in with external OAuth provider (no cookies during callback request)
+ # Note: "SameSite=Strict" prevents sign in with external OAuth provider
+ # (there would be no cookies during callback request from OAuth provider)
"SameSite=Lax"
else
"SameSite=Strict"
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index e54e196aa..54e0a35ba 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
+ alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Registration
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
diff --git a/mix.exs b/mix.exs
index 34c17bd6b..2b0d25b55 100644
--- a/mix.exs
+++ b/mix.exs
@@ -76,7 +76,7 @@ defp deps do
{:phoenix_html, "~> 2.10"},
{:calendar, "~> 0.17.4"},
{:cachex, "~> 3.0.2"},
- {:httpoison, "~> 1.2.0", override: true},
+ {:httpoison, "~> 1.2.0"},
{:poison, "~> 3.0", override: true},
{:tesla, "~> 1.2"},
{:jason, "~> 1.0"},
From 804173fc924ec591558b8ed7671e35b506be9345 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 1 Apr 2019 09:45:44 +0300
Subject: [PATCH 12/17] [#923] Minor code readability fix.
---
lib/pleroma/web/auth/authenticator.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index bb87b323c..4eeef5034 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.Authenticator do
- alias Pleroma.User
alias Pleroma.Registration
+ alias Pleroma.User
def implementation do
Pleroma.Config.get(
From f7cd9131d4aa0da3c4c0174acc56ce1bbdbd284c Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Thu, 4 Apr 2019 22:41:03 +0300
Subject: [PATCH 13/17] [#923] OAuth consumer controller tests. Misc.
improvements.
---
lib/pleroma/web/oauth/oauth_controller.ex | 4 +
.../templates/o_auth/o_auth/register.html.eex | 1 +
.../web/templates/o_auth/o_auth/show.html.eex | 2 +-
test/support/factory.ex | 16 +
test/web/oauth/oauth_controller_test.exs | 329 +++++++++++++++++-
5 files changed, 344 insertions(+), 8 deletions(-)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 1b467e983..2dcaaabc1 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -253,6 +253,7 @@ def callback(conn, params) do
auth_params = %{
"client_id" => params["client_id"],
"redirect_uri" => params["redirect_uri"],
+ "state" => params["state"],
"scopes" => oauth_scopes(params, nil)
}
@@ -289,6 +290,7 @@ def registration_details(conn, params) do
render(conn, "register.html", %{
client_id: params["client_id"],
redirect_uri: params["redirect_uri"],
+ state: params["state"],
scopes: oauth_scopes(params, []),
nickname: params["nickname"],
email: params["email"]
@@ -313,6 +315,8 @@ def register(conn, %{"op" => "connect"} = params) do
)
else
_ ->
+ params = Map.delete(params, "password")
+
conn
|> put_flash(:error, "Unknown error, please try again.")
|> redirect(to: o_auth_path(conn, :registration_details, params))
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index f4547170c..2e806e5fb 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -44,5 +44,6 @@ please provide the details below.
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
+<%= hidden_input f, :state, value: @state %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index e6cf1db45..0144675ab 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -22,7 +22,7 @@
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :response_type, value: @response_type %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
-<%= hidden_input f, :state, value: @state%>
+<%= hidden_input f, :state, value: @state %>
<%= submit "Authorize" %>
<% end %>
diff --git a/test/support/factory.ex b/test/support/factory.ex
index e1a08315a..67953931b 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -257,4 +257,20 @@ def notification_factory do
user: build(:user)
}
end
+
+ def registration_factory do
+ user = insert(:user)
+
+ %Pleroma.Registration{
+ user: user,
+ provider: "twitter",
+ uid: "171799000",
+ info: %{
+ "name" => "John Doe",
+ "email" => "john@doe.com",
+ "nickname" => "johndoe",
+ "description" => "My bio"
+ }
+ }
+ end
end
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index a9a0b9ed4..e13f4700d 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -5,24 +5,339 @@
defmodule Pleroma.Web.OAuth.OAuthControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
+ import Mock
+ alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
+ @session_opts [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ describe "in OAuth consumer mode, " do
+ setup do
+ oauth_consumer_enabled_path = [:auth, :oauth_consumer_enabled]
+ oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
+ oauth_consumer_enabled = Pleroma.Config.get(oauth_consumer_enabled_path)
+ oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
+
+ Pleroma.Config.put(oauth_consumer_enabled_path, true)
+ Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
+
+ on_exit(fn ->
+ Pleroma.Config.put(oauth_consumer_enabled_path, oauth_consumer_enabled)
+ Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
+ end)
+
+ [
+ app: insert(:oauth_app),
+ conn:
+ build_conn()
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
+ |> fetch_session()
+ ]
+ end
+
+ test "GET /oauth/authorize also renders OAuth consumer form", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/authorize",
+ %{
+ "response_type" => "code",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "scope" => "read"
+ }
+ )
+
+ assert response = html_response(conn, 200)
+ assert response =~ "Sign in with Twitter"
+ assert response =~ o_auth_path(conn, :prepare_request)
+ end
+
+ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/prepare_request",
+ %{
+ "provider" => "twitter",
+ "scope" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state"
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ redirected_to = redirected_to(conn)
+ [state] = Regex.run(~r/(?<=state=).*?(?=\Z|&)/, redirected_to)
+ state = URI.decode(state)
+ assert {:ok, state_params} = Poison.decode(state)
+
+ expected_scope_param = Enum.join(app.scopes, "+")
+ expected_client_id_param = app.client_id
+ expected_redirect_uri_param = app.redirect_uris
+
+ assert %{
+ "scope" => ^expected_scope_param,
+ "client_id" => ^expected_client_id_param,
+ "redirect_uri" => ^expected_redirect_uri_param,
+ "state" => "a_state"
+ } = state_params
+ end
+
+ test "on authentication error, redirects to `redirect_uri`", %{app: app, conn: conn} do
+ state_params = %{
+ "scope" => Enum.join(app.scopes, " "),
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => ""
+ }
+
+ conn =
+ conn
+ |> assign(:ueberauth_failure, %{errors: [%{message: "unknown error"}]})
+ |> get(
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) == app.redirect_uris
+ end
+
+ test "with user-bound registration, GET /oauth//callback redirects to `redirect_uri` with `code`",
+ %{app: app, conn: conn} do
+ registration = insert(:registration)
+
+ state_params = %{
+ "scope" => Enum.join(app.scopes, " "),
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => ""
+ }
+
+ with_mock Pleroma.Web.Auth.Authenticator,
+ get_registration: fn _, _ -> {:ok, registration} end do
+ conn =
+ get(
+ conn,
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
+ end
+
+ test "with user-unbound registration, GET /oauth//callback redirects to registration_details page",
+ %{app: app, conn: conn} do
+ registration = insert(:registration, user: nil)
+
+ state_params = %{
+ "scope" => "read",
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state"
+ }
+
+ with_mock Pleroma.Web.Auth.Authenticator,
+ get_registration: fn _, _ -> {:ok, registration} end do
+ conn =
+ get(
+ conn,
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ expected_redirect_params =
+ state_params
+ |> Map.delete("scope")
+ |> Map.merge(%{
+ "scopes" => ["read"],
+ "email" => Registration.email(registration),
+ "nickname" => Registration.nickname(registration)
+ })
+
+ assert response = html_response(conn, 302)
+
+ assert redirected_to(conn) ==
+ o_auth_path(conn, :registration_details, expected_redirect_params)
+ end
+ end
+
+ test "GET /oauth/registration_details renders registration details form", %{
+ app: app,
+ conn: conn
+ } do
+ conn =
+ get(
+ conn,
+ "/oauth/registration_details",
+ %{
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => nil,
+ "email" => "john@doe.com"
+ }
+ )
+
+ assert response = html_response(conn, 200)
+ assert response =~ ~r/name="op" type="submit" value="register"/
+ assert response =~ ~r/name="op" type="submit" value="connect"/
+ end
+
+ test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
+ %{
+ app: app,
+ conn: conn
+ } do
+ registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "register",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => "availablenick",
+ "email" => "available@email.com"
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
+
+ test "with invalid params, POST /oauth/register?op=register redirects to registration_details page",
+ %{
+ app: app,
+ conn: conn
+ } do
+ another_user = insert(:user)
+ registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
+
+ params = %{
+ "op" => "register",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "nickname" => another_user.nickname,
+ "email" => another_user.email
+ }
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post("/oauth/register", params)
+
+ assert response = html_response(conn, 302)
+
+ assert redirected_to(conn) ==
+ o_auth_path(conn, :registration_details, params)
+ end
+
+ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
+ %{
+ app: app,
+ conn: conn
+ } do
+ user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
+ registration = insert(:registration, user: nil)
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post(
+ "/oauth/register",
+ %{
+ "op" => "connect",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "auth_name" => user.nickname,
+ "password" => "testpassword"
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
+ end
+
+ test "with invalid params, POST /oauth/register?op=connect redirects to registration_details page",
+ %{
+ app: app,
+ conn: conn
+ } do
+ user = insert(:user)
+ registration = insert(:registration, user: nil)
+
+ params = %{
+ "op" => "connect",
+ "scopes" => app.scopes,
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => "a_state",
+ "auth_name" => user.nickname,
+ "password" => "wrong password"
+ }
+
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post("/oauth/register", params)
+
+ assert response = html_response(conn, 302)
+
+ assert redirected_to(conn) ==
+ o_auth_path(conn, :registration_details, Map.delete(params, "password"))
+ end
+ end
+
describe "GET /oauth/authorize" do
setup do
- session_opts = [
- store: :cookie,
- key: "_test",
- signing_salt: "cooldude"
- ]
-
[
app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
conn:
build_conn()
- |> Plug.Session.call(Plug.Session.init(session_opts))
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
|> fetch_session()
]
end
From 3e7f2bfc2f4769af3cedea3126fa0b3cab3f2b7b Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Fri, 5 Apr 2019 09:19:17 +0300
Subject: [PATCH 14/17] [#923] OAuthController#callback adjustments (with
tests).
---
lib/pleroma/web/oauth/oauth_controller.ex | 8 +------
test/web/oauth/oauth_controller_test.exs | 27 +++++++++++------------
2 files changed, 14 insertions(+), 21 deletions(-)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 2dcaaabc1..404728899 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -249,13 +249,7 @@ def callback(conn, params) do
with {:ok, registration} <- Authenticator.get_registration(conn, params) do
user = Repo.preload(registration, :user).user
-
- auth_params = %{
- "client_id" => params["client_id"],
- "redirect_uri" => params["redirect_uri"],
- "state" => params["state"],
- "scopes" => oauth_scopes(params, nil)
- }
+ auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
if user do
create_authorization(
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index e13f4700d..75333f2d5 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -73,7 +73,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
"/oauth/prepare_request",
%{
"provider" => "twitter",
- "scope" => app.scopes,
+ "scope" => "read follow",
"client_id" => app.client_id,
"redirect_uri" => app.redirect_uris,
"state" => "a_state"
@@ -81,21 +81,20 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
)
assert response = html_response(conn, 302)
- redirected_to = redirected_to(conn)
- [state] = Regex.run(~r/(?<=state=).*?(?=\Z|&)/, redirected_to)
- state = URI.decode(state)
- assert {:ok, state_params} = Poison.decode(state)
- expected_scope_param = Enum.join(app.scopes, "+")
- expected_client_id_param = app.client_id
- expected_redirect_uri_param = app.redirect_uris
+ redirect_query = URI.parse(redirected_to(conn)).query
+ assert %{"state" => state_param} = URI.decode_query(redirect_query)
+ assert {:ok, state_components} = Poison.decode(state_param)
+
+ expected_client_id = app.client_id
+ expected_redirect_uri = app.redirect_uris
assert %{
- "scope" => ^expected_scope_param,
- "client_id" => ^expected_client_id_param,
- "redirect_uri" => ^expected_redirect_uri_param,
+ "scope" => "read follow",
+ "client_id" => ^expected_client_id,
+ "redirect_uri" => ^expected_redirect_uri,
"state" => "a_state"
- } = state_params
+ } = state_components
end
test "on authentication error, redirects to `redirect_uri`", %{app: app, conn: conn} do
@@ -158,7 +157,7 @@ test "with user-unbound registration, GET /oauth//callback redirects t
registration = insert(:registration, user: nil)
state_params = %{
- "scope" => "read",
+ "scope" => "read write",
"client_id" => app.client_id,
"redirect_uri" => app.redirect_uris,
"state" => "a_state"
@@ -182,7 +181,7 @@ test "with user-unbound registration, GET /oauth//callback redirects t
state_params
|> Map.delete("scope")
|> Map.merge(%{
- "scopes" => ["read"],
+ "scope" => "read write",
"email" => Registration.email(registration),
"nickname" => Registration.nickname(registration)
})
From 47a236f7537ad4366d07361d184c84f3912648f1 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Fri, 5 Apr 2019 15:12:02 +0300
Subject: [PATCH 15/17] [#923] OAuth consumer mode refactoring, new tests,
tests adjustments, readme.
---
config/config.exs | 4 +-
docs/config.md | 55 +++++++
lib/pleroma/config.ex | 4 +
lib/pleroma/web/endpoint.ex | 2 +-
lib/pleroma/web/oauth/fallback_controller.ex | 17 ++-
lib/pleroma/web/oauth/oauth_controller.ex | 136 +++++++++---------
.../templates/o_auth/o_auth/consumer.html.eex | 2 +-
.../web/templates/o_auth/o_auth/show.html.eex | 2 +-
test/registration_test.exs | 59 ++++++++
test/web/oauth/oauth_controller_test.exs | 112 +++++++--------
10 files changed, 258 insertions(+), 135 deletions(-)
create mode 100644 test/registration_test.exs
diff --git a/config/config.exs b/config/config.exs
index 9bc79f939..05b164273 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -397,9 +397,7 @@
base_path: "/oauth",
providers: ueberauth_providers
-config :pleroma, :auth,
- oauth_consumer_strategies: oauth_consumer_strategies,
- oauth_consumer_enabled: oauth_consumer_strategies != []
+config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
diff --git a/docs/config.md b/docs/config.md
index 06d6fd757..36d7f1273 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -412,3 +412,58 @@ Pleroma account will be created with the same name as the LDAP user name.
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
+
+## :auth
+
+Authentication / authorization settings.
+
+* `oauth_consumer_strategies`: lists enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
+
+OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
+Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
+
+Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,
+e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.
+The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
+
+Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.
+
+* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https:///oauth/twitter/callback
+
+* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https:///oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps//fb-login/settings/
+
+* For Google, [register an app](https://console.developers.google.com), configure callback URL to https:///oauth/google/callback
+
+* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https:///oauth/microsoft/callback
+
+Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
+per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
+
+```
+# Twitter
+config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
+ consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
+ consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
+
+# Facebook
+config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
+ client_id: System.get_env("FACEBOOK_APP_ID"),
+ client_secret: System.get_env("FACEBOOK_APP_SECRET"),
+ redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
+
+# Google
+config :ueberauth, Ueberauth.Strategy.Google.OAuth,
+ client_id: System.get_env("GOOGLE_CLIENT_ID"),
+ client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
+ redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
+
+# Microsoft
+config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
+ client_id: System.get_env("MICROSOFT_CLIENT_ID"),
+ client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
+
+config :ueberauth, Ueberauth,
+ providers: [
+ microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
+ ]
+```
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index 21507cd38..189faa15f 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -57,4 +57,8 @@ def delete([parent_key | keys]) do
def delete(key) do
Application.delete_env(:pleroma, key)
end
+
+ def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
+
+ def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index b85b95bf9..085f23159 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.Endpoint do
else: "pleroma_key"
same_site =
- if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do
+ if Pleroma.Config.oauth_consumer_enabled?() do
# Note: "SameSite=Strict" prevents sign in with external OAuth provider
# (there would be no cookies during callback request from OAuth provider)
"SameSite=Lax"
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex
index f0fe3b578..afaa00242 100644
--- a/lib/pleroma/web/oauth/fallback_controller.ex
+++ b/lib/pleroma/web/oauth/fallback_controller.ex
@@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.OAuthController
- # No user/password
- def call(conn, _) do
+ def call(conn, {:register, :generic_error}) do
+ conn
+ |> put_status(:internal_server_error)
+ |> put_flash(:error, "Unknown error, please check the details and try again.")
+ |> OAuthController.registration_details(conn.params)
+ end
+
+ def call(conn, {:register, _error}) do
+ conn
+ |> put_status(:unauthorized)
+ |> put_flash(:error, "Invalid Username/Password")
+ |> OAuthController.registration_details(conn.params)
+ end
+
+ def call(conn, _error) do
conn
|> put_status(:unauthorized)
|> put_flash(:error, "Invalid Username/Password")
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 404728899..108303eb2 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
- if Pleroma.Config.get([:auth, :oauth_consumer_enabled]), do: plug(Ueberauth)
+ if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
plug(:fetch_session)
plug(:fetch_flash)
@@ -62,60 +62,65 @@ defp do_authorize(conn, params) do
def create_authorization(
conn,
- %{
- "authorization" => %{"redirect_uri" => redirect_uri} = auth_params
- } = params,
+ %{"authorization" => auth_params} = params,
opts \\ []
) do
- with {:ok, auth} <-
- (opts[:auth] && {:ok, opts[:auth]}) ||
- do_create_authorization(conn, params, opts[:user]) do
- redirect_uri = redirect_uri(conn, redirect_uri)
-
- cond do
- redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
- render(conn, "results.html", %{
- auth: auth
- })
-
- true ->
- connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
- url = "#{redirect_uri}#{connector}"
- url_params = %{:code => auth.token}
-
- url_params =
- if auth_params["state"] do
- Map.put(url_params, :state, auth_params["state"])
- else
- url_params
- end
-
- url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
-
- redirect(conn, external: url)
- end
+ with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
+ after_create_authorization(conn, auth, auth_params)
else
- {scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
- conn
- |> put_flash(:error, "This action is outside the authorized scopes")
- |> put_status(:unauthorized)
- |> authorize(auth_params)
-
- {:auth_active, false} ->
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
- conn
- |> put_flash(:error, "Your login is missing a confirmed e-mail address")
- |> put_status(:forbidden)
- |> authorize(auth_params)
-
error ->
- Authenticator.handle_error(conn, error)
+ handle_create_authorization_error(conn, error, auth_params)
end
end
+ def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
+ redirect_uri = redirect_uri(conn, redirect_uri)
+
+ if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
+ render(conn, "results.html", %{
+ auth: auth
+ })
+ else
+ connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
+ url = "#{redirect_uri}#{connector}"
+ url_params = %{:code => auth.token}
+
+ url_params =
+ if auth_params["state"] do
+ Map.put(url_params, :state, auth_params["state"])
+ else
+ url_params
+ end
+
+ url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
+
+ redirect(conn, external: url)
+ end
+ end
+
+ defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
+ when scopes_issue in [:unsupported_scopes, :missing_scopes] do
+ # Per https://github.com/tootsuite/mastodon/blob/
+ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
+ conn
+ |> put_flash(:error, "This action is outside the authorized scopes")
+ |> put_status(:unauthorized)
+ |> authorize(auth_params)
+ end
+
+ defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
+ # Per https://github.com/tootsuite/mastodon/blob/
+ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
+ conn
+ |> put_flash(:error, "Your login is missing a confirmed e-mail address")
+ |> put_status(:forbidden)
+ |> authorize(auth_params)
+ end
+
+ defp handle_create_authorization_error(conn, error, _auth_params) do
+ Authenticator.handle_error(conn, error)
+ end
+
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- get_app_from_request(conn, params),
fixed_token = fix_padding(params["code"]),
@@ -202,6 +207,7 @@ def token_revoke(conn, %{"token" => token} = params) do
end
end
+ @doc "Prepares OAuth request to provider for Ueberauth"
def prepare_request(conn, %{"provider" => provider} = params) do
scope =
oauth_scopes(params, [])
@@ -218,6 +224,7 @@ def prepare_request(conn, %{"provider" => provider} = params) do
|> Map.drop(~w(scope scopes client_id redirect_uri))
|> Map.put("state", state)
+ # Handing the request to Ueberauth
redirect(conn, to: o_auth_path(conn, :request, provider, params))
end
@@ -266,7 +273,7 @@ def callback(conn, params) do
conn
|> put_session(:registration_id, registration.id)
- |> redirect(to: o_auth_path(conn, :registration_details, registration_params))
+ |> registration_details(registration_params)
end
else
_ ->
@@ -292,32 +299,28 @@ def registration_details(conn, params) do
end
def register(conn, %{"op" => "connect"} = params) do
- create_authorization_params = %{
- "authorization" => Map.merge(params, %{"name" => params["auth_name"]})
- }
+ authorization_params = Map.put(params, "name", params["auth_name"])
+ create_authorization_params = %{"authorization" => authorization_params}
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
%Registration{} = registration <- Repo.get(Registration, registration_id),
- {:ok, auth} <- do_create_authorization(conn, create_authorization_params),
+ {_, {:ok, auth}} <-
+ {:create_authorization, do_create_authorization(conn, create_authorization_params)},
%User{} = user <- Repo.preload(auth, :user).user,
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
conn
|> put_session_registration_id(nil)
- |> create_authorization(
- create_authorization_params,
- auth: auth
- )
+ |> after_create_authorization(auth, authorization_params)
else
- _ ->
- params = Map.delete(params, "password")
+ {:create_authorization, error} ->
+ {:register, handle_create_authorization_error(conn, error, create_authorization_params)}
- conn
- |> put_flash(:error, "Unknown error, please try again.")
- |> redirect(to: o_auth_path(conn, :registration_details, params))
+ _ ->
+ {:register, :generic_error}
end
end
- def register(conn, params) do
+ def register(conn, %{"op" => "register"} = params) do
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
%Registration{} = registration <- Repo.get(Registration, registration_id),
{:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
@@ -349,13 +352,12 @@ def register(conn, params) do
)
conn
+ |> put_status(:forbidden)
|> put_flash(:error, "Error: #{message}.")
- |> redirect(to: o_auth_path(conn, :registration_details, params))
+ |> registration_details(params)
_ ->
- conn
- |> put_flash(:error, "Unknown error, please try again.")
- |> redirect(to: o_auth_path(conn, :registration_details, params))
+ {:register, :generic_error}
end
end
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index 002f014e6..9365c7c44 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -9,7 +9,7 @@
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
<%= hidden_input f, :state, value: @state %>
- <%= for strategy <- Pleroma.Config.get([:auth, :oauth_consumer_strategies], []) do %>
+ <%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
<%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 0144675ab..87278e636 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -26,6 +26,6 @@
<%= submit "Authorize" %>
<% end %>
-<%= if Pleroma.Config.get([:auth, :oauth_consumer_enabled]) do %>
+<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
<%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
<% end %>
diff --git a/test/registration_test.exs b/test/registration_test.exs
new file mode 100644
index 000000000..6143b82c7
--- /dev/null
+++ b/test/registration_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.RegistrationTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Registration
+ alias Pleroma.Repo
+
+ describe "generic changeset" do
+ test "requires :provider, :uid" do
+ registration = build(:registration, provider: nil, uid: nil)
+
+ cs = Registration.changeset(registration, %{})
+ refute cs.valid?
+
+ assert [
+ provider: {"can't be blank", [validation: :required]},
+ uid: {"can't be blank", [validation: :required]}
+ ] == cs.errors
+ end
+
+ test "ensures uniqueness of [:provider, :uid]" do
+ registration = insert(:registration)
+ registration2 = build(:registration, provider: registration.provider, uid: registration.uid)
+
+ cs = Registration.changeset(registration2, %{})
+ assert cs.valid?
+
+ assert {:error,
+ %Ecto.Changeset{
+ errors: [
+ uid:
+ {"has already been taken",
+ [constraint: :unique, constraint_name: "registrations_provider_uid_index"]}
+ ]
+ }} = Repo.insert(cs)
+
+ # Note: multiple :uid values per [:user_id, :provider] are intentionally allowed
+ cs2 = Registration.changeset(registration2, %{uid: "available.uid"})
+ assert cs2.valid?
+ assert {:ok, _} = Repo.insert(cs2)
+
+ cs3 = Registration.changeset(registration2, %{provider: "provider2"})
+ assert cs3.valid?
+ assert {:ok, _} = Repo.insert(cs3)
+ end
+
+ test "allows `nil` :user_id (user-unbound registration)" do
+ registration = build(:registration, user_id: nil)
+ cs = Registration.changeset(registration, %{})
+ assert cs.valid?
+ assert {:ok, _} = Repo.insert(cs)
+ end
+ end
+end
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 75333f2d5..385896dc6 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -20,16 +20,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
describe "in OAuth consumer mode, " do
setup do
- oauth_consumer_enabled_path = [:auth, :oauth_consumer_enabled]
oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
- oauth_consumer_enabled = Pleroma.Config.get(oauth_consumer_enabled_path)
oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
-
- Pleroma.Config.put(oauth_consumer_enabled_path, true)
Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
on_exit(fn ->
- Pleroma.Config.put(oauth_consumer_enabled_path, oauth_consumer_enabled)
Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
end)
@@ -42,7 +37,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
]
end
- test "GET /oauth/authorize also renders OAuth consumer form", %{
+ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
app: app,
conn: conn
} do
@@ -97,31 +92,6 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
} = state_components
end
- test "on authentication error, redirects to `redirect_uri`", %{app: app, conn: conn} do
- state_params = %{
- "scope" => Enum.join(app.scopes, " "),
- "client_id" => app.client_id,
- "redirect_uri" => app.redirect_uris,
- "state" => ""
- }
-
- conn =
- conn
- |> assign(:ueberauth_failure, %{errors: [%{message: "unknown error"}]})
- |> get(
- "/oauth/twitter/callback",
- %{
- "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
- "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
- "provider" => "twitter",
- "state" => Poison.encode!(state_params)
- }
- )
-
- assert response = html_response(conn, 302)
- assert redirected_to(conn) == app.redirect_uris
- end
-
test "with user-bound registration, GET /oauth//callback redirects to `redirect_uri` with `code`",
%{app: app, conn: conn} do
registration = insert(:registration)
@@ -152,7 +122,7 @@ test "with user-bound registration, GET /oauth//callback redirects to
end
end
- test "with user-unbound registration, GET /oauth//callback redirects to registration_details page",
+ test "with user-unbound registration, GET /oauth//callback renders registration_details page",
%{app: app, conn: conn} do
registration = insert(:registration, user: nil)
@@ -177,22 +147,43 @@ test "with user-unbound registration, GET /oauth//callback redirects t
}
)
- expected_redirect_params =
- state_params
- |> Map.delete("scope")
- |> Map.merge(%{
- "scope" => "read write",
- "email" => Registration.email(registration),
- "nickname" => Registration.nickname(registration)
- })
-
- assert response = html_response(conn, 302)
-
- assert redirected_to(conn) ==
- o_auth_path(conn, :registration_details, expected_redirect_params)
+ assert response = html_response(conn, 200)
+ assert response =~ ~r/name="op" type="submit" value="register"/
+ assert response =~ ~r/name="op" type="submit" value="connect"/
+ assert response =~ Registration.email(registration)
+ assert response =~ Registration.nickname(registration)
end
end
+ test "on authentication error, GET /oauth//callback redirects to `redirect_uri`", %{
+ app: app,
+ conn: conn
+ } do
+ state_params = %{
+ "scope" => Enum.join(app.scopes, " "),
+ "client_id" => app.client_id,
+ "redirect_uri" => app.redirect_uris,
+ "state" => ""
+ }
+
+ conn =
+ conn
+ |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
+ |> get(
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
+
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) == app.redirect_uris
+ assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
+ end
+
test "GET /oauth/registration_details renders registration details form", %{
app: app,
conn: conn
@@ -243,7 +234,7 @@ test "with valid params, POST /oauth/register?op=register redirects to `redirect
assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
end
- test "with invalid params, POST /oauth/register?op=register redirects to registration_details page",
+ test "with invalid params, POST /oauth/register?op=register renders registration_details page",
%{
app: app,
conn: conn
@@ -257,19 +248,22 @@ test "with invalid params, POST /oauth/register?op=register redirects to registr
"client_id" => app.client_id,
"redirect_uri" => app.redirect_uris,
"state" => "a_state",
- "nickname" => another_user.nickname,
- "email" => another_user.email
+ "nickname" => "availablenickname",
+ "email" => "available@email.com"
}
- conn =
- conn
- |> put_session(:registration_id, registration.id)
- |> post("/oauth/register", params)
+ for {bad_param, bad_param_value} <-
+ [{"nickname", another_user.nickname}, {"email", another_user.email}] do
+ bad_params = Map.put(params, bad_param, bad_param_value)
- assert response = html_response(conn, 302)
+ conn =
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> post("/oauth/register", bad_params)
- assert redirected_to(conn) ==
- o_auth_path(conn, :registration_details, params)
+ assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
+ assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
+ end
end
test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
@@ -300,7 +294,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_
assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
end
- test "with invalid params, POST /oauth/register?op=connect redirects to registration_details page",
+ test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
%{
app: app,
conn: conn
@@ -323,10 +317,8 @@ test "with invalid params, POST /oauth/register?op=connect redirects to registra
|> put_session(:registration_id, registration.id)
|> post("/oauth/register", params)
- assert response = html_response(conn, 302)
-
- assert redirected_to(conn) ==
- o_auth_path(conn, :registration_details, Map.delete(params, "password"))
+ assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
+ assert get_flash(conn, :error) == "Invalid Username/Password"
end
end
From e3328bc1382315c9067c099995a29db70d9d0433 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Sun, 7 Apr 2019 11:08:37 +0300
Subject: [PATCH 16/17] [#923] Removed
elements from auth forms, adjusted
docs, minor auth settings refactoring.
---
docs/config.md | 16 ++++++++++------
lib/pleroma/web/auth/authenticator.ex | 7 +++++--
.../templates/o_auth/o_auth/consumer.html.eex | 2 --
.../templates/o_auth/o_auth/register.html.eex | 8 +-------
4 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/docs/config.md b/docs/config.md
index 36d7f1273..686f1f36b 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -390,6 +390,11 @@ config :auto_linker,
]
```
+## 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 Pleroma
@@ -408,16 +413,15 @@ Pleroma account will be created with the same name as the LDAP user name.
* `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"
-## Pleroma.Web.Auth.Authenticator
-
-* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
-* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
-
## :auth
Authentication / authorization settings.
-* `oauth_consumer_strategies`: lists enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
+* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
+* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
+* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
+
+# OAuth consumer mode
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 4eeef5034..89d88af32 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -31,12 +31,15 @@ def handle_error(plug, error), do: implementation().handle_error(plug, error)
@callback auth_template() :: String.t() | nil
def auth_template do
- implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
+ # Note: `config :pleroma, :auth_template, "..."` support is deprecated
+ implementation().auth_template() ||
+ Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
+ "show.html"
end
@callback oauth_consumer_template() :: String.t() | nil
def oauth_consumer_template do
implementation().oauth_consumer_template() ||
- Pleroma.Config.get(:oauth_consumer_template, "consumer.html")
+ Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
end
end
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index 9365c7c44..85f62ca64 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,5 +1,3 @@
-
-
Sign in with external provider
<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index 2e806e5fb..126390391 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -7,10 +7,7 @@
Registration Details
-If you'd like to register a new account,
-
-please provide the details below.
-
+If you'd like to register a new account, please provide the details below.
<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
@@ -25,9 +22,6 @@ please provide the details below.
<%= submit "Proceed as new user", name: "op", value: "register" %>
-
-
-
Alternatively, sign in to connect to existing account.
From 44829d91818e66da1cbeb13aafecc52a931af17d Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Mon, 8 Apr 2019 12:32:55 +0300
Subject: [PATCH 17/17] AdminApiControllerTest unused variables fix.
---
.../admin_api/admin_api_controller_test.exs | 30 +++++++++----------
1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index dd2fbfb15..ca6bd0e97 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -80,14 +80,13 @@ test "allows to force-follow another user" do
user = insert(:user)
follower = insert(:user)
- conn =
- build_conn()
- |> assign(:user, admin)
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/user/follow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/user/follow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
user = User.get_by_id(user.id)
follower = User.get_by_id(follower.id)
@@ -104,14 +103,13 @@ test "allows to force-unfollow another user" do
User.follow(follower, user)
- conn =
- build_conn()
- |> assign(:user, admin)
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/user/unfollow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/user/unfollow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
user = User.get_by_id(user.id)
follower = User.get_by_id(follower.id)