Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
89 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9bde8fc0d..989bab122 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -1,732 +1,741 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Reference
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ActorType
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.List
alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
import Pleroma.Web.ApiSpec.Helpers
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
tags: ["accounts"],
summary: "Register an account",
description:
"Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
operationId: "AccountController.create",
requestBody: request_body("Parameters", create_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", create_response()),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError),
429 => Operation.response("Error", "application/json", ApiError)
}
}
end
def verify_credentials_operation do
%Operation{
tags: ["accounts"],
description: "Test to make sure that the user token works.",
summary: "Verify account credentials",
operationId: "AccountController.verify_credentials",
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
200 => Operation.response("Account", "application/json", Account)
}
}
end
def update_credentials_operation do
%Operation{
tags: ["accounts"],
summary: "Update account credentials",
description: "Update the user's display and preferences.",
operationId: "AccountController.update_credentials",
security: [%{"oAuth" => ["write:accounts"]}],
requestBody: request_body("Parameters", update_creadentials_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def relationships_operation do
%Operation{
tags: ["accounts"],
summary: "Check relationships to other accounts",
operationId: "AccountController.relationships",
description: "Find out whether a given account is followed, blocked, muted, etc.",
security: [%{"oAuth" => ["read:follows"]}],
parameters: [
Operation.parameter(
:id,
:query,
%Schema{
oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
},
"Account IDs",
example: "123"
)
],
responses: %{
200 => Operation.response("Account", "application/json", array_of_relationships())
}
}
end
def show_operation do
%Operation{
tags: ["accounts"],
summary: "Account",
operationId: "AccountController.show",
description: "View information about a profile.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Account", "application/json", Account),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def statuses_operation do
%Operation{
tags: ["accounts"],
summary: "Statuses",
operationId: "AccountController.statuses",
description:
"Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
parameters:
[
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
Operation.parameter(:tagged, :query, :string, "With tag"),
Operation.parameter(
:only_media,
:query,
BooleanLike,
"Include only statuses with media attached"
),
Operation.parameter(
:with_muted,
:query,
BooleanLike,
"Include statuses from muted acccounts."
),
Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
Operation.parameter(
:exclude_visibilities,
:query,
%Schema{type: :array, items: VisibilityScope},
"Exclude visibilities"
)
] ++ pagination_params(),
responses: %{
200 => Operation.response("Statuses", "application/json", array_of_statuses()),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def followers_operation do
%Operation{
tags: ["accounts"],
summary: "Followers",
operationId: "AccountController.followers",
security: [%{"oAuth" => ["read:accounts"]}],
description:
"Accounts which follow the given account, if network is not hidden by the account owner.",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
with_relationships_param() | pagination_params()
],
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
}
end
def following_operation do
%Operation{
tags: ["accounts"],
summary: "Following",
operationId: "AccountController.following",
security: [%{"oAuth" => ["read:accounts"]}],
description:
"Accounts which the given account is following, if network is not hidden by the account owner.",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
with_relationships_param() | pagination_params()
],
responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
}
end
def lists_operation do
%Operation{
tags: ["accounts"],
summary: "Lists containing this account",
operationId: "AccountController.lists",
security: [%{"oAuth" => ["read:lists"]}],
description: "User lists that you have added this account to.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
}
end
def follow_operation do
%Operation{
tags: ["accounts"],
summary: "Follow",
operationId: "AccountController.follow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
description: "Follow the given account",
parameters: [
- %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
- Operation.parameter(
- :reblogs,
- :query,
- BooleanLike,
- "Receive this account's reblogs in home timeline? Defaults to true."
- )
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
],
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ properties: %{
+ reblogs: %Schema{
+ type: :boolean,
+ description: "Receive this account's reblogs in home timeline? Defaults to true.",
+ default: true
+ }
+ }
+ },
+ required: false
+ ),
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def unfollow_operation do
%Operation{
tags: ["accounts"],
summary: "Unfollow",
operationId: "AccountController.unfollow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
description: "Unfollow the given account",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def mute_operation do
%Operation{
tags: ["accounts"],
summary: "Mute",
operationId: "AccountController.mute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
requestBody: request_body("Parameters", mute_request()),
description:
"Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(
:notifications,
:query,
%Schema{allOf: [BooleanLike], default: true},
"Mute notifications in addition to statuses? Defaults to `true`."
)
],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def unmute_operation do
%Operation{
tags: ["accounts"],
summary: "Unmute",
operationId: "AccountController.unmute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
description: "Unmute the given account.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def block_operation do
%Operation{
tags: ["accounts"],
summary: "Block",
operationId: "AccountController.block",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
description:
"Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def unblock_operation do
%Operation{
tags: ["accounts"],
summary: "Unblock",
operationId: "AccountController.unblock",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
description: "Unblock the given account.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def follow_by_uri_operation do
%Operation{
tags: ["accounts"],
summary: "Follow by URI",
operationId: "AccountController.follows",
security: [%{"oAuth" => ["follow", "write:follows"]}],
requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def mutes_operation do
%Operation{
tags: ["accounts"],
summary: "Muted accounts",
operationId: "AccountController.mutes",
description: "Accounts the user has muted.",
security: [%{"oAuth" => ["follow", "read:mutes"]}],
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
}
end
def blocks_operation do
%Operation{
tags: ["accounts"],
summary: "Blocked users",
operationId: "AccountController.blocks",
description: "View your blocks. See also accounts/:id/{block,unblock}",
security: [%{"oAuth" => ["read:blocks"]}],
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
}
end
def endorsements_operation do
%Operation{
tags: ["accounts"],
summary: "Endorsements",
operationId: "AccountController.endorsements",
description: "Not implemented",
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
200 => empty_array_response()
}
}
end
def identity_proofs_operation do
%Operation{
tags: ["accounts"],
summary: "Identity proofs",
operationId: "AccountController.identity_proofs",
description: "Not implemented",
responses: %{
200 => empty_array_response()
}
}
end
defp create_request do
%Schema{
title: "AccountCreateRequest",
description: "POST body for creating an account",
type: :object,
required: [:username, :password, :agreement],
properties: %{
reason: %Schema{
type: :string,
nullable: true,
description:
"Text that will be reviewed by moderators if registrations require manual approval"
},
username: %Schema{type: :string, description: "The desired username for the account"},
email: %Schema{
type: :string,
nullable: true,
description:
"The email address to be used for login. Required when `account_activation_required` is enabled.",
format: :email
},
password: %Schema{
type: :string,
description: "The password to be used for login",
format: :password
},
agreement: %Schema{
allOf: [BooleanLike],
description:
"Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
},
locale: %Schema{
type: :string,
nullable: true,
description: "The language of the confirmation email that will be sent"
},
# Pleroma-specific properties:
fullname: %Schema{type: :string, nullable: true, description: "Full name"},
bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
captcha_solution: %Schema{
type: :string,
nullable: true,
description: "Provider-specific captcha solution"
},
captcha_token: %Schema{
type: :string,
nullable: true,
description: "Provider-specific captcha token"
},
captcha_answer_data: %Schema{
type: :string,
nullable: true,
description: "Provider-specific captcha data"
},
token: %Schema{
type: :string,
nullable: true,
description: "Invite token required when the registrations aren't public"
}
},
example: %{
"username" => "cofe",
"email" => "cofe@example.com",
"password" => "secret",
"agreement" => "true",
"bio" => "☕️"
}
}
end
defp create_response do
%Schema{
title: "AccountCreateResponse",
description: "Response schema for an account",
type: :object,
properties: %{
token_type: %Schema{type: :string},
access_token: %Schema{type: :string},
scope: %Schema{type: :array, items: %Schema{type: :string}},
created_at: %Schema{type: :integer, format: :"date-time"}
},
example: %{
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
"created_at" => 1_585_918_714,
"scope" => ["read", "write", "follow", "push"],
"token_type" => "Bearer"
}
}
end
defp update_creadentials_request do
%Schema{
title: "AccountUpdateCredentialsRequest",
description: "POST body for creating an account",
type: :object,
properties: %{
bot: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Whether the account has a bot flag."
},
display_name: %Schema{
type: :string,
nullable: true,
description: "The display name to use for the profile."
},
note: %Schema{type: :string, description: "The account bio."},
avatar: %Schema{
type: :string,
nullable: true,
description: "Avatar image encoded using multipart/form-data",
format: :binary
},
header: %Schema{
type: :string,
nullable: true,
description: "Header image encoded using multipart/form-data",
format: :binary
},
locked: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Whether manual approval of follow requests is required."
},
fields_attributes: %Schema{
nullable: true,
oneOf: [
%Schema{type: :array, items: attribute_field()},
%Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
]
},
# NOTE: `source` field is not supported
#
# source: %Schema{
# type: :object,
# properties: %{
# privacy: %Schema{type: :string},
# sensitive: %Schema{type: :boolean},
# language: %Schema{type: :string}
# }
# },
# Pleroma-specific fields
no_rich_text: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "html tags are stripped from all statuses requested from the API"
},
hide_followers: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's followers will be hidden"
},
hide_follows: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's follows will be hidden"
},
hide_followers_count: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's follower count will be hidden"
},
hide_follows_count: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's follow count will be hidden"
},
hide_favorites: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's favorites timeline will be hidden"
},
show_role: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "user's role (e.g admin, moderator) will be exposed to anyone in the
API"
},
default_scope: VisibilityScope,
pleroma_settings_store: %Schema{
type: :object,
nullable: true,
description: "Opaque user settings to be saved on the backend."
},
skip_thread_containment: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Skip filtering out broken threads"
},
allow_following_move: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Allows automatically follow moved following accounts"
},
pleroma_background_image: %Schema{
type: :string,
nullable: true,
description: "Sets the background image of the user.",
format: :binary
},
discoverable: %Schema{
allOf: [BooleanLike],
nullable: true,
description:
"Discovery of this account in search results and other services is allowed."
},
actor_type: ActorType
},
example: %{
bot: false,
display_name: "cofe",
note: "foobar",
fields_attributes: [%{name: "foo", value: "bar"}],
no_rich_text: false,
hide_followers: true,
hide_follows: false,
hide_followers_count: false,
hide_follows_count: false,
hide_favorites: false,
show_role: false,
default_scope: "private",
pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
skip_thread_containment: false,
allow_following_move: false,
discoverable: false,
actor_type: "Person"
}
}
end
def array_of_accounts do
%Schema{
title: "ArrayOfAccounts",
type: :array,
items: Account,
example: [Account.schema().example]
}
end
defp array_of_relationships do
%Schema{
title: "ArrayOfRelationships",
description: "Response schema for account relationships",
type: :array,
items: AccountRelationship,
example: [
%{
"id" => "1",
"following" => true,
"showing_reblogs" => true,
"followed_by" => true,
"blocking" => false,
"blocked_by" => true,
"muting" => false,
"muting_notifications" => false,
"requested" => false,
"domain_blocking" => false,
"subscribing" => false,
"endorsed" => true
},
%{
"id" => "2",
"following" => true,
"showing_reblogs" => true,
"followed_by" => true,
"blocking" => false,
"blocked_by" => true,
"muting" => true,
"muting_notifications" => false,
"requested" => true,
"domain_blocking" => false,
"subscribing" => false,
"endorsed" => false
},
%{
"id" => "3",
"following" => true,
"showing_reblogs" => true,
"followed_by" => true,
"blocking" => true,
"blocked_by" => false,
"muting" => true,
"muting_notifications" => false,
"requested" => false,
"domain_blocking" => true,
"subscribing" => true,
"endorsed" => false
}
]
}
end
defp follow_by_uri_request do
%Schema{
title: "AccountFollowsRequest",
description: "POST body for muting an account",
type: :object,
properties: %{
uri: %Schema{type: :string, nullable: true, format: :uri}
},
required: [:uri]
}
end
defp mute_request do
%Schema{
title: "AccountMuteRequest",
description: "POST body for muting an account",
type: :object,
properties: %{
notifications: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Mute notifications in addition to statuses? Defaults to true.",
default: true
}
},
example: %{
"notifications" => true
}
}
end
defp array_of_lists do
%Schema{
title: "ArrayOfLists",
description: "Response schema for lists",
type: :array,
items: List,
example: [
%{"id" => "123", "title" => "my list"},
%{"id" => "1337", "title" => "anotehr list"}
]
}
end
defp array_of_statuses do
%Schema{
title: "ArrayOfStatuses",
type: :array,
items: Status
}
end
defp attribute_field do
%Schema{
title: "AccountAttributeField",
description: "Request schema for account custom fields",
type: :object,
properties: %{
name: %Schema{type: :string},
value: %Schema{type: :string}
},
required: [:name, :value],
example: %{
"name" => "Website",
"value" => "https://pleroma.com"
}
}
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index d4532258c..fd89faf02 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -1,441 +1,441 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [
add_link_headers: 2,
truthy_param?: 1,
assign_account_by_id: 2,
embed_relationships?: 1,
json_response: 3
]
alias Pleroma.Maps
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
when action in [:show, :followers, :following]
)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]}
when action == :statuses
)
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]}
when action in [:verify_credentials, :endorsements, :identity_proofs]
)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "read:blocks"]} when action == :blocks
)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
)
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
plug(
RateLimiter,
[name: :relation_id_action, params: [:id, :uri]] when action in @relationship_actions
)
plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
plug(RateLimiter, [name: :app_account_creation] when action == :create)
plug(:assign_account_by_id when action in @needs_account)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
@doc "POST /api/v1/accounts"
def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
json(conn, %{
token_type: "Bearer",
access_token: token.token,
scope: app.scopes,
created_at: Token.Utils.format_created_at(token)
})
else
{:error, error} -> json_response(conn, :bad_request, %{error: error})
end
end
def create(%{assigns: %{app: _app}} = conn, _) do
render_error(conn, :bad_request, "Missing parameters")
end
def create(conn, _) do
render_error(conn, :forbidden, "Invalid credentials")
end
defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok
defp validate_email_param(_) do
case Pleroma.Config.get([:instance, :account_activation_required]) do
true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")}
_ -> :ok
end
end
@doc "GET /api/v1/accounts/verify_credentials"
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
render(conn, "show.json",
user: user,
for: user,
with_pleroma_settings: true,
with_chat_token: chat_token
)
end
@doc "PATCH /api/v1/accounts/update_credentials"
def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do
params =
params
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|> Enum.into(%{})
# We use an empty string as a special value to reset
# avatars, banners, backgrounds
user_image_value = fn
"" -> {:ok, nil}
value -> {:ok, value}
end
user_params =
[
:no_rich_text,
:locked,
:hide_followers_count,
:hide_follows_count,
:hide_followers,
:hide_follows,
:hide_favorites,
:show_role,
:skip_thread_containment,
:allow_following_move,
:discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
end)
|> Maps.put_if_present(:name, params[:display_name])
|> Maps.put_if_present(:bio, params[:note])
|> Maps.put_if_present(:raw_bio, params[:note])
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|> Maps.put_if_present(
:raw_fields,
params[:fields_attributes],
&{:ok, normalize_fields_attributes(&1)}
)
|> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store])
|> Maps.put_if_present(:default_scope, params[:default_scope])
|> Maps.put_if_present(:default_scope, params["source"]["privacy"])
|> Maps.put_if_present(:actor_type, params[:bot], fn bot ->
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
# What happens here:
#
# We want to update the user through the pipeline, but the ActivityPub
# update information is not quite enough for this, because this also
# contains local settings that don't federate and don't even appear
# in the Update activity.
#
# So we first build the normal local changeset, then apply it to the
# user data, but don't persist it. With this, we generate the object
# data for our update activity. We feed this and the changeset as meta
# inforation into the pipeline, where they will be properly updated and
# federated.
with changeset <- User.update_changeset(user, user_params),
{:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
updated_object <-
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
|> Map.delete("@context"),
{:ok, update_data, []} <- Builder.update(user, updated_object),
{:ok, _update, _} <-
Pipeline.common_pipeline(update_data,
local: true,
user_update_changeset: changeset
) do
render(conn, "show.json",
user: unpersisted_user,
for: unpersisted_user,
with_pleroma_settings: true
)
else
_e -> render_error(conn, :forbidden, "Invalid request")
end
end
defp normalize_fields_attributes(fields) do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
Enum.map(fields, fn
%{} = field -> %{"name" => field.name, "value" => field.value}
field -> field
end)
end
end
@doc "GET /api/v1/accounts/relationships"
def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do
targets = User.get_all_by_ids(List.wrap(id))
render(conn, "relationships.json", user: user, targets: targets)
end
# Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
:visible <- User.visible_for(user, for_user) do
render(conn, "show.json", user: user, for: for_user)
else
error -> user_visibility_error(conn, error)
end
end
@doc "GET /api/v1/accounts/:id/statuses"
def statuses(%{assigns: %{user: reading_user}} = conn, params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
:visible <- User.visible_for(user, reading_user) do
params =
params
|> Map.delete(:tagged)
|> Map.put(:tag, params[:tagged])
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json",
activities: activities,
for: reading_user,
as: :activity
)
else
error -> user_visibility_error(conn, error)
end
end
defp user_visibility_error(conn, error) do
case error do
:restrict_unauthenticated ->
render_error(conn, :unauthorized, "This API requires an authenticated user")
_ ->
render_error(conn, :not_found, "Can't find user")
end
end
@doc "GET /api/v1/accounts/:id/followers"
def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
params =
params
|> Enum.map(fn {key, value} -> {to_string(key), value} end)
|> Enum.into(%{})
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
user.hide_followers -> []
true -> MastodonAPI.get_followers(user, params)
end
conn
|> add_link_headers(followers)
# https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
|> render("index.json",
for: for_user,
users: followers,
as: :user,
embed_relationships: embed_relationships?(params)
)
end
@doc "GET /api/v1/accounts/:id/following"
def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
params =
params
|> Enum.map(fn {key, value} -> {to_string(key), value} end)
|> Enum.into(%{})
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
user.hide_follows -> []
true -> MastodonAPI.get_friends(user, params)
end
conn
|> add_link_headers(followers)
# https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
|> render("index.json",
for: for_user,
users: followers,
as: :user,
embed_relationships: embed_relationships?(params)
)
end
@doc "GET /api/v1/accounts/:id/lists"
def lists(%{assigns: %{user: user, account: account}} = conn, _params) do
lists = Pleroma.List.get_lists_account_belongs(user, account)
conn
|> put_view(ListView)
|> render("index.json", lists: lists)
end
@doc "POST /api/v1/accounts/:id/follow"
def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
{:error, "Can not follow yourself"}
end
- def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+ def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do
with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
render(conn, "relationship.json", user: follower, target: followed)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/accounts/:id/unfollow"
def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
{:error, "Can not unfollow yourself"}
end
def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
with {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
render(conn, "relationship.json", user: follower, target: followed)
end
end
@doc "POST /api/v1/accounts/:id/mute"
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
render(conn, "relationship.json", user: muter, target: muted)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/accounts/:id/unmute"
def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
with {:ok, _user_relationships} <- User.unmute(muter, muted) do
render(conn, "relationship.json", user: muter, target: muted)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/accounts/:id/block"
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/accounts/:id/unblock"
def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, _activity} <- CommonAPI.unblock(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do
%User{} = user ->
conn
|> assign(:account, user)
|> follow(%{})
nil ->
{:error, :not_found}
end
end
@doc "GET /api/v1/mutes"
def mutes(%{assigns: %{user: user}} = conn, _) do
users = User.muted_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/blocks"
def blocks(%{assigns: %{user: user}} = conn, _) do
users = User.blocked_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/endorsements"
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
@doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 260ad2306..f102c0cd2 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -1,1347 +1,1380 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
import Pleroma.Factory
describe "account fetching" do
setup do: clear_config([:instance, :limit_to_local_content])
test "works by id" do
%User{id: user_id} = insert(:user)
assert %{"id" => ^user_id} =
build_conn()
|> get("/api/v1/accounts/#{user_id}")
|> json_response_and_validate_schema(200)
assert %{"error" => "Can't find user"} =
build_conn()
|> get("/api/v1/accounts/-1")
|> json_response_and_validate_schema(404)
end
test "works by nickname" do
user = insert(:user)
assert %{"id" => user_id} =
build_conn()
|> get("/api/v1/accounts/#{user.nickname}")
|> json_response_and_validate_schema(200)
end
test "works by nickname for remote users" do
Config.put([:instance, :limit_to_local_content], false)
user = insert(:user, nickname: "user@example.com", local: false)
assert %{"id" => user_id} =
build_conn()
|> get("/api/v1/accounts/#{user.nickname}")
|> json_response_and_validate_schema(200)
end
test "respects limit_to_local_content == :all for remote user nicknames" do
Config.put([:instance, :limit_to_local_content], :all)
user = insert(:user, nickname: "user@example.com", local: false)
assert build_conn()
|> get("/api/v1/accounts/#{user.nickname}")
|> json_response_and_validate_schema(404)
end
test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
Config.put([:instance, :limit_to_local_content], :unauthenticated)
user = insert(:user, nickname: "user@example.com", local: false)
reading_user = insert(:user)
conn =
build_conn()
|> get("/api/v1/accounts/#{user.nickname}")
assert json_response_and_validate_schema(conn, 404)
conn =
build_conn()
|> assign(:user, reading_user)
|> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"]))
|> get("/api/v1/accounts/#{user.nickname}")
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
assert id == user.id
end
test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
# Need to set an old-style integer ID to reproduce the problem
# (these are no longer assigned to new accounts but were preserved
# for existing accounts during the migration to flakeIDs)
user_one = insert(:user, %{id: 1212})
user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
acc_one =
conn
|> get("/api/v1/accounts/#{user_one.id}")
|> json_response_and_validate_schema(:ok)
acc_two =
conn
|> get("/api/v1/accounts/#{user_two.nickname}")
|> json_response_and_validate_schema(:ok)
acc_three =
conn
|> get("/api/v1/accounts/#{user_two.id}")
|> json_response_and_validate_schema(:ok)
refute acc_one == acc_two
assert acc_two == acc_three
end
test "returns 404 when user is invisible", %{conn: conn} do
user = insert(:user, %{invisible: true})
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.nickname}")
|> json_response_and_validate_schema(404)
end
test "returns 404 for internal.fetch actor", %{conn: conn} do
%User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/internal.fetch")
|> json_response_and_validate_schema(404)
end
test "returns 404 for deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(:not_found)
end
end
defp local_and_remote_users do
local = insert(:user)
remote = insert(:user, local: false)
{:ok, local: local, remote: remote}
end
describe "user fetching with restrict unauthenticated profiles for local and remote" do
setup do: local_and_remote_users()
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}")
|> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}")
|> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
end
end
describe "user fetching with restrict unauthenticated profiles for local" do
setup do: local_and_remote_users()
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "This API requires an authenticated user"
}
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
end
end
describe "user fetching with restrict unauthenticated profiles for remote" do
setup do: local_and_remote_users()
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "This API requires an authenticated user"
}
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
end
end
describe "user timelines" do
setup do: oauth_access(["read:statuses"])
test "works with announces that are just addressed to public", %{conn: conn} do
user = insert(:user, ap_id: "https://honktest/u/test", local: false)
other_user = insert(:user)
{:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"})
{:ok, announce, _} =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"actor" => "https://honktest/u/test",
"id" => "https://honktest/u/test/bonk/1793M7B9MQ48847vdx",
"object" => post.data["object"],
"published" => "2019-06-25T19:33:58Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"type" => "Announce"
}
|> ActivityPub.persist(local: false)
assert resp =
conn
|> get("/api/v1/accounts/#{user.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id}] = resp
assert id == announce.id
end
test "deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} ==
conn
|> get("/api/v1/accounts/#{user.id}/statuses")
|> json_response_and_validate_schema(:not_found)
end
test "returns 404 when user is invisible", %{conn: conn} do
user = insert(:user, %{invisible: true})
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(404)
end
test "respects blocks", %{user: user_one, conn: conn} do
user_two = insert(:user)
user_three = insert(:user)
User.block(user_one, user_two)
{:ok, activity} = CommonAPI.post(user_two, %{status: "User one sux0rz"})
{:ok, repeat} = CommonAPI.repeat(activity.id, user_three)
assert resp =
conn
|> get("/api/v1/accounts/#{user_two.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id}] = resp
assert id == activity.id
# Even a blocked user will deliver the full user timeline, there would be
# no point in looking at a blocked users timeline otherwise
assert resp =
conn
|> get("/api/v1/accounts/#{user_two.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id}] = resp
assert id == activity.id
# Third user's timeline includes the repeat when viewed by unauthenticated user
resp =
build_conn()
|> get("/api/v1/accounts/#{user_three.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id}] = resp
assert id == repeat.id
# When viewing a third user's timeline, the blocked users' statuses will NOT be shown
resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses")
assert [] == json_response_and_validate_schema(resp, 200)
end
test "gets users statuses", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
user_three = insert(:user)
{:ok, _user_three} = User.follow(user_three, user_one)
{:ok, activity} = CommonAPI.post(user_one, %{status: "HI!!!"})
{:ok, direct_activity} =
CommonAPI.post(user_one, %{
status: "Hi, @#{user_two.nickname}.",
visibility: "direct"
})
{:ok, private_activity} =
CommonAPI.post(user_one, %{status: "private", visibility: "private"})
# TODO!!!
resp =
conn
|> get("/api/v1/accounts/#{user_one.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id}] = resp
assert id == to_string(activity.id)
resp =
conn
|> assign(:user, user_two)
|> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
|> get("/api/v1/accounts/#{user_one.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id_one}, %{"id" => id_two}] = resp
assert id_one == to_string(direct_activity.id)
assert id_two == to_string(activity.id)
resp =
conn
|> assign(:user, user_three)
|> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"]))
|> get("/api/v1/accounts/#{user_one.id}/statuses")
|> json_response_and_validate_schema(200)
assert [%{"id" => id_one}, %{"id" => id_two}] = resp
assert id_one == to_string(private_activity.id)
assert id_two == to_string(activity.id)
end
test "unimplemented pinned statuses feature", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true")
assert json_response_and_validate_schema(conn, 200) == []
end
test "gets an users media, excludes reblogs", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
other_user = insert(:user)
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
{:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
{:ok, %{id: image_post_id}} = CommonAPI.post(user, %{status: "cofe", media_ids: [media_id]})
{:ok, %{id: media_id}} = ActivityPub.upload(file, actor: other_user.ap_id)
{:ok, %{id: other_image_post_id}} =
CommonAPI.post(other_user, %{status: "cofe2", media_ids: [media_id]})
{:ok, _announce} = CommonAPI.repeat(other_image_post_id, user)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1")
assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
end
test "gets a user's statuses without reblogs", %{user: user, conn: conn} do
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"})
{:ok, _} = CommonAPI.repeat(post_id, user)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true")
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1")
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
end
test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "#hashtag"})
{:ok, _post} = CommonAPI.post(user, %{status: "hashtag"})
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag")
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
end
test "the user views their own timelines and excludes direct messages", %{
user: user,
conn: conn
} do
{:ok, %{id: public_activity_id}} =
CommonAPI.post(user, %{status: ".", visibility: "public"})
{:ok, _direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
assert [%{"id" => ^public_activity_id}] = json_response_and_validate_schema(conn, 200)
end
end
defp local_and_remote_activities(%{local: local, remote: remote}) do
insert(:note_activity, user: local)
insert(:note_activity, user: remote, local: false)
:ok
end
describe "statuses with restrict unauthenticated profiles for local and remote" do
setup do: local_and_remote_users()
setup :local_and_remote_activities
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
end
end
describe "statuses with restrict unauthenticated profiles for local" do
setup do: local_and_remote_users()
setup :local_and_remote_activities
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:unauthorized)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
end
end
describe "statuses with restrict unauthenticated profiles for remote" do
setup do: local_and_remote_users()
setup :local_and_remote_activities
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
end
end
describe "followers" do
setup do: oauth_access(["read:accounts"])
test "getting followers", %{user: user, conn: conn} do
other_user = insert(:user)
{:ok, %{id: user_id}} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
assert [%{"id" => ^user_id}] = json_response_and_validate_schema(conn, 200)
end
test "getting followers, hide_followers", %{user: user, conn: conn} do
other_user = insert(:user, hide_followers: true)
{:ok, _user} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
assert [] == json_response_and_validate_schema(conn, 200)
end
test "getting followers, hide_followers, same user requesting" do
user = insert(:user)
other_user = insert(:user, hide_followers: true)
{:ok, _user} = User.follow(user, other_user)
conn =
build_conn()
|> assign(:user, other_user)
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
|> get("/api/v1/accounts/#{other_user.id}/followers")
refute [] == json_response_and_validate_schema(conn, 200)
end
test "getting followers, pagination", %{user: user, conn: conn} do
{:ok, %User{id: follower1_id}} = :user |> insert() |> User.follow(user)
{:ok, %User{id: follower2_id}} = :user |> insert() |> User.follow(user)
{:ok, %User{id: follower3_id}} = :user |> insert() |> User.follow(user)
assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] =
conn
|> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1_id}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^follower2_id}, %{"id" => ^follower1_id}] =
conn
|> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3_id}")
|> json_response_and_validate_schema(200)
res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3_id}")
assert [%{"id" => ^follower2_id}] = json_response_and_validate_schema(res_conn, 200)
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{follower2_id}/
assert link_header =~ ~r/max_id=#{follower2_id}/
end
end
describe "following" do
setup do: oauth_access(["read:accounts"])
test "getting following", %{user: user, conn: conn} do
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{user.id}/following")
assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)
assert id == to_string(other_user.id)
end
test "getting following, hide_follows, other user requesting" do
user = insert(:user, hide_follows: true)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
build_conn()
|> assign(:user, other_user)
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
|> get("/api/v1/accounts/#{user.id}/following")
assert [] == json_response_and_validate_schema(conn, 200)
end
test "getting following, hide_follows, same user requesting" do
user = insert(:user, hide_follows: true)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
build_conn()
|> assign(:user, user)
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"]))
|> get("/api/v1/accounts/#{user.id}/following")
refute [] == json_response_and_validate_schema(conn, 200)
end
test "getting following, pagination", %{user: user, conn: conn} do
following1 = insert(:user)
following2 = insert(:user)
following3 = insert(:user)
{:ok, _} = User.follow(user, following1)
{:ok, _} = User.follow(user, following2)
{:ok, _} = User.follow(user, following3)
res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
assert [%{"id" => id3}, %{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
assert id3 == following3.id
assert id2 == following2.id
res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response_and_validate_schema(res_conn, 200)
assert id2 == following2.id
assert id1 == following1.id
res_conn =
get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
assert [%{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
assert id2 == following2.id
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{following2.id}/
assert link_header =~ ~r/max_id=#{following2.id}/
end
end
describe "follow/unfollow" do
setup do: oauth_access(["follow"])
test "following / unfollowing a user", %{conn: conn} do
%{id: other_user_id, nickname: other_user_nickname} = insert(:user)
assert %{"id" => _id, "following" => true} =
conn
|> post("/api/v1/accounts/#{other_user_id}/follow")
|> json_response_and_validate_schema(200)
assert %{"id" => _id, "following" => false} =
conn
|> post("/api/v1/accounts/#{other_user_id}/unfollow")
|> json_response_and_validate_schema(200)
assert %{"id" => ^other_user_id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/follows", %{"uri" => other_user_nickname})
|> json_response_and_validate_schema(200)
end
test "cancelling follow request", %{conn: conn} do
%{id: other_user_id} = insert(:user, %{locked: true})
assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
conn
|> post("/api/v1/accounts/#{other_user_id}/follow")
|> json_response_and_validate_schema(:ok)
assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
conn
|> post("/api/v1/accounts/#{other_user_id}/unfollow")
|> json_response_and_validate_schema(:ok)
end
test "following without reblogs" do
%{conn: conn} = oauth_access(["follow", "read:statuses"])
followed = insert(:user)
other_user = insert(:user)
- ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
{:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, followed)
assert [] ==
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
assert %{"showing_reblogs" => true} =
conn
- |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: true})
|> json_response_and_validate_schema(200)
assert [%{"id" => ^reblog_id}] =
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
end
+ test "following with reblogs" do
+ %{conn: conn} = oauth_access(["follow", "read:statuses"])
+ followed = insert(:user)
+ other_user = insert(:user)
+
+ ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow")
+
+ assert %{"showing_reblogs" => true} = json_response_and_validate_schema(ret_conn, 200)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
+ {:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, followed)
+
+ assert [%{"id" => ^reblog_id}] =
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+
+ assert %{"showing_reblogs" => false} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
+ |> json_response_and_validate_schema(200)
+
+ assert [] ==
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+ end
+
test "following / unfollowing errors", %{user: user, conn: conn} do
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
assert %{"error" => "Can not follow yourself"} =
json_response_and_validate_schema(conn_res, 400)
# self unfollow
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
assert %{"error" => "Can not unfollow yourself"} =
json_response_and_validate_schema(conn_res, 400)
# self follow via uri
user = User.get_cached_by_id(user.id)
assert %{"error" => "Can not follow yourself"} =
conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/follows", %{"uri" => user.nickname})
|> json_response_and_validate_schema(400)
# follow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
# follow non existing user via uri
conn_res =
conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/follows", %{"uri" => "doesntexist"})
assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
# unfollow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
end
end
describe "mute/unmute" do
setup do: oauth_access(["write:mutes"])
test "with notifications", %{conn: conn} do
other_user = insert(:user)
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
conn
|> post("/api/v1/accounts/#{other_user.id}/mute")
|> json_response_and_validate_schema(200)
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
json_response_and_validate_schema(conn, 200)
end
test "without notifications", %{conn: conn} do
other_user = insert(:user)
ret_conn =
conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
assert %{"id" => _id, "muting" => true, "muting_notifications" => false} =
json_response_and_validate_schema(ret_conn, 200)
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
json_response_and_validate_schema(conn, 200)
end
end
describe "pinned statuses" do
setup do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
%{conn: conn} = oauth_access(["read:statuses"], user: user)
[conn: conn, user: user, activity: activity]
end
test "returns pinned statuses", %{conn: conn, user: user, activity: %{id: activity_id}} do
{:ok, _} = CommonAPI.pin(activity_id, user)
assert [%{"id" => ^activity_id, "pinned" => true}] =
conn
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response_and_validate_schema(200)
end
end
test "blocking / unblocking a user" do
%{conn: conn} = oauth_access(["follow"])
other_user = insert(:user)
ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => _id, "blocking" => true} = json_response_and_validate_schema(ret_conn, 200)
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => _id, "blocking" => false} = json_response_and_validate_schema(conn, 200)
end
describe "create account by app" do
setup do
valid_params = %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true
}
[valid_params: valid_params]
end
setup do: clear_config([:instance, :account_activation_required])
test "Account registration via Application", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/apps", %{
client_name: "client_name",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: "read, write, follow"
})
assert %{
"client_id" => client_id,
"client_secret" => client_secret,
"id" => _,
"name" => "client_name",
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
"vapid_key" => _,
"website" => nil
} = json_response_and_validate_schema(conn, 200)
conn =
post(conn, "/oauth/token", %{
grant_type: "client_credentials",
client_id: client_id,
client_secret: client_secret
})
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
json_response(conn, 200)
assert token
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
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
})
%{
"access_token" => token,
"created_at" => _created_at,
"scope" => _scope,
"token_type" => "Bearer"
} = json_response_and_validate_schema(conn, 200)
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
token_from_db = Repo.preload(token_from_db, :user)
assert token_from_db.user
assert token_from_db.user.confirmation_pending
end
test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
_user = insert(:user, email: "lain@example.org")
app_token = insert(:oauth_token, user: nil)
res =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> put_req_header("content-type", "application/json")
|> post("/api/v1/accounts", valid_params)
assert json_response_and_validate_schema(res, 400) == %{
"error" => "{\"email\":[\"has already been taken\"]}"
}
end
test "returns bad_request if missing required params", %{
conn: conn,
valid_params: valid_params
} do
app_token = insert(:oauth_token, user: nil)
conn =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> put_req_header("content-type", "application/json")
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response_and_validate_schema(res, 200)
[{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
|> Stream.zip(Map.delete(valid_params, :email))
|> Enum.each(fn {ip, {attr, _}} ->
res =
conn
|> Map.put(:remote_ip, ip)
|> post("/api/v1/accounts", Map.delete(valid_params, attr))
|> json_response_and_validate_schema(400)
assert res == %{
"error" => "Missing field: #{attr}.",
"errors" => [
%{
"message" => "Missing field: #{attr}",
"source" => %{"pointer" => "/#{attr}"},
"title" => "Invalid value"
}
]
}
end)
end
setup do: clear_config([:instance, :account_activation_required])
test "returns bad_request if missing email params when :account_activation_required is enabled",
%{conn: conn, valid_params: valid_params} do
Pleroma.Config.put([:instance, :account_activation_required], true)
app_token = insert(:oauth_token, user: nil)
conn =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> put_req_header("content-type", "application/json")
res =
conn
|> Map.put(:remote_ip, {127, 0, 0, 5})
|> post("/api/v1/accounts", Map.delete(valid_params, :email))
assert json_response_and_validate_schema(res, 400) ==
%{"error" => "Missing parameter: email"}
res =
conn
|> Map.put(:remote_ip, {127, 0, 0, 6})
|> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
assert json_response_and_validate_schema(res, 400) == %{
"error" => "{\"email\":[\"can't be blank\"]}"
}
end
test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
app_token = insert(:oauth_token, user: nil)
conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
res =
conn
|> put_req_header("content-type", "application/json")
|> Map.put(:remote_ip, {127, 0, 0, 7})
|> post("/api/v1/accounts", Map.delete(valid_params, :email))
assert json_response_and_validate_schema(res, 200)
end
test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
app_token = insert(:oauth_token, user: nil)
conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
res =
conn
|> put_req_header("content-type", "application/json")
|> Map.put(:remote_ip, {127, 0, 0, 8})
|> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
assert json_response_and_validate_schema(res, 200)
end
test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
res =
conn
|> put_req_header("authorization", "Bearer " <> "invalid-token")
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/accounts", valid_params)
assert json_response_and_validate_schema(res, 403) == %{"error" => "Invalid credentials"}
end
test "registration from trusted app" do
clear_config([Pleroma.Captcha, :enabled], true)
app = insert(:oauth_app, trusted: true, scopes: ["read", "write", "follow", "push"])
conn =
build_conn()
|> post("/oauth/token", %{
"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)
response =
build_conn()
|> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/accounts", %{
nickname: "nickanme",
agreement: true,
email: "email@example.com",
fullname: "Lain",
username: "Lain",
password: "some_password",
confirm: "some_password"
})
|> json_response_and_validate_schema(200)
assert %{
"access_token" => access_token,
"created_at" => _,
"scope" => ["read", "write", "follow", "push"],
"token_type" => "Bearer"
} = response
response =
build_conn()
|> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
|> get("/api/v1/accounts/verify_credentials")
|> json_response_and_validate_schema(200)
assert %{
"acct" => "Lain",
"bot" => false,
"display_name" => "Lain",
"follow_requests_count" => 0,
"followers_count" => 0,
"following_count" => 0,
"locked" => false,
"note" => "",
"source" => %{
"fields" => [],
"note" => "",
"pleroma" => %{
"actor_type" => "Person",
"discoverable" => false,
"no_rich_text" => false,
"show_role" => true
},
"privacy" => "public",
"sensitive" => false
},
"statuses_count" => 0,
"username" => "Lain"
} = response
end
end
describe "create account by app / rate limit" do
setup do: clear_config([:rate_limit, :app_account_creation], {10_000, 2})
test "respects rate limit setting", %{conn: conn} do
app_token = insert(:oauth_token, user: nil)
conn =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> Map.put(:remote_ip, {15, 15, 15, 15})
|> put_req_header("content-type", "multipart/form-data")
for i <- 1..2 do
conn =
conn
|> post("/api/v1/accounts", %{
username: "#{i}lain",
email: "#{i}lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
%{
"access_token" => token,
"created_at" => _created_at,
"scope" => _scope,
"token_type" => "Bearer"
} = json_response_and_validate_schema(conn, 200)
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
token_from_db = Repo.preload(token_from_db, :user)
assert token_from_db.user
assert token_from_db.user.confirmation_pending
end
conn =
post(conn, "/api/v1/accounts", %{
username: "6lain",
email: "6lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
assert json_response_and_validate_schema(conn, :too_many_requests) == %{
"error" => "Throttled"
}
end
end
describe "create account with enabled captcha" do
setup %{conn: conn} do
app_token = insert(:oauth_token, user: nil)
conn =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> put_req_header("content-type", "multipart/form-data")
[conn: conn]
end
setup do: clear_config([Pleroma.Captcha, :enabled], true)
test "creates an account and returns 200 if captcha is valid", %{conn: conn} do
%{token: token, answer_data: answer_data} = Pleroma.Captcha.new()
params = %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true,
captcha_solution: Pleroma.Captcha.Mock.solution(),
captcha_token: token,
captcha_answer_data: answer_data
}
assert %{
"access_token" => access_token,
"created_at" => _,
"scope" => ["read"],
"token_type" => "Bearer"
} =
conn
|> post("/api/v1/accounts", params)
|> json_response_and_validate_schema(:ok)
assert Token |> Repo.get_by(token: access_token) |> Repo.preload(:user) |> Map.get(:user)
Cachex.del(:used_captcha_cache, token)
end
test "returns 400 if any captcha field is not provided", %{conn: conn} do
captcha_fields = [:captcha_solution, :captcha_token, :captcha_answer_data]
valid_params = %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true,
captcha_solution: "xx",
captcha_token: "xx",
captcha_answer_data: "xx"
}
for field <- captcha_fields do
expected = %{
"error" => "{\"captcha\":[\"Invalid CAPTCHA (Missing parameter: #{field})\"]}"
}
assert expected ==
conn
|> post("/api/v1/accounts", Map.delete(valid_params, field))
|> json_response_and_validate_schema(:bad_request)
end
end
test "returns an error if captcha is invalid", %{conn: conn} do
params = %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true,
captcha_solution: "cofe",
captcha_token: "cofe",
captcha_answer_data: "cofe"
}
assert %{"error" => "{\"captcha\":[\"Invalid answer data\"]}"} ==
conn
|> post("/api/v1/accounts", params)
|> json_response_and_validate_schema(:bad_request)
end
end
describe "GET /api/v1/accounts/:id/lists - account_lists" do
test "returns lists to which the account belongs" do
%{user: user, conn: conn} = oauth_access(["read:lists"])
other_user = insert(:user)
assert {:ok, %Pleroma.List{id: list_id} = list} = Pleroma.List.create("Test List", user)
{:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
assert [%{"id" => list_id, "title" => "Test List"}] =
conn
|> get("/api/v1/accounts/#{other_user.id}/lists")
|> json_response_and_validate_schema(200)
end
end
describe "verify_credentials" do
test "verify_credentials" do
%{user: user, conn: conn} = oauth_access(["read:accounts"])
[notification | _] = insert_list(7, :notification, user: user)
Pleroma.Notification.set_read_up_to(user, notification.id)
conn = get(conn, "/api/v1/accounts/verify_credentials")
response = json_response_and_validate_schema(conn, 200)
assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
assert response["pleroma"]["chat_token"]
assert response["pleroma"]["unread_notifications_count"] == 6
assert id == to_string(user.id)
end
test "verify_credentials default scope unlisted" do
user = insert(:user, default_scope: "unlisted")
%{conn: conn} = oauth_access(["read:accounts"], user: user)
conn = get(conn, "/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} =
json_response_and_validate_schema(conn, 200)
assert id == to_string(user.id)
end
test "locked accounts" do
user = insert(:user, default_scope: "private")
%{conn: conn} = oauth_access(["read:accounts"], user: user)
conn = get(conn, "/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "private"}} =
json_response_and_validate_schema(conn, 200)
assert id == to_string(user.id)
end
end
describe "user relationships" do
setup do: oauth_access(["read:follows"])
test "returns the relationships for the current user", %{user: user, conn: conn} do
%{id: other_user_id} = other_user = insert(:user)
{:ok, _user} = User.follow(user, other_user)
assert [%{"id" => ^other_user_id}] =
conn
|> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^other_user_id}] =
conn
|> get("/api/v1/accounts/relationships?id[]=#{other_user.id}")
|> json_response_and_validate_schema(200)
end
test "returns an empty list on a bad request", %{conn: conn} do
conn = get(conn, "/api/v1/accounts/relationships", %{})
assert [] = json_response_and_validate_schema(conn, 200)
end
end
test "getting a list of mutes" do
%{user: user, conn: conn} = oauth_access(["read:mutes"])
other_user = insert(:user)
{:ok, _user_relationships} = User.mute(user, other_user)
conn = get(conn, "/api/v1/mutes")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
end
test "getting a list of blocks" do
%{user: user, conn: conn} = oauth_access(["read:blocks"])
other_user = insert(:user)
{:ok, _user_relationship} = User.block(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/blocks")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 29, 10:39 AM (18 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1797990
Default Alt Text
(89 KB)

Event Timeline