commit: 477c6c8e558c1b1f193caadb6172db840c1910a4
parent 1436a2fa2dfd5fef6d61d1bba9ba074b359b40a3
Author: lain <lain@soykaf.club>
Date: Wed, 9 Dec 2020 15:55:45 +0000
Merge branch 'auth-improvements' into 'develop'
Cookie auth rework / Auth subsystem refactoring and tweaks
Closes pleroma/secteam/pleroma#3
See merge request pleroma/pleroma!3112
Diffstat:
45 files changed, 959 insertions(+), 776 deletions(-)
diff --git a/.gitattributes b/.gitattributes
@@ -1,8 +1,10 @@
*.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.
+
+priv/static/instance/static.css diff=css
+
+# Most of js/css files included in the repo are minified bundles,
+# and we don't want to search/diff those as text files.
*.js binary
*.js.map binary
*.css binary
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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.
- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`
+- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
+- OAuth improvements and fixes: more secure session-based authentication (by token that could be revoked anytime), ability to revoke belonging OAuth token from any client etc.
<details>
<summary>API Changes</summary>
diff --git a/docs/configuration/static_dir.md b/docs/configuration/static_dir.md
@@ -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.
diff --git a/docs/dev.md b/docs/dev.md
@@ -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
@@ -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
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
@@ -2408,4 +2408,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
diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
@@ -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
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
@@ -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"
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -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, %{
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -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
diff --git a/lib/pleroma/web/o_auth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex
@@ -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
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -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 ->
diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
@@ -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
@@ -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
@@ -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
@@ -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
diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
@@ -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
@@ -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
diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex
@@ -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
diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex
@@ -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
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
@@ -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)
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
@@ -1,233 +1,19 @@
<!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>
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
@@ -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
@@ -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;
+}
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
@@ -2176,4 +2176,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
diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
@@ -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)
diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1417,11 +1417,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)
@@ -1439,11 +1435,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)
diff --git a/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs
@@ -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
diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
@@ -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
diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
@@ -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", %{})
diff --git a/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs
@@ -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
diff --git a/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs
@@ -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
diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs
@@ -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
@@ -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
@@ -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
@@ -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
diff --git a/test/pleroma/web/plugs/o_auth_plug_test.exs b/test/pleroma/web/plugs/o_auth_plug_test.exs
@@ -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
@@ -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
diff --git a/test/pleroma/web/plugs/set_user_session_id_plug_test.exs b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs
@@ -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