Merge remote-tracking branch 'upstream/develop' into admin-create-users
authorSachin Joshi <satchin.joshi@gmail.com>
Sat, 1 Jun 2019 05:57:37 +0000 (11:42 +0545)
committerSachin Joshi <satchin.joshi@gmail.com>
Sat, 1 Jun 2019 05:57:37 +0000 (11:42 +0545)
138 files changed:
.buildpacks [new file with mode: 0644]
.gitlab-ci.yml
CHANGELOG.md
Procfile [new file with mode: 0644]
config/config.exs
config/dokku.exs [new file with mode: 0644]
config/test.exs
docs/api/admin_api.md
docs/api/pleroma_api.md
docs/config.md
docs/config/howto_mongooseim.md [new file with mode: 0644]
docs/config/mrf.md
docs/installation/alpine_linux_en.md
docs/installation/arch_linux_en.md
docs/installation/centos7_en.md
docs/installation/debian_based_en.md
docs/installation/debian_based_jp.md
docs/installation/gentoo_en.md
docs/installation/netbsd_en.md
docs/installation/openbsd_en.md
docs/installation/openbsd_fi.md
docs/introduction.md
elixir_buildpack.config [new file with mode: 0644]
installation/caddyfile-pleroma.example
installation/pleroma-apache.conf
installation/pleroma-mongooseim.cfg [new file with mode: 0755]
installation/pleroma.nginx
installation/pleroma.vcl
lib/healthcheck.ex
lib/mix/tasks/pleroma/database.ex
lib/mix/tasks/pleroma/user.ex
lib/pleroma/activity.ex
lib/pleroma/application.ex
lib/pleroma/bbs/handler.ex
lib/pleroma/conversation.ex
lib/pleroma/conversation/participation.ex
lib/pleroma/emails/admin_email.ex
lib/pleroma/emoji.ex
lib/pleroma/filter.ex
lib/pleroma/formatter.ex
lib/pleroma/html.ex
lib/pleroma/http/connection.ex
lib/pleroma/http/http.ex
lib/pleroma/http/request_builder.ex
lib/pleroma/keys.ex [new file with mode: 0644]
lib/pleroma/object.ex
lib/pleroma/object/fetcher.ex
lib/pleroma/plugs/federating_plug.ex
lib/pleroma/plugs/http_security_plug.ex
lib/pleroma/reverse_proxy.ex
lib/pleroma/signature.ex
lib/pleroma/uploaders/mdii.ex
lib/pleroma/user.ex
lib/pleroma/user/info.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/mrf.ex
lib/pleroma/web/activity_pub/mrf/simple_policy.ex
lib/pleroma/web/activity_pub/mrf/tag_policy.ex
lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
lib/pleroma/web/activity_pub/publisher.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex [new file with mode: 0644]
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/endpoint.ex
lib/pleroma/web/federator/federator.ex
lib/pleroma/web/federator/publisher.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/media_proxy/media_proxy.ex
lib/pleroma/web/mongooseim/mongoose_im_controller.ex [new file with mode: 0644]
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
lib/pleroma/web/oauth/authorization.ex
lib/pleroma/web/oauth/token.ex
lib/pleroma/web/oauth/token/clean_worker.ex [new file with mode: 0644]
lib/pleroma/web/oauth/token/query.ex [new file with mode: 0644]
lib/pleroma/web/ostatus/ostatus.ex
lib/pleroma/web/rich_media/helpers.ex
lib/pleroma/web/rich_media/parser.ex
lib/pleroma/web/router.ex
lib/pleroma/web/salmon/salmon.ex
lib/pleroma/web/templates/layout/app.html.eex
lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/twitter_api/views/activity_view.ex
lib/pleroma/web/web_finger/web_finger.ex
lib/pleroma/web/websub/websub.ex
mix.exs
mix.lock
priv/repo/migrations/20190511191044_set_default_state_to_reports.exs [new file with mode: 0644]
priv/repo/migrations/20190515222404_add_thread_visibility_function.exs [new file with mode: 0644]
priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs [new file with mode: 0644]
test/activity_test.exs
test/conversation_test.exs
test/fixtures/rich_media/ogp-missing-data.html [new file with mode: 0644]
test/fixtures/rich_media/ogp.html
test/fixtures/sound.mp3 [new file with mode: 0644]
test/formatter_test.exs
test/keys_test.exs [new file with mode: 0644]
test/object/fetcher_test.exs
test/plugs/cache_control_test.exs [new file with mode: 0644]
test/plugs/http_security_plug_test.exs
test/plugs/legacy_authentication_plug_test.exs
test/repo_test.exs
test/support/factory.ex
test/support/http_request_mock.ex
test/support/ostatus_mock.ex [deleted file]
test/support/websub_mock.ex [deleted file]
test/tasks/database_test.exs [new file with mode: 0644]
test/tasks/user_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/mrf/simple_policy_test.exs [new file with mode: 0644]
test/web/activity_pub/transmogrifier_test.exs
test/web/activity_pub/views/user_view_test.exs
test/web/activity_pub/visibilty_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/common_api/common_api_test.exs
test/web/fallback_test.exs [new file with mode: 0644]
test/web/mastodon_api/account_view_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/mongooseim/mongoose_im_controller_test.exs [new file with mode: 0644]
test/web/node_info_test.exs
test/web/oauth/token_test.exs
test/web/ostatus/ostatus_test.exs
test/web/plugs/federating_plug_test.exs
test/web/rich_media/helpers_test.exs
test/web/rich_media/parser_test.exs
test/web/salmon/salmon_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/web_finger/web_finger_test.exs
test/web/websub/websub_controller_test.exs

diff --git a/.buildpacks b/.buildpacks
new file mode 100644 (file)
index 0000000..31dd570
--- /dev/null
@@ -0,0 +1 @@
+https://github.com/hashnuke/heroku-buildpack-elixir  
index f9745122a3a7ab51ce3679c2c590d286ca2966c2..53f05ae9223bdb1e298cf54bbc932c6fbea491d5 100644 (file)
@@ -45,7 +45,8 @@ docs-build:
 unit-testing:
   stage: test
   services:
-  - name: postgres:9.6.2
+  - name: lainsoykaf/postgres-with-rum
+    alias: postgres
     command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
   script:
     - mix deps.get
@@ -54,6 +55,21 @@ unit-testing:
     - mix test --trace --preload-modules
     - mix coveralls
 
+unit-testing-rum:
+  stage: test
+  services:
+  - name: lainsoykaf/postgres-with-rum
+    alias: postgres
+    command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+  variables:
+    RUM_ENABLED: "true"
+  script:
+    - mix deps.get
+    - mix ecto.create
+    - mix ecto.migrate
+    - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
+    - mix test --trace --preload-modules
+
 lint:
   stage: test
   script:
@@ -65,7 +81,6 @@ analysis:
     - mix deps.get
     - mix credo --strict --only=warnings,todo,fixme,consistency,readability
 
-
 docs-deploy:
   stage: deploy
   image: alpine:3.9
@@ -80,3 +95,50 @@ docs-deploy:
     - 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}"
+
+review_app:
+  image: alpine:3.9
+  stage: deploy
+  before_script:
+    - apk update && apk add openssh-client git
+  when: manual
+  environment:
+    name: review/$CI_COMMIT_REF_NAME
+    url: https://$CI_ENVIRONMENT_SLUG.pleroma.online/
+    on_stop: stop_review_app
+  only:
+    - branches
+  except:
+    - master
+    - develop
+  script:
+    - echo "$CI_ENVIRONMENT_SLUG"
+    - mkdir -p ~/.ssh
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+    - ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
+    - (ssh -t dokku@pleroma.online -- apps:create "$CI_ENVIRONMENT_SLUG") || true
+    - ssh -t dokku@pleroma.online -- config:set "$CI_ENVIRONMENT_SLUG" APP_NAME="$CI_ENVIRONMENT_SLUG" APP_HOST="$CI_ENVIRONMENT_SLUG.pleroma.online" MIX_ENV=dokku
+    - (ssh -t dokku@pleroma.online -- postgres:create $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db) || true
+    - (ssh -t dokku@pleroma.online -- postgres:link $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db "$CI_ENVIRONMENT_SLUG") || true
+    - (ssh -t dokku@pleroma.online -- certs:add "$CI_ENVIRONMENT_SLUG" /home/dokku/server.crt /home/dokku/server.key) || true
+    - (git remote add dokku dokku@pleroma.online:$CI_ENVIRONMENT_SLUG) || true
+    - git push -f dokku $CI_COMMIT_SHA:refs/heads/master
+
+stop_review_app:
+  image: alpine:3.9
+  stage: deploy
+  before_script:
+    - apk update && apk add openssh-client git
+  when: manual
+  environment:
+    name: review/$CI_COMMIT_REF_NAME
+    action: stop
+  script:
+    - echo "$CI_ENVIRONMENT_SLUG"
+    - mkdir -p ~/.ssh
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+    - ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
+    - ssh -t dokku@pleroma.online -- --force apps:destroy "$CI_ENVIRONMENT_SLUG"
+    - ssh -t dokku@pleroma.online -- --force postgres:destroy $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db
index 5ee853c7698a782b9a9e328faf2801cada205c5c..8ba48b72cf0d06bb912e9c04f158f9c8bef483b2 100644 (file)
@@ -5,23 +5,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
 ### Added
+- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
+- [MongooseIM](https://github.com/esl/MongooseIM) http authentication support.
 - 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 bump_all_conversations`
 - Mix Tasks: `mix pleroma.database remove_embedded_objects`
+- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
+- Mix Tasks: `mix pleroma.user toggle_confirmed`
 - 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
+- Configuration: `report_uri` option
 - Pleroma API: User subscriptions
 - Pleroma API: Healthcheck endpoint
+- Pleroma API: `/api/v1/pleroma/mascot` per-user frontend mascot configuration endpoints
 - 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
+- Admin API: Endpoints for managing reports
+- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
 - 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)
@@ -32,6 +41,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Metadata: RelMe provider
 - OAuth: added support for refresh tokens
 - Emoji packs and emoji pack manager
+- Object pruning (`mix pleroma.database prune_objects`)
+- OAuth: added job to clean expired access tokens
+- MRF: Support for rejecting reports from specific instances (`mrf_simple`)
+- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
 
 ### Changed
 - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
@@ -66,6 +79,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - 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
+- Posts which are marked sensitive or tagged nsfw no longer have link previews.
+- HTTP connection timeout is now set to 10 seconds.
+- Respond with a 404 Not implemented JSON error message when requested API is not implemented
 
 ### Fixed
 - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
@@ -97,9 +113,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - 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`]
+- Mastodon API: Replace missing non-nullable Card attributes with empty strings
+- User-Agent is now sent correctly for all HTTP requests.
+- MRF: Simple policy now properly delists imported or relayed statuses
 
 ## Removed
-- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` 
+- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
+
+## [0.9.99999] - 2019-05-31
+### Security
+- Mastodon API: Fix lists leaking private posts
 
 ## [0.9.9999] - 2019-04-05
 ### Security
diff --git a/Procfile b/Procfile
new file mode 100644 (file)
index 0000000..7ac187b
--- /dev/null
+++ b/Procfile
@@ -0,0 +1,2 @@
+web: mix phx.server
+release: mix ecto.migrate
index 9cbac35ebdbeb3767a18b2310bbdde235d9d425a..68168b279665c34e71cc20185a7a6b1216c64ac7 100644 (file)
@@ -48,7 +48,8 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
 
 config :pleroma, Pleroma.Repo,
   types: Pleroma.PostgresTypes,
-  telemetry_event: [Pleroma.Repo.Instrumenter]
+  telemetry_event: [Pleroma.Repo.Instrumenter],
+  migration_lock: nil
 
 config :pleroma, Pleroma.Captcha,
   enabled: false,
@@ -183,14 +184,12 @@ config :mime, :types, %{
   "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,
+  send_user_agent: true,
   adapter: [
     ssl_options: [
       # We don't support TLS v1.3 yet
@@ -237,7 +236,8 @@ config :pleroma, :instance,
   welcome_message: nil,
   max_report_comment_size: 1000,
   safe_dm_mentions: false,
-  healthcheck: false
+  healthcheck: false,
+  remote_post_retention_days: 90
 
 config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
 
@@ -274,6 +274,19 @@ config :pleroma, :frontend_configurations,
     showInstanceSpecificPanel: true
   }
 
+config :pleroma, :assets,
+  mascots: [
+    pleroma_fox_tan: %{
+      url: "/images/pleroma-fox-tan-smol.png",
+      mime_type: "image/png"
+    },
+    pleroma_fox_tan_shy: %{
+      url: "/images/pleroma-fox-tan-shy.png",
+      mime_type: "image/png"
+    }
+  ],
+  default_mascot: :pleroma_fox_tan
+
 config :pleroma, :activitypub,
   accept_blocks: true,
   unfollow_blocked: true,
@@ -296,8 +309,11 @@ config :pleroma, :mrf_simple,
   media_removal: [],
   media_nsfw: [],
   federated_timeline_removal: [],
+  report_removal: [],
   reject: [],
-  accept: []
+  accept: [],
+  avatar_removal: [],
+  banner_removal: []
 
 config :pleroma, :mrf_keyword,
   reject: [],
@@ -368,6 +384,7 @@ config :pleroma, Pleroma.User,
     "activities",
     "api",
     "auth",
+    "check_password",
     "dev",
     "friend-requests",
     "inbox",
@@ -388,6 +405,7 @@ config :pleroma, Pleroma.User,
     "status",
     "tag",
     "user-search",
+    "user_exists",
     "users",
     "web"
   ]
@@ -462,7 +480,11 @@ config :pleroma, Pleroma.ScheduledActivity,
 
 config :pleroma, :oauth2,
   token_expires_in: 600,
-  issue_new_refresh_token: true
+  issue_new_refresh_token: true,
+  clean_expired_tokens: false,
+  clean_expired_tokens_interval: 86_400_000
+
+config :pleroma, :database, rum_enabled: false
 
 config :http_signatures,
   adapter: Pleroma.Signature
diff --git a/config/dokku.exs b/config/dokku.exs
new file mode 100644 (file)
index 0000000..9ea0ec4
--- /dev/null
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+  http: [
+    port: String.to_integer(System.get_env("PORT") || "4000"),
+    protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
+  ],
+  protocol: "http",
+  secure_cookie_flag: false,
+  url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443],
+  secret_key_base: "+S+ULgf7+N37c/lc9K66SMphnjQIRGklTu0BRr2vLm2ZzvK0Z6OH/PE77wlUNtvP"
+
+database_url =
+  System.get_env("DATABASE_URL") ||
+    raise """
+    environment variable DATABASE_URL is missing.
+    For example: ecto://USER:PASS@HOST/DATABASE
+    """
+
+config :pleroma, Pleroma.Repo,
+  # ssl: true,
+  url: database_url,
+  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
+
+config :pleroma, :instance, name: "#{System.get_env("APP_NAME")} CI Instance"
index a0c90c3717c5fbcfcd0a8fba11699a8f98a9b54a..41cddb9bd8938b52b7d1f4c70e1fae552cb2b2a6 100644 (file)
@@ -39,8 +39,6 @@ config :pleroma, Pleroma.Repo,
 # Reduce hash rounds for testing
 config :pbkdf2_elixir, rounds: 1
 
-config :pleroma, :websub, Pleroma.Web.WebsubMock
-config :pleroma, :ostatus, Pleroma.Web.OStatusMock
 config :tesla, adapter: Tesla.Mock
 config :pleroma, :rich_media, enabled: false
 
@@ -61,6 +59,14 @@ config :pleroma, Pleroma.ScheduledActivity,
 
 config :pleroma, :app_account_creation, max_requests: 5
 
+config :pleroma, :http_security, report_uri: "https://endpoint.com"
+
+config :pleroma, :http, send_user_agent: false
+
+rum_enabled = System.get_env("RUM_ENABLED") == "true"
+config :pleroma, :database, rum_enabled: rum_enabled
+IO.puts("RUM enabled: #{rum_enabled}")
+
 try do
   import_config "test.secret.exs"
 rescue
index 75fa2ee83e3e32ffdc5126c02cb6f6fd9a2ed70b..b45c5e2856778c1fef1f3ec3e84162eba51761fa 100644 (file)
@@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
 - 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
+```json
 {
   "page_size": integer,
   "count": integer,
@@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
   - `nickname`
 - Response: User’s object
 
-```JSON
+```json
 {
   "deactivated": bool,
   "id": integer,
@@ -106,15 +106,15 @@ Authentication is required and the user must be an admin.
 
 - 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`
 
@@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
 - Params: none
 - Response:
 
-```JSON
+```json
 {
   "is_moderator": bool,
   "is_admin": bool
@@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
 
-```JSON
+```json
 {
   "is_moderator": bool,
   "is_admin": bool
@@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
 
-```JSON
+```json
 {
 
   "invites": [
@@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `token`
 - Response:
 
-```JSON
+```json
 {
   "id": integer,
   "token": string,
@@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Methods: `GET`
 - Params: none
 - Response: password reset token (base64 string)
+
+## `/api/pleroma/admin/reports`
+### Get a list of reports
+- Method `GET`
+- Params:
+  - `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
+  - `limit`: optional, the number of records to retrieve
+  - `since_id`: optional, returns results that are more recent than the specified id
+  - `max_id`: optional, returns results that are older than the specified id
+- Response: 
+  - On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
+  - On success: JSON, returns a list of reports, where:
+    - `account`: the user who has been reported
+    - `actor`: the user who has sent the report
+    - `statuses`: list of statuses that have been included to the report
+
+```json
+{
+  "reports": [
+    {
+      "account": {
+        "acct": "user",
+        "avatar": "https://pleroma.example.org/images/avi.png",
+        "avatar_static": "https://pleroma.example.org/images/avi.png",
+        "bot": false,
+        "created_at": "2019-04-23T17:32:04.000Z",
+        "display_name": "User",
+        "emojis": [],
+        "fields": [],
+        "followers_count": 1,
+        "following_count": 1,
+        "header": "https://pleroma.example.org/images/banner.png",
+        "header_static": "https://pleroma.example.org/images/banner.png",
+        "id": "9i6dAJqSGSKMzLG2Lo",
+        "locked": false,
+        "note": "",
+        "pleroma": {
+          "confirmation_pending": false,
+          "hide_favorites": true,
+          "hide_followers": false,
+          "hide_follows": false,
+          "is_admin": false,
+          "is_moderator": false,
+          "relationship": {},
+          "tags": []
+        },
+        "source": {
+          "note": "",
+          "pleroma": {},
+          "sensitive": false
+        },
+        "statuses_count": 3,
+        "url": "https://pleroma.example.org/users/user",
+        "username": "user"
+      },
+      "actor": {
+        "acct": "lain",
+        "avatar": "https://pleroma.example.org/images/avi.png",
+        "avatar_static": "https://pleroma.example.org/images/avi.png",
+        "bot": false,
+        "created_at": "2019-03-28T17:36:03.000Z",
+        "display_name": "Roger Braun",
+        "emojis": [],
+        "fields": [],
+        "followers_count": 1,
+        "following_count": 1,
+        "header": "https://pleroma.example.org/images/banner.png",
+        "header_static": "https://pleroma.example.org/images/banner.png",
+        "id": "9hEkA5JsvAdlSrocam",
+        "locked": false,
+        "note": "",
+        "pleroma": {
+          "confirmation_pending": false,
+          "hide_favorites": false,
+          "hide_followers": false,
+          "hide_follows": false,
+          "is_admin": false,
+          "is_moderator": false,
+          "relationship": {},
+          "tags": []
+        },
+        "source": {
+          "note": "",
+          "pleroma": {},
+          "sensitive": false
+        },
+        "statuses_count": 1,
+        "url": "https://pleroma.example.org/users/lain",
+        "username": "lain"
+      },
+      "content": "Please delete it",
+      "created_at": "2019-04-29T19:48:15.000Z",
+      "id": "9iJGOv1j8hxuw19bcm",
+      "state": "open",
+      "statuses": [
+        {
+          "account": { ... },
+          "application": {
+            "name": "Web",
+            "website": null
+          },
+          "bookmarked": false,
+          "card": null,
+          "content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
+          "created_at": "2019-04-23T19:15:47.000Z",
+          "emojis": [],
+          "favourited": false,
+          "favourites_count": 0,
+          "id": "9i6mQ9uVrrOmOime8m",
+          "in_reply_to_account_id": null,
+          "in_reply_to_id": null,
+          "language": null,
+          "media_attachments": [],
+          "mentions": [
+            {
+              "acct": "lain",
+              "id": "9hEkA5JsvAdlSrocam",
+              "url": "https://pleroma.example.org/users/lain",
+              "username": "lain"
+            },
+            {
+              "acct": "user",
+              "id": "9i6dAJqSGSKMzLG2Lo",
+              "url": "https://pleroma.example.org/users/user",
+              "username": "user"
+            }
+          ],
+          "muted": false,
+          "pinned": false,
+          "pleroma": {
+            "content": {
+              "text/plain": "@lain click on my link https://www.google.com/"
+            },
+            "conversation_id": 28,
+            "in_reply_to_account_acct": null,
+            "local": true,
+            "spoiler_text": {
+              "text/plain": ""
+            }
+          },
+          "reblog": null,
+          "reblogged": false,
+          "reblogs_count": 0,
+          "replies_count": 0,
+          "sensitive": false,
+          "spoiler_text": "",
+          "tags": [],
+          "uri": "https://pleroma.example.org/objects/8717b90f-8e09-4b58-97b0-e3305472b396",
+          "url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
+          "visibility": "direct"
+        }
+      ]
+    }
+  ]
+}
+```
+
+## `/api/pleroma/admin/reports/:id`
+### Get an individual report
+- Method `GET`
+- Params:
+  - `id`
+- Response:
+  - On failure: 
+    - 403 Forbidden `{"error": "error_msg"}`
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id`
+### Change the state of the report
+- Method `PUT`
+- Params:
+  - `id`
+  - `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Unsupported state"`
+    - 403 Forbidden `{"error": "error_msg"}`
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id/respond`
+### Respond to a report
+- Method `POST`
+- Params:
+  - `id`
+  - `status`: required, the message
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Invalid parameters"` when `status` is missing 
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: JSON, created Mastodon Status entity
+
+```json
+{
+  "account": { ... },
+  "application": {
+    "name": "Web",
+    "website": null
+  },
+  "bookmarked": false,
+  "card": null,
+  "content": "Your claim is going to be closed",
+  "created_at": "2019-05-11T17:13:03.000Z",
+  "emojis": [],
+  "favourited": false,
+  "favourites_count": 0,
+  "id": "9ihuiSL1405I65TmEq",
+  "in_reply_to_account_id": null,
+  "in_reply_to_id": null,
+  "language": null,
+  "media_attachments": [],
+  "mentions": [
+    {
+      "acct": "user",
+      "id": "9i6dAJqSGSKMzLG2Lo",
+      "url": "https://pleroma.example.org/users/user",
+      "username": "user"
+    },
+    {
+      "acct": "admin",
+      "id": "9hEkA5JsvAdlSrocam",
+      "url": "https://pleroma.example.org/users/admin",
+      "username": "admin"
+    }
+  ],
+  "muted": false,
+  "pinned": false,
+  "pleroma": {
+    "content": {
+      "text/plain": "Your claim is going to be closed"
+    },
+    "conversation_id": 35,
+    "in_reply_to_account_acct": null,
+    "local": true,
+    "spoiler_text": {
+      "text/plain": ""
+    }
+  },
+  "reblog": null,
+  "reblogged": false,
+  "reblogs_count": 0,
+  "replies_count": 0,
+  "sensitive": false,
+  "spoiler_text": "",
+  "tags": [],
+  "uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
+  "url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
+  "visibility": "direct"
+}
+```
+
+## `/api/pleroma/admin/statuses/:id`
+### Change the scope of an individual reported status
+- Method `PUT`
+- Params:
+  - `id`
+  - `sensitive`: optional, valid values are `true` or `false`
+  - `visibility`: optional, valid values are `public`, `private` and `unlisted`
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Unsupported visibility"`
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Mastodon Status entity
+
+## `/api/pleroma/admin/statuses/:id`
+### Delete an individual reported status
+- Method `DELETE`
+- Params:
+  - `id`
+- Response: 
+  - On failure: 
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: 200 OK `{}`
index dd0b6ca733dc79e47ccc75c4ef9aa7c4956d7ebc..4d99a2d2bfdaad8976fc763561df976ec1d428f8 100644 (file)
@@ -252,6 +252,45 @@ See [Admin-API](Admin-API.md)
 ]
 ```
 
+## `/api/v1/pleroma/mascot`
+### Gets user mascot image
+* Method `GET`
+* Authentication: required
+
+* Response: JSON. Returns a mastodon media attachment entity.
+* Example response:
+```json
+{
+    "id": "abcdefg",
+    "url": "https://pleroma.example.org/media/abcdefg.png",
+    "type": "image",
+    "pleroma": {
+        "mime_type": "image/png"
+    }
+}
+```
+
+### Updates user mascot image
+* Method `PUT`
+* Authentication: required
+* Params:
+    * `image`: Multipart image
+* Response: JSON. Returns a mastodon media attachment entity
+  when successful, otherwise returns HTTP 415 `{"error": "error_msg"}`
+* Example response:
+```json
+{
+    "id": "abcdefg",
+    "url": "https://pleroma.example.org/media/abcdefg.png",
+    "type": "image",
+    "pleroma": {
+        "mime_type": "image/png"
+    }
+}
+```
+* Note: Behaves exactly the same as `POST /api/v1/upload`.
+  Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.
+
 ## `/api/pleroma/notification_settings`
 ### Updates user notification settings
 * Method `PUT`
index 470f71b7cf71c9c289aedec2d9573be878031966..67b062fe930b776112f680eae2ac24161cbbc1dc 100644 (file)
@@ -104,6 +104,7 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
 * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
 * `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
+* `remote_post_retention_days`: the default amount of days to retain remote posts when pruning the database
 
 ## :app_account_creation
 REST API for creating an account settings
@@ -203,12 +204,25 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
 * `hide_post_stats`: Hide notices statistics(repeats, favorites, …)
 * `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)
 
+## :assets
+
+This section configures assets to be used with various frontends. Currently the only option
+relates to mascots on the mastodon frontend
+
+* `mascots`: KeywordList of mascots, each element __MUST__ contain both a `url` and a
+  `mime_type` key.
+* `default_mascot`: An element from `mascots` - This will be used as the default mascot
+  on MastoFE (default: `:pleroma_fox_tan`)
+
 ## :mrf_simple
 * `media_removal`: List of instances to remove medias from
 * `media_nsfw`: List of instances to put medias as NSFW(sensitive) from
 * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline
 * `reject`: List of instances to reject any activities from
 * `accept`: List of instances to accept any activities from
+* `report_removal`: List of instances to reject reports from
+* `avatar_removal`: List of instances to strip avatars from
+* `banner_removal`: List of instances to strip banners from
 
 ## :mrf_rejectnonpublic
 * `allow_followersonly`: whether to allow followers-only posts
@@ -286,7 +300,8 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
 * ``sts``: Whether to additionally send a `Strict-Transport-Security` header
 * ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
 * ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
-* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
+* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`
+* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
 
 ## :mrf_user_allowlist
 
@@ -466,7 +481,7 @@ config :esshd,
   password_authenticator: "Pleroma.BBS.Authenticator"
 ```
 
-Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
+Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
 
 ## :auth
 
@@ -538,8 +553,25 @@ Configure OAuth 2 provider capabilities:
 
 * `token_expires_in` - The lifetime in seconds of the access token.
 * `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.
+* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`.
+* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours).
 
 ## :emoji
 * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
 * `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
 * `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).
+
+## Database options
+
+### RUM indexing for full text search
+* `rum_enabled`: If RUM indexes should be used. Defaults to `false`.
+
+RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. While they may eventually be mainlined, for now they have to be installed as a PostgreSQL extension from https://github.com/postgrespro/rum.
+
+Their advantage over the standard GIN indexes is that they allow efficient ordering of search results by timestamp, which makes search queries a lot faster on larger servers, by one or two orders of magnitude. They take up around 3 times as much space as GIN indexes.
+
+To enable them, both the `rum_enabled` flag has to be set and the following special migration has to be run:
+
+`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`
+
+This will probably take a long time.
diff --git a/docs/config/howto_mongooseim.md b/docs/config/howto_mongooseim.md
new file mode 100644 (file)
index 0000000..a33e590
--- /dev/null
@@ -0,0 +1,10 @@
+# Configuring MongooseIM (XMPP Server) to use Pleroma for authentication
+
+If you want to give your Pleroma users an XMPP (chat) account, you can configure [MongooseIM](https://github.com/esl/MongooseIM) to use your Pleroma server for user authentication, automatically giving every local user an XMPP account.
+
+In general, you just have to follow the configuration described at [https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/](https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/) and do these changes to your mongooseim.cfg.
+
+1. Set the auth_method to `{auth_method, http}`.
+2. Add the http auth pool like this: `{http, global, auth, [{workers, 50}], [{server, "https://yourpleromainstance.com"}]}`
+
+Restart your MongooseIM server, your users should now be able to connect with their Pleroma credentials.
index 2cc16cef0d2a60e09dab456893672ab3c9c9cd7f..45be18fc5a80d4476f42d104579f81fa8445ff1b 100644 (file)
@@ -5,11 +5,12 @@ Possible uses include:
 
 * marking incoming messages with media from a given account or instance as sensitive
 * rejecting messages from a specific instance
+* rejecting reports (flags) from a specific instance
 * removing/unlisting messages from the public timelines
 * removing media from messages
 * sending only public messages to a specific instance
 
-The MRF provides user-configurable policies.  The default policy is `NoOpPolicy`, which disables the MRF functionality.  Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.  
+The MRF provides user-configurable policies.  The default policy is `NoOpPolicy`, which disables the MRF functionality.  Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.
 It is possible to use multiple, active MRF policies at the same time.
 
 ## Quarantine Instances
@@ -41,12 +42,13 @@ Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_si
 * `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media.
 * `reject`: Servers in this group will have their messages rejected.
 * `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
+* `report_removal`: Servers in this group will have their reports (flags) rejected.
 
 Servers should be configured as lists.
 
 ### Example
 
-This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com` and remove messages from `spam.university` from the federated timeline:
+This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
 
 ```
 config :pleroma, :instance,
@@ -56,7 +58,8 @@ config :pleroma, :mrf_simple,
   media_removal: ["illegalporn.biz"],
   media_nsfw: ["porn.biz", "porn.business"],
   reject: ["spam.com"],
-  federated_timeline_removal: ["spam.university"]
+  federated_timeline_removal: ["spam.university"],
+  report_removal: ["whiny.whiner"]
 
 ```
 
index c493816d6f9c3d30e0c59a60a94a5044cdedd61d..e1d69c873b3bfbca9f8921c3c26cd28109f237dd 100644 (file)
@@ -87,7 +87,7 @@ sudo adduser -S -s /bin/false -h /opt/pleroma -H pleroma
 ```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
+sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
 ```
 
 * Change to the new directory:
index 2b040cfbc0f3a99cf4beb6461bd18fe2d50d28a5..26e1ab86aac8f4a9d8d8871cba40fa4cb8c8b5ab 100644 (file)
@@ -66,7 +66,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
 ```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
+sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
 ```
 
 * Change to the new directory:
index 76de21ed84f6103190d2454532d94b0117feecc2..19bff7461cc02e4cb048f1729eca63a3318adadb 100644 (file)
@@ -143,7 +143,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
 ```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
+sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
 ```
 
 * Change to the new directory:
index 9613a329b8f861d3c847434d2253ff2e2c6b5a9e..7d39ca5f92312b6aa43ceb6886582c28452f09a6 100644 (file)
@@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
 * `erlang-tools`
 * `erlang-parsetools`
 * `erlang-eldap`, if you want to enable ldap authenticator
+* `erlang-ssh`
 * `erlang-xmerl`
 * `git`
 * `build-essential`
@@ -49,7 +50,7 @@ sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
 
 ```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
@@ -67,7 +68,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
 ```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
+sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
 ```
 
 * Change to the new directory:
index ac5dcaaee205bf7fea317f3867b772ad96398a59..84b9666c8c74cc928fd53d96ab1f9dd2b4ccf3b2 100644 (file)
@@ -14,6 +14,7 @@
 - erlang-dev
 - erlang-tools
 - erlang-parsetools
+- erlang-ssh
 - erlang-xmerl (Jessieではバックポートからインストールすること!)
 - git
 - build-essential
@@ -44,7 +45,7 @@ wget -P /tmp/ https://packages.erlang-solutions.com/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 (バックエンド) をインストールします
@@ -68,7 +69,7 @@ cd ~
 
 *  Gitリポジトリをクローンします。
 ```
-git clone https://git.pleroma.social/pleroma/pleroma
+git clone -b master https://git.pleroma.social/pleroma/pleroma
 ```
 
 *  新しいディレクトリに移動します。
index fccaad378805d9e07f68f8de435a910369e1fce4..b7c42a4775ab206c11bf5c32c41c3fe5371b5aaa 100644 (file)
@@ -106,7 +106,7 @@ It is highly recommended you use your own fork for the `https://path/to/repo` pa
 
 ```shell
  pleroma$ cd ~
- pleroma$ git clone https://path/to/repo
+ pleroma$ git clone -b master https://path/to/repo
 ```
 
 * Change to the new directory:
index e0ac983592ed7ceedfdaca7e3b8b1b6d6a782837..a096d5354d663aa8f7a829aaac657255d68796ce 100644 (file)
@@ -58,7 +58,7 @@ Clone the repository:
 
 ```
 $ cd /home/pleroma
-$ git clone https://git.pleroma.social/pleroma/pleroma.git
+$ git clone -b master https://git.pleroma.social/pleroma/pleroma.git
 ```
 
 Configure Pleroma. Note that you need a domain name at this point:
index 633b08e6cf95da157c1ae3ba2c5a62359eb0e2ef..fcba38b2c8800579f06fe38de2ac5ef32014640a 100644 (file)
@@ -29,7 +29,7 @@ This creates a "pleroma" login class and sets higher values than default for dat
 Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/): `useradd -m -L pleroma _pleroma`
 
 #### Clone pleroma's directory
-Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
+Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b master https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
 
 #### Postgresql
 Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:  
index fa6faa62ded284c27d25dfe58513c9de14521956..39819a8c8e8d0d08d53fdcc3fe0cf409cb9d436e 100644 (file)
@@ -44,7 +44,7 @@ Vaihda pleroma-käyttäjään ja mene kotihakemistoosi:
 
 Lataa pleroman lähdekoodi:
 
-`$ git clone https://git.pleroma.social/pleroma/pleroma.git`
+`$ git clone -b master https://git.pleroma.social/pleroma/pleroma.git`
 
 `$ cd pleroma`
 
index 4af0747fea3d083a5de701379a6768db78f5e32c..045dc7c0501816a450af42388a9f3d165a7b1826 100644 (file)
@@ -1,30 +1,30 @@
 # Introduction to Pleroma
 ## What is Pleroma?
-Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.  
-It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.  
-It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.  
+Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
+It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
+It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
 One account on a instance is enough to talk to the entire fediverse!
-  
+
 ## How can I use it?
 
-Pleroma instances are already widely deployed, a list can be found here:  
+Pleroma instances are already widely deployed, a list can be found here:
 http://distsn.org/pleroma-instances.html
 
-If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!  
-Installation instructions can be found here:  
+If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
+Installation instructions can be found here:
 [main Pleroma wiki](/)
-  
+
 ## I got an account, now what?
-Great! Now you can explore the fediverse!  
-- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.  
-(If you don't have one yet, click on Register) :slightly_smiling_face:  
+Great! Now you can explore the fediverse!
+- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
+(If you don't have one yet, click on Register) :slightly_smiling_face:
 
 At this point you will have two columns in front of you.
 
 ### Left column
-- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).  
-Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).  
-If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:   
+- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
+Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
+If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
 To post your status, simply press Submit.
 
 - second block: Here you can switch between the different timelines:
@@ -38,7 +38,7 @@ To post your status, simply press Submit.
 - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
 
 ### Right column
-This is where the interesting stuff happens! :slight_smile:   
+This is where the interesting stuff happens! :slight_smile:
 Depending on the timeline you will see different statuses, but each status has a standard structure:
 - Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
 - A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
@@ -47,9 +47,9 @@ Depending on the timeline you will see different statuses, but each status has a
 - Four buttons (left to right): Reply, Repeat, Favorite, Delete.
 
 ## Mastodon interface
-If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:  
-Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:  
-For more information on the Mastodon interface, please look here:  
+If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
+Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
+For more information on the Mastodon interface, please look here:
 https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
 
 Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
