Merge remote-tracking branch 'remotes/origin/develop' into auth-improvements
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 6 Dec 2020 10:59:35 +0000 (13:59 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 6 Dec 2020 10:59:35 +0000 (13:59 +0300)
45 files changed:
.gitattributes
CHANGELOG.md
docs/configuration/static_dir.md
docs/dev.md
lib/pleroma/helpers/auth_helper.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/web.ex
lib/pleroma/web/masto_fe_controller.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
lib/pleroma/web/o_auth/mfa_controller.ex
lib/pleroma/web/o_auth/o_auth_controller.ex
lib/pleroma/web/o_auth/token.ex
lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
lib/pleroma/web/plugs/authentication_plug.ex
lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
lib/pleroma/web/plugs/ensure_user_key_plug.ex [deleted file]
lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex [new file with mode: 0644]
lib/pleroma/web/plugs/legacy_authentication_plug.ex [deleted file]
lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
lib/pleroma/web/plugs/o_auth_plug.ex
lib/pleroma/web/plugs/o_auth_scopes_plug.ex
lib/pleroma/web/plugs/session_authentication_plug.ex [deleted file]
lib/pleroma/web/plugs/set_user_session_id_plug.ex
lib/pleroma/web/plugs/user_enabled_plug.ex
lib/pleroma/web/plugs/user_fetcher_plug.ex
lib/pleroma/web/router.ex
lib/pleroma/web/templates/layout/app.html.eex
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
priv/static/instance/static.css [new file with mode: 0644]
test/pleroma/user_test.exs
test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
test/pleroma/web/admin_api/controllers/config_controller_test.exs
test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs
test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
test/pleroma/web/o_auth/o_auth_controller_test.exs
test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs
test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs
test/pleroma/web/plugs/authentication_plug_test.exs
test/pleroma/web/plugs/ensure_user_key_plug_test.exs [deleted file]
test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs [new file with mode: 0644]
test/pleroma/web/plugs/legacy_authentication_plug_test.exs [deleted file]
test/pleroma/web/plugs/o_auth_plug_test.exs
test/pleroma/web/plugs/session_authentication_plug_test.exs [deleted file]
test/pleroma/web/plugs/set_user_session_id_plug_test.exs

index 68895bf883004acdbc4f8faa03ae727cd1cb45ae..355e17f3cbcd8fdccb1968d863bbaa041e1f50d8 100644 (file)
@@ -1,8 +1,9 @@
 *.ex diff=elixir
 *.exs diff=elixir
-# At the time of writing all js/css files included
-# in the repo are minified bundles, and we don't want
-# to search/diff those as text files.
+
+# Most os js/css files included in the repo are minified bundles,
+#   and we don't want to search/diff those as text files. Exceptions are listed below.
 *.js binary
 *.js.map binary
 *.css binary
+priv/static/instance/static.css diff=css
index 606f6e1db7c98777c3679543a1a3cbff31262406..3974885848fbc8ccba3887f5673ae3b99ca1cf7d 100644 (file)
@@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
 - The site title is now injected as a `title` tag like preloads or metadata.
 - Password reset tokens now are not accepted after a certain age.
+- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
 
 <details>
   <summary>API Changes</summary>
index 8ac07b725f6bc34799981cea904af68780984e5c..a294bb604b0ed0e46d22998da8ec54d6fe966634 100644 (file)
@@ -88,3 +88,8 @@ config :pleroma, :frontend_configurations,
     Note the extra `static` folder for the terms-of-service.html
 
 Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by adding and changing `$static_dir/static/terms-of-service.html`.
+
+       
+## Styling rendered pages
+
+To overwrite the CSS stylesheet of the OAuth form and other static pages, you can upload your own CSS file to `instance/static/static.css`. This will completely replace the CSS used by those pages, so it might be a good idea to copy the one from `priv/static/instance/static.css` and make your changes.
index aa89a941fdfe3e011150a7115f47e372fbc12a2f..765380a58e3d504b0c13fcfc9b5723f480188d63 100644 (file)
@@ -14,9 +14,9 @@ This document contains notes and guidelines for Pleroma developers.
 
   For `:api` pipeline routes, it'll be verified whether `OAuthScopesPlug` was called or explicitly skipped, and if it was not then auth information will be dropped for request. Then `EnsurePublicOrAuthenticatedPlug` will be called to ensure that either the instance is not private or user is authenticated (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy for users.
 
-## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
+## Non-OAuth authentication
 
-* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Web.Plugs.AuthenticationPlug` and `Pleroma.Web.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided.
+* With non-OAuth authentication ([HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) or HTTP header- or params-provided auth), OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways); auth plugs invoke `Pleroma.Helpers.AuthHelper.skip_oauth(conn)` in this case.
 
 ## Auth-related configuration, OAuth consumer mode etc.
 
diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex
new file mode 100644 (file)
index 0000000..8f87b38
--- /dev/null
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Helpers.AuthHelper do
+  alias Pleroma.Web.Plugs.OAuthScopesPlug
+  alias Plug.Conn
+
+  import Plug.Conn
+
+  @oauth_token_session_key :oauth_token
+
+  @doc """
+  Skips OAuth permissions (scopes) checks, assigns nil `:token`.
+  Intended to be used with explicit authentication and only when OAuth token cannot be determined.
+  """
+  def skip_oauth(conn) do
+    conn
+    |> assign(:token, nil)
+    |> OAuthScopesPlug.skip_plug()
+  end
+
+  @doc "Drops authentication info from connection"
+  def drop_auth_info(conn) do
+    # To simplify debugging, setting a private variable on `conn` if auth info is dropped
+    conn
+    |> assign(:user, nil)
+    |> assign(:token, nil)
+    |> put_private(:authentication_ignored, true)
+  end
+
+  @doc "Gets OAuth token string from session"
+  def get_session_token(%Conn{} = conn) do
+    get_session(conn, @oauth_token_session_key)
+  end
+
+  @doc "Updates OAuth token string in session"
+  def put_session_token(%Conn{} = conn, token) when is_binary(token) do
+    put_session(conn, @oauth_token_session_key, token)
+  end
+
+  @doc "Deletes OAuth token string from session"
+  def delete_session_token(%Conn{} = conn) do
+    delete_session(conn, @oauth_token_session_key)
+  end
+end
index 4b3a9d690cd4566c8e1726f70b3a7225fc57b586..7d5e83a7f51bb5ade5599bb7f3cd5a3d0e43c57d 100644 (file)
@@ -2418,4 +2418,8 @@ defmodule Pleroma.User do
     |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
     |> Map.put(:fields, fields)
   end
+
+  def get_host(%User{ap_id: ap_id} = _user) do
+    URI.parse(ap_id).host
+  end
 end
