Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
303 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dc99b81ee..f9745122a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,81 +1,82 @@
image: elixir:1.8.1
variables:
POSTGRES_DB: pleroma_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DB_HOST: postgres
MIX_ENV: test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- deps
- _build
stages:
- build
- test
- deploy
before_script:
- mix local.hex --force
- mix local.rebar --force
build:
stage: build
script:
- mix deps.get
- mix compile --force
docs-build:
stage: build
only:
- master@pleroma/pleroma
- develop@pleroma/pleroma
variables:
MIX_ENV: dev
script:
- mix deps.get
- mix compile
- mix docs
artifacts:
paths:
- priv/static/doc
unit-testing:
stage: test
services:
- name: postgres:9.6.2
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- mix test --trace --preload-modules
+ - mix coveralls
lint:
stage: test
script:
- mix format --check-formatted
analysis:
stage: test
script:
- mix deps.get
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
docs-deploy:
stage: deploy
image: alpine:3.9
only:
- master@pleroma/pleroma
- develop@pleroma/pleroma
before_script:
- apk update && apk add openssh-client rsync
script:
- mkdir -p ~/.ssh
- echo "${SSH_HOST_KEY}" > ~/.ssh/known_hosts
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- rsync -hrvz --delete -e "ssh -p ${SSH_PORT}" priv/static/doc/ "${SSH_USER_HOST_LOCATION}/${CI_COMMIT_REF_NAME}"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7536d67f..87144b7f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,122 +1,125 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased]
### Added
- LDAP authentication
- External OAuth provider authentication
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
- [Prometheus](https://prometheus.io/) metrics
- Support for Mastodon's remote interaction
- Mix Tasks: `mix pleroma.database remove_embedded_objects`
- Federation: Support for reports
- Configuration: `safe_dm_mentions` option
- Configuration: `link_name` option
- Configuration: `fetch_initial_posts` option
- Configuration: `notify_email` option
- Configuration: Media proxy `whitelist` option
- Pleroma API: User subscriptions
- Pleroma API: Healthcheck endpoint
- Admin API: Endpoints for listing/revoking invite tokens
- Admin API: Endpoints for making users follow/unfollow each other
- Admin API: added filters (role, tags, email, name) for users endpoint
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
- Mastodon API: `POST /api/v1/accounts` (account creation API)
- ActivityPub C2S: OAuth endpoints
- Metadata: RelMe provider
- OAuth: added support for refresh tokens
- Emoji packs and emoji pack manager
- Addressable lists
### Changed
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
- Enforcement of OAuth scopes
- Add multiple use/time expiring invite token
- Restyled OAuth pages to fit with Pleroma's default theme
- Link/mention/hashtag detection is now handled by [auto_linker](https://git.pleroma.social/pleroma/auto_linker)
- NodeInfo: Return `safe_dm_mentions` feature flag
- Federation: Expand the audience of delete activities to all recipients of the deleted object
- Federation: Removed `inReplyToStatusId` from objects
- Configuration: Dedupe enabled by default
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
- Admin API: Move the user related API to `api/pleroma/admin/users`
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
- Mastodon API: Add `pleroma.conversation_id`, `pleroma.in_reply_to_account_acct` fields to the Status entity
- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending`, `pleroma.hide_followers`, `pleroma.hide_follows`, `pleroma.hide_favorites` fields to the User entity
- Mastodon API: Add `pleroma.show_role`, `pleroma.no_rich_text` fields to the Source subentity
- Mastodon API: Add support for updating `no_rich_text`, `hide_followers`, `hide_follows`, `hide_favorites`, `show_role` in `PATCH /api/v1/update_credentials`
- Mastodon API: Add `pleroma.is_seen` to the Notification entity
- Mastodon API: Add `pleroma.local` to the Status entity
- Mastodon API: Add `preview` parameter to `POST /api/v1/statuses`
- Mastodon API: Add `with_muted` parameter to timeline endpoints
- Mastodon API: Actual reblog hiding instead of a dummy
- Mastodon API: Remove attachment limit in the Status entity
- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
- Deps: Updated Cowboy to 2.6
- Deps: Updated Ecto to 3.0.7
- Don't ship finmoji by default, they can be installed as an emoji pack
- Hide deactivated users and their statuses
### Fixed
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
- Followers counter not being updated when a follower is blocked
- Deactivated users being able to request an access token
- Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
- Proper Twitter Card generation instead of a dummy
- Deletions failing for users with a large number of posts
- NodeInfo: Include admins in `staffAccounts`
- ActivityPub: Crashing when requesting empty local user's outbox
- Federation: Handling of objects without `summary` property
- Federation: Add a language tag to activities as required by ActivityStreams 2.0
- Federation: Do not federate avatar/banner if set to default allowing other servers/clients to use their defaults
- Federation: Cope with missing or explicitly nulled address lists
- Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection
- Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics
- Federation: Follow requests from remote users who have been blocked will be automatically rejected if appropriate
- MediaProxy: Parse name from content disposition headers even for non-whitelisted types
- MediaProxy: S3 link encoding
- Rich Media: Reject any data which cannot be explicitly encoded into JSON
- Pleroma API: Importing follows from Mastodon 2.8+
- Twitter API: Exposing default scope, `no_rich_text` of the user to anyone
- Twitter API: Returning the `role` object in user entity despite `show_role = false`
- Mastodon API: `/api/v1/favourites` serving only public activities
- Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies
- Mastodon API: Streaming API broadcasting wrong activity id
- Mastodon API: 500 errors when requesting a card for a private conversation
- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
- Mastodon API: Exposing default scope of the user to anyone
- Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
+## Removed
+- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
+
## [0.9.9999] - 2019-04-05
### Security
- Mastodon API: Fix content warnings skipping HTML sanitization
## [0.9.999] - 2019-03-13
Frontend changes only.
### Added
- Added floating action button for posting status on mobile
### Changed
- Changed user-settings icon to a pencil
### Fixed
- Keyboard shortcuts activating when typing a message
- Gaps when scrolling down on a timeline after showing new
## [0.9.99] - 2019-03-08
### Changed
- Update the frontend to the 0.9.99 tag
### Fixed
- Sign the date header in federation to fix Mastodon federation.
## [0.9.9] - 2019-02-22
This is our first stable release.
diff --git a/config/config.exs b/config/config.exs
index 32c7fecb8..9cbac35eb 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,489 +1,472 @@
# .i;;;;i.
# iYcviii;vXY:
# .YXi .i1c.
# .YC. . in7.
# .vc. ...... ;1c.
# i7, .. .;1;
# i7, .. ... .Y1i
# ,7v .6MMM@; .YX,
# .7;. ..IMMMMMM1 :t7.
# .;Y. ;$MMMMMM9. :tc.
# vY. .. .nMMM@MMU. ;1v.
# i7i ... .#MM@M@C. .....:71i
# it: .... $MMM@9;.,i;;;i,;tti
# :t7. ..... 0MMMWv.,iii:::,,;St.
# .nC. ..... IMMMQ..,::::::,.,czX.
# .ct: ....... .ZMMMI..,:::::::,,:76Y.
# c2: ......,i..Y$M@t..:::::::,,..inZY
# vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
# i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
# iIS. ......:ii::..;@MI....,............;Ez.
# .I9. ......:i::::...8M1..................C0z.
# .z9; ......:i::::,.. .i:...................zWX.
# vbv ......,i::::,,. ................. :AQY
# c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
# :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
# :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
# .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
# 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
# 7C...::::::::::::,,,,.. .................... vSi.
# ;1;...,,::::::,......... .................. Yz:
# v97,......... .voC.
# izAotX7777777777777777777777777777777777777777Y7n92:
# .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
#
# !!! ATTENTION !!!
# DO NOT EDIT THIS FILE! THIS FILE CONTAINS THE DEFAULT VALUES FOR THE CON-
# FIGURATION! EDIT YOUR SECRET FILE (either prod.secret.exs, dev.secret.exs).
#
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config
# General application configuration
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
types: Pleroma.PostgresTypes,
telemetry_event: [Pleroma.Repo.Instrumenter]
config :pleroma, Pleroma.Captcha,
enabled: false,
seconds_valid: 60,
method: Pleroma.Captcha.Kocaptcha
config :pleroma, :hackney_pools,
federation: [
max_connections: 50,
timeout: 150_000
],
media: [
max_connections: 50,
timeout: 150_000
],
upload: [
max_connections: 25,
timeout: 300_000
]
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: true,
proxy_remote: false,
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
http: [
follow_redirect: true,
pool: :upload
]
]
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
public_endpoint: "https://s3.amazonaws.com"
config :pleroma, Pleroma.Uploaders.MDII,
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
files: "https://mdii.sakura.ne.jp"
config :pleroma, :emoji,
shortcode_globs: ["/emoji/custom/**/*.png"],
groups: [
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
],
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
config :pleroma, :uri_schemes,
valid_schemes: [
"https",
"http",
"dat",
"dweb",
"gopher",
"ipfs",
"ipns",
"irc",
"ircs",
"magnet",
"mailto",
"mumble",
"ssb",
"xmpp"
]
websocket_config = [
path: "/websocket",
serializer: [
{Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
{Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
],
timeout: 60_000,
transport_log: false,
compress: false
]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
url: [host: "localhost"],
http: [
dispatch: [
{:_,
[
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]}
]
],
protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
secure_cookie_flag: true,
extra_cookie_attrs: [
"SameSite=Lax"
]
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
config :logger, :ex_syslogger,
level: :debug,
ident: "Pleroma",
format: "$metadata[$level] $message",
metadata: [:request_id]
config :quack,
level: :warn,
meta: [:all],
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
config :mime, :types, %{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"]
}
config :pleroma, :websub, Pleroma.Web.Websub
config :pleroma, :ostatus, Pleroma.Web.OStatus
config :pleroma, :httpoison, Pleroma.HTTP
config :tesla, adapter: Tesla.Adapter.Hackney
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil,
adapter: [
ssl_options: [
# We don't support TLS v1.3 yet
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
]
]
config :pleroma, :instance,
name: "Pleroma",
email: "example@example.com",
notify_email: "noreply@example.com",
description: "A Pleroma instance, an alternative fediverse server",
limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
registrations_open: true,
federating: true,
federation_reachability_timeout_days: 7,
federation_publisher_modules: [
Pleroma.Web.ActivityPub.Publisher,
Pleroma.Web.Websub,
Pleroma.Web.Salmon
],
allow_relay: true,
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
public: true,
quarantined_instances: [],
managed_config: true,
static_dir: "instance/static/",
allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
mrf_transparency: true,
autofollowed_nicknames: [],
max_pinned_statuses: 1,
no_attachment_links: false,
welcome_user_nickname: nil,
welcome_message: nil,
max_report_comment_size: 1000,
safe_dm_mentions: false,
healthcheck: false
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
# of custom emoji. Issue #275 discusses defanging that somehow.
allow_inline_images: true,
allow_headings: false,
allow_tables: false,
allow_fonts: false,
scrub_policy: [
Pleroma.HTML.Transform.MediaProxy,
Pleroma.HTML.Scrubber.Default
]
-# Deprecated, will be gone in 1.0
-config :pleroma, :fe,
- theme: "pleroma-dark",
- logo: "/static/logo.png",
- logo_mask: true,
- logo_margin: "0.1em",
- background: "/static/aurora_borealis.jpg",
- redirect_root_no_login: "/main/all",
- redirect_root_login: "/main/friends",
- show_instance_panel: true,
- scope_options_enabled: false,
- formatting_options_enabled: false,
- collapse_message_with_subject: false,
- hide_post_stats: false,
- hide_user_stats: false,
- scope_copy: true,
- subject_line_behavior: "email",
- always_show_subject_input: true
-
config :pleroma, :frontend_configurations,
pleroma_fe: %{
theme: "pleroma-dark",
logo: "/static/logo.png",
background: "/images/city.jpg",
redirectRootNoLogin: "/main/all",
redirectRootLogin: "/main/friends",
showInstanceSpecificPanel: true,
scopeOptionsEnabled: false,
formattingOptionsEnabled: false,
collapseMessageWithSubject: false,
hidePostStats: false,
hideUserStats: false,
scopeCopy: true,
subjectLineBehavior: "email",
alwaysShowSubjectInput: true
},
masto_fe: %{
showInstanceSpecificPanel: true
}
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
outgoing_blocks: true,
follow_handshake_timeout: 500
config :pleroma, :user, deny_follow_blocked: true
config :pleroma, :mrf_normalize_markup, scrub_policy: Pleroma.HTML.Scrubber.Default
config :pleroma, :mrf_rejectnonpublic,
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_hellthread,
delist_threshold: 10,
reject_threshold: 20
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
reject: [],
accept: []
config :pleroma, :mrf_keyword,
reject: [],
federated_timeline_removal: [],
replace: []
config :pleroma, :rich_media, enabled: true
config :pleroma, :media_proxy,
enabled: false,
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
http: [
follow_redirect: true,
pool: :media
]
],
whitelist: []
config :pleroma, :chat, enabled: true
config :phoenix, :format_encoders, json: Jason
config :pleroma, :gopher,
enabled: false,
ip: {0, 0, 0, 0},
port: 9999
config :pleroma, Pleroma.Web.Metadata,
providers: [Pleroma.Web.Metadata.Providers.RelMe],
unfurl_nsfw: false
config :pleroma, :suggestions,
enabled: false,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
timeout: 300_000,
limit: 23,
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
config :pleroma, :http_security,
enabled: true,
sts: false,
sts_max_age: 31_536_000,
ct_max_age: 2_592_000,
referrer_policy: "same-origin"
config :cors_plug,
max_age: 86_400,
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
expose: [
"Link",
"X-RateLimit-Reset",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-Request-Id",
"Idempotency-Key"
],
credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
config :pleroma, Pleroma.User,
restricted_nicknames: [
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"users",
"web"
]
config :pleroma, Pleroma.Web.Federator.RetryQueue,
enabled: false,
max_jobs: 20,
initial_timeout: 30,
max_retries: 5
config :pleroma_job_queue, :queues,
federator_incoming: 50,
federator_outgoing: 50,
web_push: 50,
mailer: 10,
transmogrifier: 20,
scheduled_activities: 10,
- background: 5,
- user: 10
+ background: 5
config :pleroma, :fetch_initial_posts,
enabled: false,
pages: 5
config :auto_linker,
opts: [
scheme: true,
extra: true,
class: false,
strip_prefix: false,
new_window: false,
rel: false
]
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost",
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
config :esshd,
enabled: false
oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
strategy_module = String.to_atom(strategy_module_name)
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
end
config :ueberauth,
Ueberauth,
base_path: "/oauth",
providers: ueberauth_providers
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 25,
total_user_limit: 300,
enabled: true
config :pleroma, :oauth2,
token_expires_in: 600,
issue_new_refresh_token: true
+config :http_signatures,
+ adapter: Pleroma.Signature
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index 75fa2ee83..59578f8d1 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -1,282 +1,282 @@
# Admin API
Authentication is required and the user must be an admin.
## `/api/pleroma/admin/users`
### List users
- Method `GET`
- Query Params:
- *optional* `query`: **string** search term (e.g. nickname, domain, nickname@domain)
- *optional* `filters`: **string** comma-separated string of filters:
- `local`: only local users
- `external`: only external users
- `active`: only active users
- `deactivated`: only deactivated users
- `is_admin`: users with admin role
- `is_moderator`: users with moderator role
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of users per page (default is `50`)
- *optional* `tags`: **[string]** tags list
- *optional* `name`: **string** user display name
- *optional* `email`: **string** user email
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
- Response:
```JSON
{
"page_size": integer,
"count": integer,
"users": [
{
"deactivated": bool,
"id": integer,
"nickname": string,
"roles": {
"admin": bool,
"moderator": bool
},
"local": bool,
"tags": array
},
...
]
}
```
## `/api/pleroma/admin/users`
### Remove a user
- Method `DELETE`
- Params:
- `nickname`
- Response: User’s nickname
### Create a user
- Method: `POST`
- Params:
- `nickname`
- `email`
- `password`
- Response: User’s nickname
## `/api/pleroma/admin/users/follow`
### Make a user follow another user
- Methods: `POST`
- Params:
- `follower`: The nickname of the follower
- `followed`: The nickname of the followed
- Response:
- "ok"
## `/api/pleroma/admin/users/unfollow`
### Make a user unfollow another user
- Methods: `POST`
- Params:
- `follower`: The nickname of the follower
- `followed`: The nickname of the followed
- Response:
- "ok"
## `/api/pleroma/admin/users/:nickname/toggle_activation`
### Toggle user activation
- Method: `PATCH`
- Params:
- `nickname`
- Response: User’s object
```JSON
{
"deactivated": bool,
"id": integer,
"nickname": string
}
```
## `/api/pleroma/admin/users/tag`
### Tag a list of users
- Method: `PUT`
- Params:
- - `nickname`
- - `tags`
+ - `nicknames` (array)
+ - `tags` (array)
### Untag a list of users
- Method: `DELETE`
- Params:
- - `nickname`
- - `tags`
+ - `nicknames` (array)
+ - `tags` (array)
## `/api/pleroma/admin/users/:nickname/permission_group`
### Get user user permission groups membership
- Method: `GET`
- Params: none
- Response:
```JSON
{
"is_moderator": bool,
"is_admin": bool
}
```
## `/api/pleroma/admin/users/:nickname/permission_group/:permission_group`
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
### Get user user permission groups membership per permission group
- Method: `GET`
- Params: none
- Response:
```JSON
{
"is_moderator": bool,
"is_admin": bool
}
```
### Add user in permission group
- Method: `POST`
- Params: none
- Response:
- On failure: `{"error": "…"}`
- On success: JSON of the `user.info`
### Remove user from permission group
- Method: `DELETE`
- Params: none
- Response:
- On failure: `{"error": "…"}`
- On success: JSON of the `user.info`
- Note: An admin cannot revoke their own admin status.
## `/api/pleroma/admin/users/:nickname/activation_status`
### Active or deactivate a user
- Method: `PUT`
- Params:
- `nickname`
- `status` BOOLEAN field, false value means deactivation.
## `/api/pleroma/admin/users/:nickname`
### Retrive the details of a user
- Method: `GET`
- Params:
- `nickname`
- Response:
- On failure: `Not found`
- On success: JSON of the user
## `/api/pleroma/admin/relay`
### Follow a Relay
- Methods: `POST`
- Params:
- `relay_url`
- Response:
- On success: URL of the followed relay
### Unfollow a Relay
- Methods: `DELETE`
- Params:
- `relay_url`
- Response:
- On success: URL of the unfollowed relay
## `/api/pleroma/admin/users/invite_token`
### Get an account registration invite token
- Methods: `GET`
- Params:
- *optional* `invite` => [
- *optional* `max_use` (integer)
- *optional* `expires_at` (date string e.g. "2019-04-07")
]
- Response: invite token (base64 string)
## `/api/pleroma/admin/users/invites`
### Get a list of generated invites
- Methods: `GET`
- Params: none
- Response:
```JSON
{
"invites": [
{
"id": integer,
"token": string,
"used": boolean,
"expires_at": date,
"uses": integer,
"max_use": integer,
"invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`)
},
...
]
}
```
## `/api/pleroma/admin/users/revoke_invite`
### Revoke invite by token
- Methods: `POST`
- Params:
- `token`
- Response:
```JSON
{
"id": integer,
"token": string,
"used": boolean,
"expires_at": date,
"uses": integer,
"max_use": integer,
"invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`)
}
```
## `/api/pleroma/admin/users/email_invite`
### Sends registration invite via email
- Methods: `POST`
- Params:
- `email`
- `name`, optional
## `/api/pleroma/admin/users/:nickname/password_reset`
### Get a password reset token for a given nickname
- Methods: `GET`
- Params: none
- Response: password reset token (base64 string)
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index 9613a329b..9c0ef92d4 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -1,202 +1,203 @@
# Installing on Debian Based Distributions
## Installation
This guide will assume you are on Debian Stretch. This guide should also work with Ubuntu 16.04 and 18.04. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
### Required packages
* `postgresql` (9.6+, Ubuntu 16.04 comes with 9.5, you can get a newer version from [here](https://www.postgresql.org/download/linux/ubuntu/))
* `postgresql-contrib` (9.6+, same situtation as above)
* `elixir` (1.5+, [install from here, Debian and Ubuntu ship older versions](https://elixir-lang.org/install.html#unix-and-unix-like) or use [asdf](https://github.com/asdf-vm/asdf) as the pleroma user)
* `erlang-dev`
* `erlang-tools`
* `erlang-parsetools`
* `erlang-eldap`, if you want to enable ldap authenticator
+* `erlang-ssh`
* `erlang-xmerl`
* `git`
* `build-essential`
#### Optional packages used in this guide
* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
* `certbot` (or any other ACME client for Let’s Encrypt certificates)
### Prepare the system
* First update the system, if not already done:
```shell
sudo apt update
sudo apt full-upgrade
```
* Install some of the above mentioned programs:
```shell
sudo apt install git build-essential postgresql postgresql-contrib
```
### Install Elixir and Erlang
* Download and add the Erlang repository:
```shell
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
```
* Install Elixir and Erlang:
```shell
sudo apt update
-sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
+sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
```
### Install PleromaBE
* Add a new system user for the Pleroma service:
```shell
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
```
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
```shell
sudo mkdir -p /opt/pleroma
sudo chown -R pleroma:pleroma /opt/pleroma
sudo -Hu pleroma git clone https://git.pleroma.social/pleroma/pleroma /opt/pleroma
```
* Change to the new directory:
```shell
cd /opt/pleroma
```
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
```shell
sudo -Hu pleroma mix deps.get
```
* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
* Answer with `yes` if it asks you to install `rebar3`.
* This may take some time, because parts of pleroma get compiled first.
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
```shell
mv config/{generated_config.exs,prod.secret.exs}
```
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
```shell
sudo -Hu postgres psql -f config/setup_db.psql
```
* Now run the database migration:
```shell
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
```
* Now you can start Pleroma already
```shell
sudo -Hu pleroma MIX_ENV=prod mix phx.server
```
### Finalize installation
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma.
#### Nginx
* Install nginx, if not already done:
```shell
sudo apt install nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo apt install certbot
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
---
* Copy the example nginx configuration and activate it:
```shell
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
* Enable and start nginx:
```shell
sudo systemctl enable --now nginx.service
```
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### Other webserver/proxies
You can find example configurations for them in `/opt/pleroma/installation/`.
#### Systemd service
* Copy example service file
```shell
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
```
* Edit the service file and make sure that all paths fit your installation
* Enable and start `pleroma.service`:
```shell
sudo systemctl enable --now pleroma.service
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### Further reading
* [Admin tasks](Admin tasks)
* [Backup your instance](Backup-your-instance)
* [Configuration tips](General tips for customizing pleroma fe)
* [Hardening your instance](Hardening-your-instance)
* [How to activate mediaproxy](How-to-activate-mediaproxy)
* [Small Pleroma-FE customizations](Small customizations)
* [Updating your instance](Updating-your-instance)
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index ac5dcaaee..41cce6792 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -1,191 +1,192 @@
# Pleromaの入れ方
## 日本語訳について
この記事は [Installing on Debian based distributions](Installing on Debian based distributions) の日本語訳です。何かがおかしいと思ったら、原文を見てください。
## インストール
このガイドはDebian Stretchを仮定しています。Ubuntu 16.04でも可能です。
### 必要なソフトウェア
- PostgreSQL 9.6+ (postgresql-contrib-9.6 または他のバージョンの PSQL をインストールしてください)
- Elixir 1.5 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like))。または [asdf](https://github.com/asdf-vm/asdf) を pleroma ユーザーでインストール。
- erlang-dev
- erlang-tools
- erlang-parsetools
+- erlang-ssh
- erlang-xmerl (Jessieではバックポートからインストールすること!)
- git
- build-essential
- openssh
- openssl
- nginx prefered (Apacheも動くかもしれませんが、誰もテストしていません!)
- certbot (または何らかのACME Let's encryptクライアント)
### システムを準備する
* まずシステムをアップデートしてください。
```
apt update && apt dist-upgrade
```
* 複数のツールとpostgresqlをインストールします。あとで必要になるので。
```
apt install git build-essential openssl ssh sudo postgresql-9.6 postgresql-contrib-9.6
```
(postgresqlのバージョンは、あなたのディストロにあわせて変えてください。または、バージョン番号がいらないかもしれません。)
### ElixirとErlangをインストールします
* Erlangのリポジトリをダウンロードおよびインストールします。
```
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
```
* ElixirとErlangをインストールします、
```
-apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
+apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
```
### Pleroma BE (バックエンド) をインストールします
* 新しいユーザーを作ります。
```
adduser pleroma
```
(Give it any password you want, make it STRONG)
* 新しいユーザーをsudoグループに入れます。
```
usermod -aG sudo pleroma
```
* 新しいユーザーに変身し、ホームディレクトリに移動します。
```
su pleroma
cd ~
```
* Gitリポジトリをクローンします。
```
git clone https://git.pleroma.social/pleroma/pleroma
```
* 新しいディレクトリに移動します。
```
cd pleroma/
```
* Pleromaが依存するパッケージをインストールします。Hexをインストールしてもよいか聞かれたら、yesを入力してください。
```
mix deps.get
```
* コンフィギュレーションを生成します。
```
mix pleroma.instance gen
```
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
* この処理には時間がかかります。私もよく分かりませんが、何らかのコンパイルが行われているようです。
* あなたのインスタンスについて、いくつかの質問があります。その回答は `config/generated_config.exs` というコンフィギュレーションファイルに保存されます。
**注意**: メディアプロクシを有効にすると回答して、なおかつ、キャッシュのURLは空欄のままにしている場合は、`generated_config.exs` を編集して、`base_url` で始まる行をコメントアウトまたは削除してください。そして、上にある行の `true` の後にあるコンマを消してください。
* コンフィギュレーションを確認して、もし問題なければ、ファイル名を変更してください。
```
mv config/{generated_config.exs,prod.secret.exs}
```
* これまでのコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
```
sudo su postgres -c 'psql -f config/setup_db.psql'
```
* そして、データベースのミグレーションを実行します。
```
MIX_ENV=prod mix ecto.migrate
```
* Pleromaを起動できるようになりました。
```
MIX_ENV=prod mix phx.server
```
### インストールを終わらせる
あなたの新しいインスタンスを世界に向けて公開するには、nginxまたは何らかのウェブサーバー (プロクシ) を使用する必要があります。また、Pleroma のためにシステムサービスファイルを作成する必要があります。
#### Nginx
* まだインストールしていないなら、nginxをインストールします。
```
apt install nginx
```
* SSLをセットアップします。他の方法でもよいですが、ここではcertbotを説明します。
certbotを使うならば、まずそれをインストールします。
```
apt install certbot
```
そしてセットアップします。
```
mkdir -p /var/lib/letsencrypt/.well-known
% certbot certonly --email your@emailaddress --webroot -w /var/lib/letsencrypt/ -d yourdomain
```
もしうまくいかないときは、先にnginxを設定してください。ssl "on" を "off" に変えてから再試行してください。
---
* nginxコンフィギュレーションの例をnginxフォルダーにコピーします。
```
cp /home/pleroma/pleroma/installation/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* nginxを起動する前に、コンフィギュレーションを編集してください。例えば、サーバー名、証明書のパスなどを変更する必要があります。
* nginxを再起動します。
```
systemctl reload nginx.service
```
#### Systemd サービス
* サービスファイルの例をコピーします。
```
cp /home/pleroma/pleroma/installation/pleroma.service /usr/lib/systemd/system/pleroma.service
```
* サービスファイルを変更します。すべてのパスが正しいことを確認してください。また、`[Service]` セクションに以下の行があることを確認してください。
```
Environment="MIX_ENV=prod"
```
* `pleroma.service` を enable および start してください。
```
systemctl enable --now pleroma.service
```
#### モデレーターを作る
新たにユーザーを作ったら、モデレーター権限を与えたいかもしれません。以下のタスクで可能です。
```
mix set_moderator username [true|false]
```
モデレーターはすべてのポストを消すことができます。将来的には他のことも可能になるかもしれません。
#### メディアプロクシを有効にする
`generate_config` でメディアプロクシを有効にしているなら、すでにメディアプロクシが動作しています。あとから設定を変更したいなら、[How to activate mediaproxy](How-to-activate-mediaproxy) を見てください。
#### コンフィギュレーションとカスタマイズ
* [Configuration tips](General tips for customizing pleroma fe)
* [Small Pleroma-FE customizations](Small customizations)
* [Admin tasks](Admin tasks)
## 質問ある?
インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
* [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org)
* **Freenode** の **#pleroma** IRCチャンネル
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 0345ac19c..240fb1c37 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -1,30 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
require Logger
- def check_frontend_config_mechanism do
- if Pleroma.Config.get(:fe) do
- Logger.warn("""
- !!!DEPRECATION WARNING!!!
- You are using the old configuration mechanism for the frontend. Please check config.md.
- """)
- end
- end
-
def check_hellthread_threshold do
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
""")
end
end
def warn do
- check_frontend_config_mechanism()
check_hellthread_threshold()
end
end
diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
new file mode 100644
index 000000000..317fd5445
--- /dev/null
+++ b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
+ import Plug.Conn
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _) do
+ public? = Config.get!([:instance, :public])
+
+ case {public?, conn} do
+ {true, _} ->
+ conn
+
+ {false, %{assigns: %{user: %User{}}}} ->
+ conn
+
+ {false, _} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
+ |> halt
+ end
+ end
+end
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex
index 21c195713..e2874c469 100644
--- a/lib/pleroma/plugs/http_signature.ex
+++ b/lib/pleroma/plugs/http_signature.ex
@@ -1,54 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.HTTPSignatures
import Plug.Conn
require Logger
def init(options) do
options
end
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
conn
end
def call(conn, _opts) do
user = Utils.get_ap_id(conn.params["actor"])
Logger.debug("Checking sig for #{user}")
[signature | _] = get_req_header(conn, "signature")
cond do
signature && String.contains?(signature, user) ->
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
signature ->
Logger.debug("Signature not from actor")
assign(conn, :valid_signature, false)
true ->
Logger.debug("No signature header!")
conn
end
end
end
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
new file mode 100644
index 000000000..b7ecf00a0
--- /dev/null
+++ b/lib/pleroma/signature.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Signature do
+ @behaviour HTTPSignatures.Adapter
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.Salmon
+ alias Pleroma.Web.WebFinger
+
+ def fetch_public_key(conn) do
+ with actor_id <- Utils.get_ap_id(conn.params["actor"]),
+ {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
+ {:ok, public_key}
+ else
+ e ->
+ {:error, e}
+ end
+ end
+
+ def refetch_public_key(conn) do
+ with actor_id <- Utils.get_ap_id(conn.params["actor"]),
+ {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
+ {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
+ {:ok, public_key}
+ else
+ e ->
+ {:error, e}
+ end
+ end
+
+ def sign(%User{} = user, headers) do
+ with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user),
+ {:ok, private_key, _} <- Salmon.keys_from_pem(keys) do
+ HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 036ac892e..fdebdf85c 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -1,201 +1,201 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
import Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.Federator.Publisher
require Logger
@httpoison Application.get_env(:pleroma, :httpoison)
@moduledoc """
ActivityPub outgoing federation module.
"""
@doc """
Determine if an activity can be represented by running it through Transmogrifier.
"""
def is_representable?(%Activity{} = activity) do
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
true
else
_e ->
false
end
end
@doc """
Publish a single message to a peer. Takes a struct with the following
parameters set:
* `inbox`: the inbox to publish to
* `json`: the JSON message body representing the ActivityPub message
* `actor`: the actor which is signing the message
* `id`: the ActivityStreams URI of the message
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date =
NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
signature =
- Pleroma.Web.HTTPSignatures.sign(actor, %{
+ Pleroma.Signature.sign(actor, %{
host: host,
"content-length": byte_size(json),
digest: digest,
date: date
})
with {:ok, %{status: code}} when code in 200..299 <-
result =
@httpoison.post(
inbox,
json,
[
{"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature},
{"digest", digest}
]
) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
do: Instances.set_reachable(inbox)
result
else
{_post_result, response} ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
{:error, response}
end
end
defp should_federate?(inbox, public) do
if public do
true
else
inbox_info = URI.parse(inbox)
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
end
end
defp recipients(actor, activity) do
followers =
if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor)
Enum.filter(followers, &(!&1.local))
else
[]
end
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
end
defp get_cc_ap_ids(ap_id, recipients) do
host = Map.get(URI.parse(ap_id), :host)
recipients
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|> Enum.map(& &1.ap_id)
end
@doc """
Publishes an activity with BCC to all relevant peers.
"""
def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
public = is_public?(activity)
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
recipients = recipients(actor, activity)
recipients
|> Enum.filter(&User.ap_enabled?/1)
|> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
%User{ap_id: ap_id} =
Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
cc = get_cc_ap_ids(ap_id, recipients)
json =
data
|> Map.put("cc", cc)
|> Map.put("directMessage", true)
|> Jason.encode!()
Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
inbox: inbox,
json: json,
actor: actor,
id: activity.data["id"],
unreachable_since: unreachable_since
})
end)
end
@doc """
Publishes an activity to all relevant peers.
"""
def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
end
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
Pleroma.Web.Federator.Publisher.enqueue_one(
__MODULE__,
%{
inbox: inbox,
json: json,
actor: actor,
id: activity.data["id"],
unreachable_since: unreachable_since
}
)
end)
end
def gather_webfinger_links(%User{} = user) do
[
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
%{
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
}
]
end
def gather_nodeinfo_protocol_names, do: ["activitypub"]
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 6dee61dd6..b38ee0442 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -1,61 +1,85 @@
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end
def is_private?(activity) do
unless is_public?(activity) do
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
Enum.any?(activity.data["to"], &(&1 == follower_address))
else
false
end
end
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
def is_direct?(activity) do
!is_public?(activity) && !is_private?(activity)
end
def visible_for_user?(activity, nil) do
is_public?(activity)
end
def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
# guard
def entire_thread_visible_for_user?(nil, _user), do: false
# XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def entire_thread_visible_for_user?(
%Activity{} = tail,
# %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
) do
case Object.normalize(tail) do
%{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
_ ->
visible_for_user?(tail, user)
end
end
+
+ def get_visibility(object) do
+ public = "https://www.w3.org/ns/activitystreams#Public"
+ to = object.data["to"] || []
+ cc = object.data["cc"] || []
+
+ cond do
+ public in to ->
+ "public"
+
+ public in cc ->
+ "unlisted"
+
+ # this should use the sql for the object's activity
+ Enum.any?(to, &String.contains?(&1, "/followers")) ->
+ "private"
+
+ length(cc) > 0 ->
+ "private"
+
+ true ->
+ "direct"
+ end
+ end
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index ed2c0017f..97134cd19 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -1,335 +1,340 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
import Pleroma.Web.CommonAPI.Utils
def follow(follower, followed) do
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
{:ok, activity} <- ActivityPub.follow(follower, followed),
{:ok, follower, followed} <-
User.wait_and_refresh(
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
follower,
followed
) do
{:ok, follower, followed, activity}
end
end
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
{:ok, follower}
end
end
def accept_follow_request(follower, followed) do
with {:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Accept"
}) do
{:ok, follower}
end
end
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Reject"
}) do
{:ok, follower}
end
end
def delete(activity_id, user) do
with %Activity{data: %{"object" => _}} = activity <-
Activity.get_by_id_with_object(activity_id),
%Object{} = object <- Object.normalize(activity),
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, _} <- unpin(activity_id, user),
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
end
end
def repeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity),
nil <- Utils.get_existing_announce(user.ap_id, object) do
ActivityPub.announce(user, object)
else
_ ->
{:error, "Could not repeat"}
end
end
def unrepeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity) do
ActivityPub.unannounce(user, object)
else
_ ->
{:error, "Could not unrepeat"}
end
end
def favorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity),
nil <- Utils.get_existing_like(user.ap_id, object) do
ActivityPub.like(user, object)
else
_ ->
{:error, "Could not favorite"}
end
end
def unfavorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity) do
ActivityPub.unlike(user, object)
else
_ ->
{:error, "Could not unfavorite"}
end
end
- def get_visibility(%{"visibility" => visibility})
+ def get_visibility(%{"visibility" => visibility}, in_reply_to)
when visibility in ~w{public unlisted private direct},
- do: visibility
+ do: {visibility, get_replied_to_visibility(in_reply_to)}
- def get_visibility(%{"visibility" => "list:" <> list_id}) do
- {:list, String.to_integer(list_id)}
+ def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
+ visibility = {:list, String.to_integer(list_id)}
+ {visibility, get_replied_to_visibility(in_reply_to)}
end
- def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
- case get_replied_to_activity(status_id) do
- nil ->
- "public"
+ def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
+ visibility = get_replied_to_visibility(in_reply_to)
+ {visibility, visibility}
+ end
+
+ def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
+
+ def get_replied_to_visibility(nil), do: nil
- in_reply_to ->
- # XXX: these heuristics should be moved out of MastodonAPI.
- with %Object{} = object <- Object.normalize(in_reply_to) do
- Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
- end
+ def get_replied_to_visibility(activity) do
+ with %Object{} = object <- Object.normalize(activity) do
+ Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
end
end
- def get_visibility(_), do: "public"
-
def post(user, %{"status" => status} = data) do
- visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit])
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
+ {visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
+ {_, false} <-
+ {:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <-
make_content_html(
status,
attachments,
data,
visibility
),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
bcc <- bcc_for_list(user, visibility),
context <- make_context(in_reply_to),
cw <- data["spoiler_text"] || "",
full_payload <- String.trim(status <> cw),
length when length in 1..limit <- String.length(full_payload),
object <-
make_note_data(
user.ap_id,
to,
context,
content_html,
attachments,
in_reply_to,
tags,
cw,
cc
),
object <-
Map.put(
object,
"emoji",
Formatter.get_emoji_map(full_payload)
) do
ActivityPub.create(
%{
to: to,
actor: user,
context: context,
object: object,
additional: %{"cc" => cc, "bcc" => bcc, "directMessage" => visibility == "direct"}
},
Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
)
+ else
+ e -> {:error, e}
end
end
# Updates the emojis for a user based on their profile
def update(user) do
user =
with emoji <- emoji_from_profile(user),
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
info_cng <- User.Info.set_source_data(user.info, source_data),
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(change) do
user
else
_e ->
user
end
ActivityPub.update(%{
local: true,
to: [user.follower_address],
cc: [],
actor: user.ap_id,
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
})
end
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
with %Activity{
actor: ^user_ap_id,
data: %{
"type" => "Create"
},
object: %Object{
data: %{
"to" => object_to,
"type" => "Note"
}
}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
%{valid?: true} = info_changeset <-
User.Info.add_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
{:ok, activity}
else
%{errors: [pinned_activities: {err, _}]} ->
{:error, err}
_ ->
{:error, "Could not pin"}
end
end
def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
%{valid?: true} = info_changeset <-
User.Info.remove_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
{:ok, activity}
else
%{errors: [pinned_activities: {err, _}]} ->
{:error, err}
_ ->
{:error, "Could not unpin"}
end
end
def add_mute(user, activity) do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
{:ok, activity}
else
{:error, _} -> {:error, "conversation is already muted"}
end
end
def remove_mute(user, activity) do
ThreadMute.remove_mute(user.id, activity.data["context"])
{:ok, activity}
end
def thread_muted?(%{id: nil} = _user, _activity), do: false
def thread_muted?(user, activity) do
with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
false
else
_ -> true
end
end
def bookmarked?(user, activity) do
with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
true
else
_ ->
false
end
end
def report(user, data) do
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
{:ok, statuses} <- get_report_statuses(account, data),
{:ok, activity} <-
ActivityPub.flag(%{
context: Utils.generate_context_id(),
actor: user,
account: account,
statuses: statuses,
content: content_html,
forward: data["forward"] || false
}) do
{:ok, activity}
else
{:error, err} -> {:error, err}
{:account_id, %{}} -> {:error, "Valid `account_id` required"}
{:account, nil} -> {:error, "Account not found"}
end
end
def hide_reblogs(user, muted) do
ap_id = muted.ap_id
if ap_id not in user.info.muted_reblogs do
info_changeset = User.Info.add_reblog_mute(user.info, ap_id)
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end
end
def show_reblogs(user, muted) do
ap_id = muted.ap_id
if ap_id in user.info.muted_reblogs do
info_changeset = User.Info.remove_reblog_mute(user.info, ap_id)
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end
end
end
diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex
index 916bcdcba..fb4e8548d 100644
--- a/lib/pleroma/web/federator/publisher.ex
+++ b/lib/pleroma/web/federator/publisher.ex
@@ -1,95 +1,95 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.Federator.RetryQueue
require Logger
@moduledoc """
Defines the contract used by federation implementations to publish messages to
their peers.
"""
@doc """
Determine whether an activity can be relayed using the federation module.
"""
@callback is_representable?(Pleroma.Activity.t()) :: boolean()
@doc """
Relays an activity to a specified peer, determined by the parameters. The
parameters used are controlled by the federation module.
"""
@callback publish_one(Map.t()) :: {:ok, Map.t()} | {:error, any()}
@doc """
Enqueue publishing a single activity.
"""
@spec enqueue_one(module(), Map.t()) :: :ok
def enqueue_one(module, %{} = params),
- do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params])
+ do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
def perform(:publish_one, module, params) do
case apply(module, :publish_one, [params]) do
{:ok, _} ->
:ok
{:error, _e} ->
RetryQueue.enqueue(params, module)
end
end
def perform(type, _, _) do
Logger.debug("Unknown task: #{type}")
{:error, "Don't know what to do with this"}
end
@doc """
Relays an activity to all specified peers.
"""
@callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
@spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
def publish(%User{} = user, %Activity{} = activity) do
Config.get([:instance, :federation_publisher_modules])
|> Enum.each(fn module ->
if module.is_representable?(activity) do
Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}")
module.publish(user, activity)
end
end)
:ok
end
@doc """
Gathers links used by an outgoing federation module for WebFinger output.
"""
@callback gather_webfinger_links(Pleroma.User.t()) :: list()
@spec gather_webfinger_links(Pleroma.User.t()) :: list()
def gather_webfinger_links(%User{} = user) do
Config.get([:instance, :federation_publisher_modules])
|> Enum.reduce([], fn module, links ->
links ++ module.gather_webfinger_links(user)
end)
end
@doc """
Gathers nodeinfo protocol names supported by the federation module.
"""
@callback gather_nodeinfo_protocol_names() :: list()
@spec gather_nodeinfo_protocol_names() :: list()
def gather_nodeinfo_protocol_names do
Config.get([:instance, :federation_publisher_modules])
|> Enum.reduce([], fn module, links ->
links ++ module.gather_nodeinfo_protocol_names()
end)
end
end
diff --git a/lib/pleroma/web/http_signatures/http_signatures.ex b/lib/pleroma/web/http_signatures/http_signatures.ex
deleted file mode 100644
index 8e2e2a44b..000000000
--- a/lib/pleroma/web/http_signatures/http_signatures.ex
+++ /dev/null
@@ -1,91 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-# https://tools.ietf.org/html/draft-cavage-http-signatures-08
-defmodule Pleroma.Web.HTTPSignatures do
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Utils
-
- require Logger
-
- def split_signature(sig) do
- default = %{"headers" => "date"}
-
- sig =
- sig
- |> String.trim()
- |> String.split(",")
- |> Enum.reduce(default, fn part, acc ->
- [key | rest] = String.split(part, "=")
- value = Enum.join(rest, "=")
- Map.put(acc, key, String.trim(value, "\""))
- end)
-
- Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
- end
-
- def validate(headers, signature, public_key) do
- sigstring = build_signing_string(headers, signature["headers"])
- Logger.debug("Signature: #{signature["signature"]}")
- Logger.debug("Sigstring: #{sigstring}")
- {:ok, sig} = Base.decode64(signature["signature"])
- :public_key.verify(sigstring, :sha256, sig, public_key)
- end
-
- def validate_conn(conn) do
- # TODO: How to get the right key and see if it is actually valid for that request.
- # For now, fetch the key for the actor.
- with actor_id <- Utils.get_ap_id(conn.params["actor"]),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
- if validate_conn(conn, public_key) do
- true
- else
- Logger.debug("Could not validate, re-fetching user and trying one more time")
- # Fetch user anew and try one more time
- with actor_id <- Utils.get_ap_id(conn.params["actor"]),
- {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
- validate_conn(conn, public_key)
- end
- end
- else
- _e ->
- Logger.debug("Could not public key!")
- false
- end
- end
-
- def validate_conn(conn, public_key) do
- headers = Enum.into(conn.req_headers, %{})
- signature = split_signature(headers["signature"])
- validate(headers, signature, public_key)
- end
-
- def build_signing_string(headers, used_headers) do
- used_headers
- |> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
- |> Enum.join("\n")
- end
-
- def sign(user, headers) do
- with {:ok, %{info: %{keys: keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
- {:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
- sigstring = build_signing_string(headers, Map.keys(headers))
-
- signature =
- :public_key.sign(sigstring, :sha256, private_key)
- |> Base.encode64()
-
- [
- keyId: user.ap_id <> "#main-key",
- algorithm: "rsa-sha256",
- headers: Map.keys(headers) |> Enum.join(" "),
- signature: signature
- ]
- |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
- |> Enum.join(",")
- end
- end
-end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index bd2372944..c93d915e5 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -1,444 +1,422 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.StatusView do
use Pleroma.Web, :view
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
+ import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
+
# TODO: Add cached version.
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
%{data: %{"type" => "Create", "object" => object}} ->
object = Object.normalize(object)
object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
nil
end)
|> Enum.filter(& &1)
|> Activity.create_by_object_ap_id()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity)
Map.put(acc, object.data["id"], activity)
end)
end
defp get_user(ap_id) do
cond do
user = User.get_cached_by_ap_id(ap_id) ->
user
user = User.get_by_guessed_nickname(ap_id) ->
user
true ->
User.error_user(ap_id)
end
end
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
do: context_id
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
do: Utils.context_to_conversation_id(context)
defp get_context_id(_), do: nil
defp reblogged?(activity, user) do
object = Object.normalize(activity) || %{}
present?(user && user.ap_id in (object.data["announcements"] || []))
end
def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities)
opts.activities
|> safe_render_many(
StatusView,
"status.json",
Map.put(opts, :replied_to_activities, replied_to_activities)
)
end
def render(
"status.json",
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
) do
user = get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity)
reblogged_activity =
Activity.create_by_object_ap_id(activity_object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for])
|> Repo.one()
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
mentions =
activity.recipients
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
%{
id: to_string(activity.id),
uri: activity_object.data["id"],
url: activity_object.data["id"],
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
reblog: reblogged,
content: reblogged[:content] || "",
created_at: created_at,
reblogs_count: 0,
replies_count: 0,
favourites_count: 0,
reblogged: reblogged?(reblogged_activity, opts[:for]),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
muted: false,
pinned: pinned?(activity, user),
sensitive: false,
spoiler_text: "",
visibility: "public",
media_attachments: reblogged[:media_attachments] || [],
mentions: mentions,
tags: reblogged[:tags] || [],
application: %{
name: "Web",
website: nil
},
language: nil,
emojis: [],
pleroma: %{
local: activity.local
}
}
end
def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
object = Object.normalize(activity)
user = get_user(activity.data["actor"])
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
tags = object.data["tag"] || []
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
mentions =
activity.recipients
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
created_at = Utils.to_masto_date(object.data["published"])
reply_to = get_reply_to(activity, opts)
reply_to_user = reply_to && get_user(reply_to.data["actor"])
content =
object
|> render_content()
content_html =
content
|> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"mastoapi:content"
)
content_plaintext =
content
|> HTML.get_cached_stripped_html_for_activity(
activity,
"mastoapi:content"
)
summary = object.data["summary"] || ""
summary_html =
summary
|> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"mastoapi:summary"
)
summary_plaintext =
summary
|> HTML.get_cached_stripped_html_for_activity(
activity,
"mastoapi:summary"
)
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
url =
if user.local do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
else
object.data["external_url"] || object.data["id"]
end
%{
id: to_string(activity.id),
uri: object.data["id"],
url: url,
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
card: card,
content: content_html,
created_at: created_at,
reblogs_count: announcement_count,
replies_count: object.data["repliesCount"] || 0,
favourites_count: like_count,
reblogged: reblogged?(activity, opts[:for]),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user),
sensitive: sensitive,
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
mentions: mentions,
tags: build_tags(tags),
application: %{
name: "Web",
website: nil
},
language: nil,
emojis: build_emojis(object.data["emoji"]),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext}
}
}
end
def render("status.json", _) do
nil
end
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
page_url_data = URI.parse(page_url)
page_url_data =
if rich_media[:url] != nil do
URI.merge(page_url_data, URI.parse(rich_media[:url]))
else
page_url_data
end
page_url = page_url_data |> to_string
image_url =
if rich_media[:image] != nil do
URI.merge(page_url_data, URI.parse(rich_media[:image]))
|> to_string
else
nil
end
site_name = rich_media[:site_name] || page_url_data.host
%{
type: "link",
provider_name: site_name,
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url |> MediaProxy.url(),
title: rich_media[:title],
description: rich_media[:description],
pleroma: %{
opengraph: rich_media
}
}
end
def render("card.json", _) do
nil
end
def render("attachment.json", %{attachment: attachment}) do
[attachment_url | _] = attachment["url"]
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
href = attachment_url["href"] |> MediaProxy.url()
type =
cond do
String.contains?(media_type, "image") -> "image"
String.contains?(media_type, "video") -> "video"
String.contains?(media_type, "audio") -> "audio"
true -> "unknown"
end
<<hash_id::signed-32, _rest::binary>> = :crypto.hash(:md5, href)
%{
id: to_string(attachment["id"] || hash_id),
url: href,
remote_url: href,
preview_url: href,
text_url: href,
type: type,
description: attachment["name"],
pleroma: %{mime_type: media_type}
}
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity)
with nil <- replied_to_activities[object.data["inReplyTo"]] do
# If user didn't participate in the thread
Activity.get_in_reply_to_activity(activity)
end
end
def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
object = Object.normalize(activity)
if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do
Activity.get_create_by_object_ap_id(object.data["inReplyTo"])
else
nil
end
end
- def get_visibility(object) do
- public = "https://www.w3.org/ns/activitystreams#Public"
- to = object.data["to"] || []
- cc = object.data["cc"] || []
-
- cond do
- public in to ->
- "public"
-
- public in cc ->
- "unlisted"
-
- # this should use the sql for the object's activity
- Enum.any?(to, &String.contains?(&1, "/followers")) ->
- "private"
-
- length(cc) > 0 ->
- "private"
-
- true ->
- "direct"
- end
- end
-
def render_content(%{data: %{"type" => "Video"}} = object) do
with name when not is_nil(name) and name != "" <- object.data["name"] do
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
else
_ -> object.data["content"] || ""
end
end
def render_content(%{data: %{"type" => object_type}} = object)
when object_type in ["Article", "Page"] do
with summary when not is_nil(summary) and summary != "" <- object.data["name"],
url when is_bitstring(url) <- object.data["url"] do
"<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"
else
_ -> object.data["content"] || ""
end
end
def render_content(object), do: object.data["content"] || ""
@doc """
Builds a dictionary tags.
## Examples
iex> Pleroma.Web.MastodonAPI.StatusView.build_tags(["fediverse", "nextcloud"])
[{"name": "fediverse", "url": "/tag/fediverse"},
{"name": "nextcloud", "url": "/tag/nextcloud"}]
"""
@spec build_tags(list(any())) :: list(map())
def build_tags(object_tags) when is_list(object_tags) do
object_tags = for tag when is_binary(tag) <- object_tags, do: tag
Enum.reduce(object_tags, [], fn tag, tags ->
tags ++ [%{name: tag, url: "/tag/#{tag}"}]
end)
end
def build_tags(_), do: []
@doc """
Builds list emojis.
Arguments: `nil` or list tuple of name and url.
Returns list emojis.
## Examples
iex> Pleroma.Web.MastodonAPI.StatusView.build_emojis([{"2hu", "corndog.png"}])
[%{shortcode: "2hu", static_url: "corndog.png", url: "corndog.png", visible_in_picker: false}]
"""
@spec build_emojis(nil | list(tuple())) :: list(map())
def build_emojis(nil), do: []
def build_emojis(emojis) do
emojis
|> Enum.map(fn {name, url} ->
name = HTML.strip_tags(name)
url =
url
|> HTML.strip_tags()
|> MediaProxy.url()
%{shortcode: name, url: url, static_url: url, visible_in_picker: false}
end)
end
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
defp pinned?(%Activity{id: id}, %User{info: %{pinned_activities: pinned_activities}}),
do: id in pinned_activities
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 80af0afe1..7fef82f82 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -1,749 +1,751 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
end
pipeline :oauth do
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
end
pipeline :api do
plug(:accepts, ["json"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
pipeline :authenticated_api do
plug(:accepts, ["json"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
end
pipeline :admin_api do
plug(:accepts, ["json"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.AdminSecretAuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.UserIsAdminPlug)
end
pipeline :mastodon_html do
plug(:accepts, ["html"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
pipeline :pleroma_html do
plug(:accepts, ["html"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
- pipeline :oauth_read_or_unauthenticated do
+ pipeline :oauth_read_or_public do
plug(Pleroma.Plugs.OAuthScopesPlug, %{
scopes: ["read"],
fallback: :proceed_unauthenticated
})
+
+ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
end
pipeline :oauth_read do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]})
end
pipeline :oauth_write do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"]})
end
pipeline :oauth_follow do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["follow"]})
end
pipeline :oauth_push do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
end
pipeline :well_known do
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end
pipeline :config do
plug(:accepts, ["json", "xml"])
end
pipeline :pleroma_api do
plug(:accepts, ["html", "json"])
end
pipeline :mailbox_preview do
plug(:accepts, ["html"])
plug(:put_secure_browser_headers, %{
"content-security-policy" =>
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'"
})
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api)
get("/password_reset/:token", UtilController, :show_password_reset)
post("/password_reset", UtilController, :password_reset)
get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
end
scope "/api/pleroma", Pleroma.Web do
pipe_through(:pleroma_api)
post("/uploader_callback/:upload_path", UploaderController, :callback)
end
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :oauth_write])
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
# TODO: to be removed at version 1.0
delete("/user", AdminAPIController, :user_delete)
post("/user", AdminAPIController, :user_create)
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :user_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
# TODO: to be removed at version 1.0
get("/permission_group/:nickname", AdminAPIController, :right_get)
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
delete(
"/users/:nickname/permission_group/:permission_group",
AdminAPIController,
:right_delete
)
put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
post("/relay", AdminAPIController, :relay_follow)
delete("/relay", AdminAPIController, :relay_unfollow)
get("/users/invite_token", AdminAPIController, :get_invite_token)
get("/users/invites", AdminAPIController, :invites)
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
post("/users/email_invite", AdminAPIController, :email_invite)
# TODO: to be removed at version 1.0
get("/password_reset", AdminAPIController, :get_password_reset)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
end
scope "/", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_html)
post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow)
scope [] do
pipe_through(:oauth_follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow)
end
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api)
scope [] do
pipe_through(:oauth_write)
post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
end
scope [] do
pipe_through(:oauth_follow)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
scope [] do
pipe_through(:oauth_read)
post("/notifications/read", UtilController, :notifications_read)
end
end
scope "/oauth", Pleroma.Web.OAuth do
scope [] do
pipe_through(:oauth)
get("/authorize", OAuthController, :authorize)
end
post("/authorize", OAuthController, :create_authorization)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
get("/registration_details", OAuthController, :registration_details)
scope [] do
pipe_through(:browser)
get("/prepare_request", OAuthController, :prepare_request)
get("/:provider", OAuthController, :request)
get("/:provider/callback", OAuthController, :callback)
post("/register", OAuthController, :register)
end
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
scope [] do
pipe_through(:oauth_read)
get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
get("/accounts/relationships", MastodonAPIController, :relationships)
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
get("/follow_requests", MastodonAPIController, :follow_requests)
get("/blocks", MastodonAPIController, :blocks)
get("/mutes", MastodonAPIController, :mutes)
get("/timelines/home", MastodonAPIController, :home_timeline)
get("/timelines/direct", MastodonAPIController, :dm_timeline)
get("/favourites", MastodonAPIController, :favourites)
get("/bookmarks", MastodonAPIController, :bookmarks)
post("/notifications/clear", MastodonAPIController, :clear_notifications)
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
get("/notifications", MastodonAPIController, :notifications)
get("/notifications/:id", MastodonAPIController, :get_notification)
delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple)
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
get("/lists", MastodonAPIController, :get_lists)
get("/lists/:id", MastodonAPIController, :get_list)
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
get("/domain_blocks", MastodonAPIController, :domain_blocks)
get("/filters", MastodonAPIController, :get_filters)
get("/suggestions", MastodonAPIController, :suggestions)
get("/conversations", MastodonAPIController, :conversations)
post("/conversations/:id/read", MastodonAPIController, :conversation_read)
get("/endorsements", MastodonAPIController, :empty_array)
get("/pleroma/flavour", MastodonAPIController, :get_flavour)
end
scope [] do
pipe_through(:oauth_write)
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
post("/statuses", MastodonAPIController, :post_status)
delete("/statuses/:id", MastodonAPIController, :delete_status)
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media)
delete("/lists/:id", MastodonAPIController, :delete_list)
post("/lists", MastodonAPIController, :create_list)
put("/lists/:id", MastodonAPIController, :rename_list)
post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
post("/filters", MastodonAPIController, :create_filter)
get("/filters/:id", MastodonAPIController, :get_filter)
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
post("/reports", MastodonAPIController, :reports)
end
scope [] do
pipe_through(:oauth_follow)
post("/follows", MastodonAPIController, :follow)
post("/accounts/:id/follow", MastodonAPIController, :follow)
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
post("/accounts/:id/block", MastodonAPIController, :block)
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
post("/accounts/:id/mute", MastodonAPIController, :mute)
post("/accounts/:id/unmute", MastodonAPIController, :unmute)
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
end
scope [] do
pipe_through(:oauth_push)
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
end
end
scope "/api/web", Pleroma.Web.MastodonAPI do
pipe_through([:authenticated_api, :oauth_write])
put("/settings", MastodonAPIController, :put_settings)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
post("/accounts", MastodonAPIController, :account_register)
get("/instance", MastodonAPIController, :masto_instance)
get("/instance/peers", MastodonAPIController, :peers)
post("/apps", MastodonAPIController, :create_app)
get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
get("/custom_emojis", MastodonAPIController, :custom_emojis)
get("/statuses/:id/card", MastodonAPIController, :status_card)
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
get("/trends", MastodonAPIController, :empty_array)
get("/accounts/search", MastodonAPIController, :account_search)
scope [] do
- pipe_through(:oauth_read_or_unauthenticated)
+ pipe_through(:oauth_read_or_public)
get("/timelines/public", MastodonAPIController, :public_timeline)
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
get("/accounts/:id/followers", MastodonAPIController, :followers)
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
get("/search", MastodonAPIController, :search)
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
- pipe_through([:api, :oauth_read_or_unauthenticated])
+ pipe_through([:api, :oauth_read_or_public])
get("/search", MastodonAPIController, :search2)
end
scope "/api", Pleroma.Web do
pipe_through(:config)
get("/help/test", TwitterAPI.UtilController, :help_test)
post("/help/test", TwitterAPI.UtilController, :help_test)
get("/statusnet/config", TwitterAPI.UtilController, :config)
get("/statusnet/version", TwitterAPI.UtilController, :version)
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
end
scope "/api", Pleroma.Web do
pipe_through(:api)
post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
get(
"/account/confirm_email/:user_id/:token",
TwitterAPI.Controller,
:confirm_email,
as: :confirm_email
)
scope [] do
- pipe_through(:oauth_read_or_unauthenticated)
+ pipe_through(:oauth_read_or_public)
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/users/show", TwitterAPI.Controller, :show_user)
get("/statuses/followers", TwitterAPI.Controller, :followers)
get("/statuses/friends", TwitterAPI.Controller, :friends)
get("/statuses/blocks", TwitterAPI.Controller, :blocks)
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
end
end
scope "/api", Pleroma.Web do
- pipe_through([:api, :oauth_read_or_unauthenticated])
+ pipe_through([:api, :oauth_read_or_public])
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
get(
"/statuses/public_and_external_timeline",
TwitterAPI.Controller,
:public_and_external_timeline
)
get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
end
scope "/api", Pleroma.Web, as: :twitter_api_search do
- pipe_through([:api, :oauth_read_or_unauthenticated])
+ pipe_through([:api, :oauth_read_or_public])
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
pipe_through(:authenticated_api)
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
scope [] do
pipe_through(:oauth_read)
get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
get("/friends/ids", TwitterAPI.Controller, :friends_ids)
get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)
get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array)
get("/externalprofile/show", TwitterAPI.Controller, :external_profile)
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end
scope [] do
pipe_through(:oauth_write)
post("/account/update_profile", TwitterAPI.Controller, :update_profile)
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)
post("/statuses/update", TwitterAPI.Controller, :status_update)
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
post("/statuses/pin/:id", TwitterAPI.Controller, :pin)
post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin)
post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
post("/media/upload", TwitterAPI.Controller, :upload_json)
post("/media/metadata/create", TwitterAPI.Controller, :update_media)
post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
post("/favorites/create", TwitterAPI.Controller, :favorite)
post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)
post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar)
end
scope [] do
pipe_through(:oauth_follow)
post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
post("/friendships/create", TwitterAPI.Controller, :follow)
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
post("/blocks/create", TwitterAPI.Controller, :block)
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
end
end
pipeline :ap_relay do
plug(:accepts, ["activity+json", "json"])
end
pipeline :ostatus do
plug(:accepts, ["html", "xml", "atom", "activity+json", "json"])
end
pipeline :oembed do
plug(:accepts, ["json", "xml"])
end
scope "/", Pleroma.Web do
pipe_through(:ostatus)
get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
end
scope "/", Pleroma.Web do
pipe_through(:oembed)
get("/oembed", OEmbed.OEmbedController, :url)
end
pipeline :activitypub do
plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
end
scope "/", Pleroma.Web.ActivityPub do
# XXX: not really ostatus
pipe_through(:ostatus)
get("/users/:nickname/followers", ActivityPubController, :followers)
get("/users/:nickname/following", ActivityPubController, :following)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
end
pipeline :activitypub_client do
plug(:accepts, ["activity+json", "json"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
scope [] do
pipe_through(:oauth_read)
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
end
scope [] do
pipe_through(:oauth_write)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
end
end
scope "/relay", Pleroma.Web.ActivityPub do
pipe_through(:ap_relay)
get("/", ActivityPubController, :relay)
end
scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub)
post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
scope "/.well-known", Pleroma.Web do
pipe_through(:well_known)
get("/host-meta", WebFinger.WebFingerController, :host_meta)
get("/webfinger", WebFinger.WebFingerController, :webfinger)
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
end
scope "/nodeinfo", Pleroma.Web do
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
end
scope "/", Pleroma.Web.MastodonAPI do
pipe_through(:mastodon_html)
get("/web/login", MastodonAPIController, :login)
delete("/auth/sign_out", MastodonAPIController, :logout)
scope [] do
- pipe_through(:oauth_read_or_unauthenticated)
+ pipe_through(:oauth_read_or_public)
get("/web/*path", MastodonAPIController, :index)
end
end
pipeline :remote_media do
end
scope "/proxy/", Pleroma.Web.MediaProxy do
pipe_through(:remote_media)
get("/:sig/:url", MediaProxyController, :remote)
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
if Mix.env() == :dev do
scope "/dev" do
pipe_through([:mailbox_preview])
forward("/mailbox", Plug.Swoosh.MailboxPreview, base_path: "/dev/mailbox")
end
end
scope "/", Fallback do
get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
get("/*path", RedirectController, :redirector)
options("/*path", RedirectController, :empty)
end
end
defmodule Fallback.RedirectController do
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Web.Metadata
def redirector(conn, _params, code \\ 200) do
conn
|> put_resp_content_type("text/html")
|> send_file(code, index_file_path())
end
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
redirector_with_meta(conn, %{user: user})
else
nil ->
redirector(conn, params)
end
end
def redirector_with_meta(conn, params) do
{:ok, index_content} = File.read(index_file_path())
tags = Metadata.build_tags(params)
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
conn
|> put_resp_content_type("text/html")
|> send_resp(200, response)
end
def index_file_path do
Pleroma.Plugs.InstanceStatic.file_path("index.html")
end
def registration_page(conn, params) do
redirector(conn, params)
end
def empty(conn, _params) do
conn
|> put_status(204)
|> text("")
end
end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 89c55ef0e..489170d80 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -1,405 +1,379 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.UtilController do
use Pleroma.Web, :controller
require Logger
alias Comeonin.Pbkdf2
alias Pleroma.Activity
alias Pleroma.Emoji
alias Pleroma.Notification
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
def show_password_reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
%User{} = user <- User.get_cached_by_id(token.user_id) do
render(conn, "password_reset.html", %{
token: token,
user: user
})
else
_e -> render(conn, "invalid_token.html")
end
end
def password_reset(conn, %{"data" => data}) do
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
render(conn, "password_reset_success.html")
else
_e -> render(conn, "password_reset_failed.html")
end
end
def help_test(conn, _params) do
json(conn, "ok")
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error: "Could not find user"
})
end
end
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error: "Something went wrong."
})
end
end
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
if is_status?(acct) do
{:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
%Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
redirect(conn, to: "/notice/#{activity_id}")
else
{err, followee} = OStatus.find_or_make_user(acct)
avatar = User.avatar_url(followee)
name = followee.nickname
id = followee.id
if !!user do
conn
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
else
conn
|> render("follow_login.html", %{
error: false,
acct: acct,
avatar: avatar,
name: name,
id: id
})
end
end
end
defp is_status?(acct) do
case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
{:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
true
_ ->
false
end
end
def do_remote_follow(conn, %{
"authorization" => %{"name" => username, "password" => password, "id" => id}
}) do
followee = User.get_cached_by_id(id)
avatar = User.avatar_url(followee)
name = followee.nickname
with %User{} = user <- User.get_cached_by_nickname(username),
true <- Pbkdf2.checkpw(password, user.password_hash),
%User{} = _followed <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
render(conn, "followed.html", %{error: false})
_e ->
conn
|> render("follow_login.html", %{
error: "Wrong username or password",
id: id,
name: name,
avatar: avatar
})
end
end
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
with %User{} = followee <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
conn
|> render("followed.html", %{error: false})
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
conn
|> render("followed.html", %{error: inspect(e)})
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 config(conn, _params) do
instance = Pleroma.Config.get(:instance)
- instance_fe = Pleroma.Config.get(:fe)
- instance_chat = Pleroma.Config.get(:chat)
case get_format(conn) do
"xml" ->
response = """
<config>
<site>
<name>#{Keyword.get(instance, :name)}</name>
<site>#{Web.base_url()}</site>
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
</site>
</config>
"""
conn
|> put_resp_content_type("application/xml")
|> send_resp(200, response)
_ ->
vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
uploadlimit = %{
uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
}
data = %{
name: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(instance, :limit)),
uploadlimit: uploadlimit,
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
vapidPublicKey: vapid_public_key,
accountActivationRequired:
if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"),
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0"),
safeDMMentionsEnabled:
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
}
- pleroma_fe =
- if instance_fe do
- %{
- theme: Keyword.get(instance_fe, :theme),
- background: Keyword.get(instance_fe, :background),
- logo: Keyword.get(instance_fe, :logo),
- logoMask: Keyword.get(instance_fe, :logo_mask),
- logoMargin: Keyword.get(instance_fe, :logo_margin),
- redirectRootNoLogin: Keyword.get(instance_fe, :redirect_root_no_login),
- redirectRootLogin: Keyword.get(instance_fe, :redirect_root_login),
- chatDisabled: !Keyword.get(instance_chat, :enabled),
- showInstanceSpecificPanel: Keyword.get(instance_fe, :show_instance_panel),
- scopeOptionsEnabled: Keyword.get(instance_fe, :scope_options_enabled),
- formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
- collapseMessageWithSubject:
- Keyword.get(instance_fe, :collapse_message_with_subject),
- hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
- hideUserStats: Keyword.get(instance_fe, :hide_user_stats),
- scopeCopy: Keyword.get(instance_fe, :scope_copy),
- subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior),
- alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input)
- }
- else
- Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
- end
+ pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
managed_config = Keyword.get(instance, :managed_config)
data =
if managed_config do
data |> Map.put("pleromafe", pleroma_fe)
else
data
end
json(conn, %{site: data})
end
end
def frontend_configurations(conn, _params) do
config =
Pleroma.Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
json(conn, config)
end
def version(conn, _params) do
version = Pleroma.Application.named_version()
case get_format(conn) do
"xml" ->
response = "<version>#{version}</version>"
conn
|> put_resp_content_type("application/xml")
|> send_resp(200, response)
_ ->
json(conn, version)
end
end
def emoji(conn, _params) do
emoji =
Emoji.get_all()
|> Enum.map(fn {short_code, path, tags} ->
{short_code, %{image_url: path, tags: tags}}
end)
|> Enum.into(%{})
json(conn, emoji)
end
def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
with {:ok, _} <- User.update_notification_settings(user, params) do
json(conn, %{status: "success"})
end
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
follow_import(conn, %{"list" => File.read!(listfile.path)})
end
def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
with lines <- String.split(list, "\n"),
followed_identifiers <-
Enum.map(lines, fn line ->
String.split(line, ",") |> List.first()
end)
|> List.delete("Account address") do
PleromaJobQueue.enqueue(:background, User, [
:follow_import,
follower,
followed_identifiers
])
json(conn, "job started")
end
end
def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
blocks_import(conn, %{"list" => File.read!(listfile.path)})
end
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
with blocked_identifiers <- String.split(list) do
PleromaJobQueue.enqueue(:background, User, [
:blocks_import,
blocker,
blocked_identifiers
])
json(conn, "job started")
end
end
def change_password(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
with {:ok, _user} <-
User.reset_password(user, %{
password: params["new_password"],
password_confirmation: params["new_password_confirmation"]
}) do
json(conn, %{status: "success"})
else
{:error, changeset} ->
{_, {error, _}} = Enum.at(changeset.errors, 0)
json(conn, %{error: "New password #{error}."})
_ ->
json(conn, %{error: "Unable to change password."})
end
{:error, msg} ->
json(conn, %{error: msg})
end
end
def delete_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
User.delete(user)
json(conn, %{status: "success"})
{:error, msg} ->
json(conn, %{error: msg})
end
end
def disable_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
User.deactivate_async(user)
json(conn, %{status: "success"})
{:error, msg} ->
json(conn, %{error: msg})
end
end
def captcha(conn, _params) do
json(conn, Pleroma.Captcha.new())
end
def healthcheck(conn, _params) do
info =
if Pleroma.Config.get([:instance, :healthcheck]) do
Pleroma.Healthcheck.system_info()
else
%{}
end
conn =
if info[:healthy] do
conn
else
Plug.Conn.put_status(conn, :service_unavailable)
end
json(conn, info)
end
end
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index d084ad734..44bcafe0e 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -1,359 +1,359 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.ActivityView do
use Pleroma.Web, :view
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.Web.TwitterAPI.UserView
import Ecto.Query
require Logger
defp query_context_ids([]), do: []
defp query_context_ids(contexts) do
query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts))
Repo.all(query)
end
defp query_users([]), do: []
defp query_users(user_ids) do
query = from(user in User, where: user.ap_id in ^user_ids)
Repo.all(query)
end
defp collect_context_ids(activities) do
_contexts =
activities
|> Enum.reject(& &1.data["context_id"])
|> Enum.map(fn %{data: data} ->
data["context"]
end)
|> Enum.filter(& &1)
|> query_context_ids()
|> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc ->
Map.put(acc, ap_id, id)
end)
end
defp collect_users(activities) do
activities
|> Enum.map(fn activity ->
case activity.data do
data = %{"type" => "Follow"} ->
[data["actor"], data["object"]]
data ->
[data["actor"]]
end ++ activity.recipients
end)
|> List.flatten()
|> Enum.uniq()
|> query_users()
|> Enum.reduce(%{}, fn user, acc ->
Map.put(acc, user.ap_id, user)
end)
end
defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id),
do: context_id
defp get_context_id(%{data: %{"context" => nil}}, _), do: nil
defp get_context_id(%{data: %{"context" => context}}, options) do
cond do
id = options[:context_ids][context] -> id
true -> Utils.context_to_conversation_id(context)
end
end
defp get_context_id(_, _), do: nil
defp get_user(ap_id, opts) do
cond do
user = opts[:users][ap_id] ->
user
String.ends_with?(ap_id, "/followers") ->
nil
ap_id == "https://www.w3.org/ns/activitystreams#Public" ->
nil
user = User.get_cached_by_ap_id(ap_id) ->
user
user = User.get_by_guessed_nickname(ap_id) ->
user
true ->
User.error_user(ap_id)
end
end
def render("index.json", opts) do
context_ids = collect_context_ids(opts.activities)
users = collect_users(opts.activities)
opts =
opts
|> Map.put(:context_ids, context_ids)
|> Map.put(:users, users)
safe_render_many(
opts.activities,
ActivityView,
"activity.json",
opts
)
end
def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] |> Utils.date_to_asctime()
%{
"id" => activity.id,
"uri" => activity.data["object"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => "deleted notice {{tag",
"text" => "deleted notice {{tag",
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "delete"
}
end
def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at)
created_at = created_at |> Utils.date_to_asctime()
followed = get_user(activity.data["object"], opts)
text = "#{user.nickname} started following #{followed.nickname}"
%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "follow"
}
end
def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] |> Utils.date_to_asctime()
announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
text = "#{user.nickname} repeated a status."
retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity}))
%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=note",
"created_at" => created_at,
"retweeted_status" => retweeted_status,
"statusnet_conversation_id" => get_context_id(announced_activity, opts),
"external_url" => activity.data["id"],
"activity_type" => "repeat"
}
end
def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
liked_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
liked_activity_id = if liked_activity, do: liked_activity.id, else: nil
created_at =
activity.data["published"]
|> Utils.date_to_asctime()
text = "#{user.nickname} favorited a status."
favorited_status =
if liked_activity,
do: render("activity.json", Map.merge(opts, %{activity: liked_activity})),
else: nil
%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
"created_at" => created_at,
"favorited_status" => favorited_status,
"in_reply_to_status_id" => liked_activity_id,
"external_url" => activity.data["id"],
"activity_type" => "like"
}
end
def render(
"activity.json",
%{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts
) do
user = get_user(activity.data["actor"], opts)
object = Object.normalize(object_id)
created_at = object.data["published"] |> Utils.date_to_asctime()
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || [])
pinned = activity.id in user.info.pinned_activities
attentions =
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Enum.map(fn ap_id -> get_user(ap_id, opts) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
conversation_id = get_context_id(activity, opts)
tags = object.data["tag"] || []
possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
{summary, content} = render_content(object.data)
html =
content
|> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"twitterapi:content"
)
|> Formatter.emojify(object.data["emoji"])
text =
if content do
content
|> String.replace(~r/<br\s?\/?>/, "\n")
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
else
""
end
reply_parent = Activity.get_in_reply_to_activity(activity)
reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
summary = HTML.strip_tags(summary)
card =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)
%{
"id" => activity.id,
"uri" => object.data["id"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => html,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => true,
"created_at" => created_at,
"in_reply_to_status_id" => reply_parent && reply_parent.id,
"in_reply_to_screen_name" => reply_user && reply_user.nickname,
"in_reply_to_profileurl" => User.profile_url(reply_user),
"in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
"in_reply_to_user_id" => reply_user && reply_user.id,
"statusnet_conversation_id" => conversation_id,
"attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
"attentions" => attentions,
"fave_num" => like_count,
"repeat_num" => announcement_count,
"favorited" => !!favorited,
"repeated" => !!repeated,
"pinned" => pinned,
"external_url" => object.data["external_url"] || object.data["id"],
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
- "visibility" => StatusView.get_visibility(object),
+ "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
"card" => card,
"muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user)
}
end
def render("activity.json", %{activity: unhandled_activity}) do
Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}")
nil
end
def render_content(%{"type" => "Note"} = object) do
summary = object["summary"]
content =
if !!summary and summary != "" do
"<p>#{summary}</p>#{object["content"]}"
else
object["content"]
end
{summary, content}
end
def render_content(%{"type" => object_type} = object)
when object_type in ["Article", "Page", "Video"] do
summary = object["name"] || object["summary"]
content =
if !!summary and summary != "" and is_bitstring(object["url"]) do
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
else
object["content"]
end
{summary, content}
end
def render_content(object) do
summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
content = "<p>#{summary}</p>#{object["content"]}"
{summary, content}
end
end
diff --git a/mix.exs b/mix.exs
index b7b9d534d..1396d0072 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,182 +1,187 @@
defmodule Pleroma.Mixfile do
use Mix.Project
def project do
[
app: :pleroma,
version: version("0.9.0"),
elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: true],
xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
+ test_coverage: [tool: ExCoveralls],
# Docs
name: "Pleroma",
homepage_url: "https://pleroma.social/",
source_url: "https://git.pleroma.social/pleroma/pleroma",
docs: [
source_url_pattern:
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
logo: "priv/static/static/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [
"Installation manuals": Path.wildcard("docs/installation/*.md"),
Configuration: Path.wildcard("docs/config/*.md"),
Administration: Path.wildcard("docs/admin/*.md"),
"Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/api/*.md")
],
main: "readme",
output: "priv/static/doc"
]
]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {Pleroma.Application, []},
extra_applications: [:logger, :runtime_tools, :comeonin, :esshd, :quack],
included_applications: [:ex_syslogger]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
oauth_deps =
for s <- oauth_strategies,
do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
[
{:phoenix, "~> 1.4.1"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~>3.0.5"},
{:postgrex, ">= 0.13.5"},
{:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"},
{:pbkdf2_elixir, "~> 0.12.3"},
{:trailing_format_plug, "~> 0.0.7"},
{:html_sanitize_ex, "~> 1.3.0"},
{:html_entities, "~> 0.4"},
{:phoenix_html, "~> 2.10"},
{:calendar, "~> 0.17.4"},
{:cachex, "~> 3.0.2"},
{:httpoison, "~> 1.2.0"},
{:poison, "~> 3.0", override: true},
{:tesla, "~> 1.2"},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.0"},
{:ex_aws_s3, "~> 2.0"},
{:earmark, "~> 1.3"},
{:bbcode, "~> 0.1"},
{:ex_machina, "~> 2.3", only: :test},
{:credo, "~> 0.9.3", only: [:dev, :test]},
{:mock, "~> 0.3.3", only: :test},
{:crypt,
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
{:cors_plug, "~> 1.5"},
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.20"},
{:gen_smtp, "~> 0.13"},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
{:floki, "~> 0.20.0"},
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
{:timex, "~> 3.5"},
{:ueberauth, "~> 0.4"},
{:auto_linker,
git: "https://git.pleroma.social/pleroma/auto_linker.git",
ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
+ {:http_signatures,
+ git: "https://git.pleroma.social/pleroma/http_signatures.git",
+ ref: "9789401987096ead65646b52b5a2ca6bf52fc531"},
{:pleroma_job_queue, "~> 0.2.0"},
{:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.2"},
{:prometheus_ecto, "~> 1.4"},
{:prometheus_process_collector, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"},
{:benchee, "~> 1.0"},
{:esshd, "~> 0.1.0"},
{:ex_rated, "~> 1.2"},
- {:plug_static_index_html, "~> 1.0.0"}
+ {:plug_static_index_html, "~> 1.0.0"},
+ {:excoveralls, "~> 0.11.1", only: :test}
] ++ oauth_deps
end
# Aliases are shortcuts or tasks specific to the current project.
# For example, to create, migrate and run the seeds file at once:
#
# $ mix ecto.setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"]
]
end
# Builds a version string made of:
# * the application version
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
# * build info:
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
# * the mix environment if different than prod
defp version(version) do
{git_tag, git_pre_release} =
with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"]),
tag = String.trim(tag),
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
describe = String.trim(describe),
ahead <- String.replace(describe, tag, "") do
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
else
_ -> {nil, nil}
end
if git_tag && version != git_tag do
Mix.shell().error(
"Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}"
)
end
build_name =
cond do
name = Application.get_env(:pleroma, :build_name) -> name
name = System.get_env("PLEROMA_BUILD_NAME") -> name
true -> nil
end
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
build =
[build_name, env_name]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join("-")
|> (fn
"" -> nil
string -> "+" <> string
end).()
[version, git_pre_release, build]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join()
end
end
diff --git a/mix.lock b/mix.lock
index 0b24818c5..cd2d2370a 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,90 +1,92 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "c00c4e75b35367fa42c95ffd9b8c455bf9995829", [ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"]},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"db_connection": {:hex, :db_connection, "2.0.5", "ddb2ba6761a08b2bb9ca0e7d260e8f4dd39067426d835c24491a321b7f92a4da", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rated": {:hex, :ex_rated, "1.3.2", "6aeb32abb46ea6076f417a9ce8cb1cf08abf35fb2d42375beaad4dd72b550bf1", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
+ "excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
+ "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]},
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.1", "801f9d632808657f1f7c657c8bbe624caaf2ba91429123ebe3801598aea4c3d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
}
diff --git a/test/plugs/ensure_public_or_authenticated_plug_test.exs b/test/plugs/ensure_public_or_authenticated_plug_test.exs
new file mode 100644
index 000000000..ce5d77ff7
--- /dev/null
+++ b/test/plugs/ensure_public_or_authenticated_plug_test.exs
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Config
+ alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.User
+
+ test "it halts if not public and no user is assigned", %{conn: conn} do
+ set_public_to(false)
+
+ conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it continues if public", %{conn: conn} do
+ set_public_to(true)
+
+ ret_conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ test "it continues if a user is assigned, even if not public", %{conn: conn} do
+ set_public_to(false)
+
+ conn =
+ conn
+ |> assign(:user, %User{})
+
+ ret_conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ defp set_public_to(value) do
+ orig = Config.get!([:instance, :public])
+ Config.put([:instance, :public], value)
+
+ on_exit(fn ->
+ Config.put([:instance, :public], orig)
+ end)
+ end
+end
diff --git a/test/plugs/http_signature_plug_test.exs b/test/plugs/http_signature_plug_test.exs
index 6a00dd4fd..efd811df7 100644
--- a/test/plugs/http_signature_plug_test.exs
+++ b/test/plugs/http_signature_plug_test.exs
@@ -1,48 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Web.HTTPSignatures
alias Pleroma.Web.Plugs.HTTPSignaturePlug
import Plug.Conn
import Mock
test "it call HTTPSignatures to check validity if the actor sighed it" do
params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params)
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert called(HTTPSignatures.validate_conn(:_))
end
end
test "bails out early if the signature isn't by the activity actor" do
params = %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"}
conn = build_conn(:get, "/doesntmattter", params)
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == false
refute called(HTTPSignatures.validate_conn(:_))
end
end
end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index 24b96c4aa..9c03c8be2 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -1,98 +1,110 @@
defmodule Pleroma.Web.ActivityPub.VisibilityTest do
use Pleroma.DataCase
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup do
user = insert(:user)
mentioned = insert(:user)
following = insert(:user)
unrelated = insert(:user)
{:ok, following} = Pleroma.User.follow(following, user)
{:ok, public} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"})
{:ok, private} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"})
{:ok, direct} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"})
{:ok, unlisted} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"})
%{
public: public,
private: private,
direct: direct,
unlisted: unlisted,
user: user,
mentioned: mentioned,
following: following,
unrelated: unrelated
}
end
test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
assert Visibility.is_direct?(direct)
refute Visibility.is_direct?(public)
refute Visibility.is_direct?(private)
refute Visibility.is_direct?(unlisted)
end
test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
refute Visibility.is_public?(direct)
assert Visibility.is_public?(public)
refute Visibility.is_public?(private)
assert Visibility.is_public?(unlisted)
end
test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
refute Visibility.is_private?(direct)
refute Visibility.is_private?(public)
assert Visibility.is_private?(private)
refute Visibility.is_private?(unlisted)
end
test "visible_for_user?", %{
public: public,
private: private,
direct: direct,
unlisted: unlisted,
user: user,
mentioned: mentioned,
following: following,
unrelated: unrelated
} do
# All visible to author
assert Visibility.visible_for_user?(public, user)
assert Visibility.visible_for_user?(private, user)
assert Visibility.visible_for_user?(unlisted, user)
assert Visibility.visible_for_user?(direct, user)
# All visible to a mentioned user
assert Visibility.visible_for_user?(public, mentioned)
assert Visibility.visible_for_user?(private, mentioned)
assert Visibility.visible_for_user?(unlisted, mentioned)
assert Visibility.visible_for_user?(direct, mentioned)
# DM not visible for just follower
assert Visibility.visible_for_user?(public, following)
assert Visibility.visible_for_user?(private, following)
assert Visibility.visible_for_user?(unlisted, following)
refute Visibility.visible_for_user?(direct, following)
# Public and unlisted visible for unrelated user
assert Visibility.visible_for_user?(public, unrelated)
assert Visibility.visible_for_user?(unlisted, unrelated)
refute Visibility.visible_for_user?(private, unrelated)
refute Visibility.visible_for_user?(direct, unrelated)
end
+
+ test "get_visibility", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted
+ } do
+ assert Visibility.get_visibility(public) == "public"
+ assert Visibility.get_visibility(private) == "private"
+ assert Visibility.get_visibility(direct) == "direct"
+ assert Visibility.get_visibility(unlisted) == "unlisted"
+ end
end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 11f3c8357..23e89d685 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -1,283 +1,305 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPITest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
har = insert(:user)
jafnhar = insert(:user)
tridi = insert(:user)
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
{:ok, activity} =
CommonAPI.post(har, %{
"status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
"visibility" => "direct"
})
refute tridi.ap_id in activity.recipients
assert jafnhar.ap_id in activity.recipients
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
end
test "it de-duplicates tags" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
object = Object.normalize(activity.data["object"])
assert object.data["tag"] == ["2hu"]
end
test "it adds emoji in the object" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => ":firefox:"})
assert Object.normalize(activity).data["emoji"]["firefox"]
end
test "it adds emoji when updating profiles" do
user = insert(:user, %{name: ":firefox:"})
CommonAPI.update(user)
user = User.get_cached_by_ap_id(user.ap_id)
[firefox] = user.info.source_data["tag"]
assert firefox["name"] == ":firefox:"
end
describe "posting" do
test "it filters out obviously bad tags when accepting a post as HTML" do
user = insert(:user)
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
{:ok, activity} =
CommonAPI.post(user, %{
"status" => post,
"content_type" => "text/html"
})
object = Object.normalize(activity.data["object"])
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end
test "it filters out obviously bad tags when accepting a post as Markdown" do
user = insert(:user)
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
{:ok, activity} =
CommonAPI.post(user, %{
"status" => post,
"content_type" => "text/markdown"
})
object = Object.normalize(activity.data["object"])
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end
+ test "it does not allow replies to direct messages that are not direct messages themselves" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
+
+ assert {:ok, _} =
+ CommonAPI.post(user, %{
+ "status" => "suya..",
+ "visibility" => "direct",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ Enum.each(["public", "private", "unlisted"], fn visibility ->
+ assert {:error, {:private_to_public, _}} =
+ CommonAPI.post(user, %{
+ "status" => "suya..",
+ "visibility" => visibility,
+ "in_reply_to_status_id" => activity.id
+ })
+ end)
+ end
+
test "it allows to address a list" do
user = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user)
list_ap_id = Pleroma.List.ap_id(user, list.id)
{:ok, activity} =
CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
assert activity.data["bcc"] == [list_ap_id]
assert activity.recipients == [list_ap_id, user.ap_id]
end
end
describe "reactions" do
test "repeating a status" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
end
test "favoriting a status" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
end
test "retweeting a status twice returns an error" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
{:error, _} = CommonAPI.repeat(activity.id, user)
end
test "favoriting a status twice returns an error" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
{:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
{:error, _} = CommonAPI.favorite(activity.id, user)
end
end
describe "pinned statuses" do
setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
[user: user, activity: activity]
end
test "pin status", %{user: user, activity: activity} do
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
id = activity.id
user = refresh_record(user)
assert %User{info: %{pinned_activities: [^id]}} = user
end
test "only self-authored can be pinned", %{activity: activity} do
user = insert(:user)
assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
end
test "max pinned statuses", %{user: user, activity: activity_one} do
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
user = refresh_record(user)
assert {:error, "You have already pinned the maximum number of statuses"} =
CommonAPI.pin(activity_two.id, user)
end
test "unpin status", %{user: user, activity: activity} do
{:ok, activity} = CommonAPI.pin(activity.id, user)
user = refresh_record(user)
assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
user = refresh_record(user)
assert %User{info: %{pinned_activities: []}} = user
end
test "should unpin when deleting a status", %{user: user, activity: activity} do
{:ok, activity} = CommonAPI.pin(activity.id, user)
user = refresh_record(user)
assert {:ok, _} = CommonAPI.delete(activity.id, user)
user = refresh_record(user)
assert %User{info: %{pinned_activities: []}} = user
end
end
describe "mute tests" do
setup do
user = insert(:user)
activity = insert(:note_activity)
[user: user, activity: activity]
end
test "add mute", %{user: user, activity: activity} do
{:ok, _} = CommonAPI.add_mute(user, activity)
assert CommonAPI.thread_muted?(user, activity)
end
test "remove mute", %{user: user, activity: activity} do
CommonAPI.add_mute(user, activity)
{:ok, _} = CommonAPI.remove_mute(user, activity)
refute CommonAPI.thread_muted?(user, activity)
end
test "check that mutes can't be duplicate", %{user: user, activity: activity} do
CommonAPI.add_mute(user, activity)
{:error, _} = CommonAPI.add_mute(user, activity)
end
end
describe "reports" do
test "creates a report" do
reporter = insert(:user)
target_user = insert(:user)
{:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
reporter_ap_id = reporter.ap_id
target_ap_id = target_user.ap_id
activity_ap_id = activity.data["id"]
comment = "foobar"
report_data = %{
"account_id" => target_user.id,
"comment" => comment,
"status_ids" => [activity.id]
}
assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
assert %Activity{
actor: ^reporter_ap_id,
data: %{
"type" => "Flag",
"content" => ^comment,
"object" => [^target_ap_id, ^activity_ap_id]
}
} = flag_activity
end
end
describe "reblog muting" do
setup do
muter = insert(:user)
muted = insert(:user)
[muter: muter, muted: muted]
end
test "add a reblog mute", %{muter: muter, muted: muted} do
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
assert Pleroma.User.showing_reblogs?(muter, muted) == false
end
test "remove a reblog mute", %{muter: muter, muted: muted} do
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
{:ok, muter} = CommonAPI.show_reblogs(muter, muted)
assert Pleroma.User.showing_reblogs?(muter, muted) == true
end
end
end
diff --git a/test/web/http_sigs/http_sig_test.exs b/test/web/http_sigs/http_sig_test.exs
deleted file mode 100644
index c4d2eaf78..000000000
--- a/test/web/http_sigs/http_sig_test.exs
+++ /dev/null
@@ -1,194 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-# http signatures
-# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
-defmodule Pleroma.Web.HTTPSignaturesTest do
- use Pleroma.DataCase
- alias Pleroma.Web.HTTPSignatures
- import Pleroma.Factory
- import Tesla.Mock
-
- setup do
- mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
- :ok
- end
-
- @public_key hd(:public_key.pem_decode(File.read!("test/web/http_sigs/pub.key")))
- |> :public_key.pem_entry_decode()
-
- @headers %{
- "(request-target)" => "post /foo?param=value&pet=dog",
- "host" => "example.com",
- "date" => "Thu, 05 Jan 2014 21:31:40 GMT",
- "content-type" => "application/json",
- "digest" => "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
- "content-length" => "18"
- }
-
- @default_signature """
- keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
- """
-
- @basic_signature """
- keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="HUxc9BS3P/kPhSmJo+0pQ4IsCo007vkv6bUm4Qehrx+B1Eo4Mq5/6KylET72ZpMUS80XvjlOPjKzxfeTQj4DiKbAzwJAb4HX3qX6obQTa00/qPDXlMepD2JtTw33yNnm/0xV7fQuvILN/ys+378Ysi082+4xBQFwvhNvSoVsGv4="
- """
-
- @all_headers_signature """
- keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0="
- """
-
- test "split up a signature" do
- expected = %{
- "keyId" => "Test",
- "algorithm" => "rsa-sha256",
- "signature" =>
- "jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=",
- "headers" => ["date"]
- }
-
- assert HTTPSignatures.split_signature(@default_signature) == expected
- end
-
- test "validates the default case" do
- signature = HTTPSignatures.split_signature(@default_signature)
- assert HTTPSignatures.validate(@headers, signature, @public_key)
- end
-
- test "validates the basic case" do
- signature = HTTPSignatures.split_signature(@basic_signature)
- assert HTTPSignatures.validate(@headers, signature, @public_key)
- end
-
- test "validates the all-headers case" do
- signature = HTTPSignatures.split_signature(@all_headers_signature)
- assert HTTPSignatures.validate(@headers, signature, @public_key)
- end
-
- test "it contructs a signing string" do
- expected = "date: Thu, 05 Jan 2014 21:31:40 GMT\ncontent-length: 18"
- assert expected == HTTPSignatures.build_signing_string(@headers, ["date", "content-length"])
- end
-
- test "it validates a conn" do
- public_key_pem =
- "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGb42rPZIapY4Hfhxrgn\nxKVJczBkfDviCrrYaYjfGxawSw93dWTUlenCVTymJo8meBlFgIQ70ar4rUbzl6GX\nMYvRdku072d1WpglNHXkjKPkXQgngFDrh2sGKtNB/cEtJcAPRO8OiCgPFqRtMiNM\nc8VdPfPdZuHEIZsJ/aUM38EnqHi9YnVDQik2xxDe3wPghOhqjxUM6eLC9jrjI+7i\naIaEygUdyst9qVg8e2FGQlwAeS2Eh8ygCxn+bBlT5OyV59jSzbYfbhtF2qnWHtZy\nkL7KOOwhIfGs7O9SoR2ZVpTEQ4HthNzainIe/6iCR5HGrao/T8dygweXFYRv+k5A\nPQIDAQAB\n-----END PUBLIC KEY-----\n"
-
- [public_key] = :public_key.pem_decode(public_key_pem)
-
- public_key =
- public_key
- |> :public_key.pem_entry_decode()
-
- conn = %{
- req_headers: [
- {"host", "localtesting.pleroma.lol"},
- {"connection", "close"},
- {"content-length", "2316"},
- {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
- {"date", "Sun, 10 Dec 2017 14:23:49 GMT"},
- {"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="},
- {"content-type", "application/activity+json"},
- {"(request-target)", "post /users/demiurge/inbox"},
- {"signature",
- "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""}
- ]
- }
-
- assert HTTPSignatures.validate_conn(conn, public_key)
- end
-
- test "it validates a conn and fetches the key" do
- conn = %{
- params: %{"actor" => "http://mastodon.example.org/users/admin"},
- req_headers: [
- {"host", "localtesting.pleroma.lol"},
- {"x-forwarded-for", "127.0.0.1"},
- {"connection", "close"},
- {"content-length", "2307"},
- {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
- {"date", "Sun, 11 Feb 2018 17:12:01 GMT"},
- {"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="},
- {"content-type", "application/activity+json"},
- {"signature",
- "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""},
- {"(request-target)", "post /users/demiurge/inbox"}
- ]
- }
-
- assert HTTPSignatures.validate_conn(conn)
- end
-
- test "validate this" do
- conn = %{
- params: %{"actor" => "https://niu.moe/users/rye"},
- req_headers: [
- {"x-forwarded-for", "149.202.73.191"},
- {"host", "testing.pleroma.lol"},
- {"x-cluster-client-ip", "149.202.73.191"},
- {"connection", "upgrade"},
- {"content-length", "2396"},
- {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
- {"date", "Sun, 18 Feb 2018 20:31:51 GMT"},
- {"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="},
- {"content-type", "application/activity+json"},
- {"signature",
- "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""},
- {"(request-target)", "post /inbox"}
- ]
- }
-
- assert HTTPSignatures.validate_conn(conn)
- end
-
- test "validate this too" do
- conn = %{
- params: %{"actor" => "https://niu.moe/users/rye"},
- req_headers: [
- {"x-forwarded-for", "149.202.73.191"},
- {"host", "testing.pleroma.lol"},
- {"x-cluster-client-ip", "149.202.73.191"},
- {"connection", "upgrade"},
- {"content-length", "2342"},
- {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
- {"date", "Sun, 18 Feb 2018 21:44:46 GMT"},
- {"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="},
- {"content-type", "application/activity+json"},
- {"signature",
- "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""},
- {"(request-target)", "post /inbox"}
- ]
- }
-
- assert HTTPSignatures.validate_conn(conn)
- end
-
- test "it generates a signature" do
- user = insert(:user)
- assert HTTPSignatures.sign(user, %{host: "mastodon.example.org"}) =~ "keyId=\""
- end
-
- test "this too" do
- conn = %{
- params: %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"},
- req_headers: [
- {"host", "soc.canned-death.us"},
- {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"},
- {"date", "Sun, 11 Mar 2018 12:19:36 GMT"},
- {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="},
- {"content-type", "application/activity+json"},
- {"signature",
- "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""},
- {"x-forwarded-for", "2607:5300:203:2899::31:1337"},
- {"x-forwarded-host", "soc.canned-death.us"},
- {"x-forwarded-server", "soc.canned-death.us"},
- {"connection", "Keep-Alive"},
- {"content-length", "2006"},
- {"(request-target)", "post /inbox"}
- ]
- }
-
- assert HTTPSignatures.validate_conn(conn)
- end
-end
diff --git a/test/web/http_sigs/priv.key b/test/web/http_sigs/priv.key
deleted file mode 100644
index 425518a06..000000000
--- a/test/web/http_sigs/priv.key
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
-NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
-UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
-AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
-QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
-kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
-f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
-412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
-mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
-kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
-gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
-G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
-7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
------END RSA PRIVATE KEY-----
diff --git a/test/web/http_sigs/pub.key b/test/web/http_sigs/pub.key
deleted file mode 100644
index b3bbf6cb9..000000000
--- a/test/web/http_sigs/pub.key
+++ /dev/null
@@ -1,6 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
-6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
-Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
-oYi+1hqp1fIekaxsyQIDAQAB
------END PUBLIC KEY-----
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 5c79ee633..40e7739e7 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1,3345 +1,3358 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
use Pleroma.Web.ConnCase
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OStatus
alias Pleroma.Web.Push
alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
test "the home timeline", %{conn: conn} do
user = insert(:user)
following = insert(:user)
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/home")
assert Enum.empty?(json_response(conn, 200))
{:ok, user} = User.follow(user, following)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/timelines/home")
assert [%{"content" => "test"}] = json_response(conn, 200)
end
test "the public timeline", %{conn: conn} do
following = insert(:user)
capture_log(fn ->
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
conn =
conn
|> get("/api/v1/timelines/public", %{"local" => "False"})
assert length(json_response(conn, 200)) == 2
conn =
build_conn()
|> get("/api/v1/timelines/public", %{"local" => "True"})
assert [%{"content" => "test"}] = json_response(conn, 200)
conn =
build_conn()
|> get("/api/v1/timelines/public", %{"local" => "1"})
assert [%{"content" => "test"}] = json_response(conn, 200)
end)
end
+ test "the public timeline when public is set to false", %{conn: conn} do
+ public = Pleroma.Config.get([:instance, :public])
+ Pleroma.Config.put([:instance, :public], false)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :public], public)
+ end)
+
+ assert conn
+ |> get("/api/v1/timelines/public", %{"local" => "False"})
+ |> json_response(403) == %{"error" => "This resource requires authentication."}
+ end
+
test "posting a status", %{conn: conn} do
user = insert(:user)
idempotency_key = "Pikachu rocks!"
conn_one =
conn
|> assign(:user, user)
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
{:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
# Six hours
assert ttl > :timer.seconds(6 * 60 * 60 - 1)
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
json_response(conn_one, 200)
assert Activity.get_by_id(id)
conn_two =
conn
|> assign(:user, user)
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
assert %{"id" => second_id} = json_response(conn_two, 200)
assert id == second_id
conn_three =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
assert %{"id" => third_id} = json_response(conn_three, 200)
refute id == third_id
end
test "posting a sensitive status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
assert Activity.get_by_id(id)
end
test "posting a fake status", %{conn: conn} do
user = insert(:user)
real_conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" =>
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
})
real_status = json_response(real_conn, 200)
assert real_status
assert Object.get_by_ap_id(real_status["uri"])
real_status =
real_status
|> Map.put("id", nil)
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
fake_conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" =>
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
"preview" => true
})
fake_status = json_response(fake_conn, 200)
assert fake_status
refute Object.get_by_ap_id(fake_status["uri"])
fake_status =
fake_status
|> Map.put("id", nil)
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
assert real_status == fake_status
end
test "posting a status with OGP link preview", %{conn: conn} do
Pleroma.Config.put([:rich_media, :enabled], true)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "http://example.com/ogp"
})
assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
assert Activity.get_by_id(id)
Pleroma.Config.put([:rich_media, :enabled], false)
end
test "posting a direct status", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
content = "direct cofe @#{user2.nickname}"
conn =
conn
|> assign(:user, user1)
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
assert activity = Activity.get_by_id(id)
assert activity.recipients == [user2.ap_id, user1.ap_id]
assert activity.data["to"] == [user2.ap_id]
assert activity.data["cc"] == []
end
test "direct timeline", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "private"
})
# Only direct should be visible here
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert %{"visibility" => "direct"} = status
assert status["url"] != direct.data["id"]
# User should be able to see his own direct message
res_conn =
build_conn()
|> assign(:user, user_one)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert %{"visibility" => "direct"} = status
# Both should be visible here
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/home")
[_s1, _s2] = json_response(res_conn, 200)
# Test pagination
Enum.each(1..20, fn _ ->
{:ok, _} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
end)
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct")
statuses = json_response(res_conn, 200)
assert length(statuses) == 20
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
[status] = json_response(res_conn, 200)
assert status["url"] != direct.data["id"]
end
test "Conversations", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "private"
})
res_conn =
conn
|> assign(:user, user_one)
|> get("/api/v1/conversations")
assert response = json_response(res_conn, 200)
assert [
%{
"id" => res_id,
"accounts" => res_accounts,
"last_status" => res_last_status,
"unread" => unread
}
] = response
assert length(res_accounts) == 2
assert is_binary(res_id)
assert unread == true
assert res_last_status["id"] == direct.id
# Apparently undocumented API endpoint
res_conn =
conn
|> assign(:user, user_one)
|> post("/api/v1/conversations/#{res_id}/read")
assert response = json_response(res_conn, 200)
assert length(response["accounts"]) == 2
assert response["last_status"]["id"] == direct.id
assert response["unread"] == false
# (vanilla) Mastodon frontend behaviour
res_conn =
conn
|> assign(:user, user_one)
|> get("/api/v1/statuses/#{res_last_status["id"]}/context")
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
end
test "doesn't include DMs from blocked users", %{conn: conn} do
blocker = insert(:user)
blocked = insert(:user)
user = insert(:user)
{:ok, blocker} = User.block(blocker, blocked)
{:ok, _blocked_direct} =
CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
{:ok, direct} =
CommonAPI.post(user, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
res_conn =
conn
|> assign(:user, user)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert status["id"] == direct.id
end
test "replying to a status", %{conn: conn} do
user = insert(:user)
{:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
activity = Activity.get_by_id(id)
assert activity.data["context"] == replied_to.data["context"]
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
end
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
activity = Activity.get_by_id(id)
assert activity
end
test "verify_credentials", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200)
assert id == to_string(user.id)
end
test "verify_credentials default scope unlisted", %{conn: conn} do
user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
assert id == to_string(user.id)
end
test "apps/verify_credentials", %{conn: conn} do
token = insert(:oauth_token)
conn =
conn
|> assign(:user, token.user)
|> assign(:token, token)
|> get("/api/v1/apps/verify_credentials")
app = Repo.preload(token, :app).app
expected = %{
"name" => app.client_name,
"website" => app.website,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "creates an oauth app", %{conn: conn} do
user = insert(:user)
app_attrs = build(:oauth_app)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/apps", %{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
[app] = Repo.all(App)
expected = %{
"name" => app.client_name,
"website" => app.website,
"client_id" => app.client_id,
"client_secret" => app.client_secret,
"id" => app.id |> to_string(),
"redirect_uri" => app.redirect_uris,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "get a status", %{conn: conn} do
activity = insert(:note_activity)
conn =
conn
|> get("/api/v1/statuses/#{activity.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(activity.id)
end
describe "deleting a status" do
test "when you created it", %{conn: conn} do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
conn =
conn
|> assign(:user, author)
|> delete("/api/v1/statuses/#{activity.id}")
assert %{} = json_response(conn, 200)
refute Activity.get_by_id(activity.id)
end
test "when you didn't create it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/statuses/#{activity.id}")
assert %{"error" => _} = json_response(conn, 403)
assert Activity.get_by_id(activity.id) == activity
end
test "when you're an admin or moderator", %{conn: conn} do
activity1 = insert(:note_activity)
activity2 = insert(:note_activity)
admin = insert(:user, info: %{is_admin: true})
moderator = insert(:user, info: %{is_moderator: true})
res_conn =
conn
|> assign(:user, admin)
|> delete("/api/v1/statuses/#{activity1.id}")
assert %{} = json_response(res_conn, 200)
res_conn =
conn
|> assign(:user, moderator)
|> delete("/api/v1/statuses/#{activity2.id}")
assert %{} = json_response(res_conn, 200)
refute Activity.get_by_id(activity1.id)
refute Activity.get_by_id(activity2.id)
end
end
describe "filters" do
test "creating a filter", %{conn: conn} do
user = insert(:user)
filter = %Pleroma.Filter{
phrase: "knights",
context: ["home"]
}
conn =
conn
|> assign(:user, user)
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
assert response = json_response(conn, 200)
assert response["phrase"] == filter.phrase
assert response["context"] == filter.context
assert response["irreversible"] == false
assert response["id"] != nil
assert response["id"] != ""
end
test "fetching a list of filters", %{conn: conn} do
user = insert(:user)
query_one = %Pleroma.Filter{
user_id: user.id,
filter_id: 1,
phrase: "knights",
context: ["home"]
}
query_two = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "who",
context: ["home"]
}
{:ok, filter_one} = Pleroma.Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.create(query_two)
response =
conn
|> assign(:user, user)
|> get("/api/v1/filters")
|> json_response(200)
assert response ==
render_json(
FilterView,
"filters.json",
filters: [filter_two, filter_one]
)
end
test "get a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, filter} = Pleroma.Filter.create(query)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/filters/#{filter.filter_id}")
assert _response = json_response(conn, 200)
end
test "update a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, _filter} = Pleroma.Filter.create(query)
new = %Pleroma.Filter{
phrase: "nii",
context: ["home"]
}
conn =
conn
|> assign(:user, user)
|> put("/api/v1/filters/#{query.filter_id}", %{
phrase: new.phrase,
context: new.context
})
assert response = json_response(conn, 200)
assert response["phrase"] == new.phrase
assert response["context"] == new.context
end
test "delete a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, filter} = Pleroma.Filter.create(query)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/filters/#{filter.filter_id}")
assert response = json_response(conn, 200)
assert response == %{}
end
end
describe "lists" do
test "creating a list", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/lists", %{"title" => "cuties"})
assert %{"title" => title} = json_response(conn, 200)
assert title == "cuties"
end
test "adding users to a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [other_user.follower_address]
end
test "removing users from a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
{:ok, list} = Pleroma.List.follow(list, third_user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [third_user.follower_address]
end
test "listing users in a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "retrieving a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/lists/#{list.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(list.id)
end
test "renaming a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
assert %{"title" => name} = json_response(conn, 200)
assert name == "newname"
end
test "deleting a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/lists/#{list.id}")
assert %{} = json_response(conn, 200)
assert is_nil(Repo.get(Pleroma.List, list.id))
end
test "list timeline", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
{:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/list/#{list.id}")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(activity_two.id)
end
test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
{:ok, _activity_two} =
TwitterAPI.create_status(other_user, %{
"status" => "Marisa is cute.",
"visibility" => "private"
})
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/list/#{list.id}")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(activity_one.id)
end
end
describe "notifications" do
test "list of notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
end
test "getting a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications/#{notification.id}")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
end
test "dismissing a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
assert %{} = json_response(conn, 200)
end
test "clearing all notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/notifications/clear")
assert %{} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/notifications")
assert all = json_response(conn, 200)
assert all == []
end
test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
conn =
conn
|> assign(:user, user)
# min_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
# since_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
# max_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
end
test "filters notifications using exclude_types", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
{:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
{:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
mention_notification_id =
Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
favorite_notification_id =
Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
reblog_notification_id =
Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
follow_notification_id =
Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
conn =
conn
|> assign(:user, user)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
end
test "destroy multiple", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
{:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
conn =
conn
|> assign(:user, user)
conn_res =
conn
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
conn2 =
conn
|> assign(:user, other_user)
conn_res =
conn2
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
conn_destroy =
conn
|> delete("/api/v1/notifications/destroy_multiple", %{
"ids" => [notification1_id, notification2_id]
})
assert json_response(conn_destroy, 200) == %{}
conn_res =
conn2
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
end
end
describe "reblogging" do
test "reblogs and returns the reblogged status", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/reblog")
assert %{
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
"reblogged" => true
} = json_response(conn, 200)
assert to_string(activity.id) == id
end
test "reblogged status for another user", %{conn: conn} do
activity = insert(:note_activity)
user1 = insert(:user)
user2 = insert(:user)
user3 = insert(:user)
CommonAPI.favorite(activity.id, user2)
{:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
{:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
{:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
conn_res =
conn
|> assign(:user, user3)
|> get("/api/v1/statuses/#{reblog_activity1.id}")
assert %{
"reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
"reblogged" => false,
"favourited" => false,
"bookmarked" => false
} = json_response(conn_res, 200)
conn_res =
conn
|> assign(:user, user2)
|> get("/api/v1/statuses/#{reblog_activity1.id}")
assert %{
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
"reblogged" => true,
"favourited" => true,
"bookmarked" => true
} = json_response(conn_res, 200)
assert to_string(activity.id) == id
end
end
describe "unreblogging" do
test "unreblogs and returns the unreblogged status", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
{:ok, _, _} = CommonAPI.repeat(activity.id, user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unreblog")
assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
assert to_string(activity.id) == id
end
end
describe "favoriting" do
test "favs a status and returns it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/favourite")
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
json_response(conn, 200)
assert to_string(activity.id) == id
end
test "returns 500 for a wrong id", %{conn: conn} do
user = insert(:user)
resp =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/1/favourite")
|> json_response(500)
assert resp == "Something went wrong"
end
end
describe "unfavoriting" do
test "unfavorites a status and returns it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
json_response(conn, 200)
assert to_string(activity.id) == id
end
end
describe "user timelines" do
test "gets a 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"})
resp =
conn
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id}] = json_response(resp, 200)
assert id == to_string(activity.id)
resp =
conn
|> assign(:user, user_two)
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
assert id_one == to_string(direct_activity.id)
assert id_two == to_string(activity.id)
resp =
conn
|> assign(:user, user_three)
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
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 =
conn
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
assert json_response(conn, 200) == []
end
test "gets an users media", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
media =
TwitterAPI.upload(file, user, "json")
|> Poison.decode!()
{:ok, image_post} =
TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
conn =
build_conn()
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
end
test "gets a user's statuses without reblogs", %{conn: conn} do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
{:ok, _, _} = CommonAPI.repeat(post.id, user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
end
end
describe "user relationships" do
test "returns the relationships for the current user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
end
describe "locked accounts" do
test "/api/v1/follow_requests works" do
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/follow_requests")
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
test "/api/v1/follow_requests/:id/authorize works" do
user = insert(:user, %{info: %User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == true
end
test "verify_credentials", %{conn: conn} do
user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
assert id == to_string(user.id)
end
test "/api/v1/follow_requests/:id/reject works" do
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
end
end
test "account fetching", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(user.id)
conn =
build_conn()
|> get("/api/v1/accounts/-1")
assert %{"error" => "Can't find user"} = json_response(conn, 404)
end
test "account fetching also works nickname", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/v1/accounts/#{user.nickname}")
assert %{"id" => id} = json_response(conn, 200)
assert id == user.id
end
test "media upload", %{conn: conn} do
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
desc = "Description of the image"
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/media", %{"file" => file, "description" => desc})
assert media = json_response(conn, 200)
assert media["type"] == "image"
assert media["description"] == desc
assert media["id"]
object = Repo.get(Object, media["id"])
assert object.data["actor"] == User.ap_id(user)
end
test "hashtag timeline", %{conn: conn} do
following = insert(:user)
capture_log(fn ->
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
nconn =
conn
|> get("/api/v1/timelines/tag/2hu")
assert [%{"id" => id}] = json_response(nconn, 200)
assert id == to_string(activity.id)
# works for different capitalization too
nconn =
conn
|> get("/api/v1/timelines/tag/2HU")
assert [%{"id" => id}] = json_response(nconn, 200)
assert id == to_string(activity.id)
end)
end
test "multi-hashtag timeline", %{conn: conn} do
user = insert(:user)
{:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
{:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
{:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
any_test =
conn
|> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
[status_none, status_test1, status_test] = json_response(any_test, 200)
assert to_string(activity_test.id) == status_test["id"]
assert to_string(activity_test1.id) == status_test1["id"]
assert to_string(activity_none.id) == status_none["id"]
restricted_test =
conn
|> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
assert [status_test1] == json_response(restricted_test, 200)
all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
assert [status_none] == json_response(all_test, 200)
end
test "getting followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{other_user.id}/followers")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(user.id)
end
test "getting followers, hide_followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{info: %{hide_followers: true}})
{:ok, _user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{other_user.id}/followers")
assert [] == json_response(conn, 200)
end
test "getting followers, hide_followers, same user requesting", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{info: %{hide_followers: true}})
{:ok, _user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, other_user)
|> get("/api/v1/accounts/#{other_user.id}/followers")
refute [] == json_response(conn, 200)
end
test "getting followers, pagination", %{conn: conn} do
user = insert(:user)
follower1 = insert(:user)
follower2 = insert(:user)
follower3 = insert(:user)
{:ok, _} = User.follow(follower1, user)
{:ok, _} = User.follow(follower2, user)
{:ok, _} = User.follow(follower3, user)
conn =
conn
|> assign(:user, user)
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == follower3.id
assert id2 == follower2.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
assert id2 == follower2.id
assert id1 == follower1.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
assert [%{"id" => id2}] = json_response(res_conn, 200)
assert id2 == follower2.id
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
test "getting following", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/following")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "getting following, hide_follows", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/following")
assert [] == json_response(conn, 200)
end
test "getting following, hide_follows, same user requesting", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/following")
refute [] == json_response(conn, 200)
end
test "getting following, pagination", %{conn: conn} do
user = insert(:user)
following1 = insert(:user)
following2 = insert(:user)
following3 = insert(:user)
{:ok, _} = User.follow(user, following1)
{:ok, _} = User.follow(user, following2)
{:ok, _} = User.follow(user, following3)
conn =
conn
|> assign(:user, user)
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == following3.id
assert id2 == following2.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
assert id2 == following2.id
assert id1 == following1.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
assert [%{"id" => id2}] = json_response(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
test "following / unfollowing a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/follow")
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "following without reblogs" do
follower = insert(:user)
followed = insert(:user)
other_user = insert(:user)
conn =
build_conn()
|> assign(:user, follower)
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
assert %{"showing_reblogs" => false} = json_response(conn, 200)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
{:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
conn =
build_conn()
|> assign(:user, User.get_cached_by_id(follower.id))
|> get("/api/v1/timelines/home")
assert [] == json_response(conn, 200)
conn =
build_conn()
|> assign(:user, follower)
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
assert %{"showing_reblogs" => true} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, User.get_cached_by_id(follower.id))
|> get("/api/v1/timelines/home")
expected_activity_id = reblog.id
assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
end
test "following / unfollowing errors" do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# self unfollow
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# self follow via uri
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# follow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# follow non existing user via uri
conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# unfollow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
end
test "muting / unmuting a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/mute")
assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unmute")
assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
end
test "subscribing / unsubscribing to a user", %{conn: conn} do
user = insert(:user)
subscription_target = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
end
test "getting a list of mutes", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.mute(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/mutes")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
end
test "blocking / unblocking a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
end
test "getting a list of blocks", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = 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(conn, 200)
end
test "blocking / unblocking a domain", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
assert User.blocks?(user, other_user)
conn =
build_conn()
|> assign(:user, user)
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
refute User.blocks?(user, other_user)
end
test "getting a list of domain blocks", %{conn: conn} do
user = insert(:user)
{:ok, user} = User.block_domain(user, "bad.site")
{:ok, user} = User.block_domain(user, "even.worse.site")
conn =
conn
|> assign(:user, user)
|> get("/api/v1/domain_blocks")
domain_blocks = json_response(conn, 200)
assert "bad.site" in domain_blocks
assert "even.worse.site" in domain_blocks
end
test "unimplemented follow_requests, blocks, domain blocks" do
user = insert(:user)
["blocks", "domain_blocks", "follow_requests"]
|> Enum.each(fn endpoint ->
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/#{endpoint}")
assert [] = json_response(conn, 200)
end)
end
test "account search", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
results =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/search", %{"q" => "shp"})
|> json_response(200)
result_ids = for result <- results, do: result["acct"]
assert user_two.nickname in result_ids
assert user_three.nickname in result_ids
results =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
|> json_response(200)
result_ids = for result <- results, do: result["acct"]
assert user_three.nickname in result_ids
end
test "search", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
{:ok, _activity} =
CommonAPI.post(user, %{
"status" => "This is about 2hu, but private",
"visibility" => "private"
})
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
conn =
conn
|> get("/api/v1/search", %{"q" => "2hu"})
assert results = json_response(conn, 200)
[account | _] = results["accounts"]
assert account["id"] == to_string(user_three.id)
assert results["hashtags"] == []
[status] = results["statuses"]
assert status["id"] == to_string(activity.id)
end
test "search fetches remote statuses", %{conn: conn} do
capture_log(fn ->
conn =
conn
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
assert results = json_response(conn, 200)
[status] = results["statuses"]
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
end)
end
test "search doesn't show statuses that it shouldn't", %{conn: conn} do
{:ok, activity} =
CommonAPI.post(insert(:user), %{
"status" => "This is about 2hu, but private",
"visibility" => "private"
})
capture_log(fn ->
conn =
conn
|> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
assert results = json_response(conn, 200)
[] = results["statuses"]
end)
end
test "search fetches remote accounts", %{conn: conn} do
conn =
conn
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
assert results = json_response(conn, 200)
[account] = results["accounts"]
assert account["acct"] == "shp@social.heldscal.la"
end
test "returns the favorites of a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
first_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites")
assert [status] = json_response(first_conn, 200)
assert status["id"] == to_string(activity.id)
assert [{"link", _link_header}] =
Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
# Honours query params
{:ok, second_activity} =
CommonAPI.post(other_user, %{
"status" =>
"Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
})
{:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
last_like = status["id"]
second_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites?since_id=#{last_like}")
assert [second_status] = json_response(second_conn, 200)
assert second_status["id"] == to_string(second_activity.id)
third_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites?limit=0")
assert [] = json_response(third_conn, 200)
end
describe "getting favorites timeline of specified user" do
setup do
[current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
[current_user: current_user, user: user]
end
test "returns list of statuses favorited by specified user", %{
conn: conn,
current_user: current_user,
user: user
} do
[activity | _] = insert_pair(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
[like] = response
assert length(response) == 1
assert like["id"] == activity.id
end
test "returns favorites for specified user_id when user is not logged in", %{
conn: conn,
user: user
} do
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
end
test "returns favorited DM only when user is logged in and he is one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
{:ok, direct} =
CommonAPI.post(current_user, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
anonymous_response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(anonymous_response) == 0
end
test "does not return others' favorited DM when user is not one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
user_two = insert(:user)
{:ok, direct} =
CommonAPI.post(user_two, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 0
end
test "paginates favorites using since_id and max_id", %{
conn: conn,
current_user: current_user,
user: user
} do
activities = insert_list(10, :note_activity)
Enum.each(activities, fn activity ->
CommonAPI.favorite(activity.id, user)
end)
third_activity = Enum.at(activities, 2)
seventh_activity = Enum.at(activities, 6)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
since_id: third_activity.id,
max_id: seventh_activity.id
})
|> json_response(:ok)
assert length(response) == 3
refute third_activity in response
refute seventh_activity in response
end
test "limits favorites using limit parameter", %{
conn: conn,
current_user: current_user,
user: user
} do
7
|> insert_list(:note_activity)
|> Enum.each(fn activity ->
CommonAPI.favorite(activity.id, user)
end)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
|> json_response(:ok)
assert length(response) == 3
end
test "returns empty response when user does not have any favorited statuses", %{
conn: conn,
current_user: current_user,
user: user
} do
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "returns 404 error when specified user is not exist", %{conn: conn} do
conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
assert json_response(conn, 404) == %{"error" => "Record not found"}
end
test "returns 403 error when user has hidden own favorites", %{
conn: conn,
current_user: current_user
} do
user = insert(:user, %{info: %{hide_favorites: true}})
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
user = insert(:user)
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert user.info.hide_favorites
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
end
describe "updating credentials" do
test "updates the user's bio", %{conn: conn} do
user = insert(:user)
user2 = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{
"note" => "I drink #cofe with @#{user2.nickname}"
})
assert user = json_response(conn, 200)
assert user["note"] ==
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
user2.id <>
~s(" class="u-url mention" href=") <>
user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
end
test "updates the user's locking status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
assert user = json_response(conn, 200)
assert user["locked"] == true
end
test "updates the user's default scope", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
assert user = json_response(conn, 200)
assert user["source"]["privacy"] == "cofe"
end
test "updates the user's hide_followers status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_followers"] == true
end
test "updates the user's hide_follows status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_follows"] == true
end
test "updates the user's hide_favorites status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_favorites"] == true
end
test "updates the user's show_role status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
assert user = json_response(conn, 200)
assert user["source"]["pleroma"]["show_role"] == false
end
test "updates the user's no_rich_text status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
assert user = json_response(conn, 200)
assert user["source"]["pleroma"]["no_rich_text"] == true
end
test "updates the user's name", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
assert user = json_response(conn, 200)
assert user["display_name"] == "markorepairs"
end
test "updates the user's avatar", %{conn: conn} do
user = insert(:user)
new_avatar = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
assert user_response = json_response(conn, 200)
assert user_response["avatar"] != User.avatar_url(user)
end
test "updates the user's banner", %{conn: conn} do
user = insert(:user)
new_header = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
assert user_response = json_response(conn, 200)
assert user_response["header"] != User.banner_url(user)
end
test "requires 'write' permission", %{conn: conn} do
token1 = insert(:oauth_token, scopes: ["read"])
token2 = insert(:oauth_token, scopes: ["write", "follow"])
for token <- [token1, token2] do
conn =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> patch("/api/v1/accounts/update_credentials", %{})
if token == token1 do
assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
else
assert json_response(conn, 200)
end
end
end
test "updates profile emojos", %{conn: conn} do
user = insert(:user)
note = "*sips :blank:*"
name = "I am :firefox:"
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{
"note" => note,
"display_name" => name
})
assert json_response(conn, 200)
conn =
conn
|> get("/api/v1/accounts/#{user.id}")
assert user = json_response(conn, 200)
assert user["note"] == note
assert user["display_name"] == name
assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
end
end
test "get instance information", %{conn: conn} do
conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200)
email = Pleroma.Config.get([:instance, :email])
# Note: not checking for "max_toot_chars" since it's optional
assert %{
"uri" => _,
"title" => _,
"description" => _,
"version" => _,
"email" => from_config_email,
"urls" => %{
"streaming_api" => _
},
"stats" => _,
"thumbnail" => _,
"languages" => _,
"registrations" => _
} = result
assert email == from_config_email
end
test "get instance stats", %{conn: conn} do
user = insert(:user, %{local: true})
user2 = insert(:user, %{local: true})
{:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value
user = User.get_cached_by_id(user.id)
info_change = Changeset.change(user.info, %{deactivated: nil})
{:ok, _user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> User.update_and_set_cache()
Pleroma.Stats.update_stats()
conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200)
stats = result["stats"]
assert stats
assert stats["user_count"] == 1
assert stats["status_count"] == 1
assert stats["domain_count"] == 2
end
test "get peers", %{conn: conn} do
insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
Pleroma.Stats.update_stats()
conn = get(conn, "/api/v1/instance/peers")
assert result = json_response(conn, 200)
assert ["peer1.com", "peer2.com"] == Enum.sort(result)
end
test "put settings", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
assert _result = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.info.settings == %{"programming" => "socks"}
end
describe "pinned statuses" do
setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
[user: user, activity: activity]
end
test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.pin(activity.id, user)
result =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
id_str = to_string(activity.id)
assert [%{"id" => ^id_str, "pinned" => true}] = result
end
test "pin status", %{conn: conn, user: user, activity: activity} do
id_str = to_string(activity.id)
assert %{"id" => ^id_str, "pinned" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/pin")
|> json_response(200)
assert [%{"id" => ^id_str, "pinned" => true}] =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
end
test "unpin status", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.pin(activity.id, user)
id_str = to_string(activity.id)
user = refresh_record(user)
assert %{"id" => ^id_str, "pinned" => false} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unpin")
|> json_response(200)
assert [] =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
end
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
id_str_one = to_string(activity_one.id)
assert %{"id" => ^id_str_one, "pinned" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{id_str_one}/pin")
|> json_response(200)
user = refresh_record(user)
assert %{"error" => "You have already pinned the maximum number of statuses"} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity_two.id}/pin")
|> json_response(400)
end
test "Status rich-media Card", %{conn: conn, user: user} do
Pleroma.Config.put([:rich_media, :enabled], true)
{:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
response =
conn
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(200)
assert response == %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"provider_name" => "www.imdb.com",
"provider_url" => "http://www.imdb.com",
"title" => "The Rock",
"type" => "link",
"url" => "http://www.imdb.com/title/tt0117500/",
"description" => nil,
"pleroma" => %{
"opengraph" => %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock",
"type" => "video.movie",
"url" => "http://www.imdb.com/title/tt0117500/"
}
}
}
# works with private posts
{:ok, activity} =
CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
response_two =
conn
|> assign(:user, user)
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(200)
assert response_two == response
Pleroma.Config.put([:rich_media, :enabled], false)
end
end
test "bookmarks" do
user = insert(:user)
for_user = insert(:user)
{:ok, activity1} =
CommonAPI.post(user, %{
"status" => "heweoo?"
})
{:ok, activity2} =
CommonAPI.post(user, %{
"status" => "heweoo!"
})
response1 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity1.id}/bookmark")
assert json_response(response1, 200)["bookmarked"] == true
response2 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity2.id}/bookmark")
assert json_response(response2, 200)["bookmarked"] == true
bookmarks =
build_conn()
|> assign(:user, for_user)
|> get("/api/v1/bookmarks")
assert [json_response(response2, 200), json_response(response1, 200)] ==
json_response(bookmarks, 200)
response1 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity1.id}/unbookmark")
assert json_response(response1, 200)["bookmarked"] == false
bookmarks =
build_conn()
|> assign(:user, for_user)
|> get("/api/v1/bookmarks")
assert [json_response(response2, 200)] == json_response(bookmarks, 200)
end
describe "conversation muting" do
setup do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
[user: user, activity: activity]
end
test "mute conversation", %{conn: conn, user: user, activity: activity} do
id_str = to_string(activity.id)
assert %{"id" => ^id_str, "muted" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/mute")
|> json_response(200)
end
test "unmute conversation", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.add_mute(user, activity)
id_str = to_string(activity.id)
user = refresh_record(user)
assert %{"id" => ^id_str, "muted" => false} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unmute")
|> json_response(200)
end
end
test "flavours switching (Pleroma Extension)", %{conn: conn} do
user = insert(:user)
get_old_flavour =
conn
|> assign(:user, user)
|> get("/api/v1/pleroma/flavour")
assert "glitch" == json_response(get_old_flavour, 200)
set_flavour =
conn
|> assign(:user, user)
|> post("/api/v1/pleroma/flavour/vanilla")
assert "vanilla" == json_response(set_flavour, 200)
get_new_flavour =
conn
|> assign(:user, user)
|> post("/api/v1/pleroma/flavour/vanilla")
assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
end
describe "reports" do
setup do
reporter = insert(:user)
target_user = insert(:user)
{:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
[reporter: reporter, target_user: target_user, activity: activity]
end
test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
assert %{"action_taken" => false, "id" => _} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"account_id" => target_user.id})
|> json_response(200)
end
test "submit a report with statuses and comment", %{
conn: conn,
reporter: reporter,
target_user: target_user,
activity: activity
} do
assert %{"action_taken" => false, "id" => _} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"status_ids" => [activity.id],
"comment" => "bad status!"
})
|> json_response(200)
end
test "account_id is required", %{
conn: conn,
reporter: reporter,
activity: activity
} do
assert %{"error" => "Valid `account_id` required"} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"status_ids" => [activity.id]})
|> json_response(400)
end
test "comment must be up to the size specified in the config", %{
conn: conn,
reporter: reporter,
target_user: target_user
} do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
comment = String.pad_trailing("a", max_size + 1, "a")
error = %{"error" => "Comment must be up to #{max_size} characters"}
assert ^error =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
|> json_response(400)
end
end
describe "link headers" do
test "preserves parameters in link headers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} =
CommonAPI.post(other_user, %{
"status" => "hi @#{user.nickname}",
"visibility" => "public"
})
{:ok, activity2} =
CommonAPI.post(other_user, %{
"status" => "hi @#{user.nickname}",
"visibility" => "public"
})
notification1 = Repo.get_by(Notification, activity_id: activity1.id)
notification2 = Repo.get_by(Notification, activity_id: activity2.id)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications", %{media_only: true})
assert [link_header] = get_resp_header(conn, "link")
assert link_header =~ ~r/media_only=true/
assert link_header =~ ~r/min_id=#{notification2.id}/
assert link_header =~ ~r/max_id=#{notification1.id}/
end
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"})
resp_one =
conn
|> get("/api/v1/accounts/#{user_one.id}")
resp_two =
conn
|> get("/api/v1/accounts/#{user_two.nickname}")
resp_three =
conn
|> get("/api/v1/accounts/#{user_two.id}")
acc_one = json_response(resp_one, 200)
acc_two = json_response(resp_two, 200)
acc_three = json_response(resp_three, 200)
refute acc_one == acc_two
assert acc_two == acc_three
end
describe "custom emoji" do
test "with tags", %{conn: conn} do
[emoji | _body] =
conn
|> get("/api/v1/custom_emojis")
|> json_response(200)
assert Map.has_key?(emoji, "shortcode")
assert Map.has_key?(emoji, "static_url")
assert Map.has_key?(emoji, "tags")
assert is_list(emoji["tags"])
assert Map.has_key?(emoji, "url")
assert Map.has_key?(emoji, "visible_in_picker")
end
end
describe "index/2 redirections" do
setup %{conn: conn} do
session_opts = [
store: :cookie,
key: "_test",
signing_salt: "cooldude"
]
conn =
conn
|> Plug.Session.call(Plug.Session.init(session_opts))
|> fetch_session()
test_path = "/web/statuses/test"
%{conn: conn, path: test_path}
end
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
conn = get(conn, path)
assert conn.status == 302
assert redirected_to(conn) == "/web/login"
end
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
token = insert(:oauth_token)
conn =
conn
|> assign(:user, token.user)
|> put_session(:oauth_token, token.token)
|> get(path)
assert conn.status == 200
end
test "saves referer path to session", %{conn: conn, path: path} do
conn = get(conn, path)
return_to = Plug.Conn.get_session(conn, :return_to)
assert return_to == path
end
test "redirects to the saved path after log in", %{conn: conn, path: path} do
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
auth = insert(:oauth_authorization, app: app)
conn =
conn
|> put_session(:return_to, path)
|> get("/web/login", %{code: auth.token})
assert conn.status == 302
assert redirected_to(conn) == path
end
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
auth = insert(:oauth_authorization, app: app)
conn = get(conn, "/web/login", %{code: auth.token})
assert conn.status == 302
assert redirected_to(conn) == "/web/getting-started"
end
end
describe "scheduled activities" do
test "creates a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "scheduled",
"scheduled_at" => scheduled_at
})
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
assert [] == Repo.all(Activity)
end
test "creates a scheduled activity with a media attachment", %{conn: conn} do
user = insert(:user)
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"media_ids" => [to_string(upload.id)],
"status" => "scheduled",
"scheduled_at" => scheduled_at
})
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
assert %{"type" => "image"} = media_attachment
end
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
%{conn: conn} do
user = insert(:user)
scheduled_at =
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "not scheduled",
"scheduled_at" => scheduled_at
})
assert %{"content" => "not scheduled"} = json_response(conn, 200)
assert [] == Repo.all(ScheduledActivity)
end
test "returns error when daily user limit is exceeded", %{conn: conn} do
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, attrs)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
end
test "returns error when total user limit is exceeded", %{conn: conn} do
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
tomorrow =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
end
test "shows scheduled activities", %{conn: conn} do
user = insert(:user)
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
conn =
conn
|> assign(:user, user)
# min_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
# since_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
# max_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
end
test "shows a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
res_conn =
conn
|> assign(:user, user)
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
assert scheduled_activity_id == scheduled_activity.id |> to_string()
res_conn =
conn
|> assign(:user, user)
|> get("/api/v1/scheduled_statuses/404")
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
test "updates a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
new_scheduled_at =
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
res_conn =
conn
|> assign(:user, user)
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
scheduled_at: new_scheduled_at
})
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
res_conn =
conn
|> assign(:user, user)
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
test "deletes a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{} = json_response(res_conn, 200)
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
end
test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
user3 = insert(:user)
{:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
# Reply to status from another user
conn1 =
conn
|> assign(:user, user2)
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
activity = Activity.get_by_id_with_object(id)
assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
# Reblog from the third user
conn2 =
conn
|> assign(:user, user3)
|> post("/api/v1/statuses/#{activity.id}/reblog")
assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
json_response(conn2, 200)
assert to_string(activity.id) == id
# Getting third user status
conn3 =
conn
|> assign(:user, user3)
|> get("api/v1/timelines/home")
[reblogged_activity] = json_response(conn3, 200)
assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
end
describe "create account by app" do
setup do
enabled = Pleroma.Config.get([:app_account_creation, :enabled])
max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
interval = Pleroma.Config.get([:app_account_creation, :interval])
Pleroma.Config.put([:app_account_creation, :enabled], true)
Pleroma.Config.put([:app_account_creation, :max_requests], 5)
Pleroma.Config.put([:app_account_creation, :interval], 1)
on_exit(fn ->
Pleroma.Config.put([:app_account_creation, :enabled], enabled)
Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
Pleroma.Config.put([:app_account_creation, :interval], interval)
end)
:ok
end
test "Account registration via Application", %{conn: conn} do
conn =
conn
|> post("/api/v1/apps", %{
client_name: "client_name",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: "read, write, follow"
})
%{
"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(conn, 200)
conn =
conn
|> post("/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("authorization", "Bearer " <> token)
|> post("/api/v1/accounts", %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
%{
"access_token" => token,
"created_at" => _created_at,
"scope" => _scope,
"token_type" => "Bearer"
} = json_response(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.info.confirmation_pending
end
test "rate limit", %{conn: conn} do
app_token = insert(:oauth_token, user: nil)
conn =
put_req_header(conn, "authorization", "Bearer " <> app_token.token)
|> Map.put(:remote_ip, {15, 15, 15, 15})
for i <- 1..5 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(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.info.confirmation_pending
end
conn =
conn
|> post("/api/v1/accounts", %{
username: "6lain",
email: "6lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
end
end
end
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 14a8225f0..2cd82b3e7 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -1,272 +1,250 @@
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup do
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
describe "POST /api/pleroma/follow_import" do
test "it returns HTTP 200", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"})
|> json_response(:ok)
assert response == "job started"
end
test "it imports new-style mastodon follow lists", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/follow_import", %{
"list" => "Account address,Show boosts\n#{user2.ap_id},true"
})
|> json_response(:ok)
assert response == "job started"
end
test "requires 'follow' permission", %{conn: conn} do
token1 = insert(:oauth_token, scopes: ["read", "write"])
token2 = insert(:oauth_token, scopes: ["follow"])
another_user = insert(:user)
for token <- [token1, token2] do
conn =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"})
if token == token1 do
assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403)
else
assert json_response(conn, 200)
end
end
end
end
describe "POST /api/pleroma/blocks_import" do
test "it returns HTTP 200", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"})
|> json_response(:ok)
assert response == "job started"
end
end
describe "POST /api/pleroma/notifications/read" do
test "it marks a single notification as read", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
{:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, [notification1]} = Notification.create_notifications(activity1)
{:ok, [notification2]} = Notification.create_notifications(activity2)
conn
|> assign(:user, user1)
|> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
|> json_response(:ok)
assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
end
end
describe "PUT /api/pleroma/notification_settings" do
test "it updates notification settings", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> put("/api/pleroma/notification_settings", %{
"remote" => false,
"followers" => false,
"bar" => 1
})
|> json_response(:ok)
user = Repo.get(User, user.id)
assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
user.info.notification_settings
end
end
describe "GET /api/statusnet/config.json" do
test "returns the state of safe_dm_mentions flag", %{conn: conn} do
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
assert response["site"]["safeDMMentionsEnabled"] == "1"
Pleroma.Config.put([:instance, :safe_dm_mentions], false)
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
assert response["site"]["safeDMMentionsEnabled"] == "0"
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
end
test "it returns the managed config", %{conn: conn} do
Pleroma.Config.put([:instance, :managed_config], false)
- Pleroma.Config.put([:fe], theme: "rei-ayanami-towel")
+ Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
refute response["site"]["pleromafe"]
Pleroma.Config.put([:instance, :managed_config], true)
response =
conn
|> get("/api/statusnet/config.json")
|> json_response(:ok)
- assert response["site"]["pleromafe"]
- end
-
- test "if :pleroma, :fe is false, it returns the new style config settings", %{conn: conn} do
- Pleroma.Config.put([:instance, :managed_config], true)
- Pleroma.Config.put([:fe, :theme], "rei-ayanami-towel")
- Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- assert response["site"]["pleromafe"]["theme"] == "rei-ayanami-towel"
-
- Pleroma.Config.put([:fe], false)
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- assert response["site"]["pleromafe"]["theme"] == "asuka-hospital"
+ assert response["site"]["pleromafe"] == %{"theme" => "asuka-hospital"}
end
end
describe "GET /api/pleroma/frontend_configurations" do
test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} do
config = [
frontend_a: %{
x: 1,
y: 2
},
frontend_b: %{
z: 3
}
]
Pleroma.Config.put(:frontend_configurations, config)
response =
conn
|> get("/api/pleroma/frontend_configurations")
|> json_response(:ok)
assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
end
end
describe "/api/pleroma/emoji" do
test "returns json with custom emoji with tags", %{conn: conn} do
emoji =
conn
|> get("/api/pleroma/emoji")
|> json_response(200)
assert Enum.all?(emoji, fn
{_key,
%{
"image_url" => url,
"tags" => tags
}} ->
is_binary(url) and is_list(tags)
end)
end
end
describe "GET /ostatus_subscribe?acct=...." do
test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
conn =
get(
conn,
"/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009"
)
assert redirected_to(conn) =~ "/notice/"
end
test "show follow account page if the `acct` is a account link", %{conn: conn} do
response =
get(
conn,
"/ostatus_subscribe?acct=https://mastodon.social/users/emelie"
)
assert html_response(response, 200) =~ "Log in to follow"
end
end
test "GET /api/pleroma/healthcheck", %{conn: conn} do
conn = get(conn, "/api/pleroma/healthcheck")
assert conn.status in [200, 503]
end
describe "POST /api/pleroma/disable_account" do
test "it returns HTTP 200", %{conn: conn} do
user = insert(:user)
response =
conn
|> assign(:user, user)
|> post("/api/pleroma/disable_account", %{"password" => "test"})
|> json_response(:ok)
assert response == %{"status" => "success"}
user = User.get_cached_by_id(user.id)
assert user.info.deactivated == true
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 1, 1:22 AM (15 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802062
Default Alt Text
(303 KB)

Event Timeline