new file mode 100644 (file)
index 0000000..c23b08f
--- /dev/null
@@ -0,0 +1,2 @@
+elixir_version=1.8.2
+erlang_version=21.3.7
index fcf76718e74ebd1f47f34e9461de26fc5c226779..7985d9c6746a6c2d2ef9bd682f184f013de11f93 100644 (file)
@@ -10,7 +10,9 @@ example.tld  {
 
   gzip
 
-  proxy / localhost:4000 {
+  # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
+  # and `localhost.` resolves to [::0] on some systems: see issue #930
+  proxy / 127.0.0.1:4000 {
     websocket
     transparent
   }
index 2beb7c4cc5711e9101f3a430897fad48e09ea84a..b5640ac3dd81adca3a16faa231fa20ea525d6261 100644 (file)
@@ -58,8 +58,10 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
     RewriteRule /(.*) ws://localhost:4000/$1 [P,L]
 
     ProxyRequests off
-    ProxyPass / http://localhost:4000/
-    ProxyPassReverse / http://localhost:4000/
+    # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
+    # and `localhost.` resolves to [::0] on some systems: see issue #930
+    ProxyPass / http://127.0.0.1:4000/
+    ProxyPassReverse / http://127.0.0.1:4000/
 
     RequestHeader set Host ${servername}
     ProxyPreserveHost On
diff --git a/installation/pleroma-mongooseim.cfg b/installation/pleroma-mongooseim.cfg
new file mode 100755 (executable)
index 0000000..d756732
--- /dev/null
@@ -0,0 +1,932 @@
+%%%
+%%%               ejabberd configuration file
+%%%
+%%%'
+
+%%% The parameters used in this configuration file are explained in more detail
+%%% in the ejabberd Installation and Operation Guide.
+%%% Please consult the Guide in case of doubts, it is included with
+%%% your copy of ejabberd, and is also available online at
+%%% http://www.process-one.net/en/ejabberd/docs/
+
+%%% This configuration file contains Erlang terms.
+%%% In case you want to understand the syntax, here are the concepts:
+%%%
+%%%  - The character to comment a line is %
+%%%
+%%%  - Each term ends in a dot, for example:
+%%%      override_global.
+%%%
+%%%  - A tuple has a fixed definition, its elements are
+%%%    enclosed in {}, and separated with commas:
+%%%      {loglevel, 4}.
+%%%
+%%%  - A list can have as many elements as you want,
+%%%    and is enclosed in [], for example:
+%%%      [http_poll, web_admin, tls]
+%%%
+%%%    Pay attention that list elements are delimited with commas,
+%%%    but no comma is allowed after the last list element. This will
+%%%    give a syntax error unlike in more lenient languages (e.g. Python).
+%%%
+%%%  - A keyword of ejabberd is a word in lowercase.
+%%%    Strings are enclosed in "" and can contain spaces, dots, ...
+%%%      {language, "en"}.
+%%%      {ldap_rootdn, "dc=example,dc=com"}.
+%%%
+%%%  - This term includes a tuple, a keyword, a list, and two strings:
+%%%      {hosts, ["jabber.example.net", "im.example.com"]}.
+%%%
+%%%  - This config is preprocessed during release generation by a tool which
+%%%    interprets double curly braces as substitution markers, so avoid this
+%%%    syntax in this file (though it's valid Erlang).
+%%%
+%%%    So this is OK (though arguably looks quite ugly):
+%%%      { {s2s_addr, "example-host.net"}, {127,0,0,1} }.
+%%%
+%%%    And I can't give an example of what's not OK exactly because
+%%%    of this rule.
+%%%
+
+
+%%%.   =======================
+%%%'   OVERRIDE STORED OPTIONS
+
+%%
+%% Override the old values stored in the database.
+%%
+
+%%
+%% Override global options (shared by all ejabberd nodes in a cluster).
+%%
+%%override_global.
+
+%%
+%% Override local options (specific for this particular ejabberd node).
+%%
+%%override_local.
+
+%%
+%% Remove the Access Control Lists before new ones are added.
+%%
+%%override_acls.
+
+
+%%%.   =========
+%%%'   DEBUGGING
+
+%%
+%% loglevel: Verbosity of log files generated by ejabberd.
+%% 0: No ejabberd log at all (not recommended)
+%% 1: Critical
+%% 2: Error
+%% 3: Warning
+%% 4: Info
+%% 5: Debug
+%%
+{loglevel, 3}.
+
+%%%.   ================
+%%%'   SERVED HOSTNAMES
+
+%%
+%% hosts: Domains served by ejabberd.
+%% You can define one or several, for example:
+%% {hosts, ["example.net", "example.com", "example.org"]}.
+%%
+{hosts, ["pleroma.soykaf.com"] }.
+
+%%
+%% route_subdomains: Delegate subdomains to other XMPP servers.
+%% For example, if this ejabberd serves example.org and you want
+%% to allow communication with an XMPP server called im.example.org.
+%%
+%%{route_subdomains, s2s}.
+
+
+%%%.   ===============
+%%%'   LISTENING PORTS
+
+%%
+%% listen: The ports ejabberd will listen on, which service each is handled
+%% by and what options to start it with.
+%%
+{listen,
+ [
+  %% BOSH and WS endpoints over HTTP
+  { 5280, ejabberd_cowboy, [
+      {num_acceptors, 10},
+      {transport_options, [{max_connections, 1024}]},
+      {modules, [
+
+          {"_", "/http-bind", mod_bosh},
+          {"_", "/ws-xmpp", mod_websockets, [{ejabberd_service, [
+                                        {access, all},
+                                        {shaper_rule, fast},
+                                        {ip, {127, 0, 0, 1}},
+                                        {password, "secret"}]}
+          %% Uncomment to enable connection dropping or/and server-side pings
+          %{timeout, 600000}, {ping_rate, 2000}
+          ]}
+          %% Uncomment to serve static files
+          %{"_", "/static/[...]", cowboy_static,
+          %  {dir, "/var/www", [{mimetypes, cow_mimetypes, all}]}
+          %},
+
+          %% Example usage of mod_revproxy
+
+          %% {"_", "/[...]", mod_revproxy, [{timeout, 5000},
+          %%                                % time limit for upstream to respond
+          %%                                {body_length, 8000000},
+          %%                                % maximum body size (may be infinity)
+          %%                                {custom_headers, [{<<"header">>,<<"value">>}]}
+          %%                                % list of extra headers that are send to upstream
+          %%                               ]}
+
+          %% Example usage of mod_cowboy
+
+          %% {"_", "/[...]", mod_cowboy, [{http, mod_revproxy,
+          %%                                [{timeout, 5000},
+          %%                                 % time limit for upstream to respond
+          %%                                 {body_length, 8000000},
+          %%                                 % maximum body size (may be infinity)
+          %%                                 {custom_headers, [{<<"header">>,<<"value">>}]}
+          %%                                 % list of extra headers that are send to upstream
+          %%                                ]},
+          %%                               {ws, xmpp, mod_websockets}
+          %%                             ]}
+      ]}
+  ]},
+
+  %% BOSH and WS endpoints over HTTPS
+  { 5285, ejabberd_cowboy, [
+        {num_acceptors, 10},
+        {transport_options, [{max_connections, 1024}]},
+        {ssl, [{certfile, "priv/ssl/fullchain.pem"}, {keyfile, "priv/ssl/privkey.pem"}, {password, ""}]},
+        {modules, [
+            {"_", "/http-bind", mod_bosh},
+            {"_", "/ws-xmpp", mod_websockets, [
+            %% Uncomment to enable connection dropping or/and server-side pings
+            %{timeout, 600000}, {ping_rate, 60000}
+            ]}
+            %% Uncomment to serve static files
+            %{"_", "/static/[...]", cowboy_static,
+            %  {dir, "/var/www", [{mimetypes, cow_mimetypes, all}]}
+            %},
+        ]}
+    ]},
+
+  %% MongooseIM HTTP API it's important to start it on localhost
+  %% or some private interface only (not accessible from the outside)
+  %% At least start it on different port which will be hidden behind firewall
+
+  { {8088, "127.0.0.1"} , ejabberd_cowboy, [
+      {num_acceptors, 10},
+      {transport_options, [{max_connections, 1024}]},
+      {modules, [
+          {"localhost", "/api", mongoose_api_admin, []}
+      ]}
+  ]},
+
+  { 8089 , ejabberd_cowboy, [
+      {num_acceptors, 10},
+      {transport_options, [{max_connections, 1024}]},
+      {protocol_options, [{compress, true}]},
+      {ssl, [{certfile, "priv/ssl/fullchain.pem"}, {keyfile, "priv/ssl/privkey.pem"}, {password, ""}]},
+      {modules, [
+          {"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]},
+          {"_", "/api/messages/[:with]", mongoose_client_api_messages, []},
+          {"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []},
+          {"_", "/api/rooms/[:id]",    mongoose_client_api_rooms, []},
+          {"_", "/api/rooms/[:id]/config",    mongoose_client_api_rooms_config, []},
+          {"_", "/api/rooms/:id/users/[:user]",    mongoose_client_api_rooms_users, []},
+          {"_", "/api/rooms/[:id]/messages",    mongoose_client_api_rooms_messages, []}
+      ]}
+  ]},
+
+  %% Following HTTP API is deprected, the new one abouve should be used instead
+
+  { {5288, "127.0.0.1"} , ejabberd_cowboy, [
+      {num_acceptors, 10},
+      {transport_options, [{max_connections, 1024}]},
+      {modules, [
+          {"localhost", "/api", mongoose_api, [{handlers, [mongoose_api_metrics,
+                                                           mongoose_api_users]}]}
+      ]}
+  ]},
+
+  { 5222, ejabberd_c2s, [
+
+                       %%
+                       %% If TLS is compiled in and you installed a SSL
+                       %% certificate, specify the full path to the
+                       %% file and uncomment this line:
+                       %%
+                        {certfile, "priv/ssl/both.pem"}, starttls,
+                        
+                        %%{zlib, 10000},
+                       %% https://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS
+                       %% {ciphers, "DEFAULT:!EXPORT:!LOW:!SSLv2"},
+                       {access, c2s},
+                       {shaper, c2s_shaper},
+                       {max_stanza_size, 65536},
+                       {protocol_options, ["no_sslv3"]}
+                        
+                      ]},
+
+  
+
+  %%
+  %% To enable the old SSL connection method on port 5223:
+  %%
+  %%{5223, ejabberd_c2s, [
+  %%                   {access, c2s},
+  %%                   {shaper, c2s_shaper},
+  %%                   {certfile, "/path/to/ssl.pem"}, tls,
+  %%                   {max_stanza_size, 65536}
+  %%                  ]},
+
+  { 5269, ejabberd_s2s_in, [
+                          {shaper, s2s_shaper},
+                          {max_stanza_size, 131072},
+                          {protocol_options, ["no_sslv3"]}
+                          
+                         ]}
+
+  %%
+  %% ejabberd_service: Interact with external components (transports, ...)
+  %%
+  ,{8888, ejabberd_service, [
+                {access, all},
+                {shaper_rule, fast},
+                {ip, {127, 0, 0, 1}},
+                {password, "secret"}
+           ]}
+
+  %%
+  %% ejabberd_stun: Handles STUN Binding requests
+  %%
+  %%{ {3478, udp}, ejabberd_stun, []}
+
+ ]}.
+
+%%
+%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
+%% Allowed values are: false optional required required_trusted
+%% You must specify a certificate file.
+%%
+{s2s_use_starttls, optional}.
+%%
+%% s2s_certfile: Specify a certificate file.
+%%
+{s2s_certfile, "priv/ssl/both.pem"}.
+
+%% https://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS
+%% {s2s_ciphers, "DEFAULT:!EXPORT:!LOW:!SSLv2"}.
+
+%%
+%% domain_certfile: Specify a different certificate for each served hostname.
+%%
+%%{domain_certfile, "example.org", "/path/to/example_org.pem"}.
+%%{domain_certfile, "example.com", "/path/to/example_com.pem"}.
+
+%%
+%% S2S whitelist or blacklist
+%%
+%% Default s2s policy for undefined hosts.
+%%
+{s2s_default_policy, deny }.
+
+%%
+%% Allow or deny communication with specific servers.
+%%
+%%{ {s2s_host, "goodhost.org"}, allow}.
+%%{ {s2s_host, "badhost.org"}, deny}.
+
+{outgoing_s2s_port, 5269 }.
+
+%%
+%% IP addresses predefined for specific hosts to skip DNS lookups.
+%% Ports defined here take precedence over outgoing_s2s_port.
+%% Examples:
+%%
+%% { {s2s_addr, "example-host.net"}, {127,0,0,1} }.
+%% { {s2s_addr, "example-host.net"}, { {127,0,0,1}, 5269 } }.
+%% { {s2s_addr, "example-host.net"}, { {127,0,0,1}, 5269 } }.
+
+%%
+%% Outgoing S2S options
+%%
+%% Preferred address families (which to try first) and connect timeout
+%% in milliseconds.
+%%
+%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
+%%
+%%%.   ==============
+%%%'   SESSION BACKEND
+
+%%{sm_backend, {mnesia, []}}.
+
+%% Requires {redis, global, default, ..., ...} outgoing pool
+%%{sm_backend, {redis, []}}.
+
+{sm_backend, {mnesia, []} }.
+
+
+%%%.   ==============
+%%%'   AUTHENTICATION
+
+%% Advertised SASL mechanisms
+{sasl_mechanisms, [cyrsasl_plain]}.
+
+%%
+%% auth_method: Method used to authenticate the users.
+%% The default method is the internal.
+%% If you want to use a different method,
+%% comment this line and enable the correct ones.
+%%
+%% {auth_method, internal }.
+{auth_method, http }.
+{auth_opts, [
+             {http, global, auth, [{workers, 50}], [{server, "https://pleroma.soykaf.com"}]},
+             {password_format, plain} % default
+             %% {password_format, scram}
+             
+             %% {scram_iterations, 4096} % default
+             
+             %%
+             %% For auth_http:
+             %% {basic_auth, "user:password"}
+             %% {path_prefix, "/"} % default
+             %% auth_http requires {http, Host | global, auth, ..., ...} outgoing pool.
+             %%
+             %% For auth_external
+             %%{extauth_program, "/path/to/authentication/script"}.
+             %%
+             %% For auth_jwt
+             %% {jwt_secret_source, "/path/to/file"},
+             %% {jwt_algorithm, "RS256"},
+             %% {jwt_username_key, user}
+             %% For cyrsasl_external
+             %% {authenticate_with_cn, false}
+             {cyrsasl_external, standard}
+            ]}.
+
+%%
+%% Authentication using external script
+%% Make sure the script is executable by ejabberd.
+%%
+%%{auth_method, external}.
+
+%%
+%% Authentication using RDBMS
+%% Remember to setup a database in the next section.
+%%
+%%{auth_method, rdbms}.
+
+%%
+%% Authentication using LDAP
+%%
+%%{auth_method, ldap}.
+%%
+
+%% List of LDAP servers:
+%%{ldap_servers, ["localhost"]}.
+%%
+%% Encryption of connection to LDAP servers:
+%%{ldap_encrypt, none}.
+%%{ldap_encrypt, tls}.
+%%
+%% Port to connect to on LDAP servers:
+%%{ldap_port, 389}.
+%%{ldap_port, 636}.
+%%
+%% LDAP manager:
+%%{ldap_rootdn, "dc=example,dc=com"}.
+%%
+%% Password of LDAP manager:
+%%{ldap_password, "******"}.
+%%
+%% Search base of LDAP directory:
+%%{ldap_base, "dc=example,dc=com"}.
+%%
+%% LDAP attribute that holds user ID:
+%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
+%%
+%% LDAP filter:
+%%{ldap_filter, "(objectClass=shadowAccount)"}.
+
+%%
+%% Anonymous login support:
+%%   auth_method: anonymous
+%%   anonymous_protocol: sasl_anon | login_anon | both
+%%   allow_multiple_connections: true | false
+%%
+%%{host_config, "public.example.org", [{auth_method, anonymous},
+%%                                     {allow_multiple_connections, false},
+%%                                     {anonymous_protocol, sasl_anon}]}.
+%%
+%% To use both anonymous and internal authentication:
+%%
+%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
+
+
+%%%.   ==============
+%%%'   OUTGOING CONNECTIONS (e.g. DB)
+
+%% Here you may configure all outgoing connections used by MongooseIM,
+%% e.g. to RDBMS (such as MySQL), Riak or external HTTP components.
+%% Default MongooseIM configuration uses only Mnesia (non-Mnesia extensions are disabled),
+%% so no options here are uncommented out of the box.
+%% This section includes configuration examples; for comprehensive guide
+%% please consult MongooseIM documentation, page "Outgoing connections":
+%% - doc/advanced-configuration/outgoing-connections.md
+%% - https://mongooseim.readthedocs.io/en/latest/advanced-configuration/outgoing-connections/
+
+
+{outgoing_pools, [
+%  {riak, global, default, [{workers, 5}], [{address, "127.0.0.1"}, {port, 8087}]},
+%  {elastic, global, default, [], [{host, "elastic.host.com"}, {port, 9042}]},
+  {http, global, auth, [{workers, 50}], [{server, "https://pleroma.soykaf.com"}]}
+%  {cassandra, global, default, [{workers, 100}], [{servers, [{"server1", 9042}]}, {keyspace, "big_mongooseim"}]},
+%  {rdbms, global, default, [{workers, 10}], [{server, {mysql, "server", 3306, "database", "username", "password"}}]}
+]}.
+
+%% More examples that may be added to outgoing_pools list:
+%%
+%% == MySQL ==
+%%  {rdbms, global, default, [{workers, 10}],
+%%   [{server, {mysql, "server", 3306, "database", "username", "password"}},
+%%    {keepalive_interval, 10}]},
+%% keepalive_interval is optional
+
+%% == PostgreSQL ==
+%%  {rdbms, global, default, [{workers, 10}],
+%%   [{server, {pgsql, "server", 5432, "database", "username", "password"}}]},
+
+%% == ODBC (MSSQL) ==
+%%  {rdbms, global, default, [{workers, 10}],
+%%   [{server, "DSN=mongooseim;UID=mongooseim;PWD=mongooseim"}]},
+
+%% == Elastic Search ==
+%%  {elastic, global, default, [], [{host, "elastic.host.com"}, {port, 9042}]},
+
+%% == Riak ==
+%%  {riak, global, default, [{workers, 20}], [{address, "127.0.0.1"}, {port, 8087}]},
+
+%% == HTTP ==
+%%  {http, global, conn1, [{workers, 50}], [{server, "http://server:8080"}]},
+
+%% == Cassandra ==
+%%  {cassandra, global, default, [{workers, 100}],
+%%    [
+%%      {servers, [
+%%                 {"cassandra_server1.example.com", 9042},
+%%                 {"cassandra_server2.example.com", 9042},
+%%                 {"cassandra_server3.example.com", 9042},
+%%                 {"cassandra_server4.example.com", 9042}
+%%                ]},
+%%      {keyspace, "big_mongooseim"}
+%%    ]}
+
+%% == Extra options ==
+%%
+%% If you use PostgreSQL, have a large database, and need a
+%% faster but inexact replacement for "select count(*) from users"
+%%
+%%{pgsql_users_number_estimate, true}.
+%%
+%% rdbms_server_type specifies what database is used over the RDBMS layer
+%% Can take values mssql, pgsql, mysql
+%% In some cases (for example for MAM with pgsql) it is required to set proper value.
+%%
+%% {rdbms_server_type, pgsql}.
+
+%%%.   ===============
+%%%'   TRAFFIC SHAPERS
+
+%%
+%% The "normal" shaper limits traffic speed to 1000 B/s
+%%
+{shaper, normal, {maxrate, 1000}}.
+
+%%
+%% The "fast" shaper limits traffic speed to 50000 B/s
+%%
+{shaper, fast, {maxrate, 50000}}.
+
+%%
+%% This option specifies the maximum number of elements in the queue
+%% of the FSM. Refer to the documentation for details.
+%%
+{max_fsm_queue, 1000}.
+
+%%%.   ====================
+%%%'   ACCESS CONTROL LISTS
+
+%%
+%% The 'admin' ACL grants administrative privileges to XMPP accounts.
+%% You can put here as many accounts as you want.
+%%
+%{acl, admin, {user, "alice", "localhost"}}.
+%{acl, admin, {user, "a", "localhost"}}.
+
+%%
+%% Blocked users
+%%
+%%{acl, blocked, {user, "baduser", "example.org"}}.
+%%{acl, blocked, {user, "test"}}.
+
+%%
+%% Local users: don't modify this line.
+%%
+{acl, local, {user_regexp, ""}}.
+
+%%
+%% More examples of ACLs
+%%
+%%{acl, jabberorg, {server, "jabber.org"}}.
+%%{acl, aleksey, {user, "aleksey", "jabber.ru"}}.
+%%{acl, test, {user_regexp, "^test"}}.
+%%{acl, test, {user_glob, "test*"}}.
+
+%%
+%% Define specific ACLs in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [
+%%  {acl, admin, {user, "bob-local", "localhost"}}
+%% ]
+%%}.
+
+%%%.   ============
+%%%'   ACCESS RULES
+
+%% Maximum number of simultaneous sessions allowed for a single user:
+{access, max_user_sessions, [{10, all}]}.
+
+%% Maximum number of offline messages that users can have:
+{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
+
+%% This rule allows access only for local users:
+{access, local, [{allow, local}]}.
+
+%% Only non-blocked users can use c2s connections:
+{access, c2s, [{deny, blocked},
+              {allow, all}]}.
+
+%% For C2S connections, all users except admins use the "normal" shaper
+{access, c2s_shaper, [{none, admin},
+                     {normal, all}]}.
+
+%% All S2S connections use the "fast" shaper
+{access, s2s_shaper, [{fast, all}]}.
+
+%% Admins of this server are also admins of the MUC service:
+{access, muc_admin, [{allow, admin}]}.
+
+%% Only accounts of the local ejabberd server can create rooms:
+{access, muc_create, [{allow, local}]}.
+
+%% All users are allowed to use the MUC service:
+{access, muc, [{allow, all}]}.
+
+%% In-band registration allows registration of any possible username.
+%% To disable in-band registration, replace 'allow' with 'deny'.
+{access, register, [{allow, all}]}.
+
+%% By default the frequency of account registrations from the same IP
+%% is limited to 1 account every 10 minutes. To disable, specify: infinity
+{registration_timeout, infinity}.
+
+%% Default settings for MAM.
+%% To set non-standard value, replace 'default' with 'allow' or 'deny'.
+%% Only user can access his/her archive by default.
+%% An online user can read room's archive by default.
+%% Only an owner can change settings and purge messages by default.
+%% Empty list (i.e. `[]`) means `[{deny, all}]`.
+{access, mam_set_prefs, [{default, all}]}.
+{access, mam_get_prefs, [{default, all}]}.
+{access, mam_lookup_messages, [{default, all}]}.
+{access, mam_purge_single_message, [{default, all}]}.
+{access, mam_purge_multiple_messages, [{default, all}]}.
+
+%% 1 command of the specified type per second.
+{shaper, mam_shaper, {maxrate, 1}}.
+%% This shaper is primeraly for Mnesia overload protection during stress testing.
+%% The limit is 1000 operations of each type per second.
+{shaper, mam_global_shaper, {maxrate, 1000}}.
+
+{access, mam_set_prefs_shaper, [{mam_shaper, all}]}.
+{access, mam_get_prefs_shaper, [{mam_shaper, all}]}.
+{access, mam_lookup_messages_shaper, [{mam_shaper, all}]}.
+{access, mam_purge_single_message_shaper, [{mam_shaper, all}]}.
+{access, mam_purge_multiple_messages_shaper, [{mam_shaper, all}]}.
+
+{access, mam_set_prefs_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_get_prefs_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_lookup_messages_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_purge_single_message_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_purge_multiple_messages_global_shaper, [{mam_global_shaper, all}]}.
+
+%%
+%% Define specific Access Rules in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [
+%%  {access, c2s, [{allow, admin}, {deny, all}]},
+%%  {access, register, [{deny, all}]}
+%% ]
+%%}.
+
+%%%.   ================
+%%%'   DEFAULT LANGUAGE
+
+%%
+%% language: Default language used for server messages.
+%%
+{language, "en"}.
+
+%%
+%% Set a different default language in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [{language, "ru"}]
+%%}.
+
+%%%.   ================
+%%%'   MISCELLANEOUS
+
+{all_metrics_are_global, false }.
+
+%%%.   ========
+%%%'   SERVICES
+
+%% Unlike modules, services are started per node and provide either features which are not
+%% related to any particular host, or backend stuff which is used by modules.
+%% This is handled by `mongoose_service` module.
+
+{services,
+    [
+        {service_admin_extra, [{submods, [node, accounts, sessions, vcard,
+                                          roster, last, private, stanza, stats]}]}
+    ]
+}.
+
+%%%.   =======
+%%%'   MODULES
+
+%%
+%% Modules enabled in all mongooseim virtual hosts.
+%% For list of possible modules options, check documentation.
+%%
+{modules,
+ [
+
+  %% The format for a single route is as follows:
+  %% {Host, Path, Method, Upstream}
+  %%
+  %% "_" can be used as wildcard for Host, Path and Method
+  %% Upstream can be either host (just http(s)://host:port) or uri
+  %% The difference is that host upstreams append whole path while
+  %% uri upstreams append only remainder that follows the matched Path
+  %% (this behaviour is similar to nginx's proxy_pass rules)
+  %%
+  %% Bindings can be used to match certain parts of host or path.
+  %% They will be later overlaid with parts of the upstream uri.
+  %%
+  %% {mod_revproxy,
+  %%    [{routes, [{"www.erlang-solutions.com", "/admin", "_",
+  %%                "https://www.erlang-solutions.com/"},
+  %%               {":var.com", "/:var", "_", "http://localhost:8080/"},
+  %%               {":domain.com", "/", "_", "http://localhost:8080/:domain"}]
+  %%     }]},
+
+% {mod_http_upload, [
+    %% Set max file size in bytes. Defaults to 10 MB.
+    %% Disabled if value is `undefined`.
+%   {max_file_size, 1024},
+    %% Use S3 storage backend
+%   {backend, s3},
+    %% Set options for S3 backend
+%   {s3, [
+%     {bucket_url, "http://s3-eu-west-1.amazonaws.com/konbucket2"},
+%     {region, "eu-west-1"},
+%     {access_key_id, "AKIAIAOAONIULXQGMOUA"},
+%     {secret_access_key, "dGhlcmUgYXJlIG5vIGVhc3RlciBlZ2dzIGhlcmVf"}
+%   ]}
+% ]},
+
+  {mod_adhoc, []},
+  
+  {mod_disco, [{users_can_see_hidden_services, false}]},
+  {mod_commands, []},
+  {mod_muc_commands, []},
+  {mod_muc_light_commands, []},
+  {mod_last, []},
+  {mod_stream_management, [
+                           % default 100
+                           % size of a buffer of unacked messages
+                           % {buffer_max, 100}
+
+                           % default 1 - server sends the ack request after each stanza
+                           % {ack_freq, 1}
+
+                           % default: 600 seconds
+                           % {resume_timeout, 600}
+                          ]},
+  %% {mod_muc_light, [{host, "muclight.@HOST@"}]},
+  %% {mod_muc, [{host, "muc.@HOST@"},
+  %%            {access, muc},
+  %%            {access_create, muc_create}
+  %%           ]},
+  %% {mod_muc_log, [
+  %%                {outdir, "/tmp/muclogs"},
+  %%                {access_log, muc}
+  %%               ]},
+  {mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
+  {mod_privacy, []},
+  {mod_blocking, []},
+  {mod_private, []},
+% {mod_private, [{backend, mnesia}]},
+% {mod_private, [{backend, rdbms}]},
+% {mod_register, [
+%                %%
+%                %% Set the minimum informational entropy for passwords.
+%                %%
+%                %%{password_strength, 32},
+%
+%                %%
+%                %% After successful registration, the user receives
+%                %% a message with this subject and body.
+%                %%
+%                {welcome_message, {""}},
+%
+%                %%
+%                %% When a user registers, send a notification to
+%                %% these XMPP accounts.
+%                %%
+%
+%
+%                %%
+%                %% Only clients in the server machine can register accounts
+%                %%
+%                {ip_access, [{allow, "127.0.0.0/8"},
+%                             {deny, "0.0.0.0/0"}]},
+%
+%                %%
+%                %% Local c2s or remote s2s users cannot register accounts
+%                %%
+%                %%{access_from, deny},
+%
+%                {access, register}
+%               ]},
+  {mod_roster, []},
+  {mod_sic, []},
+  {mod_vcard, [%{matches, 1},
+%{search, true},
+%{ldap_search_operator, 'or'}, %% either 'or' or 'and'
+%{ldap_binary_search_fields, [<<"PHOTO">>]},
+%% list of binary search fields (as in vcard after mapping)
+{host, "vjud.@HOST@"}
+]},
+  {mod_bosh, []},
+  {mod_carboncopy, []}
+
+  %%
+  %% Message Archive Management (MAM, XEP-0313) for registered users and
+  %% Multi-User chats (MUCs).
+  %%
+
+% {mod_mam_meta, [
+    %% Use RDBMS backend (default)
+%   {backend, rdbms},
+
+    %% Do not store user preferences (default)
+%   {user_prefs_store, false},
+    %% Store user preferences in RDBMS
+%   {user_prefs_store, rdbms},
+    %% Store user preferences in Mnesia (recommended).
+    %% The preferences store will be called each time, as a message is routed.
+    %% That is why Mnesia is better suited for this job.
+%   {user_prefs_store, mnesia},
+
+    %% Enables a pool of asynchronous writers. (default)
+    %% Messages will be grouped together based on archive id.
+%   {async_writer, true},
+
+    %% Cache information about users (default)
+%   {cache_users, true},
+
+    %% Enable archivization for private messages (default)
+%   {pm, [
+      %% Top-level options can be overriden here if needed, for example:
+%     {async_writer, false}
+%   ]},
+
+    %%
+    %% Message Archive Management (MAM) for multi-user chats (MUC).
+    %% Enable XEP-0313 for "muc.@HOST@".
+    %%
+%   {muc, [
+%     {host, "muc.@HOST@"}
+      %% As with pm, top-level options can be overriden for MUC archive
+%   ]},
+%
+    %% Do not use a <stanza-id/> element (by default stanzaid is used)
+%   no_stanzaid_element,
+% ]},
+
+
+  %%
+  %% MAM configuration examples
+  %%
+
+  %% Only MUC, no user-defined preferences, good performance.
+% {mod_mam_meta, [
+%   {backend, rdbms},
+%   {pm, false},
+%   {muc, [
+%     {host, "muc.@HOST@"}
+%   ]}
+% ]},
+
+  %% Only archives for c2c messages, good performance.
+% {mod_mam_meta, [
+%   {backend, rdbms},
+%   {pm, [
+%     {user_prefs_store, mnesia}
+%   ]}
+% ]},
+
+  %% Basic configuration for c2c messages, bad performance, easy to debug.
+% {mod_mam_meta, [
+%   {backend, rdbms},
+%   {async_writer, false},
+%   {cache_users, false}
+% ]},
+
+  %% Cassandra archive for c2c and MUC conversations.
+  %% No custom settings supported (always archive).
+% {mod_mam_meta, [
+%   {backend, cassandra},
+%   {user_prefs_store, cassandra},
+%   {muc, [{host, "muc.@HOST@"}]}
+% ]}
+
+% {mod_event_pusher, [
+%   {backends, [
+%     %%
+%     %% Configuration for Amazon SNS notifications.
+%     %%
+%     {sns, [
+%       %% AWS credentials, region and host configuration
+%       {access_key_id, "AKIAJAZYHOIPY6A2PESA"},
+%       {secret_access_key, "c3RvcCBsb29raW5nIGZvciBlYXN0ZXIgZWdncyxr"},
+%       {region, "eu-west-1"},
+%       {account_id, "251423380551"},
+%       {region, "eu-west-1"},
+%       {sns_host, "sns.eu-west-1.amazonaws.com"},
+%
+%       %% Messages from this MUC host will be sent to the SNS topic
+%       {muc_host, "muc.@HOST@"},
+%
+%       %% Plugin module for defining custom message attributes and user identification
+%       {plugin_module, mod_event_pusher_sns_defaults},
+%
+%       %% Topic name configurations. Removing a topic will disable this specific SNS notification
+%       {presence_updates_topic, "user_presence_updated-dev-1"},  %% For presence updates
+%       {pm_messages_topic, "user_message_sent-dev-1"},           %% For private chat messages
+%       {muc_messages_topic, "user_messagegroup_sent-dev-1"}      %% For group chat messages
+%
+%       %% Pool options
+%       {pool_size, 100}, %% Worker pool size for publishing notifications
+%       {publish_retry_count, 2}, %% Retry count in case of publish error
+%       {publish_retry_time_ms, 50} %% Base exponential backoff time (in ms) for publish errors
+%      ]}
+%   ]}
+
+]}.
+
+
+%%
+%% Enable modules with custom options in a specific virtual host
+%%
+%%{host_config, "localhost",
+%% [{ {add, modules},
+%%   [
+%%    {mod_some_module, []}
+%%   ]
+%%  }
+%% ]}.
+
+%%%.
+%%%'
+
+%%% $Id$
+
+%%% Local Variables:
+%%% mode: erlang
+%%% End:
+%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker:
+%%%.
index cc75d78b2f8646c50be7b40ba538abb652886f09..7425da33f0d1879d7c3b84a5a5154050d0d5e0e8 100644 (file)
@@ -69,7 +69,9 @@ server {
         proxy_set_header Connection "upgrade";
         proxy_set_header Host $http_host;
 
-        proxy_pass http://localhost:4000;
+       # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
+       # and `localhost.` resolves to [::0] on some systems: see issue #930
+        proxy_pass http://127.0.0.1:4000;
 
         client_max_body_size 16m;
     }
index 92153d8ef4a02ea54597bc96b0d6b43e2c28073b..154747aa60b646df7658688ce7bd436b6b92de11 100644 (file)
@@ -1,4 +1,4 @@
-vcl 4.0;
+vcl 4.1;
 import std;
 
 backend default {
@@ -35,24 +35,6 @@ sub vcl_recv {
       }
       return(purge);
     }
-
-    # Pleroma MediaProxy - strip headers that will affect caching
-    if (req.url ~ "^/proxy/") {
-      unset req.http.Cookie;
-      unset req.http.Authorization;
-      unset req.http.Accept;
-      return (hash);
-    }
-
-    # Strip headers that will affect caching from all other static content
-    # This also permits caching of individual toots and AP Activities
-    if ((req.url ~ "^/(media|static)/") ||
-    (req.url ~ "(?i)\.(html|js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$"))
-    {
-      unset req.http.Cookie;
-      unset req.http.Authorization;
-      return (hash);
-    }
 }
 
 sub vcl_backend_response {
@@ -61,6 +43,12 @@ sub vcl_backend_response {
       set beresp.do_gzip = true;
     }
 
+    # Retry broken backend responses.
+    if (beresp.status == 503) {
+      set bereq.http.X-Varnish-Backend-503 = "1";
+      return (retry);
+    }
+
     # CHUNKED SUPPORT
     if (bereq.http.x-range ~ "bytes=" && beresp.status == 206) {
       set beresp.ttl = 10m;
@@ -73,8 +61,6 @@ sub vcl_backend_response {
       return (deliver);
     }
 
-    # Default object caching of 86400s;
-    set beresp.ttl = 86400s;
     # Allow serving cached content for 6h in case backend goes down
     set beresp.grace = 6h;
 
@@ -90,20 +76,6 @@ sub vcl_backend_response {
       set beresp.ttl = 30s;
       return (deliver);
     }
-
-    # Pleroma MediaProxy internally sets headers properly
-    if (bereq.url ~ "^/proxy/") {
-      return (deliver);
-    }
-
-    # Strip cache-restricting headers from Pleroma on static content that we want to cache
-    if (bereq.url ~ "(?i)\.(js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$")
-    {
-      unset beresp.http.set-cookie;
-      unset beresp.http.Cache-Control;
-      unset beresp.http.x-request-id;
-      set beresp.http.Cache-Control = "public, max-age=86400";
-    }
 }
 
 # The synthetic response for 301 redirects
@@ -132,10 +104,32 @@ sub vcl_hash {
 }
 
 sub vcl_backend_fetch {
+    # Be more lenient for slow servers on the fediverse
+    if bereq.url ~ "^/proxy/" {
+      set bereq.first_byte_timeout = 300s;
+    }
+
     # CHUNKED SUPPORT
     if (bereq.http.x-range) {
       set bereq.http.Range = bereq.http.x-range;
     }
+
+    if (bereq.retries == 0) {
+        # Clean up the X-Varnish-Backend-503 flag that is used internally
+        # to mark broken backend responses that should be retried.
+        unset bereq.http.X-Varnish-Backend-503;
+    } else {
+        if (bereq.http.X-Varnish-Backend-503) {
+            if (bereq.method != "POST" &&
+              std.healthy(bereq.backend) &&
+              bereq.retries <= 4) {
+              # Flush broken backend response flag & try again.
+              unset bereq.http.X-Varnish-Backend-503;
+            } else {
+              return (abandon);
+            }
+        }
+    }
 }
 
 sub vcl_deliver {
@@ -145,3 +139,9 @@ sub vcl_deliver {
       unset resp.http.CR;
     }
 }
+
+sub vcl_backend_error {
+    # Retry broken backend responses.
+    set bereq.http.X-Varnish-Backend-503 = "1";
+    return (retry);
+}
index 646fb3b9d1bbe7c7c622e46009efdc18a7326a64..32aafc2109bac0278b10bafc6ea4ada0d6b64244 100644 (file)
@@ -29,13 +29,13 @@ defmodule Pleroma.Healthcheck do
   end
 
   defp assign_db_info(healthcheck) do
-    database = Application.get_env(:pleroma, Repo)[:database]
+    database = Pleroma.Config.get([Repo, :database])
 
     query =
       "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;"
 
     result = Repo.query!(query)
-    pool_size = Application.get_env(:pleroma, Repo)[:pool_size]
+    pool_size = Pleroma.Config.get([Repo, :pool_size])
 
     db_info =
       Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states ->
index ab9a3a7ff95eb9d43ff357ae2c64c538a4de1abe..4d480ac3fb38d69dbab4843ea364d1a70dbac4a5 100644 (file)
@@ -4,6 +4,10 @@
 
 defmodule Mix.Tasks.Pleroma.Database do
   alias Mix.Tasks.Pleroma.Common
+  alias Pleroma.Conversation
+  alias Pleroma.Object
+  alias Pleroma.Repo
+  alias Pleroma.User
   require Logger
   use Mix.Task
 
@@ -19,6 +23,18 @@ defmodule Mix.Tasks.Pleroma.Database do
 
     Options:
     - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
+
+  ## Prune old objects from the database
+
+      mix pleroma.database prune_objects
+
+  ## Create a conversation for all existing DMs. Can be safely re-run.
+
+      mix pleroma.database bump_all_conversations
+
+  ## Remove duplicated items from following and update followers count for all users
+
+      mix pleroma.database update_users_following_followers_counts
   """
   def run(["remove_embedded_objects" | args]) do
     {options, [], []} =
@@ -32,7 +48,7 @@ defmodule Mix.Tasks.Pleroma.Database do
     Common.start_pleroma()
     Logger.info("Removing embedded objects")
 
-    Pleroma.Repo.query!(
+    Repo.query!(
       "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
       [],
       timeout: :infinity
@@ -41,7 +57,62 @@ defmodule Mix.Tasks.Pleroma.Database do
     if Keyword.get(options, :vacuum) do
       Logger.info("Runnning VACUUM FULL")
 
-      Pleroma.Repo.query!(
+      Repo.query!(
+        "vacuum full;",
+        [],
+        timeout: :infinity
+      )
+    end
+  end
+
+  def run(["bump_all_conversations"]) do
+    Common.start_pleroma()
+    Conversation.bump_for_all_activities()
+  end
+
+  def run(["update_users_following_followers_counts"]) do
+    Common.start_pleroma()
+
+    users = Repo.all(User)
+    Enum.each(users, &User.remove_duplicated_following/1)
+    Enum.each(users, &User.update_follower_count/1)
+  end
+
+  def run(["prune_objects" | args]) do
+    import Ecto.Query
+
+    {options, [], []} =
+      OptionParser.parse(
+        args,
+        strict: [
+          vacuum: :boolean
+        ]
+      )
+
+    Common.start_pleroma()
+
+    deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
+
+    Logger.info("Pruning objects older than #{deadline} days")
+
+    time_deadline =
+      NaiveDateTime.utc_now()
+      |> NaiveDateTime.add(-(deadline * 86_400))
+
+    public = "https://www.w3.org/ns/activitystreams#Public"
+
+    from(o in Object,
+      where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public),
+      where: o.inserted_at < ^time_deadline,
+      where:
+        fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
+    )
+    |> Repo.delete_all(timeout: :infinity)
+
+    if Keyword.get(options, :vacuum) do
+      Logger.info("Runnning VACUUM FULL")
+
+      Repo.query!(
         "vacuum full;",
         [],
         timeout: :infinity
index d130ff8c960e3ba919514490ffb2f22a2b7f35a2..25fc40ea7b9eca5cde32b3b278421aca83d560d2 100644 (file)
@@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
   ## Delete tags from a user.
 
       mix pleroma.user untag NICKNAME TAGS
+
+  ## Toggle confirmation of the user's account.
+
+      mix pleroma.user toggle_confirmed NICKNAME
   """
   def run(["new", nickname, email | rest]) do
     {options, [], []} =
@@ -388,6 +392,21 @@ defmodule Mix.Tasks.Pleroma.User do
     end
   end
 
+  def run(["toggle_confirmed", nickname]) do
+    Common.start_pleroma()
+
+    with %User{} = user <- User.get_cached_by_nickname(nickname) do
+      {:ok, user} = User.toggle_confirmation(user)
+
+      message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
+
+      Mix.shell().info("#{nickname} #{message} confirmation.")
+    else
+      _ ->
+        Mix.shell().error("No local user #{nickname}")
+    end
+  end
+
   defp set_moderator(user, value) do
     info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
 
index 4a09194786f3f58ca4c998fd67d99cb0a8dc4f40..99589590c1b3bddba86134e849d3935adf26145a 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Activity do
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
+  alias Pleroma.ThreadMute
   alias Pleroma.User
 
   import Ecto.Changeset
@@ -37,6 +38,7 @@ defmodule Pleroma.Activity do
     field(:local, :boolean, default: true)
     field(:actor, :string)
     field(:recipients, {:array, :string}, default: [])
+    field(:thread_muted?, :boolean, virtual: true)
     # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
     has_one(:bookmark, Bookmark)
     has_many(:notifications, Notification, on_delete: :delete_all)
@@ -60,21 +62,24 @@ defmodule Pleroma.Activity do
     timestamps()
   end
 
-  def with_preloaded_object(query) do
-    query
-    |> join(
-      :inner,
-      [activity],
-      o in Object,
+  def with_joined_object(query) do
+    join(query, :inner, [activity], o in Object,
       on:
         fragment(
           "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
           o.data,
           activity.data,
           activity.data
-        )
+        ),
+      as: :object
     )
-    |> preload([activity, object], object: object)
+  end
+
+  def with_preloaded_object(query) do
+    query
+    |> has_named_binding?(:object)
+    |> if(do: query, else: with_joined_object(query))
+    |> preload([activity, object: object], object: object)
   end
 
   def with_preloaded_bookmark(query, %User{} = user) do
@@ -87,6 +92,16 @@ defmodule Pleroma.Activity do
 
   def with_preloaded_bookmark(query, _), do: query
 
+  def with_set_thread_muted_field(query, %User{} = user) do
+    from([a] in query,
+      left_join: tm in ThreadMute,
+      on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
+      select: %Activity{a | thread_muted?: not is_nil(tm.id)}
+    )
+  end
+
+  def with_set_thread_muted_field(query, _), do: query
+
   def get_by_ap_id(ap_id) do
     Repo.one(
       from(
@@ -108,7 +123,7 @@ defmodule Pleroma.Activity do
 
   def change(struct, params \\ %{}) do
     struct
-    |> cast(params, [:data])
+    |> cast(params, [:data, :recipients])
     |> validate_required([:data])
     |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
   end
index eeb415084041cdec1e212942a9d75f9b6e94a38e..76df3945e59482b85e9d627169582ad9c5cdfd13 100644 (file)
@@ -110,6 +110,7 @@ defmodule Pleroma.Application do
         hackney_pool_children() ++
         [
           worker(Pleroma.Web.Federator.RetryQueue, []),
+          worker(Pleroma.Web.OAuth.Token.CleanWorker, []),
           worker(Pleroma.Stats, []),
           worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
           worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
@@ -131,19 +132,22 @@ defmodule Pleroma.Application do
   defp setup_instrumenters do
     require Prometheus.Registry
 
-    :ok =
-      :telemetry.attach(
-        "prometheus-ecto",
-        [:pleroma, :repo, :query],
-        &Pleroma.Repo.Instrumenter.handle_event/4,
-        %{}
-      )
+    if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do
+      :ok =
+        :telemetry.attach(
+          "prometheus-ecto",
+          [:pleroma, :repo, :query],
+          &Pleroma.Repo.Instrumenter.handle_event/4,
+          %{}
+        )
+
+      Pleroma.Repo.Instrumenter.setup()
+    end
 
     Prometheus.Registry.register_collector(:prometheus_process_collector)
     Pleroma.Web.Endpoint.MetricsExporter.setup()
     Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
     Pleroma.Web.Endpoint.Instrumenter.setup()
-    Pleroma.Repo.Instrumenter.setup()
   end
 
   def enabled_hackney_pools do
index 106fe5d18f4a634875ae6b3696b6ad93b3dbfc2f..f34be961f15065d37d86b6a7c83adc52349b1c4e 100644 (file)
@@ -95,7 +95,6 @@ defmodule Pleroma.BBS.Handler do
     activities =
       [user.ap_id | user.following]
       |> ActivityPub.fetch_activities(params)
-      |> ActivityPub.contain_timeline(user)
 
     Enum.each(activities, fn activity ->
       puts_activity(activity)
index 0db1959889ab08cb568f64646b9c48c79969bf24..238c1acf201dda42301b7cb8c4332fcb80e59ea0 100644 (file)
@@ -45,7 +45,7 @@ defmodule Pleroma.Conversation do
   2. Create a participation for all the people involved who don't have one already
   3. Bump all relevant participations to 'unread'
   """
-  def create_or_bump_for(activity) do
+  def create_or_bump_for(activity, opts \\ []) do
     with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
          "Create" <- activity.data["type"],
          object <- Pleroma.Object.normalize(activity),
@@ -58,7 +58,7 @@ defmodule Pleroma.Conversation do
       participations =
         Enum.map(users, fn user ->
           {:ok, participation} =
-            Participation.create_for_user_and_conversation(user, conversation)
+            Participation.create_for_user_and_conversation(user, conversation, opts)
 
           participation
         end)
@@ -72,4 +72,21 @@ defmodule Pleroma.Conversation do
       e -> {:error, e}
     end
   end
+
+  @doc """
+  This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
+  """
+  def bump_for_all_activities do
+    stream =
+      Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
+      |> Repo.stream()
+
+    Repo.transaction(
+      fn ->
+        stream
+        |> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
+      end,
+      timeout: :infinity
+    )
+  end
 end
index 61021fb18104bfc07e0c706f52859602d6a82811..2a11f9069940d221aec41bb39d6f758fa4dd71aa 100644 (file)
@@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
 
   def creation_cng(struct, params) do
     struct
-    |> cast(params, [:user_id, :conversation_id])
+    |> cast(params, [:user_id, :conversation_id, :read])
     |> validate_required([:user_id, :conversation_id])
   end
 
-  def create_for_user_and_conversation(user, conversation) do
+  def create_for_user_and_conversation(user, conversation, opts \\ []) do
+    read = !!opts[:read]
+
     %__MODULE__{}
-    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
+    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
     |> Repo.insert(
-      on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
+      on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
       returning: true,
       conflict_target: [:user_id, :conversation_id]
     )
index df0f72f964d597d22915d1b2033352b0a19d898a..d0e254362ba60c386582fc3c4f5835da3b36712a 100644 (file)
@@ -29,7 +29,7 @@ defmodule Pleroma.Emails.AdminEmail do
       end
 
     statuses_html =
-      if length(statuses) > 0 do
+      if is_list(statuses) && length(statuses) > 0 do
         statuses_list_html =
           statuses
           |> Enum.map(fn
index 6390cce4c5ff13a00569d7c5eabb85a5fe00475f..7d12eff7fe2982901b130ee4a604e71536c5e046 100644 (file)
@@ -22,7 +22,7 @@ defmodule Pleroma.Emoji do
 
   @ets __MODULE__.Ets
   @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
-  @groups Application.get_env(:pleroma, :emoji)[:groups]
+  @groups Pleroma.Config.get([:emoji, :groups])
 
   @doc false
   def start_link do
@@ -112,7 +112,7 @@ defmodule Pleroma.Emoji do
 
     # Compat thing for old custom emoji handling & default emoji,
     # it should run even if there are no emoji packs
-    shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
+    shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], [])
 
     emojis =
       (load_from_file("config/emoji.txt") ++
index 79efc29f0f73803463d8f94ccf79e463a3fbba4a..90457dadf8641f9c100aa067ba8678dc0ef55c60 100644 (file)
@@ -38,7 +38,8 @@ defmodule Pleroma.Filter do
     query =
       from(
         f in Pleroma.Filter,
-        where: f.user_id == ^user_id
+        where: f.user_id == ^user_id,
+        order_by: [desc: :id]
       )
 
     Repo.all(query)
index 3d7c36d215a6c5603fb4e8ac32a98f9187424014..607843a5b3cd174412f6347dc45c5d79a080d982 100644 (file)
@@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do
   alias Pleroma.User
   alias Pleroma.Web.MediaProxy
 
-  @safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/
+  @safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
   @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
   @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
 
index d1da746de0c781d6bd08a386641cad36492c9fd4..e5e78ee4f50124a1d1c2d0568b5d7c14f6c0fa67 100644 (file)
@@ -104,7 +104,6 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
   paragraphs, breaks and links are allowed through the filter.
   """
 
-  @markup Application.get_env(:pleroma, :markup)
   @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
 
   require HtmlSanitizeEx.Scrubber.Meta
@@ -142,9 +141,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
   Meta.allow_tag_with_these_attributes("span", [])
 
   # allow inline images for custom emoji
-  @allow_inline_images Keyword.get(@markup, :allow_inline_images)
-
-  if @allow_inline_images do
+  if Pleroma.Config.get([:markup, :allow_inline_images]) do
     # restrict img tags to http/https only, because of MediaProxy.
     Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
 
@@ -168,7 +165,6 @@ defmodule Pleroma.HTML.Scrubber.Default do
   # credo:disable-for-previous-line
   # No idea how to fix this one…
 
-  @markup Application.get_env(:pleroma, :markup)
   @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
 
   Meta.remove_cdata_sections_before_scrub()
@@ -213,7 +209,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
   Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
   Meta.allow_tag_with_these_attributes("span", [])
 
-  @allow_inline_images Keyword.get(@markup, :allow_inline_images)
+  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
 
   if @allow_inline_images do
     # restrict img tags to http/https only, because of MediaProxy.
@@ -228,9 +224,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
     ])
   end
 
-  @allow_tables Keyword.get(@markup, :allow_tables)
-
-  if @allow_tables do
+  if Pleroma.Config.get([:markup, :allow_tables]) do
     Meta.allow_tag_with_these_attributes("table", [])
     Meta.allow_tag_with_these_attributes("tbody", [])
     Meta.allow_tag_with_these_attributes("td", [])
@@ -239,9 +233,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
     Meta.allow_tag_with_these_attributes("tr", [])
   end
 
-  @allow_headings Keyword.get(@markup, :allow_headings)
-
-  if @allow_headings do
+  if Pleroma.Config.get([:markup, :allow_headings]) do
     Meta.allow_tag_with_these_attributes("h1", [])
     Meta.allow_tag_with_these_attributes("h2", [])
     Meta.allow_tag_with_these_attributes("h3", [])
@@ -249,9 +241,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
     Meta.allow_tag_with_these_attributes("h5", [])
   end
 
-  @allow_fonts Keyword.get(@markup, :allow_fonts)
-
-  if @allow_fonts do
+  if Pleroma.Config.get([:markup, :allow_fonts]) do
     Meta.allow_tag_with_these_attributes("font", ["face"])
   end
 
index c0173465ab741d9bf39b41750865cdbe7f30c3e7..c216cdcb11af5875d47e602b25fa327d07f88614 100644 (file)
@@ -8,7 +8,7 @@ defmodule Pleroma.HTTP.Connection do
   """
 
   @hackney_options [
-    connect_timeout: 2_000,
+    connect_timeout: 10_000,
     recv_timeout: 20_000,
     follow_redirect: true,
     pool: :federation
@@ -32,9 +32,11 @@ defmodule Pleroma.HTTP.Connection do
   defp hackney_options(opts) do
     options = Keyword.get(opts, :adapter, [])
     adapter_options = Pleroma.Config.get([:http, :adapter], [])
+    proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
 
     @hackney_options
     |> Keyword.merge(adapter_options)
     |> Keyword.merge(options)
+    |> Keyword.merge(proxy: proxy_url)
   end
 end
index c5f720bc9f756292f0c99d2c44500911ca19062f..c96ee7353db19225f367d7afb900efcb8b9629f2 100644 (file)
@@ -65,12 +65,9 @@ defmodule Pleroma.HTTP do
   end
 
   def process_request_options(options) do
-    config = Application.get_env(:pleroma, :http, [])
-    proxy = Keyword.get(config, :proxy_url, nil)
-
-    case proxy do
+    case Pleroma.Config.get([:http, :proxy_url]) do
       nil -> options
-      _ -> options ++ [proxy: proxy]
+      proxy -> options ++ [proxy: proxy]
     end
   end
 
index 5f2cff2c0d461be23195d0026cba2ad5ea49488b..e2345799932b11cdb462714f67ea59af7166123f 100644 (file)
@@ -45,8 +45,15 @@ defmodule Pleroma.HTTP.RequestBuilder do
   Add headers to the request
   """
   @spec headers(map(), list(tuple)) :: map()
-  def headers(request, h) do
-    Map.put_new(request, :headers, h)
+  def headers(request, header_list) do
+    header_list =
+      if Pleroma.Config.get([:http, :send_user_agent]) do
+        header_list ++ [{"User-Agent", Pleroma.Application.user_agent()}]
+      else
+        header_list
+      end
+
+    Map.put_new(request, :headers, header_list)
   end
 
   @doc """
diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex
new file mode 100644 (file)
index 0000000..b7bc7a4
--- /dev/null
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Keys do
+  # Native generation of RSA keys is only available since OTP 20+ and in default build conditions
+  # We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
+  try do
+    _ = :public_key.generate_key({:rsa, 2048, 65_537})
+
+    def generate_rsa_pem do
+      key = :public_key.generate_key({:rsa, 2048, 65_537})
+      entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
+      pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
+      {:ok, pem}
+    end
+  rescue
+    _ ->
+      def generate_rsa_pem do
+        port = Port.open({:spawn, "openssl genrsa"}, [:binary])
+
+        {:ok, pem} =
+          receive do
+            {^port, {:data, pem}} -> {:ok, pem}
+          end
+
+        Port.close(port)
+
+        if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
+          {:ok, pem}
+        else
+          :error
+        end
+      end
+  end
+
+  def keys_from_pem(pem) do
+    [private_key_code] = :public_key.pem_decode(pem)
+    private_key = :public_key.pem_entry_decode(private_key_code)
+    {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
+    public_key = {:RSAPublicKey, modulus, exponent}
+    {:ok, private_key, public_key}
+  end
+end
index 740d687a3505fc03ea0cc2161aa44bba4a5f8872..cc6fc9c5dfeb25c3311778fc4878090069c8dedd 100644 (file)
@@ -130,6 +130,13 @@ defmodule Pleroma.Object do
     end
   end
 
+  def prune(%Object{data: %{"id" => id}} = object) do
+    with {:ok, object} <- Repo.delete(object),
+         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
+      {:ok, object}
+    end
+  end
+
   def set_cache(%Object{data: %{"id" => ap_id}} = object) do
     Cachex.put(:object_cache, "object:#{ap_id}", object)
     {:ok, object}
index 8d4bcc95efa7771ad48fcb4a424258b93eaac4ec..ca980c62939f0ff25a4d1c5eea745fe2b86501bd 100644 (file)
@@ -1,4 +1,5 @@
 defmodule Pleroma.Object.Fetcher do
+  alias Pleroma.HTTP
   alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -6,7 +7,18 @@ defmodule Pleroma.Object.Fetcher do
 
   require Logger
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
+  defp reinject_object(data) do
+    Logger.debug("Reinjecting object #{data["id"]}")
+
+    with data <- Transmogrifier.fix_object(data),
+         {:ok, object} <- Object.create(data) do
+      {:ok, object}
+    else
+      e ->
+        Logger.error("Error while processing object: #{inspect(e)}")
+        {:error, e}
+    end
+  end
 
   # TODO:
   # This will create a Create activity, which we need internally at the moment.
@@ -26,12 +38,17 @@ defmodule Pleroma.Object.Fetcher do
              "object" => data
            },
            :ok <- Containment.contain_origin(id, params),
-           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
-        {:ok, Object.normalize(activity, false)}
+           {:ok, activity} <- Transmogrifier.handle_incoming(params),
+           {:object, _data, %Object{} = object} <-
+             {:object, data, Object.normalize(activity, false)} do
+        {:ok, object}
       else
         {:error, {:reject, nil}} ->
           {:reject, nil}
 
+        {:object, data, nil} ->
+          reinject_object(data)
+
         object = %Object{} ->
           {:ok, object}
 
@@ -60,7 +77,7 @@ defmodule Pleroma.Object.Fetcher do
 
     with true <- String.starts_with?(id, "http"),
          {:ok, %{body: body, status: code}} when code in 200..299 <-
-           @httpoison.get(
+           HTTP.get(
              id,
              [{:Accept, "application/activity+json"}]
            ),
index effc154bf274c26050098012fc7e076f04d5e13e..4dc4e927920a9f55af1dbdadbecaf3a442d310e1 100644 (file)
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.FederatingPlug do
   end
 
   def call(conn, _opts) do
-    if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do
+    if Pleroma.Config.get([:instance, :federating]) do
       conn
     else
       conn
index a476f1d49ab2ed02e98b0a1aba677b37f845a541..485ddfbc72ef03263199aaa4d4abd6c16829daa1 100644 (file)
@@ -20,8 +20,9 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
 
   defp headers do
     referrer_policy = Config.get([:http_security, :referrer_policy])
+    report_uri = Config.get([:http_security, :report_uri])
 
-    [
+    headers = [
       {"x-xss-protection", "1; mode=block"},
       {"x-permitted-cross-domain-policies", "none"},
       {"x-frame-options", "DENY"},
@@ -30,12 +31,27 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       {"x-download-options", "noopen"},
       {"content-security-policy", csp_string() <> ";"}
     ]
+
+    if report_uri do
+      report_group = %{
+        "group" => "csp-endpoint",
+        "max-age" => 10_886_400,
+        "endpoints" => [
+          %{"url" => report_uri}
+        ]
+      }
+
+      headers ++ [{"reply-to", Jason.encode!(report_group)}]
+    else
+      headers
+    end
   end
 
   defp csp_string do
     scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
     static_url = Pleroma.Web.Endpoint.static_url()
     websocket_url = Pleroma.Web.Endpoint.websocket_url()
+    report_uri = Config.get([:http_security, :report_uri])
 
     connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
 
@@ -53,7 +69,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
         "script-src 'self'"
       end
 
-    [
+    main_part = [
       "default-src 'none'",
       "base-uri 'self'",
       "frame-ancestors 'none'",
@@ -63,11 +79,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       "font-src 'self'",
       "manifest-src 'self'",
       connect_src,
-      script_src,
-      if scheme == "https" do
-        "upgrade-insecure-requests"
-      end
+      script_src
     ]
+
+    report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
+
+    insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
+
+    (main_part ++ report ++ insecure)
     |> Enum.join("; ")
   end
 
index a3f177fec06d4ea8ab3da7e41d9a159663ca02ff..983e156f53237a2120b92ec0d82fd2b412240c4e 100644 (file)
@@ -3,6 +3,8 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.ReverseProxy do
+  alias Pleroma.HTTP
+
   @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
                       ~w(if-unmodified-since if-none-match if-range range)
   @resp_cache_headers ~w(etag date last-modified cache-control)
@@ -59,8 +61,7 @@ defmodule Pleroma.ReverseProxy do
   * `http`: options for [hackney](https://github.com/benoitc/hackney).
 
   """
-  @hackney Application.get_env(:pleroma, :hackney, :hackney)
-  @httpoison Application.get_env(:pleroma, :httpoison, HTTPoison)
+  @hackney Pleroma.Config.get(:hackney, :hackney)
 
   @default_hackney_options []
 
@@ -97,7 +98,7 @@ defmodule Pleroma.ReverseProxy do
     hackney_opts =
       @default_hackney_options
       |> Keyword.merge(Keyword.get(opts, :http, []))
-      |> @httpoison.process_request_options()
+      |> HTTP.process_request_options()
 
     req_headers = build_req_headers(conn.req_headers, opts)
 
index b7ecf00a0e456a69d0a2e5b2eb7a03d07d1423f1..1a4d54c62ce030534429a0b6b493121461118200 100644 (file)
@@ -5,11 +5,10 @@
 defmodule Pleroma.Signature do
   @behaviour HTTPSignatures.Adapter
 
+  alias Pleroma.Keys
   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"]),
@@ -33,8 +32,8 @@ defmodule Pleroma.Signature do
   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
+    with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user),
+         {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
       HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
     end
   end
index 190ed9f3ac77e93fa5494597deb6bc113e96b887..23754433773fcc27b84759fea82b724c2794b066 100644 (file)
@@ -4,11 +4,10 @@
 
 defmodule Pleroma.Uploaders.MDII do
   alias Pleroma.Config
+  alias Pleroma.HTTP
 
   @behaviour Pleroma.Uploaders.Uploader
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
   # MDII-hosted images are never passed through the MediaPlug; only local media.
   # Delegate to Pleroma.Uploaders.Local
   def get_file(file) do
@@ -25,7 +24,7 @@ defmodule Pleroma.Uploaders.MDII do
     query = "#{cgi}?#{extension}"
 
     with {:ok, %{status: 200, body: body}} <-
-           @httpoison.post(query, file_data, [], adapter: [pool: :default]) do
+           HTTP.post(query, file_data, [], adapter: [pool: :default]) do
       remote_file_name = String.split(body) |> List.first()
       public_url = "#{files}/#{remote_file_name}.#{extension}"
       {:ok, {:url, public_url}}
index 722e8ff6b0736d94bf6b364bd47336f7dee3100c..6abcb7288f5f3237a80cb212409656430cc153ef 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.User do
 
   alias Comeonin.Pbkdf2
   alias Pleroma.Activity
+  alias Pleroma.Keys
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Registration
@@ -55,7 +56,7 @@ defmodule Pleroma.User do
     field(:last_refreshed_at, :naive_datetime_usec)
     has_many(:notifications, Notification)
     has_many(:registrations, Registration)
-    embeds_one(:info, Pleroma.User.Info)
+    embeds_one(:info, User.Info)
 
     timestamps()
   end
@@ -166,7 +167,7 @@ defmodule Pleroma.User do
 
   def update_changeset(struct, params \\ %{}) do
     struct
-    |> cast(params, [:bio, :name, :avatar])
+    |> cast(params, [:bio, :name, :avatar, :following])
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: 5000)
@@ -233,7 +234,7 @@ defmodule Pleroma.User do
       |> validate_confirmation(:password)
       |> unique_constraint(:email)
       |> unique_constraint(:nickname)
-      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+      |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
       |> validate_format(:nickname, local_nickname_regex())
       |> validate_format(:email, @email_regex)
       |> validate_length(:bio, max: 1000)
@@ -284,7 +285,7 @@ defmodule Pleroma.User do
   def post_register_action(%User{} = user) do
     with {:ok, user} <- autofollow_users(user),
          {:ok, user} <- set_cache(user),
-         {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
+         {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
          {:ok, _} <- try_send_confirmation_email(user) do
       {:ok, user}
     end
@@ -371,9 +372,7 @@ defmodule Pleroma.User do
   end
 
   def follow(%User{} = follower, %User{info: info} = followed) do
-    user_config = Application.get_env(:pleroma, :user)
-    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
-
+    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
     ap_followers = followed.follower_address
 
     cond do
@@ -715,6 +714,18 @@ defmodule Pleroma.User do
     end
   end
 
+  def remove_duplicated_following(%User{following: following} = user) do
+    uniq_following = Enum.uniq(following)
+
+    if length(following) == length(uniq_following) do
+      {:ok, user}
+    else
+      user
+      |> update_changeset(%{following: uniq_following})
+      |> update_and_set_cache()
+    end
+  end
+
   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
   def get_users_from_set(ap_ids, local_only \\ true) do
     criteria = %{ap_id: ap_ids, deactivated: false}
@@ -753,7 +764,7 @@ defmodule Pleroma.User do
 
     from(s in subquery(boost_search_rank_query(distinct_query, for_user)),
       order_by: [desc: s.search_rank],
-      limit: 20
+      limit: 40
     )
   end
 
@@ -1138,7 +1149,6 @@ defmodule Pleroma.User do
     stream =
       ap_id
       |> Activity.query_by_actor()
-      |> Activity.with_preloaded_object()
       |> Repo.stream()
 
     Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
@@ -1384,4 +1394,57 @@ defmodule Pleroma.User do
   def showing_reblogs?(%User{} = user, %User{} = target) do
     target.ap_id not in user.info.muted_reblogs
   end
+
+  @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
+  def toggle_confirmation(%User{} = user) do
+    need_confirmation? = !user.info.confirmation_pending
+
+    info_changeset =
+      User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
+
+    user
+    |> change()
+    |> put_embed(:info, info_changeset)
+    |> update_and_set_cache()
+  end
+
+  def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
+    mascot
+  end
+
+  def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
+    # use instance-default
+    config = Pleroma.Config.get([:assets, :mascots])
+    default_mascot = Pleroma.Config.get([:assets, :default_mascot])
+    mascot = Keyword.get(config, default_mascot)
+
+    %{
+      "id" => "default-mascot",
+      "url" => mascot[:url],
+      "preview_url" => mascot[:url],
+      "pleroma" => %{
+        "mime_type" => mascot[:mime_type]
+      }
+    }
+  end
+
+  def ensure_keys_present(user) do
+    info = user.info
+
+    if info.keys do
+      {:ok, user}
+    else
+      {:ok, pem} = Keys.generate_rsa_pem()
+
+      info_cng =
+        info
+        |> User.Info.set_keys(pem)
+
+      cng =
+        Ecto.Changeset.change(user)
+        |> Ecto.Changeset.put_embed(:info, info_cng)
+
+      update_and_set_cache(cng)
+    end
+  end
 end
index 5a50ee639a383aa7e499214d22e33158f61373bd..6397e2737b8fddc658149b420e74db539a41f27e 100644 (file)
@@ -43,6 +43,7 @@ defmodule Pleroma.User.Info do
     field(:hide_favorites, :boolean, default: true)
     field(:pinned_activities, {:array, :string}, default: [])
     field(:flavour, :string, default: nil)
+    field(:mascot, :map, default: nil)
     field(:emoji, {:array, :map}, default: [])
 
     field(:notification_settings, :map,
@@ -212,7 +213,7 @@ defmodule Pleroma.User.Info do
     ])
   end
 
-  @spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
+  @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
   def confirmation_changeset(info, opts) do
     need_confirmation? = Keyword.get(opts, :need_confirmation)
 
@@ -248,6 +249,14 @@ defmodule Pleroma.User.Info do
     |> validate_required([:flavour])
   end
 
+  def mascot_update(info, url) do
+    params = %{mascot: url}
+
+    info
+    |> cast(params, [:mascot])
+    |> validate_required([:mascot])
+  end
+
   def set_source_data(info, source_data) do
     params = %{source_data: source_data}
 
index 233fee4fa7133b43adc87accabdc423baa608dc5..8add62406a7a6ac0ab8fd053532b5ea1f1ad2661 100644 (file)
@@ -399,16 +399,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def block(blocker, blocked, activity_id \\ nil, local \\ true) do
-    ap_config = Application.get_env(:pleroma, :activitypub)
-    unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
-    outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
+    outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
+    unfollow_blocked = Pleroma.Config.get([:activitypub, :unfollow_blocked])
 
-    with true <- unfollow_blocked do
+    if unfollow_blocked do
       follow_activity = fetch_latest_follow(blocker, blocked)
-
-      if follow_activity do
-        unfollow(blocker, blocked, nil, local)
-      end
+      if follow_activity, do: unfollow(blocker, blocked, nil, local)
     end
 
     with true <- outgoing_blocks,
@@ -540,8 +536,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
             )
         )
 
-      Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
       query
     else
       Logger.error("Could not restrict visibility to #{visibility}")
@@ -557,8 +551,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
       )
 
-    Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
     query
   end
 
@@ -569,6 +561,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_visibility(query, _visibility), do: query
 
+  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
+    query =
+      from(
+        a in query,
+        where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+      )
+
+    query
+  end
+
+  defp restrict_thread_visibility(query, _), do: query
+
   def fetch_user_activities(user, reading_user, params \\ %{}) do
     params =
       params
@@ -645,20 +649,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_tag(query, _), do: query
 
-  defp restrict_to_cc(query, recipients_to, recipients_cc) do
-    from(
-      activity in query,
-      where:
-        fragment(
-          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
-          activity.data,
-          ^recipients_to,
-          activity.data,
-          ^recipients_cc
-        )
-    )
-  end
-
   defp restrict_recipients(query, [], _user), do: query
 
   defp restrict_recipients(query, recipients, nil) do
@@ -695,6 +685,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_type(query, _), do: query
 
+  defp restrict_state(query, %{"state" => state}) do
+    from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
+  end
+
+  defp restrict_state(query, _), do: query
+
   defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
     from(
       activity in query,
@@ -750,8 +746,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     blocks = info.blocks || []
     domain_blocks = info.domain_blocks || []
 
+    query =
+      if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
+
     from(
-      activity in query,
+      [activity, object: o] in query,
       where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
       where: fragment("not (? && ?)", activity.recipients, ^blocks),
       where:
@@ -761,7 +760,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           activity.data,
           ^blocks
         ),
-      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
+      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
+      where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
     )
   end
 
@@ -816,6 +816,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> Activity.with_preloaded_bookmark(opts["user"])
   end
 
+  defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
+
+  defp maybe_set_thread_muted_field(query, opts) do
+    query
+    |> Activity.with_set_thread_muted_field(opts["user"])
+  end
+
   defp maybe_order(query, %{order: :desc}) do
     query
     |> order_by(desc: :id)
@@ -834,6 +841,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     base_query
     |> maybe_preload_objects(opts)
     |> maybe_preload_bookmarks(opts)
+    |> maybe_set_thread_muted_field(opts)
     |> maybe_order(opts)
     |> restrict_recipients(recipients, opts["user"])
     |> restrict_tag(opts)
@@ -843,11 +851,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_local(opts)
     |> restrict_actor(opts)
     |> restrict_type(opts)
+    |> restrict_state(opts)
     |> restrict_favorited_by(opts)
     |> restrict_blocked(opts)
     |> restrict_muted(opts)
     |> restrict_media(opts)
     |> restrict_visibility(opts)
+    |> restrict_thread_visibility(opts)
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)
@@ -861,9 +871,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> Enum.reverse()
   end
 
-  def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
+  def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
+    from(activity in query,
+      where:
+        fragment("? && ?", activity.recipients, ^recipients) or
+          (fragment("? && ?", activity.recipients, ^recipients_with_public) and
+             "https://www.w3.org/ns/activitystreams#Public" in activity.recipients)
+    )
+  end
+
+  def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
     fetch_activities_query([], opts)
-    |> restrict_to_cc(recipients_to, recipients_cc)
+    |> fetch_activities_bounded_query(recipients, recipients_with_public)
     |> Pagination.fetch_paginated(opts)
     |> Enum.reverse()
   end
@@ -881,7 +900,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  def user_data_from_user_object(data) do
+  defp object_to_user_data(data) do
     avatar =
       data["icon"]["url"] &&
         %{
@@ -928,9 +947,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     {:ok, user_data}
   end
 
+  def user_data_from_user_object(data) do
+    with {:ok, data} <- MRF.filter(data),
+         {:ok, data} <- object_to_user_data(data) do
+      {:ok, data}
+    else
+      e -> {:error, e}
+    end
+  end
+
   def fetch_and_prepare_user_from_ap_id(ap_id) do
-    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
-      user_data_from_user_object(data)
+    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
+         {:ok, data} <- user_data_from_user_object(data) do
+      {:ok, data}
     else
       e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
     end
@@ -966,11 +995,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     contain_broken_threads(activity, user)
   end
 
-  # do post-processing on a timeline
-  def contain_timeline(timeline, user) do
-    timeline
-    |> Enum.filter(fn activity ->
-      contain_activity(activity, user)
-    end)
+  def fetch_direct_messages_query do
+    Activity
+    |> restrict_type(%{"type" => "Create"})
+    |> restrict_visibility(%{visibility: "direct"})
+    |> order_by([activity], asc: activity.id)
   end
 end
index c967ab7a9fdc4430a7414598e27eac1ffbbb45d8..0182bda46d714462482926b87fb2b1c0ec707e88 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
   plug(:relay_active? when action in [:relay])
 
   def relay_active?(conn, _) do
-    if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
+    if Pleroma.Config.get([:instance, :allow_relay]) do
       conn
     else
       conn
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def user(conn, %{"nickname" => nickname}) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("user.json", %{user: user}))
@@ -106,7 +106,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def following(conn, %{"nickname" => nickname, "page" => page}) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       {page, _} = Integer.parse(page)
 
       conn
@@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def following(conn, %{"nickname" => nickname}) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("following.json", %{user: user}))
@@ -126,7 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def followers(conn, %{"nickname" => nickname, "page" => page}) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       {page, _} = Integer.parse(page)
 
       conn
@@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def followers(conn, %{"nickname" => nickname}) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("followers.json", %{user: user}))
@@ -146,7 +146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def outbox(conn, %{"nickname" => nickname} = params) do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
@@ -195,7 +195,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def relay(conn, _params) do
     with %User{} = user <- Relay.get_actor(),
-         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+         {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("user.json", %{user: user}))
index 1aaa2005051857a1584c755d8ebb1b39fc59949c..3bf7955f32b918eb47f7585d303473e78ed3a575 100644 (file)
@@ -17,9 +17,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
   end
 
   def get_policies do
-    Application.get_env(:pleroma, :instance, [])
-    |> Keyword.get(:rewrite_policy, [])
-    |> get_policies()
+    Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
   end
 
   defp get_policies(policy) when is_atom(policy), do: [policy]
index 2f105700bc7f57ffe855a21d780a029c39468d47..433d23c5f14b791539d4dacf27780c547ba1bf85 100644 (file)
@@ -48,14 +48,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
          %{host: actor_host} = _actor_info,
          %{
            "type" => "Create",
-           "object" => %{"attachment" => child_attachment} = child_object
+           "object" => child_object
          } = object
-       )
-       when length(child_attachment) > 0 do
+       ) do
     object =
       if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
         tags = (child_object["tag"] || []) ++ ["nsfw"]
-        child_object = Map.put(child_object, "tags", tags)
+        child_object = Map.put(child_object, "tag", tags)
         child_object = Map.put(child_object, "sensitive", true)
         Map.put(object, "object", child_object)
       else
@@ -75,8 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
                actor_host
              ),
            user <- User.get_cached_by_ap_id(object["actor"]),
-           true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"],
-           true <- user.follower_address in object["cc"] do
+           true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
         to =
           List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
             [user.follower_address]
@@ -95,18 +93,63 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
     {:ok, object}
   end
 
+  defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
+    if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do
+      {:reject, nil}
+    else
+      {:ok, object}
+    end
+  end
+
+  defp check_report_removal(_actor_info, object), do: {:ok, object}
+
+  defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
+    if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do
+      {:ok, Map.delete(object, "icon")}
+    else
+      {:ok, object}
+    end
+  end
+
+  defp check_avatar_removal(_actor_info, object), do: {:ok, object}
+
+  defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
+    if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do
+      {:ok, Map.delete(object, "image")}
+    else
+      {:ok, object}
+    end
+  end
+
+  defp check_banner_removal(_actor_info, object), do: {:ok, object}
+
   @impl true
-  def filter(object) do
-    actor_info = URI.parse(object["actor"])
+  def filter(%{"actor" => actor} = object) do
+    actor_info = URI.parse(actor)
 
     with {:ok, object} <- check_accept(actor_info, object),
          {:ok, object} <- check_reject(actor_info, object),
          {:ok, object} <- check_media_removal(actor_info, object),
          {:ok, object} <- check_media_nsfw(actor_info, object),
-         {:ok, object} <- check_ftl_removal(actor_info, object) do
+         {:ok, object} <- check_ftl_removal(actor_info, object),
+         {:ok, object} <- check_report_removal(actor_info, object) do
+      {:ok, object}
+    else
+      _e -> {:reject, nil}
+    end
+  end
+
+  def filter(%{"id" => actor, "type" => obj_type} = object)
+      when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
+    actor_info = URI.parse(actor)
+
+    with {:ok, object} <- check_avatar_removal(actor_info, object),
+         {:ok, object} <- check_banner_removal(actor_info, object) do
       {:ok, object}
     else
       _e -> {:reject, nil}
     end
   end
+
+  def filter(object), do: {:ok, object}
 end
index b52be30e72c2553b375cf1b17ca4b6064247b650..6683b8d8e6a294b6ab19797f5f4d495fdfd08e1a 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
 
     object =
       object
-      |> Map.put("tags", tags)
+      |> Map.put("tag", tags)
       |> Map.put("sensitive", true)
 
     message = Map.put(message, "object", object)
index f5078d8182a60d35b441f5d68536bc4994653904..47663414a38773e0b1a6e860ff6055a76f43d112 100644 (file)
@@ -19,10 +19,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
   end
 
   @impl true
-  def filter(object) do
-    actor_info = URI.parse(object["actor"])
+  def filter(%{"actor" => actor} = object) do
+    actor_info = URI.parse(actor)
     allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
 
     filter_by_list(object, allow_list)
   end
+
+  def filter(object), do: {:ok, object}
 end
index 11dba87dea79440dd9b25d1310ab75d6520026f5..8f1399ce6f2789fb566730b8b35adcd3d4c3f525 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.Publisher do
   alias Pleroma.Activity
   alias Pleroma.Config
+  alias Pleroma.HTTP
   alias Pleroma.Instances
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Relay
@@ -16,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
 
   require Logger
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
   @moduledoc """
   ActivityPub outgoing federation module.
   """
@@ -63,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
 
     with {:ok, %{status: code}} when code in 200..299 <-
            result =
-             @httpoison.post(
+             HTTP.post(
                inbox,
                json,
                [
index 508f3532f2de46398976c5672e0ffdceff171567..d8fa2728d71505a39f91a16a114efe33518b3ee0 100644 (file)
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
@@ -94,7 +93,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       object
       |> Utils.determine_explicit_mentions()
 
-    explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"]
+    follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
+
+    explicit_mentions =
+      explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
 
     object
     |> fix_explicit_addressing(explicit_mentions)
index 236d1b4aca4f9c2b9447f68fe2af03867b1c96c1..ca8a0844be1e4840b7d384d64a4a815e2adaaba6 100644 (file)
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   require Logger
 
   @supported_object_types ["Article", "Note", "Video", "Page"]
+  @supported_report_states ~w(open closed resolved)
+  @valid_visibilities ~w(public unlisted private direct)
 
   # Some implementations send the actor URI as the actor field, others send the entire actor object,
   # so figure out what the actor's URI is based on what we have.
@@ -670,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "actor" => params.actor.ap_id,
       "content" => params.content,
       "object" => object,
-      "context" => params.context
+      "context" => params.context,
+      "state" => "open"
     }
     |> Map.merge(additional)
   end
@@ -713,4 +716,77 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       end
     end
   end
+
+  #### Report-related helpers
+
+  def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
+    with new_data <- Map.put(activity.data, "state", state),
+         changeset <- Changeset.change(activity, data: new_data),
+         {:ok, activity} <- Repo.update(changeset) do
+      {:ok, activity}
+    end
+  end
+
+  def update_report_state(_, _), do: {:error, "Unsupported state"}
+
+  def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
+    [to, cc, recipients] =
+      activity
+      |> get_updated_targets(visibility)
+      |> Enum.map(&Enum.uniq/1)
+
+    object_data =
+      activity.object.data
+      |> Map.put("to", to)
+      |> Map.put("cc", cc)
+
+    {:ok, object} =
+      activity.object
+      |> Object.change(%{data: object_data})
+      |> Object.update_and_set_cache()
+
+    activity_data =
+      activity.data
+      |> Map.put("to", to)
+      |> Map.put("cc", cc)
+
+    activity
+    |> Map.put(:object, object)
+    |> Activity.change(%{data: activity_data, recipients: recipients})
+    |> Repo.update()
+  end
+
+  def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
+
+  defp get_updated_targets(
+         %Activity{data: %{"to" => to} = data, recipients: recipients},
+         visibility
+       ) do
+    cc = Map.get(data, "cc", [])
+    follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
+    public = "https://www.w3.org/ns/activitystreams#Public"
+
+    case visibility do
+      "public" ->
+        to = [public | List.delete(to, follower_address)]
+        cc = [follower_address | List.delete(cc, public)]
+        recipients = [public | recipients]
+        [to, cc, recipients]
+
+      "private" ->
+        to = [follower_address | List.delete(to, public)]
+        cc = List.delete(cc, public)
+        recipients = List.delete(recipients, public)
+        [to, cc, recipients]
+
+      "unlisted" ->
+        to = [follower_address | List.delete(to, public)]
+        cc = [public | List.delete(cc, follower_address)]
+        recipients = recipients ++ [follower_address, public]
+        [to, cc, recipients]
+
+      _ ->
+        [to, cc, recipients]
+    end
+  end
 end
index 1254fdf6cfdf94352ff1eba73f927f638ef0978f..327e0e05bbca5a373a28dc15eb301c4b6148e102 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.UserView do
   use Pleroma.Web, :view
 
+  alias Pleroma.Keys
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -12,8 +13,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router.Helpers
-  alias Pleroma.Web.Salmon
-  alias Pleroma.Web.WebFinger
 
   import Ecto.Query
 
@@ -34,8 +33,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
 
   # the instance itself is not a Person, but instead an Application
   def render("user.json", %{user: %{nickname: nil} = user}) do
-    {:ok, user} = WebFinger.ensure_keys_present(user)
-    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)
+    {:ok, user} = User.ensure_keys_present(user)
+    {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
     public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
     public_key = :public_key.pem_encode([public_key])
 
@@ -62,8 +61,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   end
 
   def render("user.json", %{user: user}) do
-    {:ok, user} = WebFinger.ensure_keys_present(user)
-    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)
+    {:ok, user} = User.ensure_keys_present(user)
+    {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
     public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
     public_key = :public_key.pem_encode([public_key])
 
index b38ee0442db93daf83f47c7be7cd2d9f7900335a..93b50ee473b7cd9a4e27fb51105ee6efa85c42a0 100644 (file)
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.ActivityPub.Visibility do
   alias Pleroma.Activity
   alias Pleroma.Object
+  alias Pleroma.Repo
   alias Pleroma.User
 
   def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
@@ -13,11 +14,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   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))
+    with false <- is_public?(activity),
+         %User{follower_address: follower_address} <-
+           User.get_cached_by_ap_id(activity.data["actor"]) do
+      follower_address in activity.data["to"]
     else
-      false
+      _ -> false
     end
   end
 
@@ -38,25 +40,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
     visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
   end
 
-  # guard
-  def entire_thread_visible_for_user?(nil, _user), do: false
+  def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
+    {:ok, %{rows: [[result]]}} =
+      Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
+        user.ap_id,
+        activity.data["id"]
+      ])
 