index 6ed19d3dd9dfdf5c345564a680c0073992c6bd91..3ca20455d930fd4600c574b467efa64f02ccc8b4 100644 (file)
@@ -20,6 +20,7 @@ defmodule Pleroma.Web do
   below.
   """
 
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
@@ -75,7 +76,7 @@ defmodule Pleroma.Web do
       defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
         if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
              not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
-          OAuthScopesPlug.drop_auth_info(conn)
+          AuthHelper.drop_auth_info(conn)
         else
           conn
         end
index 08f92d55fb097f2fcf9a78424a360b1cd1cdfd15..20279ff45c58e92d866e169713e89ed71fb1ce24 100644 (file)
@@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastoFEController do
   use Pleroma.Web, :controller
 
   alias Pleroma.User
+  alias Pleroma.Web.MastodonAPI.AuthController
+  alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
@@ -26,27 +28,27 @@ defmodule Pleroma.Web.MastoFEController do
   )
 
   @doc "GET /web/*path"
-  def index(%{assigns: %{user: user, token: token}} = conn, _params)
-      when not is_nil(user) and not is_nil(token) do
-    conn
-    |> put_layout(false)
-    |> render("index.html",
-      token: token.token,
-      user: user,
-      custom_emojis: Pleroma.Emoji.get_all()
-    )
-  end
-
   def index(conn, _params) do
-    conn
-    |> put_session(:return_to, conn.request_path)
-    |> redirect(to: "/web/login")
+    with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
+         {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
+      conn
+      |> put_layout(false)
+      |> render("index.html",
+        token: token.token,
+        user: user,
+        custom_emojis: Pleroma.Emoji.get_all()
+      )
+    else
+      _ ->
+        conn
+        |> put_session(:return_to, conn.request_path)
+        |> redirect(to: "/web/login")
+    end
   end
 
   @doc "GET /web/manifest.json"
   def manifest(conn, _params) do
-    conn
-    |> render("manifest.json")
+    render(conn, "manifest.json")
   end
 
   @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
index 7011b7eb15a94694d1575dd81cdcb8d35a26d7a7..b4375872b6743ecf725fe60654f74fd05ddccb33 100644 (file)
@@ -25,7 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.OAuthController
-  alias Pleroma.Web.OAuth.OAuthView
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
@@ -103,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
          {:ok, user} <- TwitterAPI.register_user(params),
          {_, {:ok, token}} <-
            {:login, OAuthController.login(user, app, app.scopes)} do
-      json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+      OAuthController.after_token_exchange(conn, %{user: user, token: token})
     else
       {:login, {:account_status, :confirmation_pending}} ->
         json_response(conn, :ok, %{
index 9cc3984d0cd33fafaf9ccbd0229a5a0e082a4331..93d057a79ef6bbe35877572c6f2cd990e6bca901 100644 (file)
@@ -7,10 +7,13 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
+  alias Pleroma.Helpers.AuthHelper
+  alias Pleroma.Helpers.UriHelper
   alias Pleroma.User
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -20,24 +23,35 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
   @local_mastodon_name "Mastodon-Local"
 
   @doc "GET /web/login"
-  def login(%{assigns: %{user: %User{}}} = conn, _params) do
-    redirect(conn, to: local_mastodon_root_path(conn))
-  end
-
-  # Local Mastodon FE login init action
-  def login(conn, %{"code" => auth_token}) do
-    with {:ok, app} <- get_or_make_app(),
+  # Local Mastodon FE login callback action
+  def login(conn, %{"code" => auth_token} = params) do
+    with {:ok, app} <- local_mastofe_app(),
          {:ok, auth} <- Authorization.get_by_token(app, auth_token),
-         {:ok, token} <- Token.exchange_token(app, auth) do
+         {:ok, oauth_token} <- Token.exchange_token(app, auth) do
+      redirect_to =
+        conn
+        |> local_mastodon_post_login_path()
+        |> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
+
       conn
-      |> put_session(:oauth_token, token.token)
-      |> redirect(to: local_mastodon_root_path(conn))
+      |> AuthHelper.put_session_token(oauth_token.token)
+      |> redirect(to: redirect_to)
+    else
+      _ -> redirect_to_oauth_form(conn, params)
+    end
+  end
+
+  def login(conn, params) do
+    with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
+         {:ok, %{id: ^app_id}} <- local_mastofe_app() do
+      redirect(conn, to: local_mastodon_post_login_path(conn))
+    else
+      _ -> redirect_to_oauth_form(conn, params)
     end
   end
 
-  # Local Mastodon FE callback action
-  def login(conn, _) do
-    with {:ok, app} <- get_or_make_app() do
+  defp redirect_to_oauth_form(conn, _params) do
+    with {:ok, app} <- local_mastofe_app() do
       path =
         o_auth_path(conn, :authorize,
           response_type: "code",
@@ -52,9 +66,16 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
 
   @doc "DELETE /auth/sign_out"
   def logout(conn, _) do
-    conn
-    |> clear_session
-    |> redirect(to: "/")
+    conn =
+      with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
+           session_token = AuthHelper.get_session_token(conn),
+           {:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
+        AuthHelper.delete_session_token(conn)
+      else
+        _ -> conn
+      end
+
+    redirect(conn, to: "/")
   end
 
   @doc "POST /auth/password"
@@ -66,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
     json_response(conn, :no_content, "")
   end
 
-  defp local_mastodon_root_path(conn) do
+  defp local_mastodon_post_login_path(conn) do
     case get_session(conn, :return_to) do
       nil ->
         masto_fe_path(conn, :index, ["getting-started"])
@@ -77,9 +98,11 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
     end
   end
 
-  @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
-  defp get_or_make_app do
-    %{client_name: @local_mastodon_name, redirect_uris: "."}
-    |> App.get_or_make(["read", "write", "follow", "push", "admin"])
+  @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  def local_mastofe_app do
+    App.get_or_make(
+      %{client_name: @local_mastodon_name, redirect_uris: "."},
+      ["read", "write", "follow", "push", "admin"]
+    )
   end
 end
index f102c93e7e5bbfcd3be86f634cee3265c3f761e1..5d5ec286ae7cef12689de7a71a975b7be31ab264 100644 (file)
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.OAuth.MFAController do
   alias Pleroma.Web.Auth.TOTPAuthenticator
   alias Pleroma.Web.OAuth.MFAView, as: View
   alias Pleroma.Web.OAuth.OAuthController
-  alias Pleroma.Web.OAuth.OAuthView
   alias Pleroma.Web.OAuth.Token
 
   plug(:fetch_session when action in [:show, :verify])
@@ -75,7 +74,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
          {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
          {:ok, _} <- validates_challenge(user, params),
          {:ok, token} <- Token.exchange_token(app, auth) do
-      json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+      OAuthController.after_token_exchange(conn, %{user: user, token: token})
     else
       _error ->
         conn
index d2f9d1cebf379c941df3dfffe46d6b2f19215ac9..6e3c7e1a1c75c71372cc85e25e2ba7a5f9c1679f 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.OAuth.OAuthController do
   use Pleroma.Web, :controller
 
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.Helpers.UriHelper
   alias Pleroma.Maps
   alias Pleroma.MFA
@@ -79,6 +80,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     available_scopes = (app && app.scopes) || []
     scopes = Scopes.fetch_scopes(params, available_scopes)
 
+    user =
+      with %{assigns: %{user: %User{} = user}} <- conn do
+        user
+      else
+        _ -> nil
+      end
+
     scopes =
       if scopes == [] do
         available_scopes
@@ -88,6 +96,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
     # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
     render(conn, Authenticator.auth_template(), %{
+      user: user,
+      app: app && Map.delete(app, :client_secret),
       response_type: params["response_type"],
       client_id: params["client_id"],
       available_scopes: available_scopes,
@@ -131,11 +141,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     end
   end
 
-  def create_authorization(
-        %Plug.Conn{} = conn,
-        %{"authorization" => _} = params,
-        opts \\ []
-      ) do
+  def create_authorization(_, _, opts \\ [])
+
+  def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do
+    create_authorization(conn, params, user: user)
+  end
+
+  def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
     with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
          {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
       after_create_authorization(conn, auth, params)
@@ -248,7 +260,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     with {:ok, app} <- Token.Utils.fetch_app(conn),
          {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
          {:ok, token} <- RefreshToken.grant(token) do
-      json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+      after_token_exchange(conn, %{user: user, token: token})
     else
       _error -> render_invalid_credentials_error(conn)
     end
@@ -260,7 +272,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
          {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
          %User{} = user <- User.get_cached_by_id(auth.user_id),
          {:ok, token} <- Token.exchange_token(app, auth) do
-      json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+      after_token_exchange(conn, %{user: user, token: token})
     else
       error ->
         handle_token_exchange_error(conn, error)
@@ -275,7 +287,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
          {:ok, app} <- Token.Utils.fetch_app(conn),
          requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
          {:ok, token} <- login(user, app, requested_scopes) do
-      json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+      after_token_exchange(conn, %{user: user, token: token})
     else
       error ->
         handle_token_exchange_error(conn, error)
@@ -298,7 +310,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     with {:ok, app} <- Token.Utils.fetch_app(conn),
          {:ok, auth} <- Authorization.create_authorization(app, %User{}),
          {:ok, token} <- Token.exchange_token(app, auth) do
-      json(conn, OAuthView.render("token.json", %{token: token}))
+      after_token_exchange(conn, %{token: token})
     else
       _error ->
         handle_token_exchange_error(conn, :invalid_credentails)
@@ -308,6 +320,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   # Bad request
   def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
 
+  def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
+    conn
+    |> AuthHelper.put_session_token(token.token)
+    |> json(OAuthView.render("token.json", view_params))
+  end
+
   defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
     conn
     |> put_status(:forbidden)
@@ -361,9 +379,17 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     render_invalid_credentials_error(conn)
   end
 
-  def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
-    with {:ok, app} <- Token.Utils.fetch_app(conn),
-         {:ok, _token} <- RevokeToken.revoke(app, params) do
+  def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
+    with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
+         {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do
+      conn =
+        with session_token = AuthHelper.get_session_token(conn),
+             %Token{token: ^session_token} <- oauth_token do
+          AuthHelper.delete_session_token(conn)
+        else
+          _ -> conn
+        end
+
       json(conn, %{})
     else
       _error ->
index de37998f24578b60f28aee2c441d31840629273a..9170a7ec7daaf4fba673656f62d4e63d3da75a51 100644 (file)
@@ -27,6 +27,14 @@ defmodule Pleroma.Web.OAuth.Token do
     timestamps()
   end
 
+  @doc "Gets token by unique access token"
+  @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found}
+  def get_by_token(token) do
+    token
+    |> Query.get_by_token()
+    |> Repo.find_resource()
+  end
+
   @doc "Gets token for app by access token"
   @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
   def get_by_token(%App{id: app_id} = _app, token) do
index d7d4e4092b4ba51631262de2b8b168acb49f5d0e..ff851a8748e543f21a7bab41ff8e2f855265a2ff 100644 (file)
@@ -5,21 +5,14 @@
 defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
   import Plug.Conn
 
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.User
-  alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
 
   def init(options) do
     options
   end
 
-  def secret_token do
-    case Pleroma.Config.get(:admin_token) do
-      blank when blank in [nil, ""] -> nil
-      token -> token
-    end
-  end
-
   def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
 
   def call(conn, _) do
@@ -30,7 +23,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
     end
   end
 
-  def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
+  defp authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
     if admin_token == secret_token() do
       assign_admin_user(conn)
     else
@@ -38,7 +31,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
     end
   end
 
-  def authenticate(conn) do
+  defp authenticate(conn) do
     token = secret_token()
 
     case get_req_header(conn, "x-admin-token") do
@@ -48,10 +41,17 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
     end
   end
 
+  defp secret_token do
+    case Pleroma.Config.get(:admin_token) do
+      blank when blank in [nil, ""] -> nil
+      token -> token
+    end
+  end
+
   defp assign_admin_user(conn) do
     conn
     |> assign(:user, %User{is_admin: true})
-    |> OAuthScopesPlug.skip_plug()
+    |> AuthHelper.skip_oauth()
   end
 
   defp handle_bad_token(conn) do
index e2a8b1b69366062b8571f861ed17db1e56fd403c..a7b8a9bfe4ee5546b9e1244397751b171f0a5f94 100644 (file)
@@ -3,6 +3,9 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.AuthenticationPlug do
+  @moduledoc "Password authentication plug."
+
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.User
 
   import Plug.Conn
@@ -11,6 +14,30 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
 
   def init(options), do: options
 
+  def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+  def call(
+        %{
+          assigns: %{
+            auth_user: %{password_hash: password_hash} = auth_user,
+            auth_credentials: %{password: password}
+          }
+        } = conn,
+        _
+      ) do
+    if checkpw(password, password_hash) do
+      {:ok, auth_user} = maybe_update_password(auth_user, password)
+
+      conn
+      |> assign(:user, auth_user)
+      |> AuthHelper.skip_oauth()
+    else
+      conn
+    end
+  end
+
+  def call(conn, _), do: conn
+
   def checkpw(password, "$6" <> _ = password_hash) do
     :crypt.crypt(password, password_hash) == password_hash
   end
@@ -40,40 +67,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
   def maybe_update_password(user, _), do: {:ok, user}
 
   defp do_update_password(user, password) do
-    user
-    |> User.password_update_changeset(%{
-      "password" => password,
-      "password_confirmation" => password
-    })
-    |> Pleroma.Repo.update()
+    User.reset_password(user, %{password: password, password_confirmation: password})
   end
-
-  def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
-  def call(
-        %{
-          assigns: %{
-            auth_user: %{password_hash: password_hash} = auth_user,
-            auth_credentials: %{password: password}
-          }
-        } = conn,
-        _
-      ) do
-    if checkpw(password, password_hash) do
-      {:ok, auth_user} = maybe_update_password(auth_user, password)
-
-      conn
-      |> assign(:user, auth_user)
-      |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
-    else
-      conn
-    end
-  end
-
-  def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
-    Pbkdf2.no_user_verify()
-    conn
-  end
-
-  def call(conn, _), do: conn
 end
index 4dadfb0004526355f704f60ee7c4962b94225f3e..97529aedbae6f0d919f91e0333b0a57cf09b46a3 100644 (file)
@@ -3,6 +3,12 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do
+  @moduledoc """
+  Decodes HTTP Basic Auth information and assigns `:auth_credentials`.
+
+  NOTE: no checks are performed at this step, auth_credentials/username could be easily faked.
+  """
+
   import Plug.Conn
 
   def init(options) do
diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex
deleted file mode 100644 (file)
index 70d3091..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do
-  import Plug.Conn
-
-  def init(opts) do
-    opts
-  end
-
-  def call(%{assigns: %{user: _}} = conn, _), do: conn
-
-  def call(conn, _) do
-    conn
-    |> assign(:user, nil)
-  end
-end
diff --git a/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex
new file mode 100644 (file)
index 0000000..4253458
--- /dev/null
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do
+  import Plug.Conn
+
+  alias Pleroma.Helpers.AuthHelper
+  alias Pleroma.User
+  alias Pleroma.Web.OAuth.Token
+
+  @moduledoc "Ensures presence and consistency of :user and :token assigns."
+
+  def init(opts) do
+    opts
+  end
+
+  def call(%{assigns: %{user: %User{id: user_id}} = assigns} = conn, _) do
+    with %Token{user_id: ^user_id} <- assigns[:token] do
+      conn
+    else
+      %Token{} ->
+        # A safety net for abnormal (unexpected) scenario: :token belongs to another user
+        AuthHelper.drop_auth_info(conn)
+
+      _ ->
+        assign(conn, :token, nil)
+    end
+  end
+
+  def call(conn, _) do
+    conn
+    |> assign(:user, nil)
+    |> assign(:token, nil)
+  end
+end
diff --git a/lib/pleroma/web/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex
deleted file mode 100644 (file)
index 2a54d0b..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do
-  import Plug.Conn
-
-  alias Pleroma.User
-
-  def init(options) do
-    options
-  end
-
-  def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
-  def call(
-        %{
-          assigns: %{
-            auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user,
-            auth_credentials: %{password: password}
-          }
-        } = conn,
-        _
-      ) do
-    with ^password_hash <- :crypt.crypt(password, password_hash),
-         {:ok, user} <-
-           User.reset_password(auth_user, %{password: password, password_confirmation: password}) do
-      conn
-      |> assign(:auth_user, user)
-      |> assign(:user, user)
-      |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
-    else
-      _ ->
-        conn
-    end
-  end
-
-  def call(conn, _) do
-    conn
-  end
-end
index f44d4dee533b0bb7b2cd978b8b0662167fde8286..a0a0c5a9be0aa15287fe47fd0f953ac599a3bdd3 100644 (file)
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.Signature
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Utils
@@ -12,34 +13,16 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
 
   def init(options), do: options
 
-  defp key_id_from_conn(conn) do
-    with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
-         {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
-      ap_id
-    else
-      _ ->
-        nil
-    end
-  end
-
-  defp user_from_key_id(conn) do
-    with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
-         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
-      user
-    else
-      _ ->
-        nil
-    end
-  end
-
-  def call(%{assigns: %{user: _}} = conn, _opts), do: conn
+  def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn
 
   # if this has payload make sure it is signed by the same actor that made it
   def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
     with actor_id <- Utils.get_ap_id(actor),
          {:user, %User{} = user} <- {:user, user_from_key_id(conn)},
          {:user_match, true} <- {:user_match, user.ap_id == actor_id} do
-      assign(conn, :user, user)
+      conn
+      |> assign(:user, user)
+      |> AuthHelper.skip_oauth()
     else
       {:user_match, false} ->
         Logger.debug("Failed to map identity from signature (payload actor mismatch)")
@@ -57,7 +40,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
   # no payload, probably a signed fetch
   def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
     with %User{} = user <- user_from_key_id(conn) do
-      assign(conn, :user, user)
+      conn
+      |> assign(:user, user)
+      |> AuthHelper.skip_oauth()
     else
       _ ->
         Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
@@ -68,4 +53,24 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
 
   # no signature at all
   def call(conn, _opts), do: conn
+
+  defp key_id_from_conn(conn) do
+    with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
+         {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
+      ap_id
+    else
+      _ ->
+        nil
+    end
+  end
+
+  defp user_from_key_id(conn) do
+    with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
+         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
+      user
+    else
+      _ ->
+        nil
+    end
+  end
 end
index c7b58d90fa7dd20d28959c1664b782a38c9d74fe..eb287318b5b6b1f2be427fecbe132217574f853e 100644 (file)
@@ -3,9 +3,12 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.OAuthPlug do
+  @moduledoc "Performs OAuth authentication by token from params / headers / cookies."
+
   import Plug.Conn
   import Ecto.Query
 
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.OAuth.App
@@ -17,45 +20,26 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
 
   def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
 
-  def call(%{params: %{"access_token" => access_token}} = conn, _) do
-    with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
-      conn
-      |> assign(:token, token_record)
-      |> assign(:user, user)
-    else
-      _ ->
-        # token found, but maybe only with app
-        with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
-          conn
-          |> assign(:token, token_record)
-          |> assign(:app, app)
-        else
-          _ -> conn
-        end
-    end
-  end
-
   def call(conn, _) do
-    case fetch_token_str(conn) do
-      {:ok, token} ->
-        with {:ok, user, token_record} <- fetch_user_and_token(token) do
-          conn
-          |> assign(:token, token_record)
-          |> assign(:user, user)
-        else
-          _ ->
-            # token found, but maybe only with app
-            with {:ok, app, token_record} <- fetch_app_and_token(token) do
-              conn
-              |> assign(:token, token_record)
-              |> assign(:app, app)
-            else
-              _ -> conn
-            end
-        end
-
-      _ ->
+    with {:ok, token_str} <- fetch_token_str(conn) do
+      with {:ok, user, user_token} <- fetch_user_and_token(token_str),
+           false <- Token.is_expired?(user_token) do
         conn
+        |> assign(:token, user_token)
+        |> assign(:user, user)
+      else
+        _ ->
+          with {:ok, app, app_token} <- fetch_app_and_token(token_str),
+               false <- Token.is_expired?(app_token) do
+            conn
+            |> assign(:token, app_token)
+            |> assign(:app, app)
+          else
+            _ -> conn
+          end
+      end
+    else
+      _ -> conn
     end
   end
 
@@ -70,7 +54,6 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
         preload: [user: user]
       )
 
-    # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
     with %Token{user: user} = token_record <- Repo.one(query) do
       {:ok, user, token_record}
     end
@@ -86,29 +69,23 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
     end
   end
 
-  # Gets token from session by :oauth_token key
+  # Gets token string from conn (in params / headers / session)
   #
-  @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
-  defp fetch_token_from_session(conn) do
-    case get_session(conn, :oauth_token) do
-      nil -> :no_token_found
-      token -> {:ok, token}
-    end
+  @spec fetch_token_str(Plug.Conn.t() | list(String.t())) :: :no_token_found | {:ok, String.t()}
+  defp fetch_token_str(%Plug.Conn{params: %{"access_token" => access_token}} = _conn) do
+    {:ok, access_token}
   end
 
-  # Gets token from headers
-  #
-  @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
   defp fetch_token_str(%Plug.Conn{} = conn) do
     headers = get_req_header(conn, "authorization")
 
-    with :no_token_found <- fetch_token_str(headers),
-         do: fetch_token_from_session(conn)
+    with {:ok, token} <- fetch_token_str(headers) do
+      {:ok, token}
+    else
+      _ -> fetch_token_from_session(conn)
+    end
   end
 
-  @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
-  defp fetch_token_str([]), do: :no_token_found
-
   defp fetch_token_str([token | tail]) do
     trimmed_token = String.trim(token)
 
@@ -117,4 +94,14 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
       _ -> fetch_token_str(tail)
     end
   end
+
+  defp fetch_token_str([]), do: :no_token_found
+
+  @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
+  defp fetch_token_from_session(conn) do
+    case AuthHelper.get_session_token(conn) do
+      nil -> :no_token_found
+      token -> {:ok, token}
+    end
+  end
 end
index cfc30837c0244bc579dd7adafde57915f7950686..e6d398b149e81aa4554eb942a24adb27e8c697a0 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
   import Pleroma.Web.Gettext
 
   alias Pleroma.Config
+  alias Pleroma.Helpers.AuthHelper
 
   use Pleroma.Web, :plug
 
@@ -28,7 +29,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
         conn
 
       options[:fallback] == :proceed_unauthenticated ->
-        drop_auth_info(conn)
+        AuthHelper.drop_auth_info(conn)
 
       true ->
         missing_scopes = scopes -- matched_scopes
@@ -44,15 +45,6 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
     end
   end
 
-  @doc "Drops authentication info from connection"
-  def drop_auth_info(conn) do
-    # To simplify debugging, setting a private variable on `conn` if auth info is dropped
-    conn
-    |> put_private(:authentication_ignored, true)
-    |> assign(:user, nil)
-    |> assign(:token, nil)
-  end
-
   @doc "Keeps those of `scopes` which are descendants of `supported_scopes`"
   def filter_descendants(scopes, supported_scopes) do
     Enum.filter(
diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex
deleted file mode 100644 (file)
index 6e176d5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do
-  import Plug.Conn
-
-  def init(options) do
-    options
-  end
-
-  def call(conn, _) do
-    with saved_user_id <- get_session(conn, :user_id),
-         %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do
-      conn
-      |> assign(:user, conn.assigns.auth_user)
-    else
-      _ -> conn
-    end
-  end
-end
index e520159e4aaa5211e254e47c9e610a18bc80e307..9f4a6b6acb218d0456fe7638501ab378513886df 100644 (file)
@@ -3,16 +3,15 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do
-  import Plug.Conn
-  alias Pleroma.User
+  alias Pleroma.Helpers.AuthHelper
+  alias Pleroma.Web.OAuth.Token
 
   def init(opts) do
     opts
   end
 
-  def call(%{assigns: %{user: %User{id: id}}} = conn, _) do
-    conn
-    |> put_session(:user_id, id)
+  def call(%{assigns: %{token: %Token{} = oauth_token}} = conn, _) do
+    AuthHelper.put_session_token(conn, oauth_token.token)
   end
 
   def call(conn, _), do: conn
index fa28ee48b7179c0ffa661529f5392e30c4591091..4f1b163bd836980ef52011bad88a4e406d901f6b 100644 (file)
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.UserEnabledPlug do
-  import Plug.Conn
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.User
 
   def init(options) do
@@ -11,9 +11,10 @@ defmodule Pleroma.Web.Plugs.UserEnabledPlug do
   end
 
   def call(%{assigns: %{user: %User{} = user}} = conn, _) do
-    case User.account_status(user) do
-      :active -> conn
-      _ -> assign(conn, :user, nil)
+    if User.account_status(user) == :active do
+      conn
+    else
+      AuthHelper.drop_auth_info(conn)
     end
   end
 
index 4039600daff9ecae0d135f2ff798ef62441956ea..89e16b49f0b278a92b57483ee248354ee49c94d1 100644 (file)
@@ -3,6 +3,12 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Plugs.UserFetcherPlug do
+  @moduledoc """
+  Assigns `:auth_user` basing on `:auth_credentials`.
+
+  NOTE: no checks are performed at this step, auth_credentials/username could be easily faked.
+  """
+
   alias Pleroma.User
   import Plug.Conn
 
index 75a88537758f239bede91e85420e12221dade4fa..aefc9f0be47598df4bcf0acbff48c5347277e5ce 100644 (file)
@@ -34,6 +34,7 @@ defmodule Pleroma.Web.Router do
     plug(:fetch_session)
     plug(Pleroma.Web.Plugs.OAuthPlug)
     plug(Pleroma.Web.Plugs.UserEnabledPlug)
+    plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
   end
 
   pipeline :expect_authentication do
@@ -48,15 +49,13 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Web.Plugs.OAuthPlug)
     plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug)
     plug(Pleroma.Web.Plugs.UserFetcherPlug)
-    plug(Pleroma.Web.Plugs.SessionAuthenticationPlug)
-    plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug)
     plug(Pleroma.Web.Plugs.AuthenticationPlug)
   end
 
   pipeline :after_auth do
     plug(Pleroma.Web.Plugs.UserEnabledPlug)
     plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
-    plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
+    plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
   end
 
   pipeline :base_api do
@@ -100,7 +99,7 @@ defmodule Pleroma.Web.Router do
   pipeline :pleroma_html do
     plug(:browser)
     plug(:authenticate)
-    plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
+    plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
   end
 
   pipeline :well_known do
@@ -292,7 +291,6 @@ defmodule Pleroma.Web.Router do
 
     post("/main/ostatus", UtilController, :remote_subscribe)
     get("/ostatus_subscribe", RemoteFollowController, :follow)
-
     post("/ostatus_subscribe", RemoteFollowController, :do_follow)
   end
 
@@ -321,19 +319,25 @@ defmodule Pleroma.Web.Router do
   end
 
   scope "/oauth", Pleroma.Web.OAuth do
+    get("/registration_details", OAuthController, :registration_details)
+
+    post("/mfa/verify", MFAController, :verify, as: :mfa_verify)
+    get("/mfa", MFAController, :show)
+
     scope [] do
       pipe_through(:oauth)
+
       get("/authorize", OAuthController, :authorize)
+      post("/authorize", OAuthController, :create_authorization)
     end
 
-    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(:fetch_session)
 
-    post("/mfa/challenge", MFAController, :challenge)
-    post("/mfa/verify", MFAController, :verify, as: :mfa_verify)
-    get("/mfa", MFAController, :show)
+      post("/token", OAuthController, :token_exchange)
+      post("/revoke", OAuthController, :token_revoke)
+      post("/mfa/challenge", MFAController, :challenge)
+    end
 
     scope [] do
       pipe_through(:browser)
index 3f28f1920a1816d760ec65e9173f559f084392e1..1ede59fd850cef070e7ee4f41015426ae9fc477a 100644 (file)
 <!DOCTYPE html>
-<html>
+<html lang="en">
   <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
-    <title>
-    <%= Pleroma.Config.get([:instance, :name]) %>
-    </title>
-    <style>
-      body {
-        background-color: #121a24;
-        font-family: sans-serif;
-        color: #b9b9ba;
-        text-align: center;
-      }
-
-      .container {
-        max-width: 420px;
-        padding: 20px;
-        background-color: #182230;
-        border-radius: 4px;
-        margin: auto;
-        margin-top: 10vh;
-        box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
-      }
-
-      h1 {
-        margin: 0;
-        font-size: 24px;
-      }
-
-      h2 {
-        color: #b9b9ba;
-        font-weight: normal;
-        font-size: 18px;
-        margin-bottom: 20px;
-      }
-
-      a {
-        color: #d8a070;
-        text-decoration: none;
-      }
-
-      form {
-        width: 100%;
-      }
-
-      .input {
-        text-align: left;
-        color: #89898a;
-        display: flex;
-        flex-direction: column;
-      }
-
-      input {
-        box-sizing: content-box;
-        padding: 10px;
-        margin-top: 5px;
-        margin-bottom: 10px;
-        background-color: #121a24;
-        color: #b9b9ba;
-        border: 0;
-        transition-property: border-bottom;
-        transition-duration: 0.35s;
-        border-bottom: 2px solid #2a384a;
-        font-size: 14px;
-      }
-
-      .scopes-input {
-        display: flex;
-        flex-direction: column;
-        margin-top: 1em;
-        text-align: left;
-        color: #89898a;
-      }
-
-      .scopes-input label:first-child {
-        height: 2em;
-      }
-
-      .scopes {
-        display: flex;
-        flex-wrap: wrap;
-        text-align: left;
-        color: #b9b9ba;
-      }
-
-      .scope {
-        display: flex;
-        flex-basis: 100%;
-        height: 2em;
-        align-items: center;
-      }
-
-      .scope:before {
-        color: #b9b9ba;
-        content: "✔\fe0e";
-        margin-left: 1em;
-        margin-right: 1em;
-      }
-
-      [type="checkbox"] + label {
-        display: none;
-        cursor: pointer;
-        margin: 0.5em;
-      }
-
-      [type="checkbox"] {
-        display: none;
-      }
-
-      [type="checkbox"] + label:before {
-        cursor: pointer;
-        display: inline-block;
-        color: white;
-        background-color: #121a24;
-        border: 4px solid #121a24;
-        box-shadow: 0px 0px 1px 0 #d8a070;
-        box-sizing: border-box;
-        width: 1.2em;
-        height: 1.2em;
-        margin-right: 1.0em;
-        content: "";
-        transition-property: background-color;
-        transition-duration: 0.35s;
-        color: #121a24;
-        margin-bottom: -0.2em;
-        border-radius: 2px;
-      }
-
-      [type="checkbox"]:checked + label:before {
-        background-color: #d8a070;
-      }
-
-      input:focus {
-        outline: none;
-        border-bottom: 2px solid #d8a070;
-      }
-
-      button {
-        box-sizing: border-box;
-        width: 100%;
-        background-color: #1c2a3a;
-        color: #b9b9ba;
-        border-radius: 4px;
-        border: none;
-        padding: 10px;
-        margin-top: 20px;
-        margin-bottom: 20px;
-        text-transform: uppercase;
-        font-size: 16px;
-        box-shadow: 0px 0px 2px 0px black,
-          0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
-          0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
-      }
-
-      button:hover {
-        cursor: pointer;
-        box-shadow: 0px 0px 0px 1px #d8a070,
-          0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
-          0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
-      }
-
-      .alert-danger {
-        box-sizing: border-box;
-        width: 100%;
-        background-color: #931014;
-        border: 1px solid #a06060;
-        border-radius: 4px;
-        padding: 10px;
-        margin-top: 20px;
-        font-weight: 500;
-        font-size: 16px;
-      }
-
-      .alert-info {
-        box-sizing: border-box;
-        width: 100%;
-        border-radius: 4px;
-        border: 1px solid #7d796a;
-        padding: 10px;
-        margin-top: 20px;
-        font-weight: 500;
-        font-size: 16px;
-      }
-
-      @media all and (max-width: 440px) {
-        .container {
-          margin-top: 0
-        }
-
-        .scope {
-          flex-basis: 0%;
-        }
-
-        .scope:before {
-          content: "";
-          margin-left: 0em;
-          margin-right: 1em;
-        }
-
-        .scope:first-child:before {
-          margin-left: 1em;
-          content: "✔\fe0e";
-        }
-
-        .scope:after {
-          content: ",";
-        }
-
-        .scope:last-child:after {
-          content: "";
-        }
-      }
-      .form-row {
-        display: flex;
-      }
-      .form-row > label {
-        text-align: left;
-        line-height: 47px;
-        flex: 1;
-      }
-      .form-row > input {
-        flex: 2;
-      }
-    </style>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
+    <title><%= Pleroma.Config.get([:instance, :name]) %></title>
+    <link rel="stylesheet" href="/instance/static.css">
   </head>
   <body>
+    <div class="instance-header">
+      <a class="instance-header__content" href="/">
+        <img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:instance, :instance_thumbnail]) %>">
+        <h1 class="instance-header__title"><%= Pleroma.Config.get([:instance, :name]) %></h1>
+      </a>
+    </div>
     <div class="container">
-      <h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
       <%= @inner_content %>
     </div>
   </body>
index b17142ff8ffd066c8f6579d87de3616066fe1b3e..1a85818ecbad26e4f77371f86653b7b1a90291db 100644 (file)
@@ -5,32 +5,55 @@
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 <% end %>
 
-<h2>OAuth Authorization</h2>
 <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 
-<%= if @params["registration"] in ["true", true] do %>
-  <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
-  <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
-  <div class="input">
-    <%= label f, :nickname, "Pleroma Handle" %>
-    <%= text_input f, :nickname, placeholder: "lain" %>
+<%= if @user do %>
+  <div class="account-header">
+    <div class="account-header__banner" style="background-image: url('<%= Pleroma.User.banner_url(@user) %>')"></div>
+    <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div>
+    <div class="account-header__meta">
+      <div class="account-header__display-name"><%= @user.name %></div>
+      <div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div>
+    </div>
   </div>
-  <%= hidden_input f, :name, value: @params["name"] %>
-  <%= hidden_input f, :password, value: @params["password"] %>
-  <br>
-<% else %>
-  <div class="input">
-    <%= label f, :name, "Username" %>
-    <%= text_input f, :name %>
-  </div>
-  <div class="input">
-    <%= label f, :password, "Password" %>
-    <%= password_input f, :password %>
-  </div>
-  <%= submit "Log In" %>
-  <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
 <% end %>
 
+<div class="container__content">
+  <%= if @app do %>
+    <p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p>
+    <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+  <% end %>
+
+  <%= if @user do %>
+    <div class="actions">
+      <a class="button button--cancel" href="/">Cancel</a>
+      <%= submit "Approve", class: "button--approve" %>
+    </div>
+  <% else %>
+    <%= if @params["registration"] in ["true", true] do %>
+      <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
+      <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+      <div class="input">
+        <%= label f, :nickname, "Pleroma Handle" %>
+        <%= text_input f, :nickname, placeholder: "lain" %>
+      </div>
+      <%= hidden_input f, :name, value: @params["name"] %>
+      <%= hidden_input f, :password, value: @params["password"] %>
+      <br>
+    <% else %>
+      <div class="input">
+        <%= label f, :name, "Username" %>
+        <%= text_input f, :name %>
+      </div>
+      <div class="input">
+        <%= label f, :password, "Password" %>
+        <%= password_input f, :password %>
+      </div>
+      <%= submit "Log In" %>
+    <% end %>
+  <% end %>
+</div>
+
 <%= hidden_input f, :client_id, value: @client_id %>
 <%= hidden_input f, :response_type, value: @response_type %>
 <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
@@ -40,4 +63,3 @@
 <%= if Pleroma.Config.oauth_consumer_enabled?() do %>
   <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
 <% end %>
-
diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css
new file mode 100644 (file)
index 0000000..487e1ec
--- /dev/null
@@ -0,0 +1,296 @@
+* {
+  box-sizing: border-box;
+}
+
+:root {
+  --brand-color: #d8a070;
+  --background-color: #121a24;
+  --foreground-color: #182230;
+  --primary-text-color: #b9b9ba;
+  --muted-text-color: #89898a;
+}
+
+body {
+  background-color: var(--background-color);
+  font-family: sans-serif;
+  color: var(--primary-text-color);
+  padding: 0;
+  margin: 0;
+}
+
+.instance-header {
+  height: 60px;
+  padding: 10px;
+  background: var(--foreground-color);
+  box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
+}
+
+.instance-header__content {
+  display: flex;
+  align-items: center;
+  max-width: 400px;
+  margin: 0 auto;
+}
+
+.instance-header__thumbnail {
+  max-width: 40px;
+  border-radius: 4px;
+  margin-right: 12px;
+}
+
+.instance-header__title {
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--primary-text-color);
+}
+
+.container {
+  max-width: 400px;
+  background-color: var(--foreground-color);
+  border-radius: 4px;
+  overflow: hidden;
+  margin: 35px auto;
+  box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
+}
+
+.container__content {
+  padding: 0 20px;
+}
+
+h1 {
+  margin: 0;
+  font-size: 24px;
+  text-align: center;
+}
+
+h2 {
+  color: var(--primary-text-color);
+  font-weight: normal;
+  font-size: 18px;
+  margin-bottom: 20px;
+}
+
+a {
+  color: var(--brand-color);
+  text-decoration: none;
+}
+
+form {
+  width: 100%;
+}
+
+.input {
+  color: var(--muted-text-color);
+  display: flex;
+  flex-direction: column;
+}
+
+input {
+  box-sizing: content-box;
+  padding: 10px;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  background-color: var(--background-color);
+  color: var(--primary-text-color);
+  border: 0;
+  transition-property: border-bottom;
+  transition-duration: 0.35s;
+  border-bottom: 2px solid #2a384a;
+  font-size: 14px;
+}
+
+.scopes-input {
+  display: flex;
+  flex-direction: column;
+  margin: 1em 0;
+  color: var(--muted-text-color);
+}
+
+.scopes-input label:first-child {
+  height: 2em;
+}
+
+.scopes {
+  display: flex;
+  flex-wrap: wrap;
+  color: var(--primary-text-color);
+}
+
+.scope {
+  display: flex;
+  flex-basis: 100%;
+  height: 2em;
+  align-items: center;
+}
+
+.scope:before {
+  color: var(--primary-text-color);
+  content: "✔\fe0e";
+  margin-left: 1em;
+  margin-right: 1em;
+}
+
+[type="checkbox"] + label {
+  display: none;
+  cursor: pointer;
+  margin: 0.5em;
+}
+
+[type="checkbox"] {
+  display: none;
+}
+
+[type="checkbox"] + label:before {
+  cursor: pointer;
+  display: inline-block;
+  color: white;
+  background-color: var(--background-color);
+  border: 4px solid var(--background-color);
+  box-shadow: 0px 0px 1px 0 var(--brand-color);
+  width: 1.2em;
+  height: 1.2em;
+  margin-right: 1.0em;
+  content: "";
+  transition-property: background-color;
+  transition-duration: 0.35s;
+  color: var(--background-color);
+  margin-bottom: -0.2em;
+  border-radius: 2px;
+}
+
+[type="checkbox"]:checked + label:before {
+  background-color: var(--brand-color);
+}
+
+input:focus {
+  outline: none;
+  border-bottom: 2px solid var(--brand-color);
+}
+
+.actions {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.actions button,
+.actions a.button {
+  width: auto;
+  margin-left: 10px;
+}
+
+a.button,
+button {
+  width: 100%;
+  background-color: #1c2a3a;
+  color: var(--primary-text-color);
+  border-radius: 4px;
+  border: none;
+  padding: 10px 16px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  text-transform: uppercase;
+  font-size: 16px;
+  box-shadow: 0px 0px 2px 0px black,
+    0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
+    0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
+}
+
+a.button:hover,
+button:hover {
+  cursor: pointer;
+  box-shadow: 0px 0px 0px 1px var(--brand-color),
+    0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
+    0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
+}
+
+.alert-danger {
+  width: 100%;
+  background-color: #931014;
+  border: 1px solid #a06060;
+  border-radius: 4px;
+  padding: 10px;
+  margin-top: 20px;
+  font-weight: 500;
+  font-size: 16px;
+}
+
+.alert-info {
+  width: 100%;
+  border-radius: 4px;
+  border: 1px solid #7d796a;
+  padding: 10px;
+  margin-top: 20px;
+  font-weight: 500;
+  font-size: 16px;
+}
+
+.account-header__banner {
+  width: 100%;
+  height: 112px;
+  background-size: cover;
+  background-position: center;
+}
+
+.account-header__avatar {
+  width: 94px;
+  height: 94px;
+  background-size: cover;
+  background-position: center;
+  margin: -47px 10px 0;
+  border: 6px solid var(--foreground-color);
+  border-radius: 999px;
+}
+
+.account-header__meta {
+  padding: 6px 20px 17px;
+}
+
+.account-header__display-name {
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.account-header__nickname {
+  font-size: 14px;
+  color: var(--muted-text-color);
+}
+
+@media all and (max-width: 420px) {
+  .container {
+    margin: 0 auto;
+    border-radius: 0;
+  }
+
+  .scope {
+    flex-basis: 0%;
+  }
+
+  .scope:before {
+    content: "";
+    margin-left: 0em;
+    margin-right: 1em;
+  }
+
+  .scope:first-child:before {
+    margin-left: 1em;
+    content: "✔\fe0e";
+  }
+
+  .scope:after {
+    content: ",";
+  }
+
+  .scope:last-child:after {
+    content: "";
+  }
+}
+.form-row {
+  display: flex;
+}
+.form-row > label {
+  line-height: 47px;
+  flex: 1;
+}
+.form-row > input {
+  flex: 2;
+}
index e01a940cb0b3d9ec4d195468e036f9b0df8da073..10821c31d4c874a98d0d13d3b51e90e4911fba64 100644 (file)
@@ -2178,4 +2178,9 @@ defmodule Pleroma.UserTest do
 
     assert User.avatar_url(user, no_default: true) == nil
   end
+
+  test "get_host/1" do
+    user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain")
+    assert User.get_host(user) == "lain.com"
+  end
 end
index c06ae55cae2c7580c3850ab5cd8f12060b52bf57..e50d1425b78c068851f434f03f1bf7222bf57d99 100644 (file)
@@ -941,7 +941,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/stats" do
     test "status visibility count", %{conn: conn} do
-      admin = insert(:user, is_admin: true)
       user = insert(:user)
       CommonAPI.post(user, %{visibility: "public", status: "hey"})
       CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
@@ -949,7 +948,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       response =
         conn
-        |> assign(:user, admin)
         |> get("/api/pleroma/admin/stats")
         |> json_response(200)
 
@@ -958,7 +956,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "by instance", %{conn: conn} do
-      admin = insert(:user, is_admin: true)
       user1 = insert(:user)
       instance2 = "instance2.tld"
       user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
@@ -969,7 +966,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       response =
         conn
-        |> assign(:user, admin)
         |> get("/api/pleroma/admin/stats", instance: instance2)
         |> json_response(200)
 
index 4e897455f03bbc0b241fa04052898def8556639e..765a5a4b7fc35e29105a53837d14615f930affb2 100644 (file)
@@ -1415,11 +1415,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
 
   describe "GET /api/pleroma/admin/config/descriptions" do
     test "structure", %{conn: conn} do
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
+      conn = get(conn, "/api/pleroma/admin/config/descriptions")
 
       assert [child | _others] = json_response_and_validate_schema(conn, 200)
 
@@ -1437,11 +1433,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
         {:esshd}
       ])
 
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
+      conn = get(conn, "/api/pleroma/admin/config/descriptions")
 
       children = json_response_and_validate_schema(conn, 200)
 
index bf2438fe2da61f5c9c34a035b71fb6a3222f2892..d7834c876d820b10092cf9c61a278242aee555e8 100644 (file)
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do
         |> get("/web/login", %{code: auth.token})
 
       assert conn.status == 302
-      assert redirected_to(conn) == path
+      assert redirected_to(conn) =~ path
     end
 
     test "redirects to the getting-started page when referer is not present", %{conn: conn} do
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do
       conn = get(conn, "/web/login", %{code: auth.token})
 
       assert conn.status == 302
-      assert redirected_to(conn) == "/web/getting-started"
+      assert redirected_to(conn) =~ "/web/getting-started"
     end
   end
 
index ed8add8d21077961bdcc3446f6a542f5680fe033..b9cd050df38b85ab8dcf0a3d59a94cf363f77e2f 100644 (file)
@@ -64,7 +64,8 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do
     end
 
     test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
-      token = insert(:oauth_token, scopes: ["read"])
+      {:ok, app} = Pleroma.Web.MastodonAPI.AuthController.local_mastofe_app()
+      token = insert(:oauth_token, app: app, scopes: ["read"])
 
       conn =
         conn
index c6526d8c994c494c77dc7fb867f623e6228081a3..3221af223fa38acea21a97974d784c5b04a8af1c 100644 (file)
@@ -4,8 +4,10 @@
 
 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   use Pleroma.Web.ConnCase
+
   import Pleroma.Factory
 
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.MFA
   alias Pleroma.MFA.TOTP
   alias Pleroma.Repo
@@ -454,7 +456,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> AuthHelper.put_session_token(token.token)
         |> get(
           "/oauth/authorize",
           %{
@@ -478,7 +480,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> AuthHelper.put_session_token(token.token)
         |> get(
           "/oauth/authorize",
           %{
@@ -501,7 +503,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> AuthHelper.put_session_token(token.token)
         |> get(
           "/oauth/authorize",
           %{
@@ -527,7 +529,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> AuthHelper.put_session_token(token.token)
         |> get(
           "/oauth/authorize",
           %{
@@ -551,7 +553,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       conn =
         conn
-        |> put_session(:oauth_token, token.token)
+        |> AuthHelper.put_session_token(token.token)
         |> get(
           "/oauth/authorize",
           %{
@@ -609,6 +611,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       end
     end
 
+    test "authorize from cookie" do
+      user = insert(:user)
+      app = insert(:oauth_app)
+      oauth_token = insert(:oauth_token, user: user, app: app)
+      redirect_uri = OAuthController.default_redirect_uri(app)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> AuthHelper.put_session_token(oauth_token.token)
+        |> post(
+          "/oauth/authorize",
+          %{
+            "authorization" => %{
+              "name" => user.nickname,
+              "client_id" => app.client_id,
+              "redirect_uri" => redirect_uri,
+              "scope" => app.scopes,
+              "state" => "statepassed"
+            }
+          }
+        )
+
+      target = redirected_to(conn)
+      assert target =~ redirect_uri
+
+      query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+
+      assert %{"state" => "statepassed", "code" => code} = query
+      auth = Repo.get_by(Authorization, token: code)
+      assert auth
+      assert auth.scopes == app.scopes
+    end
+
     test "redirect to on two-factor auth page" do
       otp_secret = TOTP.generate_secret()
 
@@ -1219,8 +1256,43 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
   end
 
-  describe "POST /oauth/revoke - bad request" do
-    test "returns 500" do
+  describe "POST /oauth/revoke" do
+    test "when authenticated with request token, revokes it and clears it from session" do
+      oauth_token = insert(:oauth_token)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> AuthHelper.put_session_token(oauth_token.token)
+        |> post("/oauth/revoke", %{"token" => oauth_token.token})
+
+      assert json_response(conn, 200)
+
+      refute AuthHelper.get_session_token(conn)
+      assert Token.get_by_token(oauth_token.token) == {:error, :not_found}
+    end
+
+    test "if request is authenticated with a different token, " <>
+           "revokes requested token but keeps session token" do
+      user = insert(:user)
+      oauth_token = insert(:oauth_token, user: user)
+      other_app_oauth_token = insert(:oauth_token, user: user)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> AuthHelper.put_session_token(oauth_token.token)
+        |> post("/oauth/revoke", %{"token" => other_app_oauth_token.token})
+
+      assert json_response(conn, 200)
+
+      assert AuthHelper.get_session_token(conn) == oauth_token.token
+      assert Token.get_by_token(other_app_oauth_token.token) == {:error, :not_found}
+    end
+
+    test "returns 500 on bad request" do
       response =
         build_conn()
         |> post("/oauth/revoke", %{})
index c1e6a8cc5e18bddd931b3be6ca33535dd433fd88..a6c9d0c1b56bb26a247ff3963f5c59df72bb5ca4 100644 (file)
@@ -264,9 +264,10 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
       assert length(result) == 3
 
       # Trying to get the chat of a different user
+      other_user_chat = Chat.get(other_user.id, user.ap_id)
+
       conn
-      |> assign(:user, other_user)
-      |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
+      |> get("/api/v1/pleroma/chats/#{other_user_chat.id}/messages")
       |> json_response_and_validate_schema(404)
     end
   end
index 33394722a5e39da6d63581834067391042ab2ab8..23498badfe59e9c73dd4ef276ab53e026afe20ed 100644 (file)
@@ -49,6 +49,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do
         |> AdminSecretAuthenticationPlug.call(%{})
 
       assert conn.assigns[:user].is_admin
+      assert conn.assigns[:token] == nil
       assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
     end
 
@@ -69,6 +70,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do
         |> AdminSecretAuthenticationPlug.call(%{})
 
       assert conn.assigns[:user].is_admin
+      assert conn.assigns[:token] == nil
       assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
     end
   end
index af39352e261a39bcf5a349e678352e49d5d5cebd..3dedd38b279cb162e763a54d471d1b9946b7fc1f 100644 (file)
@@ -48,6 +48,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
       |> AuthenticationPlug.call(%{})
 
     assert conn.assigns.user == conn.assigns.auth_user
+    assert conn.assigns.token == nil
     assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
   end
 
@@ -62,6 +63,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
       |> AuthenticationPlug.call(%{})
 
     assert conn.assigns.user.id == conn.assigns.auth_user.id
+    assert conn.assigns.token == nil
     assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
 
     user = User.get_by_id(user.id)
@@ -83,6 +85,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
       |> AuthenticationPlug.call(%{})
 
     assert conn.assigns.user.id == conn.assigns.auth_user.id
+    assert conn.assigns.token == nil
     assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
 
     user = User.get_by_id(user.id)
diff --git a/test/pleroma/web/plugs/ensure_user_key_plug_test.exs b/test/pleroma/web/plugs/ensure_user_key_plug_test.exs
deleted file mode 100644 (file)
index f912ef7..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.EnsureUserKeyPlugTest do
-  use Pleroma.Web.ConnCase, async: true
-
-  alias Pleroma.Web.Plugs.EnsureUserKeyPlug
-
-  test "if the conn has a user key set, it does nothing", %{conn: conn} do
-    conn =
-      conn
-      |> assign(:user, 1)
-
-    ret_conn =
-      conn
-      |> EnsureUserKeyPlug.call(%{})
-
-    assert conn == ret_conn
-  end
-
-  test "if the conn has no key set, it sets it to nil", %{conn: conn} do
-    conn =
-      conn
-      |> EnsureUserKeyPlug.call(%{})
-
-    assert Map.has_key?(conn.assigns, :user)
-  end
-end
diff --git a/test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs b/test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs
new file mode 100644 (file)
index 0000000..9592820
--- /dev/null
@@ -0,0 +1,69 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlugTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  import Pleroma.Factory
+
+  alias Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug
+
+  test "with :user assign set to a User record " <>
+         "and :token assign set to a Token belonging to this user, " <>
+         "it does nothing" do
+    %{conn: conn} = oauth_access(["read"])
+
+    ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{})
+
+    assert conn == ret_conn
+  end
+
+  test "with :user assign set to a User record " <>
+         "but :token assign not set or not a Token, " <>
+         "it assigns :token to `nil`",
+       %{conn: conn} do
+    user = insert(:user)
+    conn = assign(conn, :user, user)
+
+    ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{})
+
+    assert %{token: nil} = ret_conn.assigns
+
+    ret_conn2 =
+      conn
+      |> assign(:token, 1)
+      |> EnsureUserTokenAssignsPlug.call(%{})
+
+    assert %{token: nil} = ret_conn2.assigns
+  end
+
+  # Abnormal (unexpected) scenario
+  test "with :user assign set to a User record " <>
+         "but :token assign set to a Token NOT belonging to :user, " <>
+         "it drops auth info" do
+    %{conn: conn} = oauth_access(["read"])
+    other_user = insert(:user)
+
+    conn = assign(conn, :user, other_user)
+
+    ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{})
+
+    assert %{user: nil, token: nil} = ret_conn.assigns
+  end
+
+  test "if :user assign is not set to a User record, it sets :user and :token to nil", %{
+    conn: conn
+  } do
+    ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{})
+
+    assert %{user: nil, token: nil} = ret_conn.assigns
+
+    ret_conn2 =
+      conn
+      |> assign(:user, 1)
+      |> EnsureUserTokenAssignsPlug.call(%{})
+
+    assert %{user: nil, token: nil} = ret_conn2.assigns
+  end
+end
diff --git a/test/pleroma/web/plugs/legacy_authentication_plug_test.exs b/test/pleroma/web/plugs/legacy_authentication_plug_test.exs
deleted file mode 100644 (file)
index 2016a31..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlugTest do
-  use Pleroma.Web.ConnCase
-
-  import Pleroma.Factory
-
-  alias Pleroma.User
-  alias Pleroma.Web.Plugs.LegacyAuthenticationPlug
-  alias Pleroma.Web.Plugs.OAuthScopesPlug
-  alias Pleroma.Web.Plugs.PlugHelper
-
-  setup do
-    user =
-      insert(:user,
-        password: "password",
-        password_hash:
-          "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
-      )
-
-    %{user: user}
-  end
-
-  test "it does nothing if a user is assigned", %{conn: conn, user: user} do
-    conn =
-      conn
-      |> assign(:auth_credentials, %{username: "dude", password: "password"})
-      |> assign(:auth_user, user)
-      |> assign(:user, %User{})
-
-    ret_conn =
-      conn
-      |> LegacyAuthenticationPlug.call(%{})
-
-    assert ret_conn == conn
-  end
-
-  @tag :skip_on_mac
-  test "if `auth_user` is present and password is correct, " <>
-         "it authenticates the user, resets the password, marks OAuthScopesPlug as skipped",
-       %{
-         conn: conn,
-         user: user
-       } do
-    conn =
-      conn
-      |> assign(:auth_credentials, %{username: "dude", password: "password"})
-      |> assign(:auth_user, user)
-
-    conn = LegacyAuthenticationPlug.call(conn, %{})
-
-    assert conn.assigns.user.id == user.id
-    assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
-  end
-
-  @tag :skip_on_mac
-  test "it does nothing if the password is wrong", %{
-    conn: conn,
-    user: user
-  } do
-    conn =
-      conn
-      |> assign(:auth_credentials, %{username: "dude", password: "wrong_password"})
-      |> assign(:auth_user, user)
-
-    ret_conn =
-      conn
-      |> LegacyAuthenticationPlug.call(%{})
-
-    assert conn == ret_conn
-  end
-
-  test "with no credentials or user it does nothing", %{conn: conn} do
-    ret_conn =
-      conn
-      |> LegacyAuthenticationPlug.call(%{})
-
-    assert ret_conn == conn
-  end
-end
index b9d722f7624c43de5992b6102c3a45ce220cef71..1186cdb14eb86d10efbf34a1203a721837262413 100644 (file)
@@ -5,43 +5,49 @@
 defmodule Pleroma.Web.Plugs.OAuthPlugTest do
   use Pleroma.Web.ConnCase, async: true
 
+  alias Pleroma.Helpers.AuthHelper
+  alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Strategy.Revoke
   alias Pleroma.Web.Plugs.OAuthPlug
-  import Pleroma.Factory
+  alias Plug.Session
 
-  @session_opts [
-    store: :cookie,
-    key: "_test",
-    signing_salt: "cooldude"
-  ]
+  import Pleroma.Factory
 
   setup %{conn: conn} do
     user = insert(:user)
-    {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user)
-    %{user: user, token: token, conn: conn}
+    {:ok, oauth_token} = Token.create(insert(:oauth_app), user)
+    %{user: user, token: oauth_token, conn: conn}
+  end
+
+  test "it does nothing if a user is assigned", %{conn: conn} do
+    conn = assign(conn, :user, %Pleroma.User{})
+    ret_conn = OAuthPlug.call(conn, %{})
+
+    assert ret_conn == conn
   end
 
-  test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do
+  test "with valid token (uppercase) in auth header, it assigns the user", %{conn: conn} = opts do
     conn =
       conn
-      |> put_req_header("authorization", "BEARER #{opts[:token]}")
+      |> put_req_header("authorization", "BEARER #{opts[:token].token}")
       |> OAuthPlug.call(%{})
 
     assert conn.assigns[:user] == opts[:user]
   end
 
-  test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
+  test "with valid token (downcase) in auth header, it assigns the user", %{conn: conn} = opts do
     conn =
       conn
-      |> put_req_header("authorization", "bearer #{opts[:token]}")
+      |> put_req_header("authorization", "bearer #{opts[:token].token}")
       |> OAuthPlug.call(%{})
 
     assert conn.assigns[:user] == opts[:user]
   end
 
-  test "with valid token(downcase) in url parameters, it assigns the user", opts do
+  test "with valid token (downcase) in url parameters, it assigns the user", opts do
     conn =
       :get
-      |> build_conn("/?access_token=#{opts[:token]}")
+      |> build_conn("/?access_token=#{opts[:token].token}")
       |> put_req_header("content-type", "application/json")
       |> fetch_query_params()
       |> OAuthPlug.call(%{})
@@ -49,16 +55,16 @@ defmodule Pleroma.Web.Plugs.OAuthPlugTest do
     assert conn.assigns[:user] == opts[:user]
   end
 
-  test "with valid token(downcase) in body parameters, it assigns the user", opts do
+  test "with valid token (downcase) in body parameters, it assigns the user", opts do
     conn =
       :post
-      |> build_conn("/api/v1/statuses", access_token: opts[:token], status: "test")
+      |> build_conn("/api/v1/statuses", access_token: opts[:token].token, status: "test")
       |> OAuthPlug.call(%{})
 
     assert conn.assigns[:user] == opts[:user]
   end
 
-  test "with invalid token, it not assigns the user", %{conn: conn} do
+  test "with invalid token, it does not assign the user", %{conn: conn} do
     conn =
       conn
       |> put_req_header("authorization", "bearer TTTTT")
@@ -67,14 +73,56 @@ defmodule Pleroma.Web.Plugs.OAuthPlugTest do
     refute conn.assigns[:user]
   end
 
-  test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do
-    conn =
-      conn
-      |> Plug.Session.call(Plug.Session.init(@session_opts))
-      |> fetch_session()
-      |> put_session(:oauth_token, opts[:token])
-      |> OAuthPlug.call(%{})
-
-    assert conn.assigns[:user] == opts[:user]
+  describe "with :oauth_token in session, " do
+    setup %{token: oauth_token, conn: conn} do
+      session_opts = [
+        store: :cookie,
+        key: "_test",
+        signing_salt: "cooldude"
+      ]
+
+      conn =
+        conn
+        |> Session.call(Session.init(session_opts))
+        |> fetch_session()
+        |> AuthHelper.put_session_token(oauth_token.token)
+
+      %{conn: conn}
+    end
+
+    test "if session-stored token matches a valid OAuth token, assigns :user and :token", %{
+      conn: conn,
+      user: user,
+      token: oauth_token
+    } do
+      conn = OAuthPlug.call(conn, %{})
+
+      assert conn.assigns.user && conn.assigns.user.id == user.id
+      assert conn.assigns.token && conn.assigns.token.id == oauth_token.id
+    end
+
+    test "if session-stored token matches an expired OAuth token, does nothing", %{
+      conn: conn,
+      token: oauth_token
+    } do
+      expired_valid_until = NaiveDateTime.add(NaiveDateTime.utc_now(), -3600 * 24, :second)
+
+      oauth_token
+      |> Ecto.Changeset.change(valid_until: expired_valid_until)
+      |> Pleroma.Repo.update()
+
+      ret_conn = OAuthPlug.call(conn, %{})
+      assert ret_conn == conn
+    end
+
+    test "if session-stored token matches a revoked OAuth token, does nothing", %{
+      conn: conn,
+      token: oauth_token
+    } do
+      Revoke.revoke(oauth_token)
+
+      ret_conn = OAuthPlug.call(conn, %{})
+      assert ret_conn == conn
+    end
   end
 end
diff --git a/test/pleroma/web/plugs/session_authentication_plug_test.exs b/test/pleroma/web/plugs/session_authentication_plug_test.exs
deleted file mode 100644 (file)
index 2b4d5bc..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.SessionAuthenticationPlugTest do
-  use Pleroma.Web.ConnCase, async: true
-
-  alias Pleroma.User
-  alias Pleroma.Web.Plugs.SessionAuthenticationPlug
-
-  setup %{conn: conn} do
-    session_opts = [
-      store: :cookie,
-      key: "_test",
-      signing_salt: "cooldude"
-    ]
-
-    conn =
-      conn
-      |> Plug.Session.call(Plug.Session.init(session_opts))
-      |> fetch_session
-      |> assign(:auth_user, %User{id: 1})
-
-    %{conn: conn}
-  end
-
-  test "it does nothing if a user is assigned", %{conn: conn} do
-    conn =
-      conn
-      |> assign(:user, %User{})
-
-    ret_conn =
-      conn
-      |> SessionAuthenticationPlug.call(%{})
-
-    assert ret_conn == conn
-  end
-
-  test "if the auth_user has the same id as the user_id in the session, it assigns the user", %{
-    conn: conn
-  } do
-    conn =
-      conn
-      |> put_session(:user_id, conn.assigns.auth_user.id)
-      |> SessionAuthenticationPlug.call(%{})
-
-    assert conn.assigns.user == conn.assigns.auth_user
-  end
-
-  test "if the auth_user has a different id as the user_id in the session, it does nothing", %{
-    conn: conn
-  } do
-    conn =
-      conn
-      |> put_session(:user_id, -1)
-
-    ret_conn =
-      conn
-      |> SessionAuthenticationPlug.call(%{})
-
-    assert ret_conn == conn
-  end
-end
index a89b5628fb0e3b0791e05215b50461b3857d6e49..21417d0e732e4e9f9632a6151d1f82ef4f822108 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do
   use Pleroma.Web.ConnCase, async: true
 
-  alias Pleroma.User
+  alias Pleroma.Helpers.AuthHelper
   alias Pleroma.Web.Plugs.SetUserSessionIdPlug
 
   setup %{conn: conn} do
@@ -18,28 +18,26 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do
     conn =
       conn
       |> Plug.Session.call(Plug.Session.init(session_opts))
-      |> fetch_session
+      |> fetch_session()
 
     %{conn: conn}
   end
 
   test "doesn't do anything if the user isn't set", %{conn: conn} do
-    ret_conn =
-      conn
-      |> SetUserSessionIdPlug.call(%{})
+    ret_conn = SetUserSessionIdPlug.call(conn, %{})
 
     assert ret_conn == conn
   end
 
-  test "sets the user_id in the session to the user id of the user assign", %{conn: conn} do
-    Code.ensure_compiled(Pleroma.User)
+  test "sets session token basing on :token assign", %{conn: conn} do
+    %{user: user, token: oauth_token} = oauth_access(["read"])
 
-    conn =
+    ret_conn =
       conn
-      |> assign(:user, %User{id: 1})
+      |> assign(:user, user)
+      |> assign(:token, oauth_token)
       |> SetUserSessionIdPlug.call(%{})
 
-    id = get_session(conn, :user_id)
-    assert id == 1
+    assert AuthHelper.get_session_token(ret_conn) == oauth_token.token
   end
 end