Compare commits
13 Commits
feature/sa
...
tests/open
Author | SHA1 | Date | |
---|---|---|---|
|
48b8efa759 | ||
|
dadeb5c36a | ||
|
a8db1189f2 | ||
|
0e511d7b80 | ||
|
3732c51cf7 | ||
|
7f0b991ad6 | ||
|
b4e0ae8a03 | ||
|
8866551b25 | ||
|
0d1ee37cd5 | ||
|
510c78028a | ||
|
8824286ee8 | ||
|
2bfd727a71 | ||
|
989cda6509 |
@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||
|
||||
### Removed
|
||||
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
|
||||
|
||||
## Unreleased (Patch)
|
||||
|
||||
## [2.3.0] - 2020-03-01
|
||||
|
@ -105,6 +105,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
401 => Operation.response("Media", "application/json", ApiError),
|
||||
403 => Operation.response("Media", "application/json", ApiError),
|
||||
422 => Operation.response("Media", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
|
243
lib/pleroma/web/api_spec/operations/o_auth_operation.ex
Normal file
243
lib/pleroma/web/api_spec/operations/o_auth_operation.ex
Normal file
@ -0,0 +1,243 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.OAuthOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
defp client_id_parameter(opts) do
|
||||
Operation.parameter(
|
||||
:client_id,
|
||||
:query,
|
||||
:string,
|
||||
"Client ID, obtained during app registration",
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
defp client_secret_parameter(opts) do
|
||||
Operation.parameter(
|
||||
:client_secret,
|
||||
:query,
|
||||
:string,
|
||||
"Client secret, obtained during app registration",
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
defp redirect_uri_parameter(opts) do
|
||||
Operation.parameter(
|
||||
:redirect_uri,
|
||||
:query,
|
||||
:string,
|
||||
"Set a URI to redirect the user to. If this parameter is set to `urn:ietf:wg:oauth:2.0:oob` then the token will be shown instead. Must match one of the redirect URIs declared during app registration.",
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
defp scope_parameter(opts) do
|
||||
Operation.parameter(
|
||||
:scope,
|
||||
:query,
|
||||
:string,
|
||||
"List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration. If not provided, defaults to `read`.",
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
def token_exchange_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "Access Token Request",
|
||||
operationId: "OAuthController.token_exchange",
|
||||
parameters: [
|
||||
# code is required when grant_type == "authorization_code"
|
||||
# Mastodon requires `redirect_uri`, we don't
|
||||
client_id_parameter(required: true),
|
||||
client_secret_parameter(required: true),
|
||||
redirect_uri_parameter([]),
|
||||
scope_parameter([]),
|
||||
Operation.parameter(
|
||||
:code,
|
||||
:query,
|
||||
:string,
|
||||
"A user authorization code, obtained via /oauth/authorize"
|
||||
),
|
||||
Operation.parameter(
|
||||
:grant_type,
|
||||
:query,
|
||||
:string,
|
||||
"Set equal to `authorization_code` if `code` is provided in order to gain user-level access. Set equal to `password` if `username` and `password` are provided. Otherwise, set equal to `client_credentials` to obtain app-level access only.",
|
||||
required: true
|
||||
),
|
||||
Operation.parameter(
|
||||
:username,
|
||||
:query,
|
||||
:string,
|
||||
"User's username, used with `grant_type=password`"
|
||||
),
|
||||
Operation.parameter(
|
||||
:password,
|
||||
:query,
|
||||
:string,
|
||||
"User's password, used with `grant_type=password`"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def token_revoke_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "Revokes token",
|
||||
operationId: "OAuthController.token_revoke",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def registration_details_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "Register",
|
||||
operationId: "OAuthController.registration_details",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def authorize_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "OAuth callback",
|
||||
operationId: "OAuthController.authorize",
|
||||
parameters: [
|
||||
client_id_parameter(required: true),
|
||||
client_secret_parameter([]),
|
||||
Operation.parameter(
|
||||
:response_type,
|
||||
:query,
|
||||
:string,
|
||||
"Note: `code` is the only value supported (MastodonAPI and OAuth 2.1)",
|
||||
required: true
|
||||
),
|
||||
redirect_uri_parameter([]),
|
||||
scope_parameter([]),
|
||||
Operation.parameter(
|
||||
:state,
|
||||
:query,
|
||||
:string,
|
||||
"An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client."
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def prepare_request_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "Prepare OAuth request for third-party auth providers",
|
||||
operationId: "OAuthController.prepare_request",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
302 =>
|
||||
Operation.response("Success", "text/html", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# The following operations should be moved to another controller, they aren't meant to be into OpenAPI
|
||||
|
||||
def request_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "",
|
||||
operationId: "OAuthController.request",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def register_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "",
|
||||
operationId: "OAuthController.register",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def callback_operation do
|
||||
%Operation{
|
||||
tags: ["OAuth"],
|
||||
summary: "",
|
||||
operationId: "OAuthController.callback",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
@ -115,7 +115,8 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
|
||||
],
|
||||
operationId: "TimelineController.hashtag",
|
||||
responses: %{
|
||||
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
|
||||
200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
|
||||
401 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
244
lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
Normal file
244
lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
Normal file
@ -0,0 +1,244 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def notifications_read_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Mark notification as read",
|
||||
security: [%{"oAuth" => ["write:notifications"]}],
|
||||
operationId: "UtilController.notifications_read",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:id,
|
||||
:query,
|
||||
:integer,
|
||||
"ID of the notification"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def emoji_operation do
|
||||
%Operation{
|
||||
tags: ["Emojis"],
|
||||
summary: "List all custom emojis",
|
||||
operationId: "UtilController.emoji",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("List", "application/json", %Schema{
|
||||
type: :object,
|
||||
additionalProperties: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
image_url: %Schema{type: :string},
|
||||
tags: %Schema{type: :array, items: %Schema{type: :string}}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"firefox" => %{
|
||||
"image_url" => "/emoji/firefox.png",
|
||||
"tag" => ["Fun"]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def frontend_configurations_operation do
|
||||
%Operation{
|
||||
tags: ["Configuration"],
|
||||
summary: "Dump frontend configurations",
|
||||
operationId: "UtilController.frontend_configurations",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("List", "application/json", %Schema{
|
||||
type: :object,
|
||||
additionalProperties: %Schema{type: :object}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def change_password_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Change account password",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.change_password",
|
||||
parameters: [
|
||||
Operation.parameter(:password, :query, :string, "Current password", required: true),
|
||||
Operation.parameter(:new_password, :query, :string, "New password", required: true),
|
||||
Operation.parameter(
|
||||
:new_password_confirmation,
|
||||
:query,
|
||||
:string,
|
||||
"New password, confirmation",
|
||||
required: true
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def change_email_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Change account email",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.change_email",
|
||||
parameters: [
|
||||
Operation.parameter(:password, :query, :string, "Current password", required: true),
|
||||
Operation.parameter(:email, :query, :string, "New email", required: true)
|
||||
],
|
||||
requestBody: nil,
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_notificaton_settings_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Update Notification Settings",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.update_notificaton_settings",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:block_from_strangers,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"blocks notifications from accounts you do not follow"
|
||||
),
|
||||
Operation.parameter(
|
||||
:hide_notification_contents,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"removes the contents of a message from the push notification"
|
||||
)
|
||||
],
|
||||
requestBody: nil,
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def disable_account_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Disable Account",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.disable_account",
|
||||
parameters: [
|
||||
Operation.parameter(:password, :query, :string, "Password")
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_account_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Delete Account",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.delete_account",
|
||||
parameters: [
|
||||
Operation.parameter(:password, :query, :string, "Password")
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{status: %Schema{type: :string, example: "success"}}
|
||||
}),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def captcha_operation do
|
||||
%Operation{
|
||||
summary: "Get a captcha",
|
||||
operationId: "UtilController.captcha",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 => Operation.response("Success", "application/json", %Schema{type: :object})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def healthcheck_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Disable Account",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.healthcheck",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
|
||||
503 =>
|
||||
Operation.response("Disabled or Unhealthy", "application/json", %Schema{type: :object})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def remote_subscribe_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Remote Subscribe",
|
||||
operationId: "UtilController.remote_subscribe",
|
||||
parameters: [],
|
||||
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
|
||||
}
|
||||
end
|
||||
end
|
@ -23,6 +23,7 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do
|
||||
requestBody: request_body("Parameters", import_request(), required: true),
|
||||
responses: %{
|
||||
200 => ok_response(),
|
||||
403 => Operation.response("Error", "application/json", ApiError),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
},
|
||||
security: [%{"oAuth" => ["write:follow"]}]
|
||||
|
@ -60,7 +60,7 @@ defmodule Pleroma.Web.Auth.Authenticator do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{:ok, {name, password}}
|
||||
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} ->
|
||||
%{grant_type: "password", username: name, password: password} ->
|
||||
{:ok, {name, password}}
|
||||
|
||||
_ ->
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
defmodule Pleroma.Web.OAuth.FallbackController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web.OAuth.OAuthBrowserController
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
def call(conn, {:register, :generic_error}) do
|
||||
@ -13,14 +14,14 @@ defmodule Pleroma.Web.OAuth.FallbackController do
|
||||
:error,
|
||||
dgettext("errors", "Unknown error, please check the details and try again.")
|
||||
)
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
|> OAuthBrowserController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, {:register, _error}) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
|> OAuthBrowserController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, _error) do
|
||||
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.Web.Auth.TOTPAuthenticator
|
||||
alias Pleroma.Web.OAuth.MFAView, as: View
|
||||
alias Pleroma.Web.OAuth.OAuthBrowserController
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@ -40,7 +41,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
|
||||
with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
|
||||
{:ok, _} <- validates_challenge(user, mfa_params) do
|
||||
conn
|
||||
|> OAuthController.after_create_authorization(auth, %{
|
||||
|> OAuthBrowserController.after_create_authorization(auth, %{
|
||||
"authorization" => %{
|
||||
"redirect_uri" => mfa_params["redirect_uri"],
|
||||
"state" => mfa_params["state"]
|
||||
|
308
lib/pleroma/web/o_auth/o_auth_browser_controller.ex
Normal file
308
lib/pleroma/web/o_auth/o_auth_browser_controller.ex
Normal file
@ -0,0 +1,308 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.OAuthBrowserController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Helpers.UriHelper
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Auth.Authenticator
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
alias Pleroma.Web.OAuth.MFAController
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.Plugs.RateLimiter
|
||||
|
||||
require Logger
|
||||
|
||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
plug(:skip_plug, [
|
||||
Pleroma.Web.Plugs.OAuthScopesPlug,
|
||||
Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
])
|
||||
|
||||
plug(RateLimiter, name: :authentication)
|
||||
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
||||
|
||||
def authorize_callback(_, _, opts \\ [])
|
||||
|
||||
def authorize_callback(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do
|
||||
authorize_callback(conn, params, user: user)
|
||||
end
|
||||
|
||||
def authorize_callback(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
|
||||
with {:ok, auth, user} <- OAuthController.do_create_authorization(conn, params, opts[:user]),
|
||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
|
||||
after_create_authorization(conn, auth, params)
|
||||
else
|
||||
error ->
|
||||
handle_create_authorization_error(conn, error, params)
|
||||
end
|
||||
end
|
||||
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
|
||||
}) do
|
||||
# Enforcing the view to reuse the template when calling from other controllers
|
||||
conn
|
||||
|> put_view(OAuthView)
|
||||
|> render("oob_authorization_created.html", %{auth: auth})
|
||||
end
|
||||
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
|
||||
}) do
|
||||
app = Repo.preload(auth, :app).app
|
||||
|
||||
# An extra safety measure before we redirect (also done in `do_create_authorization/2`)
|
||||
if redirect_uri in String.split(app.redirect_uris) do
|
||||
redirect_uri = OAuthController.redirect_uri(conn, redirect_uri)
|
||||
url_params = %{code: auth.token}
|
||||
url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
|
||||
url = UriHelper.modify_uri_params(redirect_uri, url_params)
|
||||
redirect(conn, external: url)
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|
||||
|> redirect(external: OAuthController.redirect_uri(conn, redirect_uri))
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:error, scopes_issue},
|
||||
%{"authorization" => _} = params
|
||||
)
|
||||
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
|
||||
|> put_status(:unauthorized)
|
||||
|> OAuthController.authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :confirmation_pending},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|
||||
|> put_status(:forbidden)
|
||||
|> OAuthController.authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:mfa_required, user, auth, _},
|
||||
params
|
||||
) do
|
||||
{:ok, token} = MFA.Token.create(user, auth)
|
||||
|
||||
data = %{
|
||||
"mfa_token" => token.token,
|
||||
"redirect_uri" => params["authorization"]["redirect_uri"],
|
||||
"state" => params["authorization"]["state"]
|
||||
}
|
||||
|
||||
MFAController.show(conn, data)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :password_reset_pending},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Password reset is required"))
|
||||
|> put_status(:forbidden)
|
||||
|> OAuthController.authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :deactivated},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
|
||||
|> put_status(:forbidden)
|
||||
|> OAuthController.authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(%Plug.Conn{} = conn, %{
|
||||
"provider" => provider,
|
||||
"authorization" => auth_attrs
|
||||
}) do
|
||||
scope =
|
||||
auth_attrs
|
||||
|> Scopes.fetch_scopes([])
|
||||
|> Scopes.to_string()
|
||||
|
||||
state =
|
||||
auth_attrs
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Jason.encode!()
|
||||
|
||||
params =
|
||||
auth_attrs
|
||||
|> Map.drop(~w(scope scopes client_id redirect_uri))
|
||||
|> Map.put("state", state)
|
||||
|
||||
# Handing the request to Ueberauth
|
||||
redirect(conn, to: o_auth_browser_path(conn, :provider_request, provider, params))
|
||||
end
|
||||
|
||||
def provider_request(%Plug.Conn{} = conn, params) do
|
||||
message =
|
||||
if params["provider"] do
|
||||
dgettext("errors", "Unsupported OAuth provider: %{provider}.",
|
||||
provider: params["provider"]
|
||||
)
|
||||
else
|
||||
dgettext("errors", "Bad OAuth request.")
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:error, message)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def provider_callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
params = callback_params(params)
|
||||
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||
message = Enum.join(messages, "; ")
|
||||
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
dgettext("errors", "Failed to authenticate: %{message}.", message: message)
|
||||
)
|
||||
|> redirect(external: OAuthController.redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
|
||||
def provider_callback(%Plug.Conn{} = conn, params) do
|
||||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn) do
|
||||
auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||
|
||||
case Repo.get_assoc(registration, :user) do
|
||||
{:ok, user} ->
|
||||
authorize_callback(conn, %{"authorization" => auth_attrs}, user: user)
|
||||
|
||||
_ ->
|
||||
registration_params =
|
||||
Map.merge(auth_attrs, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_session_registration_id(registration.id)
|
||||
|> registration_details(%{authorization: registration_params})
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
|
||||
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Failed to set up user account."))
|
||||
|> redirect(external: OAuthController.redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Jason.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
|
||||
render(conn, "register.html", %{
|
||||
client_id: auth_attrs["client_id"],
|
||||
redirect_uri: auth_attrs["redirect_uri"],
|
||||
state: auth_attrs["state"],
|
||||
scopes: Scopes.fetch_scopes(auth_attrs, []),
|
||||
nickname: auth_attrs["nickname"],
|
||||
email: auth_attrs["email"]
|
||||
})
|
||||
end
|
||||
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth, _user}} <-
|
||||
{:create_authorization, OAuthController.do_create_authorization(conn, params)},
|
||||
%User{} = user <- Repo.preload(auth, :user).user,
|
||||
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> after_create_authorization(auth, params)
|
||||
else
|
||||
{:create_authorization, error} ->
|
||||
{:register, handle_create_authorization_error(conn, error, params)}
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{:ok, user} <- Authenticator.create_from_registration(conn, registration) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> authorize_callback(
|
||||
params,
|
||||
user: user
|
||||
)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
message =
|
||||
Enum.map(changeset.errors, fn {field, {error, _}} ->
|
||||
"#{field} #{error}"
|
||||
end)
|
||||
|> Enum.join("; ")
|
||||
|
||||
message =
|
||||
String.replace(
|
||||
message,
|
||||
"ap_id has already been taken",
|
||||
"nickname has already been taken"
|
||||
)
|
||||
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> put_flash(:error, "Error: #{message}.")
|
||||
|> registration_details(params)
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
|
||||
|
||||
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
end
|
@ -7,16 +7,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
|
||||
alias Pleroma.Helpers.AuthHelper
|
||||
alias Pleroma.Helpers.UriHelper
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Auth.Authenticator
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.MFAController
|
||||
alias Pleroma.Web.OAuth.MFAView
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
@ -25,10 +23,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
||||
alias Pleroma.Web.Plugs.RateLimiter
|
||||
|
||||
require Logger
|
||||
|
||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
@ -37,20 +35,22 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
])
|
||||
|
||||
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
|
||||
plug(RateLimiter, name: :authentication)
|
||||
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.OAuthOperation
|
||||
|
||||
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
|
||||
def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
|
||||
{auth_attrs, params} = Map.pop(params, "authorization")
|
||||
def authorize(%Plug.Conn{} = conn, %{authorization: _} = params) do
|
||||
{auth_attrs, params} = Map.pop(params, :authorization)
|
||||
authorize(conn, Map.merge(params, auth_attrs))
|
||||
end
|
||||
|
||||
def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
|
||||
if ControllerHelper.truthy_param?(params["force_login"]) do
|
||||
def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{force_login: _} = params) do
|
||||
if ControllerHelper.truthy_param?(params[:force_login]) do
|
||||
do_authorize(conn, params)
|
||||
else
|
||||
handle_existing_authorization(conn, params)
|
||||
@ -63,7 +63,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
# So we have to check client and token.
|
||||
def authorize(
|
||||
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
|
||||
%{"client_id" => client_id} = params
|
||||
%{client_id: client_id} = params
|
||||
) do
|
||||
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
|
||||
^client_id <- t.app.client_id do
|
||||
@ -141,121 +141,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
else
|
||||
error ->
|
||||
handle_create_authorization_error(conn, error, params)
|
||||
end
|
||||
end
|
||||
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
|
||||
}) do
|
||||
# Enforcing the view to reuse the template when calling from other controllers
|
||||
conn
|
||||
|> put_view(OAuthView)
|
||||
|> render("oob_authorization_created.html", %{auth: auth})
|
||||
end
|
||||
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
|
||||
}) do
|
||||
app = Repo.preload(auth, :app).app
|
||||
|
||||
# An extra safety measure before we redirect (also done in `do_create_authorization/2`)
|
||||
if redirect_uri in String.split(app.redirect_uris) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
url_params = %{code: auth.token}
|
||||
url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
|
||||
url = UriHelper.modify_uri_params(redirect_uri, url_params)
|
||||
redirect(conn, external: url)
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|
||||
|> redirect(external: redirect_uri(conn, redirect_uri))
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:error, scopes_issue},
|
||||
%{"authorization" => _} = params
|
||||
)
|
||||
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
|
||||
|> put_status(:unauthorized)
|
||||
|> authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :confirmation_pending},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:mfa_required, user, auth, _},
|
||||
params
|
||||
) do
|
||||
{:ok, token} = MFA.Token.create(user, auth)
|
||||
|
||||
data = %{
|
||||
"mfa_token" => token.token,
|
||||
"redirect_uri" => params["authorization"]["redirect_uri"],
|
||||
"state" => params["authorization"]["state"]
|
||||
}
|
||||
|
||||
MFAController.show(conn, data)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :password_reset_pending},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Password reset is required"))
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
%Plug.Conn{} = conn,
|
||||
{:account_status, :deactivated},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
@doc "Renew access_token with refresh_token"
|
||||
def token_exchange(
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
|
||||
%{grant_type: "refresh_token", refresh_token: token} = _params
|
||||
) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||
@ -266,9 +155,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
end
|
||||
end
|
||||
|
||||
def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
def token_exchange(%Plug.Conn{} = conn, %{grant_type: "authorization_code"} = params) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||
fixed_token = Token.Utils.fix_padding(params[:code]),
|
||||
{: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
|
||||
@ -281,7 +170,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
|
||||
def token_exchange(
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "password"} = params
|
||||
%{grant_type: "password"} = params
|
||||
) do
|
||||
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||
{:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
@ -296,7 +185,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
|
||||
def token_exchange(
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "password", "name" => name, "password" => _password} = params
|
||||
%{grant_type: "password", name: name, password: _password} = params
|
||||
) do
|
||||
params =
|
||||
params
|
||||
@ -306,7 +195,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
token_exchange(conn, params)
|
||||
end
|
||||
|
||||
def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
|
||||
def token_exchange(%Plug.Conn{} = conn, %{grant_type: "client_credentials"} = _params) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
@ -318,7 +207,49 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
end
|
||||
|
||||
# Bad request
|
||||
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
def token_exchange(%Plug.Conn{} = _conn, _params), do: {:error, :bad_request}
|
||||
|
||||
# Note: intended to be a private function but opened for AccountController that logs in on signup
|
||||
@doc "If checks pass, creates authorization and token for given user, app and requested scopes."
|
||||
def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
|
||||
with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
|
||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
{:ok, token}
|
||||
end
|
||||
end
|
||||
|
||||
def do_create_authorization(conn, auth_attrs, user \\ nil)
|
||||
|
||||
def do_create_authorization(
|
||||
%Plug.Conn{} = conn,
|
||||
%{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_attrs
|
||||
},
|
||||
user
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <-
|
||||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
|
||||
{:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
|
||||
{:ok, auth, user}
|
||||
end
|
||||
end
|
||||
|
||||
def do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
|
||||
when is_list(requested_scopes) do
|
||||
with {:account_status, :active} <- {:account_status, User.account_status(user)},
|
||||
{:ok, scopes} <- validate_scopes(app, requested_scopes),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
||||
{:ok, auth}
|
||||
end
|
||||
end
|
||||
|
||||
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
|
||||
conn
|
||||
@ -379,7 +310,17 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
render_invalid_credentials_error(conn)
|
||||
end
|
||||
|
||||
def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
|
||||
defp render_invalid_credentials_error(conn) do
|
||||
render_error(conn, :bad_request, "Invalid credentials")
|
||||
end
|
||||
|
||||
defp build_and_response_mfa_token(user, auth) do
|
||||
with {:ok, token} <- MFA.Token.create(user, auth) do
|
||||
MFAView.render("mfa_response.json", %{token: token, user: user})
|
||||
end
|
||||
end
|
||||
|
||||
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 =
|
||||
@ -398,223 +339,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
end
|
||||
end
|
||||
|
||||
def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
|
||||
# Response for bad request
|
||||
defp bad_request(%Plug.Conn{} = conn, _) do
|
||||
render_error(conn, :internal_server_error, "Bad request")
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(%Plug.Conn{} = conn, %{
|
||||
"provider" => provider,
|
||||
"authorization" => auth_attrs
|
||||
}) do
|
||||
scope =
|
||||
auth_attrs
|
||||
|> Scopes.fetch_scopes([])
|
||||
|> Scopes.to_string()
|
||||
|
||||
state =
|
||||
auth_attrs
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Jason.encode!()
|
||||
|
||||
params =
|
||||
auth_attrs
|
||||
|> Map.drop(~w(scope scopes client_id redirect_uri))
|
||||
|> Map.put("state", state)
|
||||
|
||||
# Handing the request to Ueberauth
|
||||
redirect(conn, to: o_auth_path(conn, :request, provider, params))
|
||||
end
|
||||
|
||||
def request(%Plug.Conn{} = conn, params) do
|
||||
message =
|
||||
if params["provider"] do
|
||||
dgettext("errors", "Unsupported OAuth provider: %{provider}.",
|
||||
provider: params["provider"]
|
||||
)
|
||||
else
|
||||
dgettext("errors", "Bad OAuth request.")
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:error, message)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
params = callback_params(params)
|
||||
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||
message = Enum.join(messages, "; ")
|
||||
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
dgettext("errors", "Failed to authenticate: %{message}.", message: message)
|
||||
)
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
|
||||
def callback(%Plug.Conn{} = conn, params) do
|
||||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn) do
|
||||
auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||
|
||||
case Repo.get_assoc(registration, :user) do
|
||||
{:ok, user} ->
|
||||
create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
|
||||
|
||||
_ ->
|
||||
registration_params =
|
||||
Map.merge(auth_attrs, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_session_registration_id(registration.id)
|
||||
|> registration_details(%{"authorization" => registration_params})
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
|
||||
|
||||
conn
|
||||
|> put_flash(:error, dgettext("errors", "Failed to set up user account."))
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Jason.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
|
||||
render(conn, "register.html", %{
|
||||
client_id: auth_attrs["client_id"],
|
||||
redirect_uri: auth_attrs["redirect_uri"],
|
||||
state: auth_attrs["state"],
|
||||
scopes: Scopes.fetch_scopes(auth_attrs, []),
|
||||
nickname: auth_attrs["nickname"],
|
||||
email: auth_attrs["email"]
|
||||
})
|
||||
end
|
||||
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth, _user}} <-
|
||||
{:create_authorization, do_create_authorization(conn, params)},
|
||||
%User{} = user <- Repo.preload(auth, :user).user,
|
||||
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> after_create_authorization(auth, params)
|
||||
else
|
||||
{:create_authorization, error} ->
|
||||
{:register, handle_create_authorization_error(conn, error, params)}
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{:ok, user} <- Authenticator.create_from_registration(conn, registration) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> create_authorization(
|
||||
params,
|
||||
user: user
|
||||
)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
message =
|
||||
Enum.map(changeset.errors, fn {field, {error, _}} ->
|
||||
"#{field} #{error}"
|
||||
end)
|
||||
|> Enum.join("; ")
|
||||
|
||||
message =
|
||||
String.replace(
|
||||
message,
|
||||
"ap_id has already been taken",
|
||||
"nickname has already been taken"
|
||||
)
|
||||
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> put_flash(:error, "Error: #{message}.")
|
||||
|> registration_details(params)
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_create_authorization(conn, auth_attrs, user \\ nil)
|
||||
|
||||
defp do_create_authorization(
|
||||
%Plug.Conn{} = conn,
|
||||
%{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_attrs
|
||||
},
|
||||
user
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <-
|
||||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
|
||||
{:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
|
||||
{:ok, auth, user}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
|
||||
when is_list(requested_scopes) do
|
||||
with {:account_status, :active} <- {:account_status, User.account_status(user)},
|
||||
{:ok, scopes} <- validate_scopes(app, requested_scopes),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
||||
{:ok, auth}
|
||||
end
|
||||
end
|
||||
|
||||
# Note: intended to be a private function but opened for AccountController that logs in on signup
|
||||
@doc "If checks pass, creates authorization and token for given user, app and requested scopes."
|
||||
def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
|
||||
with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
|
||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
{:ok, token}
|
||||
end
|
||||
end
|
||||
def token_revoke(%Plug.Conn{} = _conn, _params), do: {:error, :bad_request}
|
||||
|
||||
# Special case: Local MastodonFE
|
||||
defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
|
||||
def redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
|
||||
|
||||
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
|
||||
|
||||
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
|
||||
|
||||
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
|
||||
defp build_and_response_mfa_token(user, auth) do
|
||||
with {:ok, token} <- MFA.Token.create(user, auth) do
|
||||
MFAView.render("mfa_response.json", %{token: token, user: user})
|
||||
end
|
||||
end
|
||||
def redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
|
||||
|
||||
@spec validate_scopes(App.t(), map() | list()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
@ -632,8 +362,4 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
|> String.split()
|
||||
|> Enum.at(0)
|
||||
end
|
||||
|
||||
defp render_invalid_credentials_error(conn) do
|
||||
render_error(conn, :bad_request, "Invalid credentials")
|
||||
end
|
||||
end
|
||||
|
@ -41,7 +41,7 @@ defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||
) do
|
||||
{id, secret}
|
||||
else
|
||||
_ -> {conn.params["client_id"], conn.params["client_secret"]}
|
||||
_ -> {conn.params[:client_id], conn.params[:client_secret]}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,6 +30,11 @@ defmodule Pleroma.Web.Router do
|
||||
plug(:fetch_session)
|
||||
end
|
||||
|
||||
pipeline :fetch_session_api do
|
||||
plug(:fetch_session)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :oauth do
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Web.Plugs.OAuthPlug)
|
||||
@ -37,6 +42,11 @@ defmodule Pleroma.Web.Router do
|
||||
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
|
||||
end
|
||||
|
||||
pipeline :oauth_api do
|
||||
plug(:oauth)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
# Note: expects _user_ authentication (user-unbound app-bound tokens don't qualify)
|
||||
pipeline :expect_user_authentication do
|
||||
plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
|
||||
@ -331,20 +341,25 @@ defmodule Pleroma.Web.Router do
|
||||
scope "/oauth", Pleroma.Web.OAuth do
|
||||
# Note: use /api/v1/accounts/verify_credentials for userinfo of signed-in user
|
||||
|
||||
get("/registration_details", OAuthController, :registration_details)
|
||||
get("/registration_details", OAuthBrowserController, :registration_details)
|
||||
|
||||
post("/mfa/verify", MFAController, :verify, as: :mfa_verify)
|
||||
get("/mfa", MFAController, :show)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth)
|
||||
pipe_through(:oauth_api)
|
||||
|
||||
get("/authorize", OAuthController, :authorize)
|
||||
post("/authorize", OAuthController, :create_authorization)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
pipe_through(:fetch_session)
|
||||
pipe_through(:oauth)
|
||||
|
||||
post("/authorize_callback", OAuthBrowserController, :authorize_callback)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
pipe_through(:fetch_session_api)
|
||||
|
||||
post("/token", OAuthController, :token_exchange)
|
||||
post("/revoke", OAuthController, :token_revoke)
|
||||
@ -354,10 +369,10 @@ defmodule Pleroma.Web.Router do
|
||||
scope [] do
|
||||
pipe_through(:browser)
|
||||
|
||||
get("/prepare_request", OAuthController, :prepare_request)
|
||||
get("/:provider", OAuthController, :request)
|
||||
get("/:provider/callback", OAuthController, :callback)
|
||||
post("/register", OAuthController, :register)
|
||||
get("/prepare_request", OAuthBrowserController, :prepare_request)
|
||||
get("/:provider", OAuthBrowserController, :provider_request)
|
||||
get("/:provider/callback", OAuthBrowserController, :provider_callback)
|
||||
post("/register", OAuthBrowserController, :register)
|
||||
end
|
||||
end
|
||||
|
||||
@ -620,12 +635,6 @@ defmodule Pleroma.Web.Router do
|
||||
|
||||
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
|
||||
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
|
||||
|
||||
post(
|
||||
"/qvitter/statuses/notifications/read",
|
||||
TwitterAPI.Controller,
|
||||
:mark_notifications_as_read
|
||||
)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web do
|
||||
|
@ -5,7 +5,7 @@
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
|
||||
<%= form_for @conn, o_auth_path(@conn, :authorize_callback), [as: "authorization"], fn f -> %>
|
||||
|
||||
<%= if @user do %>
|
||||
<div class="account-header">
|
||||
|
@ -5,7 +5,6 @@
|
||||
defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
@ -15,11 +14,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
require Logger
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
|
||||
)
|
||||
|
||||
plug(
|
||||
:skip_plug,
|
||||
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
|
||||
)
|
||||
@ -67,31 +61,4 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(status, json)
|
||||
end
|
||||
|
||||
def mark_notifications_as_read(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{"latest_id" => latest_id} = params
|
||||
) do
|
||||
Notification.set_read_up_to(user, latest_id)
|
||||
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
conn
|
||||
# XXX: This is a hack because pleroma-fe still uses that API.
|
||||
|> put_view(Pleroma.Web.MastodonAPI.NotificationView)
|
||||
|> render("index.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
|
||||
bad_request_reply(conn, "You need to specify latest_id")
|
||||
end
|
||||
|
||||
defp bad_request_reply(conn, error_message) do
|
||||
json = error_json(conn, error_message)
|
||||
json_reply(conn, 400, json)
|
||||
end
|
||||
|
||||
defp error_json(conn, error_message) do
|
||||
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
|
||||
end
|
||||
end
|
||||
|
@ -10,12 +10,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Healthcheck
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)
|
||||
plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
|
||||
|
||||
plug(
|
||||
@ -30,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
|
||||
|
||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||
@ -62,17 +62,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
end
|
||||
|
||||
def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
|
||||
with {:ok, _} <- Notification.read_one(user, notification_id) do
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def frontend_configurations(conn, _params) do
|
||||
render(conn, "frontend_configurations.json")
|
||||
end
|
||||
@ -92,13 +81,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
end
|
||||
|
||||
def change_password(%{assigns: %{user: user}} = conn, params) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||
def change_password(%{assigns: %{user: user}} = conn, %{
|
||||
password: password,
|
||||
new_password: new_password,
|
||||
new_password_confirmation: new_password_confirmation
|
||||
}) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, password) do
|
||||
{:ok, user} ->
|
||||
with {:ok, _user} <-
|
||||
User.reset_password(user, %{
|
||||
password: params["new_password"],
|
||||
password_confirmation: params["new_password_confirmation"]
|
||||
password: new_password,
|
||||
password_confirmation: new_password_confirmation
|
||||
}) do
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
@ -115,10 +108,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
end
|
||||
|
||||
def change_email(%{assigns: %{user: user}} = conn, params) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||
def change_email(%{assigns: %{user: user}} = conn, %{password: password, email: email}) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, password) do
|
||||
{:ok, user} ->
|
||||
with {:ok, _user} <- User.change_email(user, params["email"]) do
|
||||
with {:ok, _user} <- User.change_email(user, email) do
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
{:error, changeset} ->
|
||||
@ -135,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
|
||||
def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||
password = params["password"] || ""
|
||||
password = params[:password] || ""
|
||||
|
||||
case CommonAPI.Utils.confirm_current_password(user, password) do
|
||||
{:ok, user} ->
|
||||
@ -148,7 +141,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
|
||||
def disable_account(%{assigns: %{user: user}} = conn, params) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
|
||||
{:ok, user} ->
|
||||
User.set_activation_async(user, false)
|
||||
json(conn, %{status: "success"})
|
||||
|
@ -514,11 +514,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
{:ok, post_2} = CommonAPI.post(user, %{status: "second post"})
|
||||
|
||||
response_1 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1")
|
||||
assert [res] = json_response(response_1, 200)
|
||||
assert [res] = json_response_and_validate_schema(response_1, 200)
|
||||
assert res["id"] == post_2.id
|
||||
|
||||
response_2 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1&max_id=#{res["id"]}")
|
||||
assert [res] = json_response(response_2, 200)
|
||||
assert [res] = json_response_and_validate_schema(response_2, 200)
|
||||
assert res["id"] == post_1.id
|
||||
|
||||
refute response_1 == response_2
|
||||
@ -881,7 +881,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert [] ==
|
||||
conn
|
||||
|> get("/api/v1/timelines/home")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"showing_reblogs" => true} =
|
||||
conn
|
||||
@ -892,7 +892,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert [%{"id" => ^reblog_id}] =
|
||||
conn
|
||||
|> get("/api/v1/timelines/home")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "following with reblogs" do
|
||||
@ -910,7 +910,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert [%{"id" => ^reblog_id}] =
|
||||
conn
|
||||
|> get("/api/v1/timelines/home")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"showing_reblogs" => false} =
|
||||
conn
|
||||
@ -921,7 +921,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert [] ==
|
||||
conn
|
||||
|> get("/api/v1/timelines/home")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "following / unfollowing errors", %{user: user, conn: conn} do
|
||||
@ -1049,7 +1049,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
clear_config([:instance, :account_activation_required], false)
|
||||
clear_config([:instance, :account_approval_required], false)
|
||||
|
||||
conn =
|
||||
apps_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
@ -1057,6 +1057,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||
scopes: "read, write, follow"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{
|
||||
"client_id" => client_id,
|
||||
@ -1066,17 +1067,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
|
||||
"vapid_key" => _,
|
||||
"website" => nil
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
} = apps_response
|
||||
|
||||
conn =
|
||||
post(conn, "/oauth/token", %{
|
||||
grant_type: "client_credentials",
|
||||
client_id: client_id,
|
||||
client_secret: client_secret
|
||||
})
|
||||
token_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/x-www-form-urlencoded")
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "client_credentials",
|
||||
"client_id" => client_id,
|
||||
"client_secret" => client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
|
||||
json_response(conn, 200)
|
||||
token_response
|
||||
|
||||
assert token
|
||||
token_from_db = Repo.get_by(Token, token: token)
|
||||
@ -1131,7 +1139,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
clear_config([:instance, :account_approval_required], false)
|
||||
|
||||
conn =
|
||||
register_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
@ -1139,6 +1147,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||
scopes: "read, write, follow"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{
|
||||
"client_id" => client_id,
|
||||
@ -1148,17 +1157,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
|
||||
"vapid_key" => _,
|
||||
"website" => nil
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
} = register_response
|
||||
|
||||
conn =
|
||||
post(conn, "/oauth/token", %{
|
||||
grant_type: "client_credentials",
|
||||
client_id: client_id,
|
||||
client_secret: client_secret
|
||||
})
|
||||
token_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/x-www-form-urlencoded")
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "client_credentials",
|
||||
"client_id" => client_id,
|
||||
"client_secret" => client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
|
||||
json_response(conn, 200)
|
||||
token_response
|
||||
|
||||
assert token
|
||||
token_from_db = Repo.get_by(Token, token: token)
|
||||
@ -1166,19 +1182,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert refresh
|
||||
assert scope == "read write follow"
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> put_req_header("authorization", "Bearer " <> token)
|
||||
|> post("/api/v1/accounts", %{
|
||||
username: "lain",
|
||||
email: "lain@example.org",
|
||||
password: "PlzDontHackLain",
|
||||
bio: "Test Bio",
|
||||
agreement: true
|
||||
})
|
||||
assert response =
|
||||
conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> put_req_header("authorization", "Bearer " <> token)
|
||||
|> post("/api/v1/accounts", %{
|
||||
username: "lain",
|
||||
email: "lain@example.org",
|
||||
password: "PlzDontHackLain",
|
||||
bio: "Test Bio",
|
||||
agreement: true
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
response = json_response_and_validate_schema(conn, 200)
|
||||
assert %{"identifier" => "missing_confirmed_email"} = response
|
||||
refute response["access_token"]
|
||||
refute response["token_type"]
|
||||
@ -1191,7 +1207,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
clear_config([:instance, :account_approval_required], true)
|
||||
clear_config([:instance, :account_activation_required], false)
|
||||
|
||||
conn =
|
||||
apps_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
@ -1199,6 +1215,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||
scopes: "read, write, follow"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{
|
||||
"client_id" => client_id,
|
||||
@ -1208,17 +1225,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
|
||||
"vapid_key" => _,
|
||||
"website" => nil
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
} = apps_response
|
||||
|
||||
conn =
|
||||
post(conn, "/oauth/token", %{
|
||||
grant_type: "client_credentials",
|
||||
client_id: client_id,
|
||||
client_secret: client_secret
|
||||
})
|
||||
token_response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/x-www-form-urlencoded")
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "client_credentials",
|
||||
"client_id" => client_id,
|
||||
"client_secret" => client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
|
||||
json_response(conn, 200)
|
||||
token_response
|
||||
|
||||
assert token
|
||||
token_from_db = Repo.get_by(Token, token: token)
|
||||
@ -1226,7 +1250,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
assert refresh
|
||||
assert scope == "read write follow"
|
||||
|
||||
conn =
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> put_req_header("authorization", "Bearer " <> token)
|
||||
@ -1238,8 +1262,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
agreement: true,
|
||||
reason: "I'm a cool dude, bro"
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
response = json_response_and_validate_schema(conn, 200)
|
||||
assert %{"identifier" => "awaiting_approval"} = response
|
||||
refute response["access_token"]
|
||||
refute response["token_type"]
|
||||
@ -1372,13 +1396,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
"grant_type" => "client_credentials",
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> put_req_header("content-type", "application/x-www-form-urlencoded")
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "client_credentials",
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert %{"access_token" => token, "token_type" => "Bearer"} = json_response(conn, 200)
|
||||
assert %{"access_token" => token, "token_type" => "Bearer"} =
|
||||
json_response_and_validate_schema(conn, 200)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|
@ -214,7 +214,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
|
||||
|
||||
res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context")
|
||||
|
||||
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
|
||||
assert %{"ancestors" => [], "descendants" => []} ==
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
end
|
||||
|
||||
test "Removes a conversation", %{user: user_one, conn: conn} do
|
||||
|
@ -140,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
|
||||
|
||||
conn
|
||||
|> get("/api/v1/media/#{object.id}")
|
||||
|> json_response(403)
|
||||
|> json_response_and_validate_schema(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -81,6 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||
"sensitive" => 0
|
||||
})
|
||||
|
||||
# Idempotency plug response means detection fail
|
||||
assert %{"id" => second_id} = json_response(conn_two, 200)
|
||||
assert id == second_id
|
||||
|
||||
@ -1542,7 +1543,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||
|> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
|
||||
|> get("api/v1/timelines/home")
|
||||
|
||||
[reblogged_activity] = json_response(conn3, 200)
|
||||
[reblogged_activity] = json_response_and_validate_schema(conn3, 200)
|
||||
|
||||
assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
|
||||
|
||||
@ -1896,7 +1897,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||
local = Pleroma.Constants.as_local_public()
|
||||
|
||||
assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
|
||||
json_response(conn_one, 200)
|
||||
json_response_and_validate_schema(conn_one, 200)
|
||||
|
||||
assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
|
||||
end
|
||||
|
@ -905,10 +905,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||
%{conn: auth_conn} = oauth_access(["read:statuses"])
|
||||
|
||||
res_conn = get(auth_conn, "#{base_uri}?local=true")
|
||||
assert length(json_response(res_conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
|
||||
|
||||
res_conn = get(auth_conn, "#{base_uri}?local=false")
|
||||
assert length(json_response(res_conn, 200)) == 2
|
||||
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
|
||||
end
|
||||
|
||||
test "with default settings on private instances, returns 403 for unauthenticated users", %{
|
||||
@ -922,7 +922,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||
for local <- [true, false] do
|
||||
res_conn = get(conn, "#{base_uri}?local=#{local}")
|
||||
|
||||
assert json_response(res_conn, :unauthorized) == error_response
|
||||
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
|
||||
end
|
||||
|
||||
ensure_authenticated_access(base_uri)
|
||||
@ -939,7 +939,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||
for local <- [true, false] do
|
||||
res_conn = get(conn, "#{base_uri}?local=#{local}")
|
||||
|
||||
assert json_response(res_conn, :unauthorized) == error_response
|
||||
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
|
||||
end
|
||||
|
||||
ensure_authenticated_access(base_uri)
|
||||
@ -951,10 +951,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||
clear_config([:restrict_unauthenticated, :timelines, :federated], true)
|
||||
|
||||
res_conn = get(conn, "#{base_uri}?local=true")
|
||||
assert length(json_response(res_conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
|
||||
|
||||
res_conn = get(conn, "#{base_uri}?local=false")
|
||||
assert json_response(res_conn, :unauthorized) == error_response
|
||||
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
|
||||
|
||||
ensure_authenticated_access(base_uri)
|
||||
end
|
||||
@ -966,11 +966,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||
clear_config([:restrict_unauthenticated, :timelines, :federated], false)
|
||||
|
||||
res_conn = get(conn, "#{base_uri}?local=true")
|
||||
assert json_response(res_conn, :unauthorized) == error_response
|
||||
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
|
||||
|
||||
# Note: local activities get delivered as part of federated timeline
|
||||
res_conn = get(conn, "#{base_uri}?local=false")
|
||||
assert length(json_response(res_conn, 200)) == 2
|
||||
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
|
||||
|
||||
ensure_authenticated_access(base_uri)
|
||||
end
|
||||
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do
|
||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
|
||||
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
|
||||
|
||||
assert _result = json_response(conn, 200)
|
||||
assert %{} = json_response(conn, 200)
|
||||
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
assert user.mastofe_settings == %{"programming" => "socks"}
|
||||
|
@ -37,15 +37,19 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
|
||||
] do
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert %{"access_token" => token} = json_response(conn, 200)
|
||||
assert %{"access_token" => token} = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
token = Repo.get_by(Token, token: token)
|
||||
|
||||
@ -81,15 +85,19 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
|
||||
] do
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert %{"access_token" => token} = json_response(conn, 200)
|
||||
assert %{"access_token" => token} = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
token = Repo.get_by(Token, token: token) |> Repo.preload(:user)
|
||||
|
||||
@ -120,15 +128,19 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
|
||||
] do
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> post(
|
||||
"/oauth/token?#{
|
||||
URI.encode_query(%{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert %{"error" => "Invalid credentials"} = json_response(conn, 400)
|
||||
assert %{"error" => "Invalid credentials"} = json_response_and_validate_schema(conn, 400)
|
||||
assert_received :close_connection
|
||||
end
|
||||
end
|
||||
|
@ -164,7 +164,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
ap_id = user.ap_id
|
||||
|
||||
@ -192,7 +192,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
|
||||
assert response == %{"error" => "Invalid code"}
|
||||
end
|
||||
@ -209,7 +209,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
|
||||
assert response == %{"error" => "Invalid code"}
|
||||
end
|
||||
@ -227,7 +227,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => "xxx",
|
||||
"client_secret" => "xxx"
|
||||
})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
|
||||
assert response == %{"error" => "Invalid code"}
|
||||
end
|
||||
@ -272,7 +272,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
ap_id = user.ap_id
|
||||
|
||||
@ -296,7 +296,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
|
||||
assert error_response == %{"error" => "Invalid code"}
|
||||
end
|
||||
|
576
test/pleroma/web/o_auth/o_auth_browser_controller_test.exs
Normal file
576
test/pleroma/web/o_auth/o_auth_browser_controller_test.exs
Normal file
@ -0,0 +1,576 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.OAuthBrowserControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Helpers.AuthHelper
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.MFA.TOTP
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
@session_opts [
|
||||
store: :cookie,
|
||||
key: "_test",
|
||||
signing_salt: "cooldude"
|
||||
]
|
||||
setup do
|
||||
clear_config([:instance, :account_activation_required])
|
||||
clear_config([:instance, :account_approval_required])
|
||||
end
|
||||
|
||||
describe "in OAuth consumer mode, " do
|
||||
setup do
|
||||
[
|
||||
app: insert(:oauth_app),
|
||||
conn:
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session()
|
||||
]
|
||||
end
|
||||
|
||||
setup do: clear_config([:auth, :oauth_consumer_strategies], ~w(twitter facebook))
|
||||
|
||||
test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/prepare_request",
|
||||
%{
|
||||
"provider" => "twitter",
|
||||
"authorization" => %{
|
||||
"scope" => "read follow",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => "a_state"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 302)
|
||||
|
||||
redirect_query = URI.parse(redirected_to(conn)).query
|
||||
assert %{"state" => state_param} = URI.decode_query(redirect_query)
|
||||
assert {:ok, state_components} = Jason.decode(state_param)
|
||||
|
||||
expected_client_id = app.client_id
|
||||
expected_redirect_uri = app.redirect_uris
|
||||
|
||||
assert %{
|
||||
"scope" => "read follow",
|
||||
"client_id" => ^expected_client_id,
|
||||
"redirect_uri" => ^expected_redirect_uri,
|
||||
"state" => "a_state"
|
||||
} = state_components
|
||||
end
|
||||
|
||||
test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
|
||||
%{app: app, conn: conn} do
|
||||
registration = insert(:registration)
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
state_params = %{
|
||||
"scope" => Enum.join(app.scopes, " "),
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => ""
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
|
||||
|> get(
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Jason.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
|
||||
end
|
||||
|
||||
test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
|
||||
%{app: app, conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
state_params = %{
|
||||
"scope" => "read write",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => "a_state"
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:ueberauth_auth, %{
|
||||
provider: "twitter",
|
||||
uid: "171799000",
|
||||
info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
|
||||
})
|
||||
|> get(
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Jason.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 200)
|
||||
assert response =~ ~r/name="op" type="submit" value="register"/
|
||||
assert response =~ ~r/name="op" type="submit" value="connect"/
|
||||
assert response =~ user.email
|
||||
assert response =~ user.nickname
|
||||
end
|
||||
|
||||
test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
state_params = %{
|
||||
"scope" => Enum.join(app.scopes, " "),
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => ""
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
|
||||
|> get(
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Jason.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 302)
|
||||
assert redirected_to(conn) == app.redirect_uris
|
||||
assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
|
||||
end
|
||||
|
||||
test "GET /oauth/registration_details renders registration details form", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/registration_details",
|
||||
%{
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => "a_state",
|
||||
"nickname" => nil,
|
||||
"email" => "john@doe.com"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 200)
|
||||
assert response =~ ~r/name="op" type="submit" value="register"/
|
||||
assert response =~ ~r/name="op" type="submit" value="connect"/
|
||||
end
|
||||
|
||||
test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "register",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "a_state",
|
||||
"nickname" => "availablenick",
|
||||
"email" => "available@email.com"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
|
||||
end
|
||||
|
||||
test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
|
||||
unlisted_redirect_uri = "http://cross-site-request.com"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "register",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => unlisted_redirect_uri,
|
||||
"state" => "a_state",
|
||||
"nickname" => "availablenick",
|
||||
"email" => "available@email.com"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 401)
|
||||
end
|
||||
|
||||
test "with invalid params, POST /oauth/register?op=register renders registration_details page",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
another_user = insert(:user)
|
||||
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
|
||||
|
||||
params = %{
|
||||
"op" => "register",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => "a_state",
|
||||
"nickname" => "availablenickname",
|
||||
"email" => "available@email.com"
|
||||
}
|
||||
}
|
||||
|
||||
for {bad_param, bad_param_value} <-
|
||||
[{"nickname", another_user.nickname}, {"email", another_user.email}] do
|
||||
bad_registration_attrs = %{
|
||||
"authorization" => Map.put(params["authorization"], bad_param, bad_param_value)
|
||||
}
|
||||
|
||||
bad_params = Map.merge(params, bad_registration_attrs)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post("/oauth/register", bad_params)
|
||||
|
||||
assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
|
||||
assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
|
||||
end
|
||||
end
|
||||
|
||||
test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
|
||||
registration = insert(:registration, user: nil)
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "connect",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "a_state",
|
||||
"name" => user.nickname,
|
||||
"password" => "testpassword"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
|
||||
end
|
||||
|
||||
test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
|
||||
registration = insert(:registration, user: nil)
|
||||
unlisted_redirect_uri = "http://cross-site-request.com"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "connect",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => unlisted_redirect_uri,
|
||||
"state" => "a_state",
|
||||
"name" => user.nickname,
|
||||
"password" => "testpassword"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert html_response(conn, 401)
|
||||
end
|
||||
|
||||
test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user)
|
||||
registration = insert(:registration, user: nil)
|
||||
|
||||
params = %{
|
||||
"op" => "connect",
|
||||
"authorization" => %{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => OAuthController.default_redirect_uri(app),
|
||||
"state" => "a_state",
|
||||
"name" => user.nickname,
|
||||
"password" => "wrong password"
|
||||
}
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post("/oauth/register", params)
|
||||
|
||||
assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
|
||||
assert get_flash(conn, :error) == "Invalid Username/Password"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /oauth/authorize_callback" do
|
||||
test "redirects with oauth authorization, " <>
|
||||
"granting requested app-supported scopes to both admin- and non-admin users" do
|
||||
app_scopes = ["read", "write", "admin", "secret_scope"]
|
||||
app = insert(:oauth_app, scopes: app_scopes)
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
non_admin = insert(:user, is_admin: false)
|
||||
admin = insert(:user, is_admin: true)
|
||||
scopes_subset = ["read:subscope", "write", "admin"]
|
||||
|
||||
# In case scope param is missing, expecting _all_ app-supported scopes to be granted
|
||||
for user <- [non_admin, admin],
|
||||
{requested_scopes, expected_scopes} <-
|
||||
%{scopes_subset => scopes_subset, nil: app_scopes} do
|
||||
conn =
|
||||
post(
|
||||
build_conn(),
|
||||
"/oauth/authorize_callback",
|
||||
%{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"scope" => requested_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 == expected_scopes
|
||||
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_callback",
|
||||
%{
|
||||
"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()
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
multi_factor_authentication_settings: %MFA.Settings{
|
||||
enabled: true,
|
||||
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
|
||||
}
|
||||
)
|
||||
|
||||
app = insert(:oauth_app, scopes: ["read", "write", "follow"])
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/authorize_callback", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"scope" => "read write",
|
||||
"state" => "statepassed"
|
||||
}
|
||||
})
|
||||
|
||||
result = html_response(conn, 200)
|
||||
|
||||
mfa_token = Repo.get_by(MFA.Token, user_id: user.id)
|
||||
assert result =~ app.redirect_uris
|
||||
assert result =~ "statepassed"
|
||||
assert result =~ mfa_token.token
|
||||
assert result =~ "Two-factor authentication"
|
||||
end
|
||||
|
||||
test "returns 401 for wrong credentials", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
app = insert(:oauth_app)
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
result =
|
||||
conn
|
||||
|> post("/oauth/authorize_callback", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "wrong",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "statepassed",
|
||||
"scope" => Enum.join(app.scopes, " ")
|
||||
}
|
||||
})
|
||||
|> html_response(:unauthorized)
|
||||
|
||||
# Keep the details
|
||||
assert result =~ app.client_id
|
||||
assert result =~ redirect_uri
|
||||
|
||||
# Error message
|
||||
assert result =~ "Invalid Username/Password"
|
||||
end
|
||||
|
||||
test "returns 401 for missing scopes" do
|
||||
user = insert(:user, is_admin: false)
|
||||
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
result =
|
||||
build_conn()
|
||||
|> post("/oauth/authorize_callback", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "statepassed",
|
||||
"scope" => ""
|
||||
}
|
||||
})
|
||||
|> html_response(:unauthorized)
|
||||
|
||||
# Keep the details
|
||||
assert result =~ app.client_id
|
||||
assert result =~ redirect_uri
|
||||
|
||||
# Error message
|
||||
assert result =~ "This action is outside the authorized scopes"
|
||||
end
|
||||
|
||||
test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
result =
|
||||
conn
|
||||
|> post("/oauth/authorize_callback", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "statepassed",
|
||||
"scope" => "read write follow"
|
||||
}
|
||||
})
|
||||
|> html_response(:unauthorized)
|
||||
|
||||
# Keep the details
|
||||
assert result =~ app.client_id
|
||||
assert result =~ redirect_uri
|
||||
|
||||
# Error message
|
||||
assert result =~ "This action is outside the authorized scopes"
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
@ -17,14 +17,14 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/pleroma/accounts/mfa")
|
||||
|> json_response(:ok) == %{
|
||||
|> json_response_and_validate_schema(:ok) == %{
|
||||
"settings" => %{"enabled" => false, "totp" => false}
|
||||
}
|
||||
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> get("/api/pleroma/accounts/mfa")
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: read:security."
|
||||
}
|
||||
end
|
||||
@ -43,7 +43,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/pleroma/accounts/mfa")
|
||||
|> json_response(:ok) == %{
|
||||
|> json_response_and_validate_schema(:ok) == %{
|
||||
"settings" => %{"enabled" => true, "totp" => true}
|
||||
}
|
||||
end
|
||||
@ -66,7 +66,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/pleroma/accounts/mfa/backup_codes")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [<<_::bytes-size(6)>>, <<_::bytes-size(6)>>] = response["codes"]
|
||||
user = refresh_record(user)
|
||||
@ -78,7 +78,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> get("/api/pleroma/accounts/mfa/backup_codes")
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: write:security."
|
||||
}
|
||||
end
|
||||
@ -93,7 +93,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/pleroma/accounts/mfa/setup/torf")
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
|
||||
assert response == %{"error" => "undefined method"}
|
||||
end
|
||||
@ -111,7 +111,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/pleroma/accounts/mfa/setup/totp")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
user = refresh_record(user)
|
||||
mfa_settings = user.multi_factor_authentication_settings
|
||||
@ -127,7 +127,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> get("/api/pleroma/accounts/mfa/setup/totp")
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: write:security."
|
||||
}
|
||||
end
|
||||
@ -152,7 +152,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code})
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
settings = refresh_record(user).multi_factor_authentication_settings
|
||||
assert settings.enabled
|
||||
@ -163,7 +163,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code})
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: write:security."
|
||||
}
|
||||
end
|
||||
@ -186,7 +186,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "xxx", code: code})
|
||||
|> json_response(422)
|
||||
|> json_response_and_validate_schema(422)
|
||||
|
||||
settings = refresh_record(user).multi_factor_authentication_settings
|
||||
refute settings.enabled
|
||||
@ -213,7 +213,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"})
|
||||
|> json_response(422)
|
||||
|> json_response_and_validate_schema(422)
|
||||
|
||||
settings = refresh_record(user).multi_factor_authentication_settings
|
||||
refute settings.enabled
|
||||
@ -224,7 +224,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"})
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: write:security."
|
||||
}
|
||||
end
|
||||
@ -246,7 +246,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"})
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
settings = refresh_record(user).multi_factor_authentication_settings
|
||||
refute settings.enabled
|
||||
@ -256,7 +256,7 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do
|
||||
assert conn
|
||||
|> put_req_header("authorization", "Bearer #{token2.token}")
|
||||
|> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"})
|
||||
|> json_response(403) == %{
|
||||
|> json_response_and_validate_schema(403) == %{
|
||||
"error" => "Insufficient permissions: write:security."
|
||||
}
|
||||
end
|
||||
|
@ -81,9 +81,9 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
|
||||
|
||||
if token == token3 do
|
||||
assert %{"error" => "Insufficient permissions: follow | write:follows."} ==
|
||||
json_response(conn, 403)
|
||||
json_response_and_validate_schema(conn, 403)
|
||||
else
|
||||
assert json_response(conn, 200)
|
||||
assert json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -7,59 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "POST /api/qvitter/statuses/notifications/read" do
|
||||
test "without valid credentials", %{conn: conn} do
|
||||
conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567})
|
||||
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
|
||||
end
|
||||
|
||||
test "with credentials, without any params" do
|
||||
%{conn: conn} = oauth_access(["write:notifications"])
|
||||
|
||||
conn = post(conn, "/api/qvitter/statuses/notifications/read")
|
||||
|
||||
assert json_response(conn, 400) == %{
|
||||
"error" => "You need to specify latest_id",
|
||||
"request" => "/api/qvitter/statuses/notifications/read"
|
||||
}
|
||||
end
|
||||
|
||||
test "with credentials, with params" do
|
||||
%{user: current_user, conn: conn} =
|
||||
oauth_access(["read:notifications", "write:notifications"])
|
||||
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(other_user, %{
|
||||
status: "Hey @#{current_user.nickname}"
|
||||
})
|
||||
|
||||
response_conn =
|
||||
conn
|
||||
|> get("/api/v1/notifications")
|
||||
|
||||
[notification] = json_response(response_conn, 200)
|
||||
|
||||
assert notification["pleroma"]["is_seen"] == false
|
||||
|
||||
response_conn =
|
||||
conn
|
||||
|> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]})
|
||||
|
||||
[notification] = response = json_response(response_conn, 200)
|
||||
|
||||
assert length(response) == 1
|
||||
|
||||
assert notification["pleroma"]["is_seen"] == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/account/confirm_email/:id/:token" do
|
||||
setup do
|
||||
{:ok, user} =
|
||||
|
@ -25,11 +25,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
|
||||
test "it updates notification settings", %{user: user, conn: conn} do
|
||||
conn
|
||||
|> put("/api/pleroma/notification_settings", %{
|
||||
"block_from_strangers" => true,
|
||||
"bar" => 1
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|> put(
|
||||
"/api/pleroma/notification_settings?#{
|
||||
URI.encode_query(%{
|
||||
block_from_strangers: true
|
||||
})
|
||||
}"
|
||||
)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
user = refresh_record(user)
|
||||
|
||||
@ -41,8 +44,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
|
||||
test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
|
||||
conn
|
||||
|> put("/api/pleroma/notification_settings", %{"hide_notification_contents" => "1"})
|
||||
|> json_response(:ok)
|
||||
|> put(
|
||||
"/api/pleroma/notification_settings?#{
|
||||
URI.encode_query(%{
|
||||
hide_notification_contents: 1
|
||||
})
|
||||
}"
|
||||
)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
user = refresh_record(user)
|
||||
|
||||
@ -70,7 +79,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/frontend_configurations")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
|
||||
end
|
||||
@ -81,7 +90,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
emoji =
|
||||
conn
|
||||
|> get("/api/pleroma/emoji")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert Enum.all?(emoji, fn
|
||||
{_key,
|
||||
@ -103,7 +112,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/healthcheck")
|
||||
|> json_response(503)
|
||||
|> json_response_and_validate_schema(503)
|
||||
|
||||
assert response == %{}
|
||||
end
|
||||
@ -116,7 +125,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/healthcheck")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{
|
||||
"active" => _,
|
||||
@ -136,7 +145,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/healthcheck")
|
||||
|> json_response(503)
|
||||
|> json_response_and_validate_schema(503)
|
||||
|
||||
assert %{
|
||||
"active" => _,
|
||||
@ -155,8 +164,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do
|
||||
response =
|
||||
conn
|
||||
|> post("/api/pleroma/disable_account", %{"password" => "test"})
|
||||
|> json_response(:ok)
|
||||
|> post("/api/pleroma/disable_account?password=test")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == %{"status" => "success"}
|
||||
ObanHelpers.perform_all()
|
||||
@ -171,8 +180,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
|
||||
response =
|
||||
conn
|
||||
|> post("/api/pleroma/disable_account", %{"password" => "test1"})
|
||||
|> json_response(:ok)
|
||||
|> post("/api/pleroma/disable_account?password=test1")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == %{"error" => "Invalid password."}
|
||||
user = User.get_cached_by_id(user.id)
|
||||
@ -252,54 +261,61 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
conn =
|
||||
conn
|
||||
|> assign(:token, nil)
|
||||
|> post("/api/pleroma/change_email")
|
||||
|> post(
|
||||
"/api/pleroma/change_email?#{
|
||||
URI.encode_query(%{password: "hi", email: "test@test.com"})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
|
||||
assert json_response_and_validate_schema(conn, 403) == %{
|
||||
"error" => "Insufficient permissions: write:accounts."
|
||||
}
|
||||
end
|
||||
|
||||
test "with proper permissions and invalid password", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "hi",
|
||||
"email" => "test@test.com"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_email?#{
|
||||
URI.encode_query(%{password: "hi", email: "test@test.com"})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Invalid password."}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and invalid email", %{
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "test",
|
||||
"email" => "foobar"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: "foobar"})}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Email has invalid format."}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"error" => "Email has invalid format."
|
||||
}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and no email", %{
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "test"
|
||||
})
|
||||
conn = post(conn, "/api/pleroma/change_email?#{URI.encode_query(%{password: "test"})}")
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
|
||||
assert %{"error" => "Missing field: email."} = json_response_and_validate_schema(conn, 400)
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and blank email", %{
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "test",
|
||||
"email" => ""
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: ""})}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"error" => "Email can't be blank."}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and non unique email", %{
|
||||
@ -308,24 +324,28 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "test",
|
||||
"email" => user.email
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: user.email})}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Email has already been taken."}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"error" => "Email has already been taken."
|
||||
}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and valid email", %{
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_email", %{
|
||||
"password" => "test",
|
||||
"email" => "cofe@foobar.com"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_email?#{
|
||||
URI.encode_query(%{password: "test", email: "cofe@foobar.com"})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"status" => "success"}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
|
||||
end
|
||||
end
|
||||
|
||||
@ -336,20 +356,35 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
conn =
|
||||
conn
|
||||
|> assign(:token, nil)
|
||||
|> post("/api/pleroma/change_password")
|
||||
|> post(
|
||||
"/api/pleroma/change_password?#{
|
||||
URI.encode_query(%{
|
||||
password: "hi",
|
||||
new_password: "newpass",
|
||||
new_password_confirmation: "newpass"
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
|
||||
assert json_response_and_validate_schema(conn, 403) == %{
|
||||
"error" => "Insufficient permissions: write:accounts."
|
||||
}
|
||||
end
|
||||
|
||||
test "with proper permissions and invalid password", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_password", %{
|
||||
"password" => "hi",
|
||||
"new_password" => "newpass",
|
||||
"new_password_confirmation" => "newpass"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_password?#{
|
||||
URI.encode_query(%{
|
||||
password: "hi",
|
||||
new_password: "newpass",
|
||||
new_password_confirmation: "newpass"
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"error" => "Invalid password."}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and new password and confirmation not matching",
|
||||
@ -357,13 +392,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_password", %{
|
||||
"password" => "test",
|
||||
"new_password" => "newpass",
|
||||
"new_password_confirmation" => "notnewpass"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_password?#{
|
||||
URI.encode_query(%{
|
||||
password: "test",
|
||||
new_password: "newpass",
|
||||
new_password_confirmation: "notnewpass"
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"error" => "New password does not match confirmation."
|
||||
}
|
||||
end
|
||||
@ -372,13 +412,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_password", %{
|
||||
"password" => "test",
|
||||
"new_password" => "",
|
||||
"new_password_confirmation" => ""
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_password?#{
|
||||
URI.encode_query(%{password: "test", new_password: "", new_password_confirmation: ""})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"error" => "New password can't be blank."
|
||||
}
|
||||
end
|
||||
@ -388,13 +429,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
user: user
|
||||
} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/change_password", %{
|
||||
"password" => "test",
|
||||
"new_password" => "newpass",
|
||||
"new_password_confirmation" => "newpass"
|
||||
})
|
||||
post(
|
||||
conn,
|
||||
"/api/pleroma/change_password?#{
|
||||
URI.encode_query(%{
|
||||
password: "test",
|
||||
new_password: "newpass",
|
||||
new_password_confirmation: "newpass"
|
||||
})
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{"status" => "success"}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
|
||||
fetched_user = User.get_cached_by_id(user.id)
|
||||
assert Pleroma.Password.Pbkdf2.verify_pass("newpass", fetched_user.password_hash) == true
|
||||
end
|
||||
@ -409,7 +455,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
|> assign(:token, nil)
|
||||
|> post("/api/pleroma/delete_account")
|
||||
|
||||
assert json_response(conn, 403) ==
|
||||
assert json_response_and_validate_schema(conn, 403) ==
|
||||
%{"error" => "Insufficient permissions: write:accounts."}
|
||||
end
|
||||
|
||||
@ -417,14 +463,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||
for params <- [%{"password" => "hi"}, %{}] do
|
||||
ret_conn = post(conn, "/api/pleroma/delete_account", params)
|
||||
|
||||
assert json_response(ret_conn, 200) == %{"error" => "Invalid password."}
|
||||
assert json_response_and_validate_schema(ret_conn, 200) == %{
|
||||
"error" => "Invalid password."
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
test "with proper permissions and valid password", %{conn: conn, user: user} do
|
||||
conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"})
|
||||
conn = post(conn, "/api/pleroma/delete_account?password=test")
|
||||
ObanHelpers.perform_all()
|
||||
assert json_response(conn, 200) == %{"status" => "success"}
|
||||
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
refute user.is_active
|
||||
|
Loading…
Reference in New Issue
Block a user