-  # 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
+    result
   end
 
   def get_visibility(object) do
index 60fd4e57199dc531f8460ce58d9c259ac20a2729..479fd5829087aee42db457af8195e066135d93ac 100644 (file)
@@ -4,11 +4,16 @@
 
 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   use Pleroma.Web, :controller
+  alias Pleroma.Activity
   alias Pleroma.User
   alias Pleroma.UserInviteToken
+  alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.AdminAPI.AccountView
+  alias Pleroma.Web.AdminAPI.ReportView
   alias Pleroma.Web.AdminAPI.Search
+  alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.MastodonAPI.StatusView
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
@@ -315,12 +320,88 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> json(token.token)
   end
 
+  def list_reports(conn, params) do
+    params =
+      params
+      |> Map.put("type", "Flag")
+      |> Map.put("skip_preload", true)
+
+    reports =
+      []
+      |> ActivityPub.fetch_activities(params)
+      |> Enum.reverse()
+
+    conn
+    |> put_view(ReportView)
+    |> render("index.json", %{reports: reports})
+  end
+
+  def report_show(conn, %{"id" => id}) do
+    with %Activity{} = report <- Activity.get_by_id(id) do
+      conn
+      |> put_view(ReportView)
+      |> render("show.json", %{report: report})
+    else
+      _ -> {:error, :not_found}
+    end
+  end
+
+  def report_update_state(conn, %{"id" => id, "state" => state}) do
+    with {:ok, report} <- CommonAPI.update_report_state(id, state) do
+      conn
+      |> put_view(ReportView)
+      |> render("show.json", %{report: report})
+    end
+  end
+
+  def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
+    with false <- is_nil(params["status"]),
+         %Activity{} <- Activity.get_by_id(id) do
+      params =
+        params
+        |> Map.put("in_reply_to_status_id", id)
+        |> Map.put("visibility", "direct")
+
+      {:ok, activity} = CommonAPI.post(user, params)
+
+      conn
+      |> put_view(StatusView)
+      |> render("status.json", %{activity: activity})
+    else
+      true ->
+        {:param_cast, nil}
+
+      nil ->
+        {:error, :not_found}
+    end
+  end
+
+  def status_update(conn, %{"id" => id} = params) do
+    with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+      conn
+      |> put_view(StatusView)
+      |> render("status.json", %{activity: activity})
+    end
+  end
+
+  def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+      json(conn, %{})
+    end
+  end
+
   def errors(conn, {:error, :not_found}) do
     conn
     |> put_status(404)
     |> json("Not found")
   end
 
