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 @@ config :auto_linker, 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 @@ config :pleroma, Pleroma.Web.Endpoint, 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 %> <% 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 @@ defmodule Pleroma.Mixfile 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 @@ defmodule Pleroma.Mixfile 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 @@ defmodule Pleroma.Mixfile 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 @@ config :auto_linker, 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.Web.Auth.Authenticator 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController do {: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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 -> %> +
+ <%= label f, :scope, "Permissions" %> +
+ <%= text_input f, :scope, value: Enum.join(@available_scopes, " ") %> +
+
+ + <%= 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 @@ config :pleroma, :ldap, base: System.get_env("LDAP_BASE") || "dc=example,dc=com", uid: System.get_env("LDAP_UID") || "cn" -config :pleroma, :auth, oauth_consumer_enabled: false +config :pleroma, :auth, oauth_consumer_enabled: System.get_env("OAUTH_CONSUMER_ENABLED") == "true" config :ueberauth, Ueberauth, diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex new file mode 100644 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 @@ defmodule Pleroma.User do end # TODO: FIXME (WIP): - def oauth_register_changeset(struct, params \\ %{}) do + def external_registration_changeset(struct, params \\ %{}) do info_change = User.Info.confirmation_changeset(%User.Info{}, :confirmed) changeset = struct - |> cast(params, [:email, :nickname, :name, :bio, :auth_provider, :auth_provider_uid]) - |> validate_required([:auth_provider, :auth_provider_uid]) + |> cast(params, [:email, :nickname, :name, :bio]) |> unique_constraint(:email) |> unique_constraint(:nickname) |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) @@ -544,13 +543,6 @@ defmodule Pleroma.User do get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email) end - def get_by_auth_provider_uid(auth_provider, auth_provider_uid), - do: - Repo.get_by(User, - auth_provider: to_string(auth_provider), - auth_provider_uid: to_string(auth_provider_uid) - ) - def get_cached_user_info(user) do key = "user_info:#{user.id}" Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) 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 @@ defmodule Pleroma.Web.Auth.Authenticator do @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} def get_user(plug, params), do: implementation().get_user(plug, params) - @callback get_or_create_user_by_oauth(Plug.Conn.t(), Map.t()) :: + @callback get_by_external_registration(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} - def get_or_create_user_by_oauth(plug, params), - do: implementation().get_or_create_user_by_oauth(plug, params) + def get_by_external_registration(plug, params), + do: implementation().get_by_external_registration(plug, params) @callback handle_error(Plug.Conn.t(), any()) :: any() def handle_error(plug, error), do: implementation().handle_error(plug, error) 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 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do end end - def get_or_create_user_by_oauth(conn, params), do: get_user(conn, params) + def get_by_external_registration(conn, params), do: get_user(conn, params) def handle_error(%Plug.Conn{} = _conn, error) do error 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do end end - def get_or_create_user_by_oauth( + def get_by_external_registration( %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}}, _params ) do - user = User.get_by_auth_provider_uid(provider, uid) + registration = Registration.get_by_provider_uid(provider, uid) - if user do + if registration do + user = Repo.preload(registration, :user).user {:ok, user} else info = auth.info email = info.email nickname = info.nickname - # TODO: FIXME: connect to existing (non-oauth) account (need a UI flow for that) / generate a random nickname? + # Note: nullifying email in case this email is already taken email = if email && User.get_by_email(email) do nil @@ -48,31 +51,39 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do email end + # Note: generating a random numeric suffix to nickname in case this nickname is already taken nickname = if nickname && User.get_by_nickname(nickname) do - nil + "#{nickname}_#{:os.system_time()}" else nickname end - new_user = - User.oauth_register_changeset( - %User{}, - %{ - auth_provider: to_string(provider), - auth_provider_uid: to_string(uid), - name: info.name, - bio: info.description, - email: email, - nickname: nickname - } - ) - - Pleroma.Repo.insert(new_user) + with {:ok, new_user} <- + User.external_registration_changeset( + %User{}, + %{ + name: info.name, + bio: info.description, + email: email, + nickname: nickname + } + ) + |> Repo.insert(), + {:ok, _} <- + Registration.changeset(%Registration{}, %{ + user_id: new_user.id, + provider: to_string(provider), + uid: to_string(uid), + info: %{nickname: info.nickname, email: info.email} + }) + |> Repo.insert() do + {:ok, new_user} + end end end - def get_or_create_user_by_oauth(%Plug.Conn{} = _conn, _params), + def get_by_external_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials} def handle_error(%Plug.Conn{} = _conn, error) do 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 @@ defmodule Pleroma.Web.OAuth.OAuthController do conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params ) do - with {:ok, user} <- Authenticator.get_or_create_user_by_oauth(conn, params) do + with {:ok, user} <- Authenticator.get_by_external_registration(conn, params) do do_create_authorization( conn, %{ diff --git a/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs b/priv/repo/migrations/20190315101315_add_auth_provider_and_auth_provider_uid_to_users.exs deleted file mode 100644 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do # 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 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations 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 @@ defmodule Pleroma.Registration 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 @@ defmodule Pleroma.Web.Auth.Authenticator do @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} def get_user(plug, params), do: implementation().get_user(plug, params) - @callback get_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 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 %> + +<% end %> +<%= if get_flash(@conn, :error) do %> + +<% 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 @@ config :pleroma, :ldap, base: System.get_env("LDAP_BASE") || "dc=example,dc=com", uid: System.get_env("LDAP_UID") || "cn" -config :pleroma, :auth, oauth_consumer_enabled: 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController do _ -> 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 -> %> -
- <%= label f, :scope, "Permissions" %> -
- <%= text_input f, :scope, value: Enum.join(@available_scopes, " ") %> -
-
+
+
+

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 @@ defmodule Pleroma.Mixfile 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 @@ defmodule Pleroma.Mixfile do # # 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 @@ defmodule Pleroma.Mixfile 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 @@ config :pleroma, :ldap, 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 @@ oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGI 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 @@ defmodule Pleroma.Web.Auth.Authenticator do 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 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator 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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ +
+ <%= label @form, :scope, "Permissions" %> + +
+ <%= for scope <- @available_scopes do %> + <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> +
+ <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %> + <%= label @form, :"scope_#{scope}", String.capitalize(scope) %> +
+ <% 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 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 %>
-
-<%= label f, :scope, "Permissions" %> -
- <%= for scope <- @available_scopes do %> - <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> -
- <%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> - <%= label f, :"scope_#{scope}", String.capitalize(scope) %> -
- <% end %> -
-
+ +<%= 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 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations 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 @@ defmodule Pleroma.Mixfile 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "/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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do ) 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ config :ueberauth, 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 @@ defmodule Pleroma.Config 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthController 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do } = 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do } ) - 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do 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 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do |> 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 @@ defmodule Pleroma.Web.Auth.Authenticator do @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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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)