+  def errors(conn, {:error, reason}) do
+    conn
+    |> put_status(400)
+    |> json(reason)
+  end
+
   def errors(conn, {:param_cast, _}) do
     conn
     |> put_status(400)
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
new file mode 100644 (file)
index 0000000..47a73dc
--- /dev/null
@@ -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.Web.AdminAPI.ReportView do
+  use Pleroma.Web, :view
+  alias Pleroma.Activity
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI.Utils
+  alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.MastodonAPI.StatusView
+
+  def render("index.json", %{reports: reports}) do
+    %{
+      reports: render_many(reports, __MODULE__, "show.json", as: :report)
+    }
+  end
+
+  def render("show.json", %{report: report}) do
+    user = User.get_cached_by_ap_id(report.data["actor"])
+    created_at = Utils.to_masto_date(report.data["published"])
+
+    [account_ap_id | status_ap_ids] = report.data["object"]
+    account = User.get_cached_by_ap_id(account_ap_id)
+
+    statuses =
+      Enum.map(status_ap_ids, fn ap_id ->
+        Activity.get_by_ap_id_with_object(ap_id)
+      end)
+
+    %{
+      id: report.id,
+      account: AccountView.render("account.json", %{user: account}),
+      actor: AccountView.render("account.json", %{user: user}),
+      content: report.data["content"],
+      created_at: created_at,
+      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
+      state: report.data["state"]
+    }
+  end
+end
index 29c4c101425b6f4190d3b2188dc28ef0b29dc194..5a312d673fe9cd793c235d0e1b018412d029cba6 100644 (file)
@@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do
          {:ok, _} <- unpin(activity_id, user),
          {:ok, delete} <- ActivityPub.delete(object) do
       {:ok, delete}
+    else
+      _ ->
+        {:error, "Could not delete"}
     end
   end
 
@@ -154,6 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
          {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
          context <- make_context(in_reply_to),
          cw <- data["spoiler_text"] || "",
+         sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
          full_payload <- String.trim(status <> cw),
          length when length in 1..limit <- String.length(full_payload),
          object <-
@@ -166,7 +170,8 @@ defmodule Pleroma.Web.CommonAPI do
              in_reply_to,
              tags,
              cw,
-             cc
+             cc,
+             sensitive
            ),
          object <-
            Map.put(
@@ -197,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI do
     user =
       with emoji <- emoji_from_profile(user),
            source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
-           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
+           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
@@ -230,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do
          } = 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 <-
-           Pleroma.User.Info.add_pinnned_activity(user.info, activity),
+           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
@@ -247,7 +252,7 @@ defmodule Pleroma.Web.CommonAPI do
   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 <-
-           Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
+           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
@@ -315,6 +320,60 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  def update_report_state(activity_id, state) do
+    with %Activity{} = activity <- Activity.get_by_id(activity_id),
+         {:ok, activity} <- Utils.update_report_state(activity, state) do
+      {:ok, activity}
+    else
+      nil ->
+        {:error, :not_found}
+
+      {:error, reason} ->
+        {:error, reason}
+
+      _ ->
+        {:error, "Could not update state"}
+    end
+  end
+
+  def update_activity_scope(activity_id, opts \\ %{}) do
+    with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
+         {:ok, activity} <- toggle_sensitive(activity, opts),
+         {:ok, activity} <- set_visibility(activity, opts) do
+      {:ok, activity}
+    else
+      nil ->
+        {:error, :not_found}
+
+      {:error, reason} ->
+        {:error, reason}
+    end
+  end
+
+  defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
+    toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
+  end
+
+  defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
+       when is_boolean(sensitive) do
+    new_data = Map.put(object.data, "sensitive", sensitive)
+
+    {:ok, object} =
+      object
+      |> Object.change(%{data: new_data})
+      |> Object.update_and_set_cache()
+
+    {:ok, Map.put(activity, :object, object)}
+  end
+
+  defp toggle_sensitive(activity, _), do: {:ok, activity}
+
+  defp set_visibility(activity, %{"visibility" => visibility}) do
+    Utils.update_activity_visibility(activity, visibility)
+  end
+
+  defp set_visibility(activity, _), do: {:ok, activity}
+
   def hide_reblogs(user, muted) do
     ap_id = muted.ap_id
 
index 1dfe50b408266efd29f341ac37f5dc3b7b652d53..d93c0d46e5962ba93bc2796a738ac294dbbcd656 100644 (file)
@@ -223,7 +223,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
         in_reply_to,
         tags,
         cw \\ nil,
-        cc \\ []
+        cc \\ [],
+        sensitive \\ false
       ) do
     object = %{
       "type" => "Note",
@@ -231,19 +232,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
       "cc" => cc,
       "content" => content_html,
       "summary" => cw,
+      "sensitive" => !Enum.member?(["false", "False", "0", false], sensitive),
       "context" => context,
       "attachment" => attachments,
       "actor" => actor,
       "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
     }
 
-    if in_reply_to do
-      in_reply_to_object = Object.normalize(in_reply_to)
-
-      object
-      |> Map.put("inReplyTo", in_reply_to_object.data["id"])
+    with false <- is_nil(in_reply_to),
+         %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
+      Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
     else
-      object
+      _ -> object
     end
   end
 
index 9ef30e8851777327d8adf6a92dca490cfa677db2..bd76e42950167ca1466a48f99727fb852bf71a3f 100644 (file)
@@ -16,17 +16,32 @@ defmodule Pleroma.Web.Endpoint do
 
   plug(Pleroma.Plugs.UploadedMedia)
 
+  @static_cache_control "public, no-cache"
+
   # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
   # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
-  plug(Pleroma.Plugs.InstanceStatic, at: "/")
+  # Cache-control headers are duplicated in case we turn off etags in the future
+  plug(Pleroma.Plugs.InstanceStatic,
+    at: "/",
+    gzip: true,
+    cache_control_for_etags: @static_cache_control,
+    headers: %{
+      "cache-control" => @static_cache_control
+    }
+  )
 
   plug(
     Plug.Static,
     at: "/",
     from: :pleroma,
     only:
-      ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
+      ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc),
     # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+    gzip: true,
+    cache_control_for_etags: @static_cache_control,
+    headers: %{
+      "cache-control" => @static_cache_control
+    }
   )
 
   plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
@@ -51,7 +66,7 @@ defmodule Pleroma.Web.Endpoint do
     parsers: [:urlencoded, :multipart, :json],
     pass: ["*/*"],
     json_decoder: Jason,
-    length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit),
+    length: Pleroma.Config.get([:instance, :upload_limit]),
     body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
   )
 
index 169fdf4dc13ed2c88cd8c6befb50a8d54d647fa9..f4c9fe28403d4991f6569b7bc91897529fbcd155 100644 (file)
@@ -11,14 +11,11 @@ defmodule Pleroma.Web.Federator do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.Federator.Publisher
   alias Pleroma.Web.Federator.RetryQueue
-  alias Pleroma.Web.WebFinger
+  alias Pleroma.Web.OStatus
   alias Pleroma.Web.Websub
 
   require Logger
 
-  @websub Application.get_env(:pleroma, :websub)
-  @ostatus Application.get_env(:pleroma, :ostatus)
-
   def init do
     # 1 minute
     Process.sleep(1000 * 60)
@@ -77,9 +74,8 @@ defmodule Pleroma.Web.Federator do
   def perform(:publish, activity) do
     Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
 
-    with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
-      {:ok, actor} = WebFinger.ensure_keys_present(actor)
-
+    with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]),
+         {:ok, actor} <- User.ensure_keys_present(actor) do
       Publisher.publish(actor, activity)
     end
   end
@@ -89,12 +85,12 @@ defmodule Pleroma.Web.Federator do
       "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"
     end)
 
-    @websub.verify(websub)
+    Websub.verify(websub)
   end
 
   def perform(:incoming_doc, doc) do
     Logger.info("Got document, trying to parse")
-    @ostatus.handle_incoming(doc)
+    OStatus.handle_incoming(doc)
   end
 
   def perform(:incoming_ap_doc, params) do
index 916bcdcba9e721ec48eeba2cad36173005dbd44a..70f870244fbb5f7af1283985620004f3791522f7 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do
   """
   @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
@@ -52,9 +52,9 @@ defmodule Pleroma.Web.Federator.Publisher do
   @doc """
   Relays an activity to all specified peers.
   """
-  @callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
+  @callback publish(User.t(), Activity.t()) :: :ok | {:error, any()}
 
-  @spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
+  @spec publish(User.t(), Activity.t()) :: :ok
   def publish(%User{} = user, %Activity{} = activity) do
     Config.get([:instance, :federation_publisher_modules])
     |> Enum.each(fn module ->
@@ -70,9 +70,9 @@ defmodule Pleroma.Web.Federator.Publisher do
   @doc """
   Gathers links used by an outgoing federation module for WebFinger output.
   """
-  @callback gather_webfinger_links(Pleroma.User.t()) :: list()
+  @callback gather_webfinger_links(User.t()) :: list()
 
-  @spec gather_webfinger_links(Pleroma.User.t()) :: list()
+  @spec gather_webfinger_links(User.t()) :: list()
   def gather_webfinger_links(%User{} = user) do
     Config.get([:instance, :federation_publisher_modules])
     |> Enum.reduce([], fn module, links ->
index 87e597074c1b7c8c961603aff51368e07f7039e1..2110027c3da313d238f06894c21fa38a54071f78 100644 (file)
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Conversation.Participation
   alias Pleroma.Filter
   alias Pleroma.Formatter
+  alias Pleroma.HTTP
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Object.Fetcher
@@ -55,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     when action in [:account_register]
   )
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
   @local_mastodon_name "Mastodon-Local"
 
   action_fallback(:errors)
@@ -303,7 +303,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     activities =
       [user.ap_id | user.following]
       |> ActivityPub.fetch_activities(params)
-      |> ActivityPub.contain_timeline(user)
       |> Enum.reverse()
 
     conn
@@ -708,6 +707,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
+    with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
+         %{} = attachment_data <- Map.put(object.data, "id", object.id),
+         %{type: type} = rendered <-
+           StatusView.render("attachment.json", %{attachment: attachment_data}) do
+      # Reject if not an image
+      if type == "image" do
+        # Sure!
+        # Save to the user's info
+        info_changeset = User.Info.mascot_update(user.info, rendered)
+
+        user_changeset =
+          user
+          |> Ecto.Changeset.change()
+          |> Ecto.Changeset.put_embed(:info, info_changeset)
+
+        {:ok, _user} = User.update_and_set_cache(user_changeset)
+
+        conn
+        |> json(rendered)
+      else
+        conn
+        |> put_resp_content_type("application/json")
+        |> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"}))
+      end
+    end
+  end
+
+  def get_mascot(%{assigns: %{user: user}} = conn, _params) do
+    mascot = User.get_mascot(user)
+
+    conn
+    |> json(mascot)
+  end
+
   def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
          %Object{data: %{"likes" => likes}} <- Object.normalize(object) do
@@ -1010,6 +1044,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def status_search_query_with_gin(q, query) do
+    from([a, o] in q,
+      where:
+        fragment(
+          "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
+          o.data,
+          ^query
+        ),
+      order_by: [desc: :id]
+    )
+  end
+
+  def status_search_query_with_rum(q, query) do
+    from([a, o] in q,
+      where:
+        fragment(
+          "? @@ plainto_tsquery('english', ?)",
+          o.fts_content,
+          ^query
+        ),
+      order_by: [fragment("? <=> now()::date", o.inserted_at)]
+    )
+  end
+
   def status_search(user, query) do
     fetched =
       if Regex.match?(~r/https?:/, query) do
@@ -1023,20 +1081,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       end || []
 
     q =
-      from(
-        [a, o] in Activity.with_preloaded_object(Activity),
+      from([a, o] in Activity.with_preloaded_object(Activity),
         where: fragment("?->>'type' = 'Create'", a.data),
         where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
-        where:
-          fragment(
-            "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
-            o.data,
-            ^query
-          ),
-        limit: 20,
-        order_by: [desc: :id]
+        limit: 40
       )
 
+    q =
+      if Pleroma.Config.get([:database, :rum_enabled]) do
+        status_search_query_with_rum(q, query)
+      else
+        status_search_query_with_gin(q, query)
+      end
+
     Repo.all(q) ++ fetched
   end
 
@@ -1223,7 +1280,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     accounts
     |> Enum.each(fn account_id ->
       with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-           %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
+           %User{} = followed <- User.get_cached_by_id(account_id) do
         Pleroma.List.unfollow(list, followed)
       end
     end)
@@ -1307,7 +1364,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
             display_sensitive_media: false,
             reduce_motion: false,
             max_toot_chars: limit,
-            mascot: "/images/pleroma-fox-tan-smol.png"
+            mascot: User.get_mascot(user)["url"]
           },
           rights: %{
             delete_others_notice: present?(user.info.is_moderator),
@@ -1634,7 +1691,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> String.replace("{{user}}", user)
 
       with {:ok, %{status: 200, body: body}} <-
-             @httpoison.get(
+             HTTP.get(
                url,
                [],
                adapter: [
index 779b9a3824043aeb690451c68f79f4bd7c061a18..b82d3319b4c4d40da6ef356ed61eb2b0e00dca91 100644 (file)
@@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
 
     requested =
-      if follow_activity do
+      if follow_activity && !User.following?(target, user) do
         follow_activity.data["state"] == "pending"
       else
         false
@@ -112,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       fields: fields,
       bot: bot,
       source: %{
-        note: "",
+        note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
         sensitive: false,
         pleroma: %{}
       },
index c93d915e5aa851e8fa27084fec1e682a58137d9d..84ab20a1c8cdd00caab9a476a35cc0dab70f4906 100644 (file)
@@ -157,6 +157,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
 
+    thread_muted? =
+      case activity.thread_muted? do
+        thread_muted? when is_boolean(thread_muted?) -> thread_muted?
+        nil -> CommonAPI.thread_muted?(user, activity)
+      end
+
     attachment_data = object.data["attachment"] || []
     attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
 
@@ -228,7 +234,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       reblogged: reblogged?(activity, opts[:for]),
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
-      muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
+      muted: thread_muted? || User.mutes?(opts[:for], user),
       pinned: pinned?(activity, user),
       sensitive: sensitive,
       spoiler_text: summary_html,
@@ -284,8 +290,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       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],
+      title: rich_media[:title] || "",
+      description: rich_media[:description] || "",
       pleroma: %{
         opengraph: rich_media
       }
index 5762e767b8586b0b5511d4f5bd425ac77889af8b..cee6d8481a33942d1d41d51a0cb16ba6fb98ac0b 100644 (file)
@@ -12,25 +12,27 @@ defmodule Pleroma.Web.MediaProxy do
   def url("/" <> _ = url), do: url
 
   def url(url) do
-    config = Application.get_env(:pleroma, :media_proxy, [])
-    domain = URI.parse(url).host
+    if !enabled?() or local?(url) or whitelisted?(url) do
+      url
+    else
+      encode_url(url)
+    end
+  end
 
-    cond do
-      !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) ->
-        url
+  defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false)
 
-      Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
-        String.equivalent?(domain, pattern)
-      end) ->
-        url
+  defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
 
-      true ->
-        encode_url(url)
-    end
+  defp whitelisted?(url) do
+    %{host: domain} = URI.parse(url)
+
+    Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
+      String.equivalent?(domain, pattern)
+    end)
   end
 
   def encode_url(url) do
-    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
+    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
 
     # Must preserve `%2F` for compatibility with S3
     # https://git.pleroma.social/pleroma/pleroma/issues/580
@@ -52,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy do
   end
 
   def decode_url(sig, url) do
-    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
+    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
     sig = Base.url_decode64!(sig, @base64_opts)
     local_sig = :crypto.hmac(:sha, secret, url)
 
diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
new file mode 100644 (file)
index 0000000..489d5d3
--- /dev/null
@@ -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.Web.MongooseIM.MongooseIMController do
+  use Pleroma.Web, :controller
+  alias Comeonin.Pbkdf2
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  def user_exists(conn, %{"user" => username}) do
+    with %User{} <- Repo.get_by(User, nickname: username, local: true) do
+      conn
+      |> json(true)
+    else
+      _ ->
+        conn
+        |> put_status(:not_found)
+        |> json(false)
+    end
+  end
+
+  def check_password(conn, %{"user" => username, "pass" => password}) do
+    with %User{password_hash: password_hash} <-
+           Repo.get_by(User, nickname: username, local: true),
+         true <- Pbkdf2.checkpw(password, password_hash) do
+      conn
+      |> json(true)
+    else
+      false ->
+        conn
+        |> put_status(403)
+        |> json(false)
+
+      _ ->
+        conn
+        |> put_status(:not_found)
+        |> json(false)
+    end
+  end
+end
index 3bf2a0fbcb262d59fa7f235c55f2fa4fa8470199..59f3d4e11613b47d33c95d17689ee1b5ec94edd9 100644 (file)
@@ -12,8 +12,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.Federator.Publisher
 
-  plug(Pleroma.Web.FederatingPlug)
-
   def schemas(conn, _params) do
     response = %{
       links: [
@@ -34,20 +32,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
   # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
   # under software.
   def raw_nodeinfo do
-    instance = Application.get_env(:pleroma, :instance)
-    media_proxy = Application.get_env(:pleroma, :media_proxy)
-    suggestions = Application.get_env(:pleroma, :suggestions)
-    chat = Application.get_env(:pleroma, :chat)
-    gopher = Application.get_env(:pleroma, :gopher)
     stats = Stats.get_stats()
 
     mrf_simple =
-      Application.get_env(:pleroma, :mrf_simple)
+      Config.get(:mrf_simple)
       |> Enum.into(%{})
 
     # This horror is needed to convert regex sigils to strings
     mrf_keyword =
-      Application.get_env(:pleroma, :mrf_keyword, [])
+      Config.get(:mrf_keyword, [])
       |> Enum.map(fn {key, value} ->
         {key,
          Enum.map(value, fn
@@ -76,14 +69,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
       MRF.get_policies()
       |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
 
-    quarantined = Keyword.get(instance, :quarantined_instances)
-
-    quarantined =
-      if is_list(quarantined) do
-        quarantined
-      else
-        []
-      end
+    quarantined = Config.get([:instance, :quarantined_instances], [])
 
     staff_accounts =
       User.all_superusers()
@@ -94,7 +80,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
       |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
 
     federation_response =
-      if Keyword.get(instance, :mrf_transparency) do
+      if Config.get([:instance, :mrf_transparency]) do
         %{
           mrf_policies: mrf_policies,
           mrf_simple: mrf_simple,
@@ -111,22 +97,22 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         "pleroma_api",
         "mastodon_api",
         "mastodon_api_streaming",
-        if Keyword.get(media_proxy, :enabled) do
+        if Config.get([:media_proxy, :enabled]) do
           "media_proxy"
         end,
-        if Keyword.get(gopher, :enabled) do
+        if Config.get([:gopher, :enabled]) do
           "gopher"
         end,
-        if Keyword.get(chat, :enabled) do
+        if Config.get([:chat, :enabled]) do
           "chat"
         end,
-        if Keyword.get(suggestions, :enabled) do
+        if Config.get([:suggestions, :enabled]) do
           "suggestions"
         end,
-        if Keyword.get(instance, :allow_relay) do
+        if Config.get([:instance, :allow_relay]) do
           "relay"
         end,
-        if Keyword.get(instance, :safe_dm_mentions) do
+        if Config.get([:instance, :safe_dm_mentions]) do
           "safe_dm_mentions"
         end
       ]
@@ -143,7 +129,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         inbound: [],
         outbound: []
       },
-      openRegistrations: Keyword.get(instance, :registrations_open),
+      openRegistrations: Config.get([:instance, :registrations_open]),
       usage: %{
         users: %{
           total: stats.user_count || 0
@@ -151,29 +137,29 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         localPosts: stats.status_count || 0
       },
       metadata: %{
-        nodeName: Keyword.get(instance, :name),
-        nodeDescription: Keyword.get(instance, :description),
-        private: !Keyword.get(instance, :public, true),
+        nodeName: Config.get([:instance, :name]),
+        nodeDescription: Config.get([:instance, :description]),
+        private: !Config.get([:instance, :public], true),
         suggestions: %{
-          enabled: Keyword.get(suggestions, :enabled, false),
-          thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""),
-          timeout: Keyword.get(suggestions, :timeout, 5000),
-          limit: Keyword.get(suggestions, :limit, 23),
-          web: Keyword.get(suggestions, :web, "")
+          enabled: Config.get([:suggestions, :enabled], false),
+          thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""),
+          timeout: Config.get([:suggestions, :timeout], 5000),
+          limit: Config.get([:suggestions, :limit], 23),
+          web: Config.get([:suggestions, :web], "")
         },
         staffAccounts: staff_accounts,
         federation: federation_response,
-        postFormats: Keyword.get(instance, :allowed_post_formats),
+        postFormats: Config.get([:instance, :allowed_post_formats]),
         uploadLimits: %{
-          general: Keyword.get(instance, :upload_limit),
-          avatar: Keyword.get(instance, :avatar_upload_limit),
-          banner: Keyword.get(instance, :banner_upload_limit),
-          background: Keyword.get(instance, :background_upload_limit)
+          general: Config.get([:instance, :upload_limit]),
+          avatar: Config.get([:instance, :avatar_upload_limit]),
+          banner: Config.get([:instance, :banner_upload_limit]),
+          background: Config.get([:instance, :background_upload_limit])
         },
-        accountActivationRequired: Keyword.get(instance, :account_activation_required, false),
-        invitesEnabled: Keyword.get(instance, :invites_enabled, false),
+        accountActivationRequired: Config.get([:instance, :account_activation_required], false),
+        invitesEnabled: Config.get([:instance, :invites_enabled], false),
         features: features,
-        restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
+        restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames])
       }
     }
   end
index b47688de1d276524daf11820f086b9ef35980a59..18973413ea16b607fee74c8ae88ab76f3ef75831 100644 (file)
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
     field(:scopes, {:array, :string}, default: [])
     field(:valid_until, :naive_datetime_usec)
     field(:used, :boolean, default: false)
-    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
+    belongs_to(:user, User, type: Pleroma.FlakeId)
     belongs_to(:app, App)
 
     timestamps()
index ef047d565558614ffebf25f4852b758f07b39699..f412f7eb2b9c1353a8a88b73b5055864bc72507f 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.OAuth.Token do
   use Ecto.Schema
 
-  import Ecto.Query
   import Ecto.Changeset
 
   alias Pleroma.Repo
@@ -13,6 +12,7 @@ defmodule Pleroma.Web.OAuth.Token do
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Query
 
   @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
   @type t :: %__MODULE__{}
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do
     field(:refresh_token, :string)
     field(:scopes, {:array, :string}, default: [])
     field(:valid_until, :naive_datetime_usec)
-    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
+    belongs_to(:user, User, type: Pleroma.FlakeId)
     belongs_to(:app, App)
 
     timestamps()
@@ -31,17 +31,17 @@ defmodule Pleroma.Web.OAuth.Token do
   @doc "Gets token for app by access token"
   @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
   def get_by_token(%App{id: app_id} = _app, token) do
-    from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
+    Query.get_by_app(app_id)
+    |> Query.get_by_token(token)
     |> Repo.find_resource()
   end
 
   @doc "Gets token for app by refresh token"
   @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
   def get_by_refresh_token(%App{id: app_id} = _app, token) do
-    from(t in __MODULE__,
-      where: t.app_id == ^app_id and t.refresh_token == ^token,
-      preload: [:user]
-    )
+    Query.get_by_app(app_id)
+    |> Query.get_by_refresh_token(token)
+    |> Query.preload([:user])
     |> Repo.find_resource()
   end
 
@@ -97,29 +97,25 @@ defmodule Pleroma.Web.OAuth.Token do
   end
 
   def delete_user_tokens(%User{id: user_id}) do
-    from(
-      t in Token,
-      where: t.user_id == ^user_id
-    )
+    Query.get_by_user(user_id)
     |> Repo.delete_all()
   end
 
   def delete_user_token(%User{id: user_id}, token_id) do
-    from(
-      t in Token,
-      where: t.user_id == ^user_id,
-      where: t.id == ^token_id
-    )
+    Query.get_by_user(user_id)
+    |> Query.get_by_id(token_id)
+    |> Repo.delete_all()
+  end
+
+  def delete_expired_tokens do
+    Query.get_expired_tokens()
     |> Repo.delete_all()
   end
 
   def get_user_tokens(%User{id: user_id}) do
-    from(
-      t in Token,
-      where: t.user_id == ^user_id
-    )
+    Query.get_by_user(user_id)
+    |> Query.preload([:app])
     |> Repo.all()
-    |> Repo.preload(:app)
   end
 
   def is_expired?(%__MODULE__{valid_until: valid_until}) do
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
new file mode 100644 (file)
index 0000000..dca8524
--- /dev/null
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Token.CleanWorker do
+  @moduledoc """
+  The module represents functions to clean an expired oauth tokens.
+  """
+
+  # 10 seconds
+  @start_interval 10_000
+  @interval Pleroma.Config.get(
+              # 24 hours
+              [:oauth2, :clean_expired_tokens_interval],
+              86_400_000
+            )
+  @queue :background
+
+  alias Pleroma.Web.OAuth.Token
+
+  def start_link, do: GenServer.start_link(__MODULE__, nil)
+
+  def init(_) do
+    if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do
+      Process.send_after(self(), :perform, @start_interval)
+      {:ok, nil}
+    else
+      :ignore
+    end
+  end
+
+  @doc false
+  def handle_info(:perform, state) do
+    Process.send_after(self(), :perform, @interval)
+    PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean])
+    {:noreply, state}
+  end
+
+  # Job Worker Callbacks
+  def perform(:clean), do: Token.delete_expired_tokens()
+end
diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex
new file mode 100644 (file)
index 0000000..d92e1f0
--- /dev/null
@@ -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.Web.OAuth.Token.Query do
+  @moduledoc """
+  Contains queries for OAuth Token.
+  """
+
+  import Ecto.Query, only: [from: 2]
+
+  @type query :: Ecto.Queryable.t() | Token.t()
+
+  alias Pleroma.Web.OAuth.Token
+
+  @spec get_by_refresh_token(query, String.t()) :: query
+  def get_by_refresh_token(query \\ Token, refresh_token) do
+    from(q in query, where: q.refresh_token == ^refresh_token)
+  end
+
+  @spec get_by_token(query, String.t()) :: query
+  def get_by_token(query \\ Token, token) do
+    from(q in query, where: q.token == ^token)
+  end
+
+  @spec get_by_app(query, String.t()) :: query
+  def get_by_app(query \\ Token, app_id) do
+    from(q in query, where: q.app_id == ^app_id)
+  end
+
+  @spec get_by_id(query, String.t()) :: query
+  def get_by_id(query \\ Token, id) do
+    from(q in query, where: q.id == ^id)
+  end
+
+  @spec get_expired_tokens(query, DateTime.t() | nil) :: query
+  def get_expired_tokens(query \\ Token, date \\ nil) do
+    expired_date = date || Timex.now()
+    from(q in query, where: fragment("?", q.valid_until) < ^expired_date)
+  end
+
+  @spec get_by_user(query, String.t()) :: query
+  def get_by_user(query \\ Token, user_id) do
+    from(q in query, where: q.user_id == ^user_id)
+  end
+
+  @spec preload(query, any) :: query
+  def preload(query \\ Token, assoc_preload \\ [])
+
+  def preload(query, assoc_preload) when is_list(assoc_preload) do
+    from(q in query, preload: ^assoc_preload)
+  end
+
+  def preload(query, _assoc_preload), do: query
+end
index 61515b31eb80ff7d6262e445ae14a72e07e25a29..6ed089d8458f6e51aacf0ca3da25462559340faf 100644 (file)
@@ -3,13 +3,12 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.OStatus do
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
   import Ecto.Query
   import Pleroma.Web.XML
   require Logger
 
   alias Pleroma.Activity
+  alias Pleroma.HTTP
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
@@ -363,7 +362,7 @@ defmodule Pleroma.Web.OStatus do
   def fetch_activity_from_atom_url(url) do
     with true <- String.starts_with?(url, "http"),
          {:ok, %{body: body, status: code}} when code in 200..299 <-
-           @httpoison.get(
+           HTTP.get(
              url,
              [{:Accept, "application/atom+xml"}]
            ) do
@@ -380,7 +379,7 @@ defmodule Pleroma.Web.OStatus do
     Logger.debug("Trying to fetch #{url}")
 
     with true <- String.starts_with?(url, "http"),
-         {:ok, %{body: body}} <- @httpoison.get(url, []),
+         {:ok, %{body: body}} <- HTTP.get(url, []),
          {:ok, atom_url} <- get_atom_url(body) do
       fetch_activity_from_atom_url(atom_url)
     else
index 0162a5be9d99c9093ce0f9e108b8b4961bbd6da5..9bc8f2559b5d5d6c78174283bee33f156ffd7d4c 100644 (file)
@@ -24,6 +24,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
   def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
     with true <- Pleroma.Config.get([:rich_media, :enabled]),
          %Object{} = object <- Object.normalize(activity),
+         false <- object.data["sensitive"] || false,
          {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
          :ok <- validate_page_url(page_url),
          {:ok, rich_media} <- Parser.parse(page_url) do
index 62e8fa610bbf6937877eae6c26071aea41013c6e..e4595800c2f04afebbb40b381d5a28aa2eaf9b8a 100644 (file)
@@ -37,7 +37,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
     try do
       {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
 
-      html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
+      html
+      |> maybe_parse()
+      |> clean_parsed_data()
+      |> check_parsed_data()
     rescue
       e ->
         {:error, "Parsing error: #{inspect(e)}"}
index bbc2fda9bea670f557aa7ff0597c65518cc91af6..eb3ee03f30ffda19ab09c7f27e5594bb558a7926 100644 (file)
@@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do
 
     get("/users", AdminAPIController, :list_users)
     get("/users/:nickname", AdminAPIController, :user_show)
+
+    get("/reports", AdminAPIController, :list_reports)
+    get("/reports/:id", AdminAPIController, :report_show)
+    put("/reports/:id", AdminAPIController, :report_update_state)
+    post("/reports/:id/respond", AdminAPIController, :report_respond)
+
+    put("/statuses/:id", AdminAPIController, :status_update)
+    delete("/statuses/:id", AdminAPIController, :status_delete)
   end
 
   scope "/", Pleroma.Web.TwitterAPI do
@@ -344,6 +352,9 @@ defmodule Pleroma.Web.Router do
 
       post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
 
+      get("/pleroma/mascot", MastodonAPIController, :get_mascot)
+      put("/pleroma/mascot", MastodonAPIController, :set_mascot)
+
       post("/reports", MastodonAPIController, :reports)
     end
 
@@ -696,9 +707,15 @@ defmodule Pleroma.Web.Router do
     end
   end
 
+  scope "/", Pleroma.Web.MongooseIM do
+    get("/user_exists", MongooseIMController, :user_exists)
+    get("/check_password", MongooseIMController, :check_password)
+  end
+
   scope "/", Fallback do
     get("/registration/:token", RedirectController, :registration_page)
     get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
+    get("/api*path", RedirectController, :api_not_implemented)
     get("/*path", RedirectController, :redirector)
 
     options("/*path", RedirectController, :empty)
@@ -710,6 +727,12 @@ defmodule Fallback.RedirectController do
   alias Pleroma.User
   alias Pleroma.Web.Metadata
 
+  def api_not_implemented(conn, _params) do
+    conn
+    |> put_status(404)
+    |> json(%{error: "Not implemented"})
+  end
+
   def redirector(conn, _params, code \\ 200) do
     conn
     |> put_resp_content_type("text/html")
index 42709ab47251531d594cdf7938b68d089a7a729e..9e91a5a4061e9b769d3c90f0eb670d5ae3899442 100644 (file)
@@ -5,12 +5,12 @@
 defmodule Pleroma.Web.Salmon do
   @behaviour Pleroma.Web.Federator.Publisher
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
   use Bitwise
 
   alias Pleroma.Activity
+  alias Pleroma.HTTP
   alias Pleroma.Instances
+  alias Pleroma.Keys
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator.Publisher
@@ -89,45 +89,6 @@ defmodule Pleroma.Web.Salmon do
     "RSA.#{modulus_enc}.#{exponent_enc}"
   end
 
-  # Native generation of RSA keys is only available since OTP 20+ and in default build conditions
-  # We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
-  try do
-    _ = :public_key.generate_key({:rsa, 2048, 65_537})
-
-    def generate_rsa_pem do
-      key = :public_key.generate_key({:rsa, 2048, 65_537})
-      entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
-      pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
-      {:ok, pem}
-    end
-  rescue
-    _ ->
-      def generate_rsa_pem do
-        port = Port.open({:spawn, "openssl genrsa"}, [:binary])
-
-        {:ok, pem} =
-          receive do
-            {^port, {:data, pem}} -> {:ok, pem}
-          end
-
-        Port.close(port)
-
-        if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
-          {:ok, pem}
-        else
-          :error
-        end
-      end
-  end
-
-  def keys_from_pem(pem) do
-    [private_key_code] = :public_key.pem_decode(pem)
-    private_key = :public_key.pem_entry_decode(private_key_code)
-    {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
-    public_key = {:RSAPublicKey, modulus, exponent}
-    {:ok, private_key, public_key}
-  end
-
   def encode(private_key, doc) do
     type = "application/atom+xml"
     encoding = "base64url"
@@ -176,7 +137,7 @@ defmodule Pleroma.Web.Salmon do
 
   def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
     with {:ok, %{status: code}} when code in 200..299 <-
-           @httpoison.post(
+           HTTP.post(
              url,
              feed,
              [{"Content-Type", "application/magic-envelope+xml"}]
@@ -227,7 +188,7 @@ defmodule Pleroma.Web.Salmon do
         |> :xmerl.export_simple(:xmerl_xml)
         |> to_string
 
-      {:ok, private, _} = keys_from_pem(keys)
+      {:ok, private, _} = Keys.keys_from_pem(keys)
       {:ok, feed} = encode(private, feed)
 
       remote_users = remote_users(activity)
@@ -253,7 +214,7 @@ defmodule Pleroma.Web.Salmon do
   def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
 
   def gather_webfinger_links(%User{} = user) do
-    {:ok, _private, public} = keys_from_pem(user.info.keys)
+    {:ok, _private, public} = Keys.keys_from_pem(user.info.keys)
     magic_key = encode_key(public)
 
     [
index 3389c91cce11160ea89a8eec65f0100e7954f472..85ec4d76cd1a706a52e185d094b8dc289a519c09 100644 (file)
@@ -4,7 +4,7 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
     <title>
-    <%= Application.get_env(:pleroma, :instance)[:name] %>
+    <%= Pleroma.Config.get([:instance, :name]) %>
     </title>
     <style>
       body {
   </head>
   <body>
     <div class="container">
-      <h1><%= Application.get_env(:pleroma, :instance)[:name] %></h1>
+      <h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
       <%= render @view_module, @view_template, assigns %>
     </div>
   </body>
index 5659c78280695530a52750f9dd5a05216273811d..ac63811d1bb224a4435fcc0a835f1e984e90f92c 100644 (file)
@@ -4,7 +4,7 @@
 <meta charset='utf-8'>
 <meta content='width=device-width, initial-scale=1' name='viewport'>
 <title>
-<%= Application.get_env(:pleroma, :instance)[:name] %>
+<%= Pleroma.Config.get([:instance, :name]) %>
 </title>
 <link rel="icon" type="image/png" href="/favicon.png"/>
 <script crossorigin='anonymous' src="/packs/locales.js"></script>
index 3c5a70be99308e36df2ef524d21d58aa8c160a8a..1b6b33e6973eebcc1b507cfee631e340156c9c37 100644 (file)
@@ -101,9 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
       |> Map.put("blocking_user", user)
       |> Map.put("user", user)
 
-    activities =
-      ActivityPub.fetch_activities([user.ap_id | user.following], params)
-      |> ActivityPub.contain_timeline(user)
+    activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
 
     conn
     |> put_view(ActivityView)
@@ -730,7 +728,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
 
   def only_if_public_instance(conn, _) do
-    if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
+    if Pleroma.Config.get([:instance, :public]) do
       conn
     else
       conn
index 44bcafe0e2e35f98d5017ae6706a10abccfa9082..e84af84dc6932a01f4d353f4f122e910812753a8 100644 (file)
@@ -284,6 +284,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
         Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
       )
 
+    thread_muted? =
+      case activity.thread_muted? do
+        thread_muted? when is_boolean(thread_muted?) -> thread_muted?
+        nil -> CommonAPI.thread_muted?(user, activity)
+      end
+
     %{
       "id" => activity.id,
       "uri" => object.data["id"],
@@ -314,7 +320,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
       "summary" => summary,
       "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
       "card" => card,
-      "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user)
+      "muted" => thread_muted? || User.mutes?(opts[:for], user)
     }
   end
 
index 3a3b98a1052723735ed6e43d892ee7c2a2f1f605..3fca72de84f9806eddf5547638a2503a46390fc1 100644 (file)
@@ -3,12 +3,10 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.WebFinger do
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
+  alias Pleroma.HTTP
   alias Pleroma.User
   alias Pleroma.Web
   alias Pleroma.Web.Federator.Publisher
-  alias Pleroma.Web.Salmon
   alias Pleroma.Web.XML
   alias Pleroma.XmlBuilder
   require Jason
@@ -61,7 +59,7 @@ defmodule Pleroma.Web.WebFinger do
   end
 
   def represent_user(user, "JSON") do
-    {:ok, user} = ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     %{
       "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
@@ -71,7 +69,7 @@ defmodule Pleroma.Web.WebFinger do
   end
 
   def represent_user(user, "XML") do
-    {:ok, user} = ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     links =
       gather_links(user)
@@ -88,27 +86,6 @@ defmodule Pleroma.Web.WebFinger do
     |> XmlBuilder.to_doc()
   end
 
-  # This seems a better fit in Salmon
-  def ensure_keys_present(user) do
-    info = user.info
-
-    if info.keys do
-      {:ok, user}
-    else
-      {:ok, pem} = Salmon.generate_rsa_pem()
-
-      info_cng =
-        info
-        |> Pleroma.User.Info.set_keys(pem)
-
-      cng =
-        Ecto.Changeset.change(user)
-        |> Ecto.Changeset.put_embed(:info, info_cng)
-
-      User.update_and_set_cache(cng)
-    end
-  end
-
   defp get_magic_key(magic_key) do
     "data:application/magic-public-key," <> magic_key = magic_key
     {:ok, magic_key}
@@ -198,11 +175,11 @@ defmodule Pleroma.Web.WebFinger do
 
   def find_lrdd_template(domain) do
     with {:ok, %{status: status, body: body}} when status in 200..299 <-
-           @httpoison.get("http://#{domain}/.well-known/host-meta", []) do
+           HTTP.get("http://#{domain}/.well-known/host-meta", []) do
       get_template_from_xml(body)
     else
       _ ->
-        with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do
+        with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do
           get_template_from_xml(body)
         else
           e -> {:error, "Can't find LRDD template: #{inspect(e)}"}
@@ -231,7 +208,7 @@ defmodule Pleroma.Web.WebFinger do
       end
 
     with response <-
-           @httpoison.get(
+           HTTP.get(
              address,
              Accept: "application/xrd+xml,application/jrd+json"
            ),
index 7ad0414ab7db1b1516aa38e1f05f966264763c98..b61f388b87b43dad7d4dd4367fda1952d9b11197 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.Websub do
   alias Ecto.Changeset
   alias Pleroma.Activity
+  alias Pleroma.HTTP
   alias Pleroma.Instances
   alias Pleroma.Repo
   alias Pleroma.User
@@ -24,9 +25,7 @@ defmodule Pleroma.Web.Websub do
 
   @behaviour Pleroma.Web.Federator.Publisher
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
-  def verify(subscription, getter \\ &@httpoison.get/3) do
+  def verify(subscription, getter \\ &HTTP.get/3) do
     challenge = Base.encode16(:crypto.strong_rand_bytes(8))
     lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at)
     lease_seconds = lease_seconds |> to_string
@@ -207,7 +206,7 @@ defmodule Pleroma.Web.Websub do
     requester.(subscription)
   end
 
-  def gather_feed_data(topic, getter \\ &@httpoison.get/1) do
+  def gather_feed_data(topic, getter \\ &HTTP.get/1) do
     with {:ok, response} <- getter.(topic),
          status when status in 200..299 <- response.status,
          body <- response.body,
@@ -236,7 +235,7 @@ defmodule Pleroma.Web.Websub do
     end
   end
 
-  def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000) do
+  def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do
     data = [
       "hub.mode": "subscribe",
       "hub.topic": websub.topic,
@@ -294,7 +293,7 @@ defmodule Pleroma.Web.Websub do
     Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
 
     with {:ok, %{status: code}} when code in 200..299 <-
-           @httpoison.post(
+           HTTP.post(
              callback,
              xml,
              [
diff --git a/mix.exs b/mix.exs
index 1396d0072ca477d53895b131bd78b5b8b6840f4e..b2017ef9bff5ca9006210ff16c949b81febd5cf3 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -42,7 +42,7 @@ defmodule Pleroma.Mixfile do
   def application do
     [
       mod: {Pleroma.Application, []},
-      extra_applications: [:logger, :runtime_tools, :comeonin, :esshd, :quack],
+      extra_applications: [:logger, :runtime_tools, :comeonin, :quack],
       included_applications: [:ex_syslogger]
     ]
   end
@@ -66,7 +66,7 @@ defmodule Pleroma.Mixfile do
       {:plug_cowboy, "~> 2.0"},
       {:phoenix_pubsub, "~> 1.1"},
       {:phoenix_ecto, "~> 4.0"},
-      {:ecto_sql, "~>3.0.5"},
+      {:ecto_sql, "~> 3.1"},
       {:postgrex, ">= 0.13.5"},
       {:gettext, "~> 0.15"},
       {:comeonin, "~> 4.1.1"},
@@ -117,7 +117,7 @@ defmodule Pleroma.Mixfile do
       {:recon, github: "ferd/recon", tag: "2.4.0"},
       {:quack, "~> 0.1.1"},
       {:benchee, "~> 1.0"},
-      {:esshd, "~> 0.1.0"},
+      {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
       {:ex_rated, "~> 1.2"},
       {:plug_static_index_html, "~> 1.0.0"},
       {:excoveralls, "~> 0.11.1", only: :test}
index cd2d2370a042d14ad4758fffa2d32ce4b97acdb7..857bfca790090ae0acc9ac798ed28643ba3febe8 100644 (file)
--- a/mix.lock
+++ b/mix.lock
   "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"},
+  "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [: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"},
+  "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+  "ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.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"},
@@ -66,7 +66,7 @@
   "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"},
+  "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [: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"},
   "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"},
+  "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [: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"},
+  "tzdata": {:hex, :tzdata, "0.5.20", "304b9e98a02840fb32a43ec111ffbe517863c8566eb04a061f1c4dbb90b4d84c", [: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"},
diff --git a/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs b/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs
new file mode 100644 (file)
index 0000000..0d3d253
--- /dev/null
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetDefaultStateToReports do
+  use Ecto.Migration
+
+  def up do
+    execute """
+      UPDATE activities AS a
+      SET data = jsonb_set(data, '{state}', '"open"', true)
+      WHERE data->>'type' = 'Flag'
+    """
+  end
+
+  def down do
+    execute """
+      UPDATE activities AS a
+      SET data = data #- '{state}'
+      WHERE data->>'type' = 'Flag'
+    """
+  end
+end
diff --git a/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs b/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
new file mode 100644 (file)
index 0000000..dc9abc9
--- /dev/null
@@ -0,0 +1,73 @@
+defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
+  use Ecto.Migration
+  @disable_ddl_transaction true
+
+  def up do
+    statement = """
+    CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
+    DECLARE
+      public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+      child objects%ROWTYPE;
+      activity activities%ROWTYPE;
+      actor_user users%ROWTYPE;
+      author_fa varchar;
+      valid_recipients varchar[];
+    BEGIN
+      --- Fetch our actor.
+      SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
+
+      --- Fetch our initial activity.
+      SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
+
+      LOOP
+        --- Ensure that we have an activity before continuing.
+        --- If we don't, the thread is not satisfiable.
+        IF activity IS NULL THEN
+          RETURN false;
+        END IF;
+
+        --- We only care about Create activities.
+        IF activity.data->>'type' != 'Create' THEN
+          RETURN true;
+        END IF;
+
+        --- Normalize the child object into child.
+        SELECT * INTO child FROM objects
+        INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+        WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
+
+        --- Fetch the author's AS2 following collection.
+        SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
+
+        --- Prepare valid recipients array.
+        valid_recipients := ARRAY[actor, public];
+        IF ARRAY[author_fa] && actor_user.following THEN
+          valid_recipients := valid_recipients || author_fa;
+        END IF;
+
+        --- Check visibility.
+        IF NOT valid_recipients && activity.recipients THEN
+          --- activity not visible, break out of the loop
+          RETURN false;
+        END IF;
+
+        --- If there's a parent, load it and do this all over again.
+        IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
+          SELECT * INTO activity FROM activities
+          INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+          WHERE child.data->>'inReplyTo' = objects.data->>'id';
+        ELSE
+          RETURN true;
+        END IF;
+      END LOOP;
+    END;
+    $$ LANGUAGE plpgsql IMMUTABLE;
+    """
+
+    execute(statement)
+  end
+
+  def down do
+    execute("drop function thread_visibility(actor varchar, activity_id varchar)")
+  end
+end
diff --git a/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs b/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
new file mode 100644 (file)
index 0000000..b6a2444
--- /dev/null
@@ -0,0 +1,34 @@
+defmodule Pleroma.Repo.Migrations.AddFtsIndexToObjectsTwo do
+  use Ecto.Migration
+
+  def up do
+    execute("create extension if not exists rum")
+    drop_if_exists index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
+    alter table(:objects) do
+      add(:fts_content, :tsvector)
+    end
+
+    execute("CREATE FUNCTION objects_fts_update() RETURNS trigger AS $$
+    begin
+      new.fts_content := to_tsvector('english', new.data->>'content');
+      return new;
+    end
+    $$ LANGUAGE plpgsql")
+    execute("create index objects_fts on objects using RUM (fts_content rum_tsvector_addon_ops, inserted_at) with (attach = 'inserted_at', to = 'fts_content');")
+
+    execute("CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON objects
+    FOR EACH ROW EXECUTE PROCEDURE objects_fts_update()")
+
+    execute("UPDATE objects SET updated_at = NOW()")
+  end
+
+  def down do
+    execute "drop index objects_fts"
+    execute "drop trigger tsvectorupdate on objects"
+    execute "drop function objects_fts_update()"
+    alter table(:objects) do
+      remove(:fts_content, :tsvector)
+    end
+    create index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
+  end
+end
index 7e91d534b4b391d2654621a3dc27cf1c3a3152c5..15c95502ab4af78beae377dac861439a57977e0d 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.ActivityTest do
   use Pleroma.DataCase
   alias Pleroma.Activity
   alias Pleroma.Bookmark
+  alias Pleroma.ThreadMute
   import Pleroma.Factory
 
   test "returns an activity by it's AP id" do
@@ -47,6 +48,31 @@ defmodule Pleroma.ActivityTest do
     assert queried_activity.bookmark == bookmark3
   end
 
+  test "setting thread_muted?" do
+    activity = insert(:note_activity)
+    user = insert(:user)
+    annoyed_user = insert(:user)
+    {:ok, _} = ThreadMute.add_mute(annoyed_user.id, activity.data["context"])
+
+    activity_with_unset_thread_muted_field =
+      Ecto.Query.from(Activity)
+      |> Repo.one()
+
+    activity_for_user =
+      Ecto.Query.from(Activity)
+      |> Activity.with_set_thread_muted_field(user)
+      |> Repo.one()
+
+    activity_for_annoyed_user =
+      Ecto.Query.from(Activity)
+      |> Activity.with_set_thread_muted_field(annoyed_user)
+      |> Repo.one()
+
+    assert activity_with_unset_thread_muted_field.thread_muted? == nil
+    assert activity_for_user.thread_muted? == false
+    assert activity_for_annoyed_user.thread_muted? == true
+  end
+
   describe "getting a bookmark" do
     test "when association is loaded" do
       user = insert(:user)
index 864b2eb03066ec28f3244f6d6626f63fcd89a040..5903d10ff05b1ffd94b4d4594952a1b393283b6f 100644 (file)
@@ -11,6 +11,26 @@ defmodule Pleroma.ConversationTest do
 
   import Pleroma.Factory
 
+  test "it goes through old direct conversations" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, _activity} =
+      CommonAPI.post(user, %{"visibility" => "direct", "status" => "hey @#{other_user.nickname}"})
+
+    Repo.delete_all(Conversation)
+    Repo.delete_all(Conversation.Participation)
+
+    refute Repo.one(Conversation)
+
+    Conversation.bump_for_all_activities()
+
+    assert Repo.one(Conversation)
+    [participation, _p2] = Repo.all(Conversation.Participation)
+
+    assert participation.read
+  end
+
   test "it creates a conversation for given ap_id" do
     assert {:ok, %Conversation{} = conversation} =
              Conversation.create_for_ap_id("https://some_ap_id")
diff --git a/test/fixtures/rich_media/ogp-missing-data.html b/test/fixtures/rich_media/ogp-missing-data.html
new file mode 100644 (file)
index 0000000..5746dc2
--- /dev/null
@@ -0,0 +1,8 @@
+<html prefix="og: http://ogp.me/ns#">
+  <head>
+    <title>Pleroma</title>
+    <meta property="og:title" content="Pleroma" />
+    <meta property="og:type" content="website" />
+    <meta property="og:url" content="https://pleroma.social/" />
+  </head>
+</html>
index c886b5871d49a6d423df629a36b3e6dce1f8d41b..4b5a33595a005dbe8f703dec377aa162fc6033b4 100644 (file)
@@ -5,5 +5,6 @@
     <meta property="og:type" content="video.movie" />
     <meta property="og:url" content="http://www.imdb.com/title/tt0117500/" />
     <meta property="og:image" content="http://ia.media-imdb.com/images/rock.jpg" />
+    <meta property="og:description" content="Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.">
   </head>
 </html>
diff --git a/test/fixtures/sound.mp3 b/test/fixtures/sound.mp3
new file mode 100644 (file)
index 0000000..9f0f661
Binary files /dev/null and b/test/fixtures/sound.mp3 differ
index 06f4f6e50d57241d349b17c2805ab7d8860646c1..bfa673049843bcc779d249cd6dc16925eb4ac04f 100644 (file)
@@ -125,7 +125,7 @@ defmodule Pleroma.FormatterTest do
       archaeme =
         insert(:user, %{
           nickname: "archa_eme_",
-          info: %Pleroma.User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
+          info: %User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
         })
 
       archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
@@ -184,17 +184,19 @@ defmodule Pleroma.FormatterTest do
 
     test "given the 'safe_mention' option, it will only mention people in the beginning" do
       user = insert(:user)
-      _other_user = insert(:user)
+      other_user = insert(:user)
       third_user = insert(:user)
-      text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}"
+      text = " @#{user.nickname} @#{other_user.nickname} hey dudes i hate @#{third_user.nickname}"
       {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
 
-      assert mentions == [{"@#{user.nickname}", user}]
+      assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
 
       assert expected_text ==
                "<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{
                  user.ap_id
-               }'>@<span>#{user.nickname}</span></a></span> hey dude i hate <span class='h-card'><a data-user='#{
+               }'>@<span>#{user.nickname}</span></a></span> <span class='h-card'><a data-user='#{
+                 other_user.id
+               }' class='u-url mention' href='#{other_user.ap_id}'>@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class='h-card'><a data-user='#{
                  third_user.id
                }' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>"
     end
@@ -206,6 +208,15 @@ defmodule Pleroma.FormatterTest do
       assert mentions == []
       assert expected_text == text
     end
+
+    test "given the 'safe_mention' option, it will keep text after newlines" do
+      user = insert(:user)
+      text = " @#{user.nickname}\n hey dude\n\nhow are you doing?"
+
+      {expected_text, _, _} = Formatter.linkify(text, safe_mention: true)
+
+      assert expected_text =~ "how are you doing?"
+    end
   end
 
   describe ".parse_tags" do
diff --git a/test/keys_test.exs b/test/keys_test.exs
new file mode 100644 (file)
index 0000000..776fdea
--- /dev/null
@@ -0,0 +1,20 @@
+defmodule Pleroma.KeysTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Keys
+
+  test "generates an RSA private key pem" do
+    {:ok, key} = Keys.generate_rsa_pem()
+
+    assert is_binary(key)
+    assert Regex.match?(~r/RSA/, key)
+  end
+
+  test "returns a public and private key from a pem" do
+    pem = File.read!("test/fixtures/private_key.pem")
+    {:ok, private, public} = Keys.keys_from_pem(pem)
+
+    assert elem(private, 0) == :RSAPrivateKey
+    assert elem(public, 0) == :RSAPublicKey
+  end
+end
index 72f61678291d02deb61f6ccc8e8d87461fadae7c..d604fd5f59f42e1528e8df22d10cd63b5fd68c39 100644 (file)
@@ -87,4 +87,23 @@ defmodule Pleroma.Object.FetcherTest do
         )
     end
   end
+
+  describe "pruning" do
+    test "it can refetch pruned objects" do
+      object_id = "http://mastodon.example.org/@admin/99541947525187367"
+
+      {:ok, object} = Fetcher.fetch_object_from_id(object_id)
+
+      assert object
+
+      {:ok, _object} = Object.prune(object)
+
+      refute Object.get_by_ap_id(object_id)
+
+      {:ok, %Object{} = object_two} = Fetcher.fetch_object_from_id(object_id)
+
+      assert object.data["id"] == object_two.data["id"]
+      assert object.id != object_two.id
+    end
+  end
 end
diff --git a/test/plugs/cache_control_test.exs b/test/plugs/cache_control_test.exs
new file mode 100644 (file)
index 0000000..45151b2
--- /dev/null
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.CacheControlTest do
+  use Pleroma.Web.ConnCase
+  alias Plug.Conn
+
+  test "Verify Cache-Control header on static assets", %{conn: conn} do
+    conn = get(conn, "/index.html")
+
+    assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
+  end
+
+  test "Verify Cache-Control header on the API", %{conn: conn} do
+    conn = get(conn, "/api/v1/instance")
+
+    assert Conn.get_resp_header(conn, "cache-control") == ["max-age=0, private, must-revalidate"]
+  end
+end
index 0cbb7e4b11fcfd6f57bb752ad583c840ad7c91a0..7dfd50c1febd9a6a6f5ace616c853725bb16cf39 100644 (file)
@@ -7,77 +7,96 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
   alias Pleroma.Config
   alias Plug.Conn
 
-  test "it sends CSP headers when enabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-
-    conn =
-      conn
-      |> get("/api/v1/instance")
-
-    refute Conn.get_resp_header(conn, "x-xss-protection") == []
-    refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
-    refute Conn.get_resp_header(conn, "x-frame-options") == []
-    refute Conn.get_resp_header(conn, "x-content-type-options") == []
-    refute Conn.get_resp_header(conn, "x-download-options") == []
-    refute Conn.get_resp_header(conn, "referrer-policy") == []
-    refute Conn.get_resp_header(conn, "content-security-policy") == []
-  end
+  describe "http security enabled" do
+    setup do
+      enabled = Config.get([:http_securiy, :enabled])
 
-  test "it does not send CSP headers when disabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], false)
+      Config.put([:http_security, :enabled], true)
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      on_exit(fn ->
+        Config.put([:http_security, :enabled], enabled)
+      end)
 
-    assert Conn.get_resp_header(conn, "x-xss-protection") == []
-    assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
-    assert Conn.get_resp_header(conn, "x-frame-options") == []
-    assert Conn.get_resp_header(conn, "x-content-type-options") == []
-    assert Conn.get_resp_header(conn, "x-download-options") == []
-    assert Conn.get_resp_header(conn, "referrer-policy") == []
-    assert Conn.get_resp_header(conn, "content-security-policy") == []
-  end
+      :ok
+    end
 
-  test "it sends STS headers when enabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-    Config.put([:http_security, :sts], true)
+    test "it sends CSP headers when enabled", %{conn: conn} do
+      conn = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      refute Conn.get_resp_header(conn, "x-xss-protection") == []
+      refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+      refute Conn.get_resp_header(conn, "x-frame-options") == []
+      refute Conn.get_resp_header(conn, "x-content-type-options") == []
+      refute Conn.get_resp_header(conn, "x-download-options") == []
+      refute Conn.get_resp_header(conn, "referrer-policy") == []
+      refute Conn.get_resp_header(conn, "content-security-policy") == []
+    end
 
-    refute Conn.get_resp_header(conn, "strict-transport-security") == []
-    refute Conn.get_resp_header(conn, "expect-ct") == []
-  end
+    test "it sends STS headers when enabled", %{conn: conn} do
+      Config.put([:http_security, :sts], true)
 
-  test "it does not send STS headers when disabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-    Config.put([:http_security, :sts], false)
+      conn = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      refute Conn.get_resp_header(conn, "strict-transport-security") == []
+      refute Conn.get_resp_header(conn, "expect-ct") == []
+    end
 
-    assert Conn.get_resp_header(conn, "strict-transport-security") == []
-    assert Conn.get_resp_header(conn, "expect-ct") == []
-  end
+    test "it does not send STS headers when disabled", %{conn: conn} do
+      Config.put([:http_security, :sts], false)
+
+      conn = get(conn, "/api/v1/instance")
+
+      assert Conn.get_resp_header(conn, "strict-transport-security") == []
+      assert Conn.get_resp_header(conn, "expect-ct") == []
+    end
+
+    test "referrer-policy header reflects configured value", %{conn: conn} do
+      conn = get(conn, "/api/v1/instance")
+
+      assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
 
-  test "referrer-policy header reflects configured value", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
+      Config.put([:http_security, :referrer_policy], "no-referrer")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
+      assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+    end
 
-    Config.put([:http_security, :referrer_policy], "no-referrer")
+    test "it sends `report-to` & `report-uri` CSP response headers" do
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    conn =
-      build_conn()
-      |> get("/api/v1/instance")
+      [csp] = Conn.get_resp_header(conn, "content-security-policy")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+      assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
+
+      [reply_to] = Conn.get_resp_header(conn, "reply-to")
+
+      assert reply_to ==
+               "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
+    end
+  end
+
+  test "it does not send CSP headers when disabled", %{conn: conn} do
+    enabled = Config.get([:http_securiy, :enabled])
+
+    Config.put([:http_security, :enabled], false)
+
+    on_exit(fn ->
+      Config.put([:http_security, :enabled], enabled)
+    end)
+
+    conn = get(conn, "/api/v1/instance")
+
+    assert Conn.get_resp_header(conn, "x-xss-protection") == []
+    assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+    assert Conn.get_resp_header(conn, "x-frame-options") == []
+    assert Conn.get_resp_header(conn, "x-content-type-options") == []
+    assert Conn.get_resp_header(conn, "x-download-options") == []
+    assert Conn.get_resp_header(conn, "referrer-policy") == []
+    assert Conn.get_resp_header(conn, "content-security-policy") == []
   end
 end
index 8b0b06772a6273dce9e75781ce3f7e92f3b28e27..02f5300583d7d48b101e904b1892f9e2ff81a9e4 100644 (file)
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
-  use Pleroma.Web.ConnCase, async: true
+  use Pleroma.Web.ConnCase
 
   alias Pleroma.Plugs.LegacyAuthenticationPlug
   alias Pleroma.User
index 5382289c7cc717003e54d424ee991e1fc49ed84e..85085a1fa0c2ab7473a9b830dfcdcef9fb350c30 100644 (file)
@@ -1,23 +1,24 @@
 defmodule Pleroma.RepoTest do
   use Pleroma.DataCase
   import Pleroma.Factory
+  alias Pleroma.User
 
   describe "find_resource/1" do
     test "returns user" do
       user = insert(:user)
-      query = from(t in Pleroma.User, where: t.id == ^user.id)
+      query = from(t in User, where: t.id == ^user.id)
       assert Repo.find_resource(query) == {:ok, user}
     end
 
     test "returns not_found" do
-      query = from(t in Pleroma.User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
+      query = from(t in User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
       assert Repo.find_resource(query) == {:error, :not_found}
     end
   end
 
   describe "get_assoc/2" do
     test "get assoc from preloaded data" do
-      user = %Pleroma.User{name: "Agent Smith"}
+      user = %User{name: "Agent Smith"}
       token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
       assert Repo.get_assoc(token, :user) == {:ok, user}
     end
index 2a2954ad615ea0993b1479f3f81b6c697bfdc267..be6247ca4735567e40c143d2abbb7475d18c8596 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Factory do
   use ExMachina.Ecto, repo: Pleroma.Repo
+  alias Pleroma.User
 
   def participation_factory do
     conversation = insert(:conversation)
@@ -23,7 +24,7 @@ defmodule Pleroma.Factory do
   end
 
   def user_factory do
-    user = %Pleroma.User{
+    user = %User{
       name: sequence(:name, &"Test テスト User #{&1}"),
       email: sequence(:email, &"user#{&1}@example.com"),
       nickname: sequence(:nickname, &"nick#{&1}"),
@@ -34,16 +35,16 @@ defmodule Pleroma.Factory do
 
     %{
       user
-      | ap_id: Pleroma.User.ap_id(user),
-        follower_address: Pleroma.User.ap_followers(user),
-        following: [Pleroma.User.ap_id(user)]
+      | ap_id: User.ap_id(user),
+        follower_address: User.ap_followers(user),
+        following: [User.ap_id(user)]
     }
   end
 
   def note_factory(attrs \\ %{}) do
     text = sequence(:text, &"This is :moominmamma: note #{&1}")
 
-    user = insert(:user)
+    user = attrs[:user] || insert(:user)
 
     data = %{
       "type" => "Note",
@@ -113,7 +114,8 @@ defmodule Pleroma.Factory do
   end
 
   def note_activity_factory(attrs \\ %{}) do
-    note = attrs[:note] || insert(:note)
+    user = attrs[:user] || insert(:user)
+    note = attrs[:note] || insert(:note, user: user)
 
     data = %{
       "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
index 5b355bfe6088c8f87f214baa992116a88eed05e9..66d7d5ba920536d16c90a1261a7302773a34a080 100644 (file)
@@ -728,6 +728,14 @@ defmodule HttpRequestMock do
     {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
   end
 
+  def get("http://example.com/ogp-missing-data", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/rich_media/ogp-missing-data.html")
+     }}
+  end
+
   def get("http://example.com/malformed", _, _, _) do
     {:ok,
      %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}}
diff --git a/test/support/ostatus_mock.ex b/test/support/ostatus_mock.ex
deleted file mode 100644 (file)
index 9c0f2f3..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatusMock do
-  import Pleroma.Factory
-
-  def handle_incoming(_doc) do
-    insert(:note_activity)
-  end
-end
diff --git a/test/support/websub_mock.ex b/test/support/websub_mock.ex
deleted file mode 100644 (file)
index e3d5a5b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.WebsubMock do
-  def verify(sub) do
-    {:ok, sub}
-  end
-end
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
new file mode 100644 (file)
index 0000000..579130b
--- /dev/null
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.DatabaseTest do
+  alias Pleroma.Repo
+  alias Pleroma.User
+  use Pleroma.DataCase
+
+  import Pleroma.Factory
+
+  setup_all do
+    Mix.shell(Mix.Shell.Process)
+
+    on_exit(fn ->
+      Mix.shell(Mix.Shell.IO)
+    end)
+
+    :ok
+  end
+
+  describe "running update_users_following_followers_counts" do
+    test "following and followers count are updated" do
+      [user, user2] = insert_pair(:user)
+      {:ok, %User{following: following, info: info} = user} = User.follow(user, user2)
+
+      assert length(following) == 2
+      assert info.follower_count == 0
+
+      info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
+
+      {:ok, user} =
+        user
+        |> Ecto.Changeset.change(%{following: following ++ following})
+        |> Ecto.Changeset.put_embed(:info, info_cng)
+        |> Repo.update()
+
+      assert length(user.following) == 4
+      assert user.info.follower_count == 3
+
+      assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
+
+      user = User.get_by_id(user.id)
+
+      assert length(user.following) == 2
+      assert user.info.follower_count == 0
+    end
+  end
+end
index eaf4ecf8449bfe1881a1c99cae5d729d3f8c4e8d..260ce0d954973868afe0424425a62aaff538096c 100644 (file)
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Mix.Tasks.Pleroma.UserTest do
+  alias Pleroma.Repo
   alias Pleroma.User
   use Pleroma.DataCase
 
@@ -338,4 +339,31 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message == "User #{nickname} statuses deleted."
     end
   end
+
+  describe "running toggle_confirmed" do
+    test "user is confirmed" do
+      %{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
+
+      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+      assert_received {:mix_shell, :info, [message]}
+      assert message == "#{nickname} needs confirmation."
+
+      user = Repo.get(User, id)
+      assert user.info.confirmation_pending
+      assert user.info.confirmation_token
+    end
+
+    test "user is not confirmed" do
+      %{id: id, nickname: nickname} =
+        insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+
+      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+      assert_received {:mix_shell, :info, [message]}
+      assert message == "#{nickname} doesn't need confirmation."
+
+      user = Repo.get(User, id)
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
 end
index 0b65e89e9bdceaffe3aa86f2477935c5ac6a209a..019f2b56d1fb648aff531e5a4916211be5cdf3a1 100644 (file)
@@ -277,7 +277,7 @@ defmodule Pleroma.UserTest do
     end
 
     test "it restricts certain nicknames" do
-      [restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
+      [restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
 
       assert is_bitstring(restricted_name)
 
@@ -626,6 +626,37 @@ defmodule Pleroma.UserTest do
     end
   end
 
+  describe "remove duplicates from following list" do
+    test "it removes duplicates" do
+      user = insert(:user)
+      follower = insert(:user)
+
+      {:ok, %User{following: following} = follower} = User.follow(follower, user)
+      assert length(following) == 2
+
+      {:ok, follower} =
+        follower
+        |> User.update_changeset(%{following: following ++ following})
+        |> Repo.update()
+
+      assert length(follower.following) == 4
+
+      {:ok, follower} = User.remove_duplicated_following(follower)
+      assert length(follower.following) == 2
+    end
+
+    test "it does nothing when following is uniq" do
+      user = insert(:user)
+      follower = insert(:user)
+
+      {:ok, follower} = User.follow(follower, user)
+      assert length(follower.following) == 2
+
+      {:ok, follower} = User.remove_duplicated_following(follower)
+      assert length(follower.following) == 2
+    end
+  end
+
   describe "follow_import" do
     test "it imports user followings from list" do
       [user1, user2, user3] = insert_list(3, :user)
@@ -871,9 +902,8 @@ defmodule Pleroma.UserTest do
 
       assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
 
-      assert [activity] ==
+      assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
                ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
-               |> ActivityPub.contain_timeline(user2)
 
       {:ok, _user} = User.deactivate(user)
 
@@ -882,7 +912,6 @@ defmodule Pleroma.UserTest do
 
       assert [] ==
                ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
-               |> ActivityPub.contain_timeline(user2)
     end
   end
 
@@ -1194,14 +1223,47 @@ defmodule Pleroma.UserTest do
     follower2 = insert(:user)
     follower3 = insert(:user)
 
-    {:ok, follower} = Pleroma.User.follow(follower, user)
-    {:ok, _follower2} = Pleroma.User.follow(follower2, user)
-    {:ok, _follower3} = Pleroma.User.follow(follower3, user)
+    {:ok, follower} = User.follow(follower, user)
+    {:ok, _follower2} = User.follow(follower2, user)
+    {:ok, _follower3} = User.follow(follower3, user)
 
-    {:ok, _} = Pleroma.User.block(user, follower)
+    {:ok, _} = User.block(user, follower)
 
     user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
 
     assert Map.get(user_show, "followers_count") == 2
   end
+
+  describe "toggle_confirmation/1" do
+    test "if user is confirmed" do
+      user = insert(:user, info: %{confirmation_pending: false})
+      {:ok, user} = User.toggle_confirmation(user)
+
+      assert user.info.confirmation_pending
+      assert user.info.confirmation_token
+    end
+
+    test "if user is unconfirmed" do
+      user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+      {:ok, user} = User.toggle_confirmation(user)
+
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
+
+  describe "ensure_keys_present" do
+    test "it creates keys for a user and stores them in info" do
+      user = insert(:user)
+      refute is_binary(user.info.keys)
+      {:ok, user} = User.ensure_keys_present(user)
+      assert is_binary(user.info.keys)
+    end
+
+    test "it doesn't create keys if there already are some" do
+      user = insert(:user, %{info: %{keys: "xxx"}})
+      {:ok, user} = User.ensure_keys_present(user)
+      assert user.info.keys == "xxx"
+    end
+  end
 end
index 0f90aa1acb072585fed756c1f745536669d8de29..76586ee4ada3977ea2d6d5bb665568d7377f8814 100644 (file)
@@ -462,6 +462,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     refute Enum.member?(activities, activity_three.id)
   end
 
+  test "doesn't return activities from blocked domains" do
+    domain = "dogwhistle.zone"
+    domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
+    note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
+    activity = insert(:note_activity, %{note: note})
+    user = insert(:user)
+    {:ok, user} = User.block_domain(user, domain)
+
+    activities =
+      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+    refute activity in activities
+
+    followed_user = insert(:user)
+    ActivityPub.follow(user, followed_user)
+    {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
+
+    activities =
+      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+    refute repeat_activity in activities
+  end
+
   test "doesn't return muted activities" do
     activity_one = insert(:note_activity)
     activity_two = insert(:note_activity)
@@ -960,17 +983,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
           "in_reply_to_status_id" => private_activity_2.id
         })
 
-      activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
+      activities =
+        ActivityPub.fetch_activities([user1.ap_id | user1.following])
+        |> Enum.map(fn a -> a.id end)
 
       private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
 
-      assert [public_activity, private_activity_1, private_activity_3] == activities
+      assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
 
       assert length(activities) == 3
 
-      activities = ActivityPub.contain_timeline(activities, user1)
+      activities =
+        ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
+        |> Enum.map(fn a -> a.id end)
 
-      assert [public_activity, private_activity_1] == activities
+      assert [public_activity.id, private_activity_1.id] == activities
       assert length(activities) == 2
     end
   end
@@ -978,7 +1005,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
   describe "update" do
     test "it creates an update activity with the new user data" do
       user = insert(:user)
-      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+      {:ok, user} = User.ensure_keys_present(user)
       user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
 
       {:ok, update} =
@@ -1159,4 +1186,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
   def data_uri do
     File.read!("test/fixtures/avatar_data_uri")
   end
+
+  describe "fetch_activities_bounded" do
+    test "fetches private posts for followed users" do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "thought I looked cute might delete later :3",
+          "visibility" => "private"
+        })
+
+      [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
+      assert result.id == activity.id
+    end
+
+    test "fetches only public posts for other users" do
+      user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
+
+      {:ok, _private_activity} =
+        CommonAPI.post(user, %{
+          "status" => "why is tenshi eating a corndog so cute?",
+          "visibility" => "private"
+        })
+
+      [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
+      assert result.id == activity.id
+    end
+  end
 end
diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs
new file mode 100644 (file)
index 0000000..0fd68e1
--- /dev/null
@@ -0,0 +1,309 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+  alias Pleroma.Config
+  alias Pleroma.Web.ActivityPub.MRF.SimplePolicy
+
+  setup do
+    orig = Config.get!(:mrf_simple)
+
+    Config.put(:mrf_simple,
+      media_removal: [],
+      media_nsfw: [],
+      federated_timeline_removal: [],
+      report_removal: [],
+      reject: [],
+      accept: [],
+      avatar_removal: [],
+      banner_removal: []
+    )
+
+    on_exit(fn ->
+      Config.put(:mrf_simple, orig)
+    end)
+  end
+
+  describe "when :media_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :media_removal], [])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) == {:ok, media_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :media_removal], ["remote.instance"])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) ==
+               {:ok,
+                media_message
+                |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  describe "when :media_nsfw" do
+    test "is empty" do
+      Config.put([:mrf_simple, :media_nsfw], [])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) == {:ok, media_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :media_nsfw], ["remote.instance"])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) ==
+               {:ok,
+                media_message
+                |> put_in(["object", "tag"], ["foo", "nsfw"])
+                |> put_in(["object", "sensitive"], true)}
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  defp build_media_message do
+    %{
+      "actor" => "https://remote.instance/users/bob",
+      "type" => "Create",
+      "object" => %{
+        "attachment" => [%{}],
+        "tag" => ["foo"],
+        "sensitive" => false
+      }
+    }
+  end
+
+  describe "when :report_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :report_removal], [])
+      report_message = build_report_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(report_message) == {:ok, report_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :report_removal], ["remote.instance"])
+      report_message = build_report_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(report_message) == {:reject, nil}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  defp build_report_message do
+    %{
+      "actor" => "https://remote.instance/users/bob",
+      "type" => "Flag"
+    }
+  end
+
+  describe "when :federated_timeline_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :federated_timeline_removal], [])
+      {_, ftl_message} = build_ftl_actor_and_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      {actor, ftl_message} = build_ftl_actor_and_message()
+
+      ftl_message_actor_host =
+        ftl_message
+        |> Map.fetch!("actor")
+        |> URI.parse()
+        |> Map.fetch!(:host)
+
+      Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
+      local_message = build_local_message()
+
+      assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+      assert actor.follower_address in ftl_message["to"]
+      refute actor.follower_address in ftl_message["cc"]
+      refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+      assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host but only as:Public in to" do
+      {_actor, ftl_message} = build_ftl_actor_and_message()
+
+      ftl_message_actor_host =
+        ftl_message
+        |> Map.fetch!("actor")
+        |> URI.parse()
+        |> Map.fetch!(:host)
+
+      ftl_message = Map.put(ftl_message, "cc", [])
+
+      Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
+
+      assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+      refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+      assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+    end
+  end
+
+  defp build_ftl_actor_and_message do
+    actor = insert(:user)
+
+    {actor,
+     %{
+       "actor" => actor.ap_id,
+       "to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
+       "cc" => [actor.follower_address, "http://foo.bar/qux"]
+     }}
+  end
+
+  describe "when :reject" do
+    test "is empty" do
+      Config.put([:mrf_simple, :reject], [])
+
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :reject], ["remote.instance"])
+
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(remote_message) == {:reject, nil}
+    end
+  end
+
+  describe "when :accept" do
+    test "is empty" do
+      Config.put([:mrf_simple, :accept], [])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+
+    test "is not empty but it doesn't have a matching host" do
+      Config.put([:mrf_simple, :accept], ["non.matching.remote"])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:reject, nil}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :accept], ["remote.instance"])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+  end
+
+  describe "when :avatar_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :avatar_removal], [])
+
+      remote_user = build_remote_user()
+
+      assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+    end
+
+    test "is not empty but it doesn't have a matching host" do
+      Config.put([:mrf_simple, :avatar_removal], ["non.matching.remote"])
+
+      remote_user = build_remote_user()
+
+      assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :avatar_removal], ["remote.instance"])
+
+      remote_user = build_remote_user()
+      {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+      refute filtered["icon"]
+    end
+  end
+
+  describe "when :banner_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :banner_removal], [])
+
+      remote_user = build_remote_user()
+
+      assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+    end
+
+    test "is not empty but it doesn't have a matching host" do
+      Config.put([:mrf_simple, :banner_removal], ["non.matching.remote"])
+
+      remote_user = build_remote_user()
+
+      assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :banner_removal], ["remote.instance"])
+
+      remote_user = build_remote_user()
+      {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+      refute filtered["image"]
+    end
+  end
+
+  defp build_local_message do
+    %{
+      "actor" => "#{Pleroma.Web.base_url()}/users/alice",
+      "to" => [],
+      "cc" => []
+    }
+  end
+
+  defp build_remote_message do
+    %{"actor" => "https://remote.instance/users/bob"}
+  end
+
+  defp build_remote_user do
+    %{
+      "id" => "https://remote.instance/users/bob",
+      "icon" => %{
+        "url" => "http://example.com/image.jpg",
+        "type" => "Image"
+      },
+      "image" => %{
+        "url" => "http://example.com/image.jpg",
+        "type" => "Image"
+      },
+      "type" => "Person"
+    }
+  end
+end
index c24b50f8c833a4f1513edb8841b51cb7153fdc25..ee71de8d0631b345ec868ad7dd72b63f2e5f81b6 100644 (file)
@@ -1209,4 +1209,44 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
     end
   end
+
+  describe "fix_explicit_addressing" do
+    test "moves non-explicitly mentioned actors to cc" do
+      user = insert(:user)
+
+      explicitly_mentioned_actors = [
+        "https://pleroma.gold/users/user1",
+        "https://pleroma.gold/user2"
+      ]
+
+      object = %{
+        "actor" => user.ap_id,
+        "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
+        "cc" => [],
+        "tag" =>
+          Enum.map(explicitly_mentioned_actors, fn href ->
+            %{"type" => "Mention", "href" => href}
+          end)
+      }
+
+      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
+      refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
+      assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
+    end
+
+    test "does not move actor's follower collection to cc" do
+      user = insert(:user)
+
+      object = %{
+        "actor" => user.ap_id,
+        "to" => [user.follower_address],
+        "cc" => []
+      }
+
+      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      assert user.follower_address in fixed_object["to"]
+      refute user.follower_address in fixed_object["cc"]
+    end
+  end
 end
index 9fb9455d2c6693c95900d4c10ec869a3d4b6caa1..e6483db8bdc8f9dde223425fa6f3951332ffd9d7 100644 (file)
@@ -2,11 +2,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
   use Pleroma.DataCase
   import Pleroma.Factory
 
+  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.UserView
 
   test "Renders a user, including the public key" do
     user = insert(:user)
-    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     result = UserView.render("user.json", %{user: user})
 
@@ -18,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
 
   test "Does not add an avatar image if the user hasn't set one" do
     user = insert(:user)
-    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     result = UserView.render("user.json", %{user: user})
     refute result["icon"]
@@ -32,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
         }
       )
 
-    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     result = UserView.render("user.json", %{user: user})
     assert result["icon"]["url"] == "https://someurl"
@@ -42,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
   describe "endpoints" do
     test "local users have a usable endpoints structure" do
       user = insert(:user)
-      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+      {:ok, user} = User.ensure_keys_present(user)
 
       result = UserView.render("user.json", %{user: user})
 
@@ -58,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
 
     test "remote users have an empty endpoints structure" do
       user = insert(:user, local: false)
-      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+      {:ok, user} = User.ensure_keys_present(user)
 
       result = UserView.render("user.json", %{user: user})
 
@@ -68,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
 
     test "instance users do not expose oAuth endpoints" do
       user = insert(:user, nickname: nil, local: true)
-      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+      {:ok, user} = User.ensure_keys_present(user)
 
       result = UserView.render("user.json", %{user: user})
 
index 9c03c8be2ecf0c43ae89a0856282bda591cfe571..e2584f635f881e15d8c12a2cd59c7cadb76f85f9 100644 (file)
@@ -96,6 +96,16 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
     refute Visibility.visible_for_user?(direct, unrelated)
   end
 
+  test "doesn't die when the user doesn't exist",
+       %{
+         direct: direct,
+         user: user
+       } do
+    Repo.delete(user)
+    Cachex.clear(:user_cache)
+    refute Visibility.is_private?(direct)
+  end
+
   test "get_visibility", %{
     public: public,
     private: private,
index 0199051374cab85b0cb1a7c06f5d384b7b89a5b3..9721a40342bad8cc9ca71fb41aeb42af59b035c0 100644 (file)
@@ -5,8 +5,10 @@
 defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   use Pleroma.Web.ConnCase
 
+  alias Pleroma.Activity
   alias Pleroma.User
   alias Pleroma.UserInviteToken
+  alias Pleroma.Web.CommonAPI
   import Pleroma.Factory
 
   describe "/api/pleroma/admin/users" do
@@ -530,14 +532,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  test "/api/pleroma/admin/invite_token" do
+  test "/api/pleroma/admin/users/invite_token" do
     admin = insert(:user, info: %{is_admin: true})
 
     conn =
       build_conn()
       |> assign(:user, admin)
       |> put_req_header("accept", "application/json")
-      |> get("/api/pleroma/admin/invite_token")
+      |> get("/api/pleroma/admin/users/invite_token")
 
     assert conn.status == 200
   end
@@ -570,27 +572,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       user = insert(:user, local: false, tags: ["foo", "bar"])
       conn = get(conn, "/api/pleroma/admin/users?page=1")
 
+      users =
+        [
+          %{
+            "deactivated" => admin.info.deactivated,
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "roles" => %{"admin" => true, "moderator" => false},
+            "local" => true,
+            "tags" => []
+          },
+          %{
+            "deactivated" => user.info.deactivated,
+            "id" => user.id,
+            "nickname" => user.nickname,
+            "roles" => %{"admin" => false, "moderator" => false},
+            "local" => false,
+            "tags" => ["foo", "bar"]
+          }
+        ]
+        |> Enum.sort_by(& &1["nickname"])
+
       assert json_response(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
-               "users" => [
-                 %{
-                   "deactivated" => admin.info.deactivated,
-                   "id" => admin.id,
-                   "nickname" => admin.nickname,
-                   "roles" => %{"admin" => true, "moderator" => false},
-                   "local" => true,
-                   "tags" => []
-                 },
-                 %{
-                   "deactivated" => user.info.deactivated,
-                   "id" => user.id,
-                   "nickname" => user.nickname,
-                   "roles" => %{"admin" => false, "moderator" => false},
-                   "local" => false,
-                   "tags" => ["foo", "bar"]
-                 }
-               ]
+               "users" => users
              }
     end
 
@@ -792,35 +798,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         |> assign(:user, admin)
         |> get("/api/pleroma/admin/users?filters=local")
 
+      users =
+        [
+          %{
+            "deactivated" => user.info.deactivated,
+            "id" => user.id,
+            "nickname" => user.nickname,
+            "roles" => %{"admin" => false, "moderator" => false},
+            "local" => true,
+            "tags" => []
+          },
+          %{
+            "deactivated" => admin.info.deactivated,
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "roles" => %{"admin" => true, "moderator" => false},
+            "local" => true,
+            "tags" => []
+          },
+          %{
+            "deactivated" => false,
+            "id" => old_admin.id,
+            "local" => true,
+            "nickname" => old_admin.nickname,
+            "roles" => %{"admin" => true, "moderator" => false},
+            "tags" => []
+          }
+        ]
+        |> Enum.sort_by(& &1["nickname"])
+
       assert json_response(conn, 200) == %{
                "count" => 3,
                "page_size" => 50,
-               "users" => [
-                 %{
-                   "deactivated" => user.info.deactivated,
-                   "id" => user.id,
-                   "nickname" => user.nickname,
-                   "roles" => %{"admin" => false, "moderator" => false},
-                   "local" => true,
-                   "tags" => []
-                 },
-                 %{
-                   "deactivated" => admin.info.deactivated,
-                   "id" => admin.id,
-                   "nickname" => admin.nickname,
-                   "roles" => %{"admin" => true, "moderator" => false},
-                   "local" => true,
-                   "tags" => []
-                 },
-                 %{
-                   "deactivated" => false,
-                   "id" => old_admin.id,
-                   "local" => true,
-                   "nickname" => old_admin.nickname,
-                   "roles" => %{"admin" => true, "moderator" => false},
-                   "tags" => []
-                 }
-               ]
+               "users" => users
              }
     end
 
@@ -831,27 +841,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
 
+      users =
+        [
+          %{
+            "deactivated" => false,
+            "id" => admin.id,
+            "nickname" => admin.nickname,
+            "roles" => %{"admin" => true, "moderator" => false},
+            "local" => admin.local,
+            "tags" => []
+          },
+          %{
+            "deactivated" => false,
+            "id" => second_admin.id,
+            "nickname" => second_admin.nickname,
+            "roles" => %{"admin" => true, "moderator" => false},
+            "local" => second_admin.local,
+            "tags" => []
+          }
+        ]
+        |> Enum.sort_by(& &1["nickname"])
+
       assert json_response(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
-               "users" => [
-                 %{
-                   "deactivated" => false,
-                   "id" => admin.id,
-                   "nickname" => admin.nickname,
-                   "roles" => %{"admin" => true, "moderator" => false},
-                   "local" => admin.local,
-                   "tags" => []
-                 },
-                 %{
-                   "deactivated" => false,
-                   "id" => second_admin.id,
-                   "nickname" => second_admin.nickname,
-                   "roles" => %{"admin" => true, "moderator" => false},
-                   "local" => second_admin.local,
-                   "tags" => []
-                 }
-               ]
+               "users" => users
              }
     end
 
@@ -886,27 +900,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
 
+      users =
+        [
+          %{
+            "deactivated" => false,
+            "id" => user1.id,
+            "nickname" => user1.nickname,
+            "roles" => %{"admin" => false, "moderator" => false},
+            "local" => user1.local,
+            "tags" => ["first"]
+          },
+          %{
+            "deactivated" => false,
+            "id" => user2.id,
+            "nickname" => user2.nickname,
+            "roles" => %{"admin" => false, "moderator" => false},
+            "local" => user2.local,
+            "tags" => ["second"]
+          }
+        ]
+        |> Enum.sort_by(& &1["nickname"])
+
       assert json_response(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
-               "users" => [
-                 %{
-                   "deactivated" => false,
-                   "id" => user1.id,
-                   "nickname" => user1.nickname,
-                   "roles" => %{"admin" => false, "moderator" => false},
-                   "local" => user1.local,
-                   "tags" => ["first"]
-                 },
-                 %{
-                   "deactivated" => false,
-                   "id" => user2.id,
-                   "nickname" => user2.nickname,
-                   "roles" => %{"admin" => false, "moderator" => false},
-                   "local" => user2.local,
-                   "tags" => ["second"]
-                 }
-               ]
+               "users" => users
              }
     end
 
@@ -1084,4 +1102,329 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
   end
+
+  describe "GET /api/pleroma/admin/reports/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns report by its id", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports/#{report_id}")
+        |> json_response(:ok)
+
+      assert response["id"] == report_id
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/reports/test")
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "PUT /api/pleroma/admin/reports/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      %{conn: assign(conn, :user, admin), id: report_id}
+    end
+
+    test "mark report as resolved", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
+        |> json_response(:ok)
+
+      assert response["state"] == "resolved"
+    end
+
+    test "closes report", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
+        |> json_response(:ok)
+
+      assert response["state"] == "closed"
+    end
+
+    test "returns 400 when state is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
+
+      assert json_response(conn, :bad_request) == "Unsupported state"
+    end
+
+    test "returns 404 when report is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/reports" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns empty response when no reports created", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+    end
+
+    test "returns reports", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      [report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert report["id"] == report_id
+    end
+
+    test "returns reports with specified state", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: first_report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I don't like this user"
+        })
+
+      CommonAPI.update_report_state(second_report_id, "closed")
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "open"
+        })
+        |> json_response(:ok)
+
+      [open_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert open_report["id"] == first_report_id
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "closed"
+        })
+        |> json_response(:ok)
+
+      [closed_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert closed_report["id"] == second_report_id
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "resolved"
+        })
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+    end
+
+    test "returns 403 when requested by a non-admin" do
+      user = insert(:user)
+
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
+    end
+
+    test "returns 403 when requested by anonymous" do
+      conn =
+        build_conn()
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+    end
+  end
+
+  describe "POST /api/pleroma/admin/reports/:id/respond" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns created dm", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
+          "status" => "I will check it out"
+        })
+        |> json_response(:ok)
+
+      recipients = Enum.map(response["mentions"], & &1["username"])
+
+      assert conn.assigns[:user].nickname in recipients
+      assert reporter.nickname in recipients
+      assert response["content"] == "I will check it out"
+      assert response["visibility"] == "direct"
+    end
+
+    test "returns 400 when status is missing", %{conn: conn} do
+      conn = post(conn, "/api/pleroma/admin/reports/test/respond")
+
+      assert json_response(conn, :bad_request) == "Invalid parameters"
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/reports/test/respond", %{
+          "status" => "foo"
+        })
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "PUT /api/pleroma/admin/statuses/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      activity = insert(:note_activity)
+
+      %{conn: assign(conn, :user, admin), id: activity.id}
+    end
+
+    test "toggle sensitive flag", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
+        |> json_response(:ok)
+
+      assert response["sensitive"]
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
+        |> json_response(:ok)
+
+      refute response["sensitive"]
+    end
+
+    test "change visibility flag", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "public"
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "private"
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "unlisted"
+    end
+
+    test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
+
+      assert json_response(conn, :bad_request) == "Unsupported visibility"
+    end
+  end
+
+  describe "DELETE /api/pleroma/admin/statuses/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      activity = insert(:note_activity)
+
+      %{conn: assign(conn, :user, admin), id: activity.id}
+    end
+
+    test "deletes status", %{conn: conn, id: id} do
+      conn
+      |> delete("/api/pleroma/admin/statuses/#{id}")
+      |> json_response(:ok)
+
+      refute Activity.get_by_id(id)
+    end
+
+    test "returns error when status is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> delete("/api/pleroma/admin/statuses/test")
+
+      assert json_response(conn, :bad_request) == "Could not delete"
+    end
+  end
 end
index 8d4f401eec8a0303cac3c8d17243af9590589ade..696060fb12b901276cabc07f07a407b2fb814f2f 100644 (file)
@@ -261,10 +261,41 @@ defmodule Pleroma.Web.CommonAPITest do
                data: %{
                  "type" => "Flag",
                  "content" => ^comment,
-                 "object" => [^target_ap_id, ^activity_ap_id]
+                 "object" => [^target_ap_id, ^activity_ap_id],
+                 "state" => "open"
                }
              } = flag_activity
     end
+
+    test "updates report state" do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %Activity{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
+
+      assert report.data["state"] == "resolved"
+    end
+
+    test "does not update report state when state is unsupported" do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %Activity{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
+    end
   end
 
   describe "reblog muting" do
@@ -279,14 +310,14 @@ defmodule Pleroma.Web.CommonAPITest do
     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
+      assert 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
+      assert User.showing_reblogs?(muter, muted) == true
     end
   end
 end
diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs
new file mode 100644 (file)
index 0000000..cc78b3a
--- /dev/null
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FallbackTest do
+  use Pleroma.Web.ConnCase
+  import Pleroma.Factory
+
+  test "GET /registration/:token", %{conn: conn} do
+    assert conn
+           |> get("/registration/foo")
+           |> html_response(200) =~ "<!--server-generated-meta-->"
+  end
+
+  test "GET /:maybe_nickname_or_id", %{conn: conn} do
+    user = insert(:user)
+
+    assert conn
+           |> get("/foo")
+           |> html_response(200) =~ "<!--server-generated-meta-->"
+
+    refute conn
+           |> get("/" <> user.nickname)
+           |> html_response(200) =~ "<!--server-generated-meta-->"
+  end
+
+  test "GET /api*path", %{conn: conn} do
+    assert conn
+           |> get("/api/foo")
+           |> json_response(404) == %{"error" => "Not implemented"}
+  end
+
+  test "GET /*path", %{conn: conn} do
+    assert conn
+           |> get("/foo")
+           |> html_response(200) =~ "<!--server-generated-meta-->"
+
+    assert conn
+           |> get("/foo/bar")
+           |> html_response(200) =~ "<!--server-generated-meta-->"
+  end
+
+  test "OPTIONS /*path", %{conn: conn} do
+    assert conn
+           |> options("/foo")
+           |> response(204) == ""
+
+    assert conn
+           |> options("/foo/bar")
+           |> response(204) == ""
+  end
+end
index a24f2a050ff7702a93427340443865de971b29a0..aaf2261bbd2f6f98e76c73fca2edd744a2bbca13 100644 (file)
@@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       fields: [],
       bot: false,
       source: %{
-        note: "",
+        note: "valid html",
         sensitive: false,
         pleroma: %{}
       },
@@ -120,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       fields: [],
       bot: true,
       source: %{
-        note: "",
+        note: user.bio,
         sensitive: false,
         pleroma: %{}
       },
@@ -209,7 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       fields: [],
       bot: true,
       source: %{
-        note: "",
+        note: user.bio,
         sensitive: false,
         pleroma: %{}
       },
index 40e7739e71a03bd03114473bb7d8150fe4e529f2..93ef630f293c09a8bcd37a5cc5323361cf87085c 100644 (file)
@@ -446,7 +446,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   end
 
   test "verify_credentials default scope unlisted", %{conn: conn} do
-    user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
+    user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
 
     conn =
       conn
@@ -1322,7 +1322,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
   describe "locked accounts" do
     test "/api/v1/follow_requests works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -1367,7 +1367,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
 
     test "verify_credentials", %{conn: conn} do
-      user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
+      user = insert(:user, %{info: %User.Info{default_scope: "private"}})
 
       conn =
         conn
@@ -1379,7 +1379,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
 
     test "/api/v1/follow_requests/:id/reject works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -1455,6 +1455,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert object.data["actor"] == User.ap_id(user)
   end
 
+  test "mascot upload", %{conn: conn} do
+    user = insert(:user)
+
+    non_image_file = %Plug.Upload{
+      content_type: "audio/mpeg",
+      path: Path.absname("test/fixtures/sound.mp3"),
+      filename: "sound.mp3"
+    }
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
+
+    assert json_response(conn, 415)
+
+    file = %Plug.Upload{
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      filename: "an_image.jpg"
+    }
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => file})
+
+    assert %{"id" => _, "type" => image} = json_response(conn, 200)
+  end
+
+  test "mascot retrieving", %{conn: conn} do
+    user = insert(:user)
+    # When user hasn't set a mascot, we should just get pleroma tan back
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/pleroma/mascot")
+
+    assert %{"url" => url} = json_response(conn, 200)
+    assert url =~ "pleroma-fox-tan-smol"
+
+    # When a user sets their mascot, we should get that back
+    file = %Plug.Upload{
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      filename: "an_image.jpg"
+    }
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> put("/api/v1/pleroma/mascot", %{"file" => file})
+
+    assert json_response(conn, 200)
+
+    user = User.get_cached_by_id(user.id)
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> get("/api/v1/pleroma/mascot")
+
+    assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
+    assert url =~ "an_image"
+  end
+
   test "hashtag timeline", %{conn: conn} do
     following = insert(:user)
 
@@ -2129,7 +2195,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
         |> json_response(:ok)
 
-      assert length(anonymous_response) == 0
+      assert Enum.empty?(anonymous_response)
     end
 
     test "does not return others' favorited DM when user is not one of recipients", %{
@@ -2153,7 +2219,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
         |> json_response(:ok)
 
-      assert length(response) == 0
+      assert Enum.empty?(response)
     end
 
     test "paginates favorites using since_id and max_id", %{
@@ -2618,33 +2684,50 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
                |> post("/api/v1/statuses/#{activity_two.id}/pin")
                |> json_response(400)
     end
+  end
 
-    test "Status rich-media Card", %{conn: conn, user: user} do
+  describe "cards" do
+    setup do
       Pleroma.Config.put([:rich_media, :enabled], true)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:rich_media, :enabled], false)
+      end)
+
+      user = insert(:user)
+      %{user: user}
+    end
+
+    test "returns rich-media card", %{conn: conn, user: user} do
       {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
 
+      card_data = %{
+        "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" =>
+          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
+        "pleroma" => %{
+          "opengraph" => %{
+            "image" => "http://ia.media-imdb.com/images/rock.jpg",
+            "title" => "The Rock",
+            "type" => "video.movie",
+            "url" => "http://www.imdb.com/title/tt0117500/",
+            "description" =>
+              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
+          }
+        }
+      }
+
       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/"
-                 }
-               }
-             }
+      assert response == card_data
 
       # works with private posts
       {:ok, activity} =
@@ -2656,9 +2739,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/statuses/#{activity.id}/card")
         |> json_response(200)
 
-      assert response_two == response
+      assert response_two == card_data
+    end
+
+    test "replaces missing description with an empty string", %{conn: conn, user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
+
+      response =
+        conn
+        |> get("/api/v1/statuses/#{activity.id}/card")
+        |> json_response(:ok)
 
-      Pleroma.Config.put([:rich_media, :enabled], false)
+      assert response == %{
+               "type" => "link",
+               "title" => "Pleroma",
+               "description" => "",
+               "image" => nil,
+               "provider_name" => "pleroma.social",
+               "provider_url" => "https://pleroma.social",
+               "url" => "https://pleroma.social/",
+               "pleroma" => %{
+                 "opengraph" => %{
+                   "title" => "Pleroma",
+                   "type" => "website",
+                   "url" => "https://pleroma.social/"
+                 }
+               }
+             }
     end
   end
 
diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
new file mode 100644 (file)
index 0000000..eb83999
--- /dev/null
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MongooseIMController do
+  use Pleroma.Web.ConnCase
+  import Pleroma.Factory
+
+  test "/user_exists", %{conn: conn} do
+    _user = insert(:user, nickname: "lain")
+    _remote_user = insert(:user, nickname: "alice", local: false)
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :user_exists), user: "lain")
+      |> json_response(200)
+
+    assert res == true
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :user_exists), user: "alice")
+      |> json_response(404)
+
+    assert res == false
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :user_exists), user: "bob")
+      |> json_response(404)
+
+    assert res == false
+  end
+
+  test "/check_password", %{conn: conn} do
+    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
+      |> json_response(200)
+
+    assert res == true
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
+      |> json_response(403)
+
+    assert res == false
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
+      |> json_response(404)
+
+    assert res == false
+  end
+end
index 2fc42b7ccc29247fcbde94c7f6f52d035fed2aee..be11735135f6cac391e9b5538e8c0853694225b2 100644 (file)
@@ -7,6 +7,22 @@ defmodule Pleroma.Web.NodeInfoTest do
 
   import Pleroma.Factory
 
+  test "GET /.well-known/nodeinfo", %{conn: conn} do
+    links =
+      conn
+      |> get("/.well-known/nodeinfo")
+      |> json_response(200)
+      |> Map.fetch!("links")
+
+    Enum.each(links, fn link ->
+      href = Map.fetch!(link, "href")
+
+      conn
+      |> get(href)
+      |> json_response(200)
+    end)
+  end
+
   test "nodeinfo shows staff accounts", %{conn: conn} do
     moderator = insert(:user, %{local: true, info: %{is_moderator: true}})
     admin = insert(:user, %{local: true, info: %{is_admin: true}})
@@ -32,70 +48,6 @@ defmodule Pleroma.Web.NodeInfoTest do
              result["metadata"]["restrictedNicknames"]
   end
 
-  test "returns 404 when federation is disabled", %{conn: conn} do
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, false)
-
-    Application.put_env(:pleroma, :instance, instance)
-
-    conn
-    |> get("/.well-known/nodeinfo")
-    |> json_response(404)
-
-    conn
-    |> get("/nodeinfo/2.1.json")
-    |> json_response(404)
-
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, true)
-
-    Application.put_env(:pleroma, :instance, instance)
-  end
-
-  test "returns 200 when federation is enabled", %{conn: conn} do
-    conn
-    |> get("/.well-known/nodeinfo")
-    |> json_response(200)
-
-    conn
-    |> get("/nodeinfo/2.1.json")
-    |> json_response(200)
-  end
-
-  test "returns 404 when federation is disabled (nodeinfo 2.0)", %{conn: conn} do
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, false)
-
-    Application.put_env(:pleroma, :instance, instance)
-
-    conn
-    |> get("/.well-known/nodeinfo")
-    |> json_response(404)
-
-    conn
-    |> get("/nodeinfo/2.0.json")
-    |> json_response(404)
-
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, true)
-
-    Application.put_env(:pleroma, :instance, instance)
-  end
-
-  test "returns 200 when federation is enabled (nodeinfo 2.0)", %{conn: conn} do
-    conn
-    |> get("/.well-known/nodeinfo")
-    |> json_response(200)
-
-    conn
-    |> get("/nodeinfo/2.0.json")
-    |> json_response(200)
-  end
-
   test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do
     conn
     |> get("/.well-known/nodeinfo")
index ad2a49f09eb45069ea291a6c8d6b3c6d252f932f..3c07309b721af90b1dce95caf8a9455412d56514 100644 (file)
@@ -69,4 +69,17 @@ defmodule Pleroma.Web.OAuth.TokenTest do
 
     assert tokens == 2
   end
+
+  test "deletes expired tokens" do
+    insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3))
+    insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3))
+    t3 = insert(:oauth_token)
+    t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10))
+    {tokens, _} = Token.delete_expired_tokens()
+    assert tokens == 2
+    available_tokens = Pleroma.Repo.all(Token)
+
+    token_ids = available_tokens |> Enum.map(& &1.id)
+    assert token_ids == [t3.id, t4.id]
+  end
 end
index 2916caf8d937fcb209439e6a789943ad665d56a6..f6be16862e34bb6864b2780390b1fa0625176189 100644 (file)
@@ -355,7 +355,7 @@ defmodule Pleroma.Web.OStatusTest do
 
       {:ok, user} = OStatus.find_or_make_user(uri)
 
-      user = Pleroma.User.get_cached_by_id(user.id)
+      user = User.get_cached_by_id(user.id)
       assert user.name == "Constance Variable"
       assert user.nickname == "lambadalambda@social.heldscal.la"
       assert user.local == false
@@ -374,7 +374,7 @@ defmodule Pleroma.Web.OStatusTest do
       {:ok, user} = OStatus.find_or_make_user(uri)
 
       assert user.info ==
-               %Pleroma.User.Info{
+               %User.Info{
                  id: user.info.id,
                  ap_enabled: false,
                  background: %{},
@@ -407,7 +407,7 @@ defmodule Pleroma.Web.OStatusTest do
       {:ok, user} = OStatus.find_or_make_user(uri)
       old_name = user.name
       old_bio = user.bio
-      change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, old_name: nil})
+      change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, name: nil})
 
       {:ok, user} = Repo.update(change)
       refute user.avatar
index 612db7e32e608cfeec6479bf04fda039e6140c11..530562325f7a99664c48d6d2cfc9879448505987 100644 (file)
@@ -6,11 +6,7 @@ defmodule Pleroma.Web.FederatingPlugTest do
   use Pleroma.Web.ConnCase
 
   test "returns and halt the conn when federating is disabled" do
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, false)
-
-    Application.put_env(:pleroma, :instance, instance)
+    Pleroma.Config.put([:instance, :federating], false)
 
     conn =
       build_conn()
@@ -19,11 +15,7 @@ defmodule Pleroma.Web.FederatingPlugTest do
     assert conn.status == 404
     assert conn.halted
 
-    instance =
-      Application.get_env(:pleroma, :instance)
-      |> Keyword.put(:federating, true)
-
-    Application.put_env(:pleroma, :instance, instance)
+    Pleroma.Config.put([:instance, :federating], true)
   end
 
   test "does nothing when federating is enabled" do
index 60d93768f20f5b1fd5cd968b12a279d64d770e34..53b0596f582d08f7a971fe56e3d126f95c371f53 100644 (file)
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.RichMedia.HelpersTest do
   use Pleroma.DataCase
 
+  alias Pleroma.Object
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -59,4 +60,43 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
 
     Pleroma.Config.put([:rich_media, :enabled], false)
   end
+
+  test "refuses to crawl URLs from posts marked sensitive" do
+    user = insert(:user)
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" => "http://example.com/ogp",
+        "sensitive" => true
+      })
+
+    %Object{} = object = Object.normalize(activity)
+
+    assert object.data["sensitive"]
+
+    Pleroma.Config.put([:rich_media, :enabled], true)
+
+    assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+
+    Pleroma.Config.put([:rich_media, :enabled], false)
+  end
+
+  test "refuses to crawl URLs from posts tagged NSFW" do
+    user = insert(:user)
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" => "http://example.com/ogp #nsfw"
+      })
+
+    %Object{} = object = Object.normalize(activity)
+
+    assert object.data["sensitive"]
+
+    Pleroma.Config.put([:rich_media, :enabled], true)
+
+    assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+
+    Pleroma.Config.put([:rich_media, :enabled], false)
+  end
 end
index 47b127cf92b79b6c2cb43c0a80b74d4eb001dfbf..3a9cc1854935c3f375a64f0dc07273538e3fe72b 100644 (file)
@@ -44,6 +44,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
               %{
                 image: "http://ia.media-imdb.com/images/rock.jpg",
                 title: "The Rock",
+                description:
+                  "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
                 type: "video.movie",
                 url: "http://www.imdb.com/title/tt0117500/"
               }}
index 232082779fd8f13de9768c8363efbcbb0779b2b6..e86e76fe931efdf6220cb46773da8cc60af8d772 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.Salmon.SalmonTest do
   use Pleroma.DataCase
   alias Pleroma.Activity
+  alias Pleroma.Keys
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.Federator.Publisher
@@ -34,12 +35,6 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
     assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error
   end
 
-  test "generates an RSA private key pem" do
-    {:ok, key} = Salmon.generate_rsa_pem()
-    assert is_binary(key)
-    assert Regex.match?(~r/RSA/, key)
-  end
-
   test "it encodes a magic key from a public key" do
     key = Salmon.decode_key(@magickey)
     magic_key = Salmon.encode_key(key)
@@ -51,18 +46,10 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
     _key = Salmon.decode_key(@magickey_friendica)
   end
 
-  test "returns a public and private key from a pem" do
-    pem = File.read!("test/fixtures/private_key.pem")
-    {:ok, private, public} = Salmon.keys_from_pem(pem)
-
-    assert elem(private, 0) == :RSAPrivateKey
-    assert elem(public, 0) == :RSAPublicKey
-  end
-
   test "encodes an xml payload with a private key" do
     doc = File.read!("test/fixtures/incoming_note_activity.xml")
     pem = File.read!("test/fixtures/private_key.pem")
-    {:ok, private, public} = Salmon.keys_from_pem(pem)
+    {:ok, private, public} = Keys.keys_from_pem(pem)
 
     # Let's try a roundtrip.
     {:ok, salmon} = Salmon.encode(private, doc)
@@ -105,7 +92,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
 
     {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
     user = User.get_cached_by_ap_id(activity.data["actor"])
-    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+    {:ok, user} = User.ensure_keys_present(user)
 
     Salmon.publish(user, activity)
 
index e194f14fb55a80a62493582701cd664eae7f1554..bcd0f522d5c9b8edf2e3d13fad9300416bf37aa5 100644 (file)
@@ -144,41 +144,25 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
     end
 
     test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, false)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], false)
 
       conn
       |> get("/api/statuses/public_timeline.json")
       |> json_response(403)
 
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, true)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], true)
     end
 
     test "returns 200 to authenticated request when the instance is not public",
          %{conn: conn, user: user} do
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, false)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], false)
 
       conn
       |> with_credentials(user.nickname, "test")
       |> get("/api/statuses/public_timeline.json")
       |> json_response(200)
 
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, true)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], true)
     end
 
     test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
@@ -214,41 +198,25 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
     setup [:valid_user]
 
     test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, false)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], false)
 
       conn
       |> get("/api/statuses/public_and_external_timeline.json")
       |> json_response(403)
 
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, true)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], true)
     end
 
     test "returns 200 to authenticated request when the instance is not public",
          %{conn: conn, user: user} do
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, false)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], false)
 
       conn
       |> with_credentials(user.nickname, "test")
       |> get("/api/statuses/public_and_external_timeline.json")
       |> json_response(200)
 
-      instance =
-        Application.get_env(:pleroma, :instance)
-        |> Keyword.put(:public, true)
-
-      Application.put_env(:pleroma, :instance, instance)
+      Pleroma.Config.put([:instance, :public], true)
     end
 
     test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
index 6b20d8d569d5a8e33e5ef48f03c64961f92d03aa..335c95b18ba14e8fa10433aa220b0b14c5c17d86 100644 (file)
@@ -105,19 +105,4 @@ defmodule Pleroma.Web.WebFingerTest do
       assert template == "http://status.alpicola.com/main/xrd?uri={uri}"
     end
   end
-
-  describe "ensure_keys_present" do
-    test "it creates keys for a user and stores them in info" do
-      user = insert(:user)
-      refute is_binary(user.info.keys)
-      {:ok, user} = WebFinger.ensure_keys_present(user)
-      assert is_binary(user.info.keys)
-    end
-
-    test "it doesn't create keys if there already are some" do
-      user = insert(:user, %{info: %{keys: "xxx"}})
-      {:ok, user} = WebFinger.ensure_keys_present(user)
-      assert user.info.keys == "xxx"
-    end
-  end
 end
index 1e69ed01a38ec6eca84d22c6524aaf999ac5c0df..f79745d58ce340dd0691beafe84bf5f033e05d34 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.Websub.WebsubControllerTest do
   use Pleroma.Web.ConnCase
   import Pleroma.Factory
-  alias Pleroma.Activity
   alias Pleroma.Repo
   alias Pleroma.Web.Websub
   alias Pleroma.Web.Websub.WebsubClientSubscription
@@ -52,7 +51,7 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
   end
 
   describe "websub_incoming" do
-    test "handles incoming feed updates", %{conn: conn} do
+    test "accepts incoming feed updates", %{conn: conn} do
       websub = insert(:websub_client_subscription)
       doc = "some stuff"
       signature = Websub.sign(websub.secret, doc)
@@ -64,8 +63,6 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
         |> post("/push/subscriptions/#{websub.id}", doc)
 
       assert response(conn, 200) == "OK"
-
-      assert length(Repo.all(Activity)) == 1
     end
 
     test "rejects incoming feed updates with the wrong signature", %{conn: conn} do
@@ -80,8 +77,6 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
         |> post("/push/subscriptions/#{websub.id}", doc)
 
       assert response(conn, 500) == "Error"
-
-      assert Enum.empty?(Repo.all(Activity))
     end
   end
 end