Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 4 Jun 2019 09:28:23 +0000 (16:28 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Tue, 4 Jun 2019 09:28:23 +0000 (16:28 +0700)
103 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/differences_in_mastoapi_responses.md
docs/api/pleroma_api.md
docs/config.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
elixir_buildpack.config [new file with mode: 0644]
installation/pleroma-mongooseim.cfg [new file with mode: 0755]
installation/pleroma.vcl
lib/healthcheck.ex
lib/pleroma/conversation.ex
lib/pleroma/emoji.ex
lib/pleroma/formatter.ex
lib/pleroma/html.ex
lib/pleroma/http/connection.ex
lib/pleroma/http/http.ex
lib/pleroma/notification.ex
lib/pleroma/object.ex
lib/pleroma/object/fetcher.ex
lib/pleroma/plugs/federating_plug.ex
lib/pleroma/reverse_proxy.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/subchain_policy.ex [new file with mode: 0644]
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/visibility.ex
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/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/conversation_view.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/media_proxy/media_proxy.ex
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
lib/pleroma/web/ostatus/ostatus.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/templates/o_auth/o_auth/_scopes.html.eex
lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/twitter_api/views/user_view.ex
lib/pleroma/web/web_finger/web_finger.ex
lib/pleroma/web/websub/websub.ex
mix.exs
priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs [new file with mode: 0644]
priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs [new file with mode: 0644]
priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs [new file with mode: 0644]
test/fixtures/httpoison_mock/emelie.json [new file with mode: 0644]
test/fixtures/httpoison_mock/rinpatch.json [new file with mode: 0644]
test/fixtures/mastodon-question-activity.json [new file with mode: 0644]
test/fixtures/mastodon-vote.json [new file with mode: 0644]
test/fixtures/rich_media/ogp-missing-data.html [new file with mode: 0644]
test/fixtures/rich_media/ogp.html
test/formatter_test.exs
test/notification_test.exs
test/object/containment_test.exs
test/plugs/cache_control_test.exs [new file with mode: 0644]
test/support/http_request_mock.ex
test/support/ostatus_mock.ex [deleted file]
test/support/websub_mock.ex [deleted file]
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/mrf/simple_policy_test.exs
test/web/activity_pub/mrf/subchain_policy_test.exs [new file with mode: 0644]
test/web/activity_pub/transmogrifier_test.exs
test/web/activity_pub/visibilty_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/mastodon_api/account_view_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/mastodon_api/status_view_test.exs
test/web/node_info_test.exs
test/web/plugs/federating_plug_test.exs
test/web/rich_media/parser_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/twitter_api/util_controller_test.exs
test/web/twitter_api/views/user_view_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 8b5131dc3cf3879092fb1226c1a7c4d5ec4386c3..58c9de167cefb30b0c807d7792c468c5b5b34e54 100644 (file)
@@ -52,8 +52,7 @@ unit-testing:
     - mix deps.get
     - mix ecto.create
     - mix ecto.migrate
-    - mix test --trace --preload-modules
-    - mix coveralls
+    - mix coveralls --trace --preload-modules
 
 unit-testing-rum:
   stage: test
@@ -95,3 +94,49 @@ 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 push -f dokku@pleroma.online:$CI_ENVIRONMENT_SLUG $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 b8907a23fdd6d37e388b0ae2929121fa2851ca51..99b42e280aab3b858ab1cd5868e7175c27e86af2 100644 (file)
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
 ### Added
+- Add a generic settings store for frontends / clients to use.
 - Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
 - [MongooseIM](https://github.com/esl/MongooseIM) http authentication support.
 - LDAP authentication
@@ -16,7 +17,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - 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 `Question` and `Answer` objects
 - Federation: Support for reports
+- Configuration: `poll_limits` option
 - Configuration: `safe_dm_mentions` option
 - Configuration: `link_name` option
 - Configuration: `fetch_initial_posts` option
@@ -37,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
 - Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
 - Mastodon API: `POST /api/v1/accounts` (account creation API)
+- Mastodon API: [Polls](https://docs.joinmastodon.org/api/rest/polls/)
 - ActivityPub C2S: OAuth endpoints
 - Metadata: RelMe provider
 - OAuth: added support for refresh tokens
@@ -45,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - 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`)
+- MRF: Support for running subchains.
 - Addressable lists
 
 ### Changed
@@ -113,11 +118,17 @@ 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`
 
+## [0.9.99999] - 2019-05-31
+### Security
+- Mastodon API: Fix lists leaking private posts
+
 ## [0.9.9999] - 2019-04-05
 ### Security
 - Mastodon API: Fix content warnings skipping HTML sanitization
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 e90821d66daf9ae4361703579d16ec24a83da6ef..7d70c1a5e58d6450da1cbfc1041a92dad24e52c0 100644 (file)
@@ -184,9 +184,6 @@ 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.
@@ -211,6 +208,12 @@ config :pleroma, :instance,
   avatar_upload_limit: 2_000_000,
   background_upload_limit: 4_000_000,
   banner_upload_limit: 4_000_000,
+  poll_limits: %{
+    max_options: 20,
+    max_option_chars: 200,
+    min_expiration: 0,
+    max_expiration: 365 * 24 * 60 * 60
+  },
   registrations_open: true,
   federating: true,
   federation_reachability_timeout_days: 7,
@@ -323,6 +326,8 @@ config :pleroma, :mrf_keyword,
   federated_timeline_removal: [],
   replace: []
 
+config :pleroma, :mrf_subchain, match_actor: %{}
+
 config :pleroma, :rich_media, enabled: true
 
 config :pleroma, :media_proxy,
@@ -456,7 +461,11 @@ config :pleroma, :ldap,
 config :esshd,
   enabled: false
 
-oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
+oauth_consumer_strategies =
+  System.get_env("OAUTH_CONSUMER_STRATEGIES")
+  |> to_string()
+  |> String.split()
+  |> Enum.map(&hd(String.split(&1, ":")))
 
 ueberauth_providers =
   for strategy <- oauth_consumer_strategies do
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 6100989c42788197a14198dd8a50b86791ee3550..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
 
index 946e0e885c2b8a877945b9a9a3193a0de5f4dd2c..27463f8f3a2d86a2e0b6b4ef886dd5ff7a2b2aa0 100644 (file)
@@ -43,6 +43,7 @@ Has these additional fields under the `pleroma` object:
 - `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
 - `hide_followers`: boolean, true when the user has follower hiding enabled
 - `hide_follows`: boolean, true when the user has follow hiding enabled
+- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
 
 ### Source
 
@@ -81,6 +82,14 @@ Additional parameters can be added to the JSON body/Form data:
 - `hide_favorites` - if true, user's favorites timeline will be hidden
 - `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
 - `default_scope` - the scope returned under `privacy` key in Source subentity
+- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
+
+### Pleroma Settings Store
+Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
+
+The parameter should have a form of `{frontend_name: {...}}`, with `frontend_name` identifying your type of client, e.g. `pleroma_fe`. It will overwrite everything under this property, but will not overwrite other frontend's settings.
+
+This information is returned in the `verify_credentials` endpoint.
 
 ## Authentication
 
index 4d99a2d2bfdaad8976fc763561df976ec1d428f8..edc62727a46d0f4bcf7e61f834100a39d26f4506 100644 (file)
@@ -126,20 +126,6 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
 ## `/api/pleroma/admin/`…
 See [Admin-API](Admin-API.md)
 
-## `/api/v1/pleroma/flavour/:flavour`
-* Method `POST`
-* Authentication: required
-* Response: JSON string. Returns the user flavour or the default one on success, otherwise returns `{"error": "error_msg"}`
-* Example response: "glitch"
-* Note: This is intended to be used only by mastofe
-
-## `/api/v1/pleroma/flavour`
-* Method `GET`
-* Authentication: required
-* Response: JSON string. Returns the user flavour or the default one.
-* Example response: "glitch"
-* Note: This is intended to be used only by mastofe
-
 ## `/api/pleroma/notifications/read`
 ### Mark a single notification as read
 * Method `POST`
index 67b062fe930b776112f680eae2ac24161cbbc1dc..718a7912ae9841003f094a6ce10fcb2849d65661 100644 (file)
@@ -71,6 +71,11 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `avatar_upload_limit`: File size limit of user’s profile avatars
 * `background_upload_limit`: File size limit of user’s profile backgrounds
 * `banner_upload_limit`: File size limit of user’s profile banners
+* `poll_limits`: A map with poll limits for **local** polls
+  * `max_options`: Maximum number of options
+  * `max_option_chars`: Maximum number of characters per option
+  * `min_expiration`: Minimum expiration time (in seconds)
+  * `max_expiration`: Maximum expiration time (in seconds)
 * `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
 * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
 * `account_activation_required`: Require users to confirm their emails before signing in.
@@ -81,6 +86,7 @@ config :pleroma, Pleroma.Emails.Mailer,
   * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
   * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production
   * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See ``:mrf_simple`` section)
+  * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (see ``:mrf_subchain`` section)
   * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
   * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
 * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
@@ -224,6 +230,21 @@ relates to mascots on the mastodon frontend
 * `avatar_removal`: List of instances to strip avatars from
 * `banner_removal`: List of instances to strip banners from
 
+## :mrf_subchain
+This policy processes messages through an alternate pipeline when a given message matches certain criteria.
+All criteria are configured as a map of regular expressions to lists of policy modules.
+
+* `match_actor`: Matches a series of regular expressions against the actor field.
+
+Example:
+
+```
+config :pleroma, :mrf_subchain,
+  match_actor: %{
+    ~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
+  }
+```
+
 ## :mrf_rejectnonpublic
 * `allow_followersonly`: whether to allow followers-only posts
 * `allow_direct`: whether to allow direct messages
@@ -492,7 +513,7 @@ Authentication / authorization settings.
 
 * `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
 * `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
-* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
+* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
 
 ## OAuth consumer mode
 
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 9c0ef92d4c59e59c036ff59a898c064c1d0ebcf7..7d39ca5f92312b6aa43ceb6886582c28452f09a6 100644 (file)
@@ -68,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 41cce67921c6e392f0039e833456f0bb98486e01..84b9666c8c74cc928fd53d96ab1f9dd2b4ccf3b2 100644 (file)
@@ -69,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`
 
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
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 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 238c1acf201dda42301b7cb8c4332fcb80e59ea0..bc97b39ca6e6229d888f6fd721747e48f3579115 100644 (file)
@@ -49,7 +49,7 @@ defmodule Pleroma.Conversation do
     with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
          "Create" <- activity.data["type"],
          object <- Pleroma.Object.normalize(activity),
-         "Note" <- object.data["type"],
+         true <- object.data["type"] in ["Note", "Question"],
          ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
       {:ok, conversation} = create_for_ap_id(ap_id)
 
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 3e3b9fe9757f3e4e037bbd722e41a97695088daf..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>.*)/s
+  @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 558005c19260e96cee699783847c17c1980ea884..c216cdcb11af5875d47e602b25fa327d07f88614 100644 (file)
@@ -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 8442643072cf5c9bc56bda9d0ce2d0116643b576..46f2107b1d6b5405ff8b3cffd8a08ef24c5cd6fd 100644 (file)
@@ -127,10 +127,15 @@ defmodule Pleroma.Notification do
 
   def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
       when type in ["Create", "Like", "Announce", "Follow"] do
-    users = get_notified_from_activity(activity)
-
-    notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
-    {:ok, notifications}
+    object = Object.normalize(activity)
+
+    unless object && object.data["type"] == "Answer" do
+      users = get_notified_from_activity(activity)
+      notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
+      {:ok, notifications}
+    else
+      {:ok, []}
+    end
   end
 
   def create_notifications(_), do: {:ok, []}
@@ -166,7 +171,16 @@ defmodule Pleroma.Notification do
   def get_notified_from_activity(_, _local_only), do: []
 
   def skip?(activity, user) do
-    [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
+    [
+      :self,
+      :blocked,
+      :muted,
+      :followers,
+      :follows,
+      :non_followers,
+      :non_follows,
+      :recently_followed
+    ]
     |> Enum.any?(&skip?(&1, activity, user))
   end
 
@@ -179,12 +193,6 @@ defmodule Pleroma.Notification do
     User.blocks?(user, %{ap_id: actor})
   end
 
-  def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
-    do: true
-
-  def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
-    do: true
-
   def skip?(:muted, activity, user) do
     actor = activity.data["actor"]
 
@@ -201,12 +209,32 @@ defmodule Pleroma.Notification do
     User.following?(follower, user)
   end
 
+  def skip?(
+        :non_followers,
+        activity,
+        %{info: %{notification_settings: %{"non_followers" => false}}} = user
+      ) do
+    actor = activity.data["actor"]
+    follower = User.get_cached_by_ap_id(actor)
+    !User.following?(follower, user)
+  end
+
   def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
     User.following?(user, followed)
   end
 
+  def skip?(
+        :non_follows,
+        activity,
+        %{info: %{notification_settings: %{"non_follows" => false}}} = user
+      ) do
+    actor = activity.data["actor"]
+    followed = User.get_cached_by_ap_id(actor)
+    !User.following?(user, followed)
+  end
+
   def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
     actor = activity.data["actor"]
 
index cc6fc9c5dfeb25c3311778fc4878090069c8dedd..4b181ec59f0ab225d3c1c44add562587036cf226 100644 (file)
@@ -35,6 +35,9 @@ defmodule Pleroma.Object do
     |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
   end
 
+  def get_by_id(nil), do: nil
+  def get_by_id(id), do: Repo.get(Object, id)
+
   def get_by_ap_id(nil), do: nil
 
   def get_by_ap_id(ap_id) do
@@ -195,4 +198,34 @@ defmodule Pleroma.Object do
       _ -> {:error, "Not found"}
     end
   end
+
+  def increase_vote_count(ap_id, name) do
+    with %Object{} = object <- Object.normalize(ap_id),
+         "Question" <- object.data["type"] do
+      multiple = Map.has_key?(object.data, "anyOf")
+
+      options =
+        (object.data["anyOf"] || object.data["oneOf"] || [])
+        |> Enum.map(fn
+          %{"name" => ^name} = option ->
+            Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
+
+          option ->
+            option
+        end)
+
+      data =
+        if multiple do
+          Map.put(object.data, "anyOf", options)
+        else
+          Map.put(object.data, "oneOf", options)
+        end
+
+      object
+      |> Object.change(%{data: data})
+      |> update_and_set_cache()
+    else
+      _ -> :noop
+    end
+  end
 end
index bb9388d4f905e802605da7d58e5bec28b8d008ad..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,8 +7,6 @@ defmodule Pleroma.Object.Fetcher do
 
   require Logger
 
-  @httpoison Application.get_env(:pleroma, :httpoison)
-
   defp reinject_object(data) do
     Logger.debug("Reinjecting object #{data["id"]}")
 
@@ -78,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 a3f177fec06d4ea8ab3da7e41d9a159663ca02ff..285d57309ea76a488ba14a51508d14daf76c3f10 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,9 +61,6 @@ 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)
-
   @default_hackney_options []
 
   @inline_content_types [
@@ -97,7 +96,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)
 
@@ -147,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
     Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
     method = method |> String.downcase() |> String.to_existing_atom()
 
-    case @hackney.request(method, url, headers, "", hackney_opts) do
+    case :hackney.request(method, url, headers, "", hackney_opts) do
       {:ok, code, headers, client} when code in @valid_resp_codes ->
         {:ok, code, downcase_headers(headers), client}
 
@@ -197,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
              duration,
              Keyword.get(opts, :max_read_duration, @max_read_duration)
            ),
-         {:ok, data} <- @hackney.stream_body(client),
+         {:ok, data} <- :hackney.stream_body(client),
          {:ok, duration} <- increase_read_duration(duration),
          sent_so_far = sent_so_far + byte_size(data),
          :ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
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 653dec95f2b5d0515e2267008cf1eb7efe1851fe..474cd8c1a1b668026271290b253713b321bdaf6b 100644 (file)
@@ -366,9 +366,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
@@ -760,7 +758,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
 
index 6397e2737b8fddc658149b420e74db539a41f27e..fb9ab92ab851c0db79339296e9a5bc2d2e6cd058 100644 (file)
@@ -42,12 +42,17 @@ defmodule Pleroma.User.Info do
     field(:hide_follows, :boolean, default: false)
     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(:pleroma_settings_store, :map, default: %{})
 
     field(:notification_settings, :map,
-      default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
+      default: %{
+        "followers" => true,
+        "follows" => true,
+        "non_follows" => true,
+        "non_followers" => true
+      }
     )
 
     # Found in the wild
@@ -68,10 +73,15 @@ defmodule Pleroma.User.Info do
   end
 
   def update_notification_settings(info, settings) do
+    settings =
+      settings
+      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
+      |> Map.new()
+
     notification_settings =
       info.notification_settings
       |> Map.merge(settings)
-      |> Map.take(["remote", "local", "followers", "follows"])
+      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
 
     params = %{notification_settings: notification_settings}
 
@@ -209,7 +219,8 @@ defmodule Pleroma.User.Info do
       :hide_followers,
       :hide_favorites,
       :background,
-      :show_role
+      :show_role,
+      :pleroma_settings_store
     ])
   end
 
@@ -241,14 +252,6 @@ defmodule Pleroma.User.Info do
     |> validate_required([:settings])
   end
 
-  def mastodon_flavour_update(info, flavour) do
-    params = %{flavour: flavour}
-
-    info
-    |> cast(params, [:flavour])
-    |> validate_required([:flavour])
-  end
-
   def mascot_update(info, url) do
     params = %{mascot: url}
 
index 48aaabe942429a10fbe8dd88ebabb20d87f81e59..47115aa6e87fc9acafbdc43301ff073b3835c463 100644 (file)
@@ -107,6 +107,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def decrease_replies_count_if_reply(_object), do: :noop
 
+  def increase_poll_votes_if_vote(%{
+        "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
+        "type" => "Create"
+      }) do
+    Object.increase_vote_count(reply_ap_id, name)
+  end
+
+  def increase_poll_votes_if_vote(_create_data), do: :noop
+
   def insert(map, local \\ true, fake \\ false) when is_map(map) do
     with nil <- Activity.normalize(map),
          map <- lazy_put_activity_defaults(map, fake),
@@ -182,40 +191,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     public = "https://www.w3.org/ns/activitystreams#Public"
 
     if activity.data["type"] in ["Create", "Announce", "Delete"] do
-      Pleroma.Web.Streamer.stream("user", activity)
-      Pleroma.Web.Streamer.stream("list", activity)
+      object = Object.normalize(activity)
+      # Do not stream out poll replies
+      unless object.data["type"] == "Answer" do
+        Pleroma.Web.Streamer.stream("user", activity)
+        Pleroma.Web.Streamer.stream("list", activity)
 
-      if Enum.member?(activity.data["to"], public) do
-        Pleroma.Web.Streamer.stream("public", activity)
+        if Enum.member?(activity.data["to"], public) do
+          Pleroma.Web.Streamer.stream("public", activity)
 
-        if activity.local do
-          Pleroma.Web.Streamer.stream("public:local", activity)
-        end
-
-        if activity.data["type"] in ["Create"] do
-          object = Object.normalize(activity)
+          if activity.local do
+            Pleroma.Web.Streamer.stream("public:local", activity)
+          end
 
-          object.data
-          |> Map.get("tag", [])
-          |> Enum.filter(fn tag -> is_bitstring(tag) end)
-          |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
+          if activity.data["type"] in ["Create"] do
+            object.data
+            |> Map.get("tag", [])
+            |> Enum.filter(fn tag -> is_bitstring(tag) end)
+            |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
 
-          if object.data["attachment"] != [] do
-            Pleroma.Web.Streamer.stream("public:media", activity)
+            if object.data["attachment"] != [] do
+              Pleroma.Web.Streamer.stream("public:media", activity)
 
-            if activity.local do
-              Pleroma.Web.Streamer.stream("public:local:media", activity)
+              if activity.local do
+                Pleroma.Web.Streamer.stream("public:local:media", activity)
+              end
             end
           end
+        else
+          # TODO: Write test, replace with visibility test
+          if !Enum.member?(activity.data["cc"] || [], public) &&
+               !Enum.member?(
+                 activity.data["to"],
+                 User.get_cached_by_ap_id(activity.data["actor"]).follower_address
+               ),
+             do: Pleroma.Web.Streamer.stream("direct", activity)
         end
-      else
-        # TODO: Write test, replace with visibility test
-        if !Enum.member?(activity.data["cc"] || [], public) &&
-             !Enum.member?(
-               activity.data["to"],
-               User.get_cached_by_ap_id(activity.data["actor"]).follower_address
-             ),
-           do: Pleroma.Web.Streamer.stream("direct", activity)
       end
     end
   end
@@ -234,6 +245,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
          {:ok, activity} <- insert(create_data, local, fake),
          {:fake, false, activity} <- {:fake, fake, activity},
          _ <- increase_replies_count_if_reply(create_data),
+         _ <- increase_poll_votes_if_vote(create_data),
          # Changing note count prior to enqueuing federation task in order to avoid
          # race conditions on updating user.info
          {:ok, _actor} <- increase_note_count_if_public(actor, activity),
@@ -398,16 +410,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,
@@ -479,6 +487,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
 
     from(activity in Activity)
+    |> maybe_preload_objects(opts)
     |> restrict_blocked(opts)
     |> restrict_recipients(recipients, opts["user"])
     |> where(
@@ -491,6 +500,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         ^context
       )
     )
+    |> exclude_poll_votes(opts)
     |> order_by([activity], desc: activity.id)
   end
 
@@ -498,7 +508,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   def fetch_activities_for_context(context, opts \\ %{}) do
     context
     |> fetch_activities_for_context_query(opts)
-    |> Activity.with_preloaded_object()
     |> Repo.all()
   end
 
@@ -506,7 +515,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           Pleroma.FlakeId.t() | nil
   def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
     context
-    |> fetch_activities_for_context_query(opts)
+    |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
     |> limit(1)
     |> select([a], a.id)
     |> Repo.one()
@@ -652,20 +661,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
@@ -819,6 +814,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted_reblogs(query, _), do: query
 
+  defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
+
+  defp exclude_poll_votes(query, _) do
+    if has_named_binding?(query, :object) do
+      from([activity, object: o] in query,
+        where: fragment("not(?->>'type' = ?)", o.data, "Answer")
+      )
+    else
+      query
+    end
+  end
+
   defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
 
   defp maybe_preload_objects(query, _) do
@@ -878,6 +885,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_pinned(opts)
     |> restrict_muted_reblogs(opts)
     |> Activity.restrict_deactivated_users()
+    |> exclude_poll_votes(opts)
   end
 
   def fetch_activities(recipients, opts \\ %{}) do
@@ -906,9 +914,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp maybe_update_cc(activities, _, _), do: activities
 
-  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
index ad2ca1e5487511dfaf1bc8409766608a842ddc08..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
index 1aaa2005051857a1584c755d8ebb1b39fc59949c..10ceef715faed9abfef0d9009d14d0138800927e 100644 (file)
@@ -5,8 +5,8 @@
 defmodule Pleroma.Web.ActivityPub.MRF do
   @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
 
-  def filter(object) do
-    get_policies()
+  def filter(policies, %{} = object) do
+    policies
     |> Enum.reduce({:ok, object}, fn
       policy, {:ok, object} ->
         policy.filter(object)
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ActivityPub.MRF do
     end)
   end
 
+  def filter(%{} = object), do: get_policies() |> filter(object)
+
   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 890d70a7a807cca0ca7756d244f2cf3b2fcf79e0..433d23c5f14b791539d4dacf27780c547ba1bf85 100644 (file)
@@ -74,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]
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
new file mode 100644 (file)
index 0000000..7657043
--- /dev/null
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
+  alias Pleroma.Config
+  alias Pleroma.Web.ActivityPub.MRF
+
+  require Logger
+
+  @behaviour MRF
+
+  defp lookup_subchain(actor) do
+    with matches <- Config.get([:mrf_subchain, :match_actor]),
+         {match, subchain} <- Enum.find(matches, fn {k, _v} -> String.match?(actor, k) end) do
+      {:ok, match, subchain}
+    else
+      _e -> {:error, :notfound}
+    end
+  end
+
+  @impl true
+  def filter(%{"actor" => actor} = message) do
+    with {:ok, match, subchain} <- lookup_subchain(actor) do
+      Logger.debug(
+        "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
+          inspect(subchain)
+        }"
+      )
+
+      subchain
+      |> MRF.filter(message)
+    else
+      _e -> {:ok, message}
+    end
+  end
+
+  @impl true
+  def filter(message), do: {:ok, message}
+end
index fdebdf85ce3efdfdfa68f15631895a2a7b8bacb0..f376e5618cad8c4d9bb73cd3e514af252341fb3c 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 49d1610a7d9b429087aac48bcd7d16dc20d83ce4..d22d24479c8a51c8acd7aa558adbe953749f2b53 100644 (file)
@@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> fix_likes
     |> fix_addressing
     |> fix_summary
+    |> fix_type
   end
 
   def fix_summary(%{"summary" => nil} = object) do
@@ -65,7 +66,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, explicit_mentions) do
+  def fix_explicit_addressing(
+        %{"to" => to, "cc" => cc} = object,
+        explicit_mentions,
+        follower_collection
+      ) do
     explicit_to =
       to
       |> Enum.filter(fn x -> x in explicit_mentions end)
@@ -76,6 +81,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
     final_cc =
       (cc ++ explicit_cc)
+      |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
       |> Enum.uniq()
 
     object
@@ -83,7 +89,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("cc", final_cc)
   end
 
-  def fix_explicit_addressing(object, _explicit_mentions), do: object
+  def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
 
   # if directMessage flag is set to true, leave the addressing alone
   def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
@@ -93,10 +99,12 @@ 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
 
-    object
-    |> fix_explicit_addressing(explicit_mentions)
+    explicit_mentions =
+      explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
+
+    fix_explicit_addressing(object, explicit_mentions, follower_collection)
   end
 
   # if as:Public is addressed, then make sure the followers collection is also addressed
@@ -133,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> fix_addressing_list("cc")
     |> fix_addressing_list("bto")
     |> fix_addressing_list("bcc")
-    |> fix_explicit_addressing
+    |> fix_explicit_addressing()
     |> fix_implicit_addressing(followers_collection)
   end
 
@@ -328,6 +336,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def fix_content_map(object), do: object
 
+  def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
+    reply = Object.normalize(reply_id)
+
+    if reply.data["type"] == "Question" and object["name"] do
+      Map.put(object, "type", "Answer")
+    else
+      object
+    end
+  end
+
+  def fix_type(object), do: object
+
   defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
     with true <- id =~ "follows",
          %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
@@ -398,7 +418,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   # - tags
   # - emoji
   def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
-      when objtype in ["Article", "Note", "Video", "Page"] do
+      when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
     actor = Containment.get_actor(data)
 
     data =
@@ -731,6 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> set_reply_to_uri
     |> strip_internal_fields
     |> strip_internal_tags
+    |> set_type
   end
 
   #  @doc
@@ -898,6 +919,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     Map.put(object, "sensitive", "nsfw" in tags)
   end
 
+  def set_type(%{"type" => "Answer"} = object) do
+    Map.put(object, "type", "Note")
+  end
+
+  def set_type(object), do: object
+
   def add_attributed_to(object) do
     attributed_to = object["attributedTo"] || object["actor"]
 
index ca8a0844be1e4840b7d384d64a4a815e2adaaba6..b8159e9e5d1cdc2eb57fdf06208db5e2cf6297c1 100644 (file)
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
 
   require Logger
 
-  @supported_object_types ["Article", "Note", "Video", "Page"]
+  @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
   @supported_report_states ~w(open closed resolved)
   @valid_visibilities ~w(public unlisted private direct)
 
@@ -789,4 +789,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         [to, cc, recipients]
     end
   end
+
+  def get_existing_votes(actor, %{data: %{"id" => id}}) do
+    query =
+      from(
+        [activity, object: object] in Activity.with_preloaded_object(Activity),
+        where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+        where:
+          fragment(
+            "(?)->>'inReplyTo' = ?",
+            object.data,
+            ^to_string(id)
+          ),
+        where: fragment("(?)->>'type' = 'Answer'", object.data)
+      )
+
+    Repo.all(query)
+  end
 end
index 93b50ee473b7cd9a4e27fb51105ee6efa85c42a0..8965e3253de21d23591fb2743a76340d59e5b535 100644 (file)
@@ -66,6 +66,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
       Enum.any?(to, &String.contains?(&1, "/followers")) ->
         "private"
 
+      object.data["directMessage"] == true ->
+        "direct"
+
       length(cc) > 0 ->
         "private"
 
index e8199200eec225ad5f60c624d360094bceccd783..85fb32669da676340d2f9f5d69540af6729fe621 100644 (file)
@@ -119,6 +119,53 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  def vote(user, object, choices) do
+    with "Question" <- object.data["type"],
+         {:author, false} <- {:author, object.data["actor"] == user.ap_id},
+         {:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)},
+         {options, max_count} <- get_options_and_max_count(object),
+         option_count <- Enum.count(options),
+         {:choice_check, {choices, true}} <-
+           {:choice_check, normalize_and_validate_choice_indices(choices, option_count)},
+         {:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do
+      answer_activities =
+        Enum.map(choices, fn index ->
+          answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
+
+          ActivityPub.create(%{
+            to: answer_data["to"],
+            actor: user,
+            context: object.data["context"],
+            object: answer_data,
+            additional: %{"cc" => answer_data["cc"]}
+          })
+        end)
+
+      object = Object.get_cached_by_ap_id(object.data["id"])
+      {:ok, answer_activities, object}
+    else
+      {:author, _} -> {:error, "Poll's author can't vote"}
+      {:existing_votes, _} -> {:error, "Already voted"}
+      {:choice_check, {_, false}} -> {:error, "Invalid indices"}
+      {:count_check, false} -> {:error, "Too many choices"}
+    end
+  end
+
+  defp get_options_and_max_count(object) do
+    if Map.has_key?(object.data, "anyOf") do
+      {object.data["anyOf"], Enum.count(object.data["anyOf"])}
+    else
+      {object.data["oneOf"], 1}
+    end
+  end
+
+  defp normalize_and_validate_choice_indices(choices, count) do
+    Enum.map_reduce(choices, true, fn index, valid ->
+      index = if is_binary(index), do: String.to_integer(index), else: index
+      {index, if(valid, do: index < count, else: valid)}
+    end)
+  end
+
   def get_visibility(%{"visibility" => visibility}, in_reply_to)
       when visibility in ~w{public unlisted private direct},
       do: {visibility, get_replied_to_visibility(in_reply_to)}
@@ -159,6 +206,7 @@ defmodule Pleroma.Web.CommonAPI do
              data,
              visibility
            ),
+         {poll, poll_emoji} <- make_poll_data(data),
          {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
          bcc <- bcc_for_list(user, visibility),
          context <- make_context(in_reply_to),
@@ -177,13 +225,14 @@ defmodule Pleroma.Web.CommonAPI do
              tags,
              cw,
              cc,
-             sensitive
+             sensitive,
+             poll
            ),
          object <-
            Map.put(
              object,
              "emoji",
-             Formatter.get_emoji_map(full_payload)
+             Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
            ) do
       ActivityPub.create(
         %{
index d97a80dd587f22efd79a78d068e24ef74f60f5d8..9c92c6cea88975723ce6d074b3a7c4db9549ff2e 100644 (file)
@@ -111,6 +111,72 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def bcc_for_list(_, _), do: []
 
+  def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
+      when is_list(options) do
+    %{max_expiration: max_expiration, min_expiration: min_expiration} =
+      limits = Pleroma.Config.get([:instance, :poll_limits])
+
+    # XXX: There is probably a cleaner way of doing this
+    try do
+      # In some cases mastofe sends out strings instead of integers
+      expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in
+
+      if Enum.count(options) > limits.max_options do
+        raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options"
+      end
+
+      {poll, emoji} =
+        Enum.map_reduce(options, %{}, fn option, emoji ->
+          if String.length(option) > limits.max_option_chars do
+            raise ArgumentError,
+              message:
+                "Poll options cannot be longer than #{limits.max_option_chars} characters each"
+          end
+
+          {%{
+             "name" => option,
+             "type" => "Note",
+             "replies" => %{"type" => "Collection", "totalItems" => 0}
+           }, Map.merge(emoji, Formatter.get_emoji_map(option))}
+        end)
+
+      case expires_in do
+        expires_in when expires_in > max_expiration ->
+          raise ArgumentError, message: "Expiration date is too far in the future"
+
+        expires_in when expires_in < min_expiration ->
+          raise ArgumentError, message: "Expiration date is too soon"
+
+        _ ->
+          :noop
+      end
+
+      end_time =
+        NaiveDateTime.utc_now()
+        |> NaiveDateTime.add(expires_in)
+        |> NaiveDateTime.to_iso8601()
+
+      poll =
+        if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
+          %{"type" => "Question", "anyOf" => poll, "closed" => end_time}
+        else
+          %{"type" => "Question", "oneOf" => poll, "closed" => end_time}
+        end
+
+      {poll, emoji}
+    rescue
+      e in ArgumentError -> e.message
+    end
+  end
+
+  def make_poll_data(%{"poll" => poll}) when is_map(poll) do
+    "Invalid poll"
+  end
+
+  def make_poll_data(_data) do
+    {%{}, %{}}
+  end
+
   def make_content_html(
         status,
         attachments,
@@ -233,7 +299,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
         tags,
         cw \\ nil,
         cc \\ [],
-        sensitive \\ false
+        sensitive \\ false,
+        merge \\ %{}
       ) do
     object = %{
       "type" => "Note",
@@ -248,12 +315,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
       "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
     }
 
-    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
-    end
+    object =
+      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
+      end
+
+    Map.merge(object, merge)
   end
 
   def format_naive_asctime(date) do
@@ -430,4 +500,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
         {:error, "No such conversation"}
     end
   end
+
+  def make_answer_data(%User{ap_id: ap_id}, object, name) do
+    %{
+      "type" => "Answer",
+      "actor" => ap_id,
+      "cc" => [object.data["actor"]],
+      "to" => [],
+      "name" => name,
+      "inReplyTo" => object.data["id"]
+    }
+  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 6b0b75284e2dd70871df3bac6e7c381aa181f8b0..f4c9fe28403d4991f6569b7bc91897529fbcd155 100644 (file)
@@ -11,13 +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.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)
@@ -87,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 1ec0f30a1ecb74e1b04ff8714becaccc7526b3f2..fe2fdcea16cf6e5b1f709133dc36ed627f52b694 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)
@@ -124,6 +124,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end)
       end)
       |> add_if_present(params, "default_scope", :default_scope)
+      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
+        {:ok, Map.merge(user.info.pleroma_settings_store, value)}
+      end)
       |> add_if_present(params, "header", :banner, fn value ->
         with %Plug.Upload{} <- value,
              {:ok, object} <- ActivityPub.upload(value, type: :banner) do
@@ -143,7 +146,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         CommonAPI.update(user)
       end
 
-      json(conn, AccountView.render("account.json", %{user: user, for: user}))
+      json(
+        conn,
+        AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
+      )
     else
       _e ->
         conn
@@ -153,7 +159,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def verify_credentials(%{assigns: %{user: user}} = conn, _) do
-    account = AccountView.render("account.json", %{user: user, for: user})
+    account =
+      AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
+
     json(conn, account)
   end
 
@@ -197,7 +205,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       languages: ["en"],
       registrations: Pleroma.Config.get([:instance, :registrations_open]),
       # Extra (not present in Mastodon):
-      max_toot_chars: Keyword.get(instance, :limit)
+      max_toot_chars: Keyword.get(instance, :limit),
+      poll_limits: Keyword.get(instance, :poll_limits)
     }
 
     json(conn, response)
@@ -409,6 +418,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Object{} = object <- Object.get_by_id(id),
+         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+         true <- Visibility.visible_for_user?(activity, user) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("poll.json", %{object: object, for: user})
+    else
+      nil ->
+        conn
+        |> put_status(404)
+        |> json(%{error: "Record not found"})
+
+      false ->
+        conn
+        |> put_status(404)
+        |> json(%{error: "Record not found"})
+    end
+  end
+
+  def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
+    with %Object{} = object <- Object.get_by_id(id),
+         true <- object.data["type"] == "Question",
+         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+         true <- Visibility.visible_for_user?(activity, user),
+         {:ok, _activities, object} <- CommonAPI.vote(user, object, choices) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("poll.json", %{object: object, for: user})
+    else
+      nil ->
+        conn
+        |> put_status(404)
+        |> json(%{error: "Record not found"})
+
+      false ->
+        conn
+        |> put_status(404)
+        |> json(%{error: "Record not found"})
+
+      {:error, message} ->
+        conn
+        |> put_status(422)
+        |> json(%{error: message})
+    end
+  end
+
   def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
     with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
       conn
@@ -472,12 +528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       params
       |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
 
-    idempotency_key =
-      case get_req_header(conn, "idempotency-key") do
-        [key] -> key
-        _ -> Ecto.UUID.generate()
-      end
-
     scheduled_at = params["scheduled_at"]
 
     if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
@@ -490,17 +540,40 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     else
       params = Map.drop(params, ["scheduled_at"])
 
-      {:ok, activity} =
-        Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
-          CommonAPI.post(user, params)
-        end)
-
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+      case get_cached_status_or_post(conn, params) do
+        {:ignore, message} ->
+          conn
+          |> put_status(422)
+          |> json(%{error: message})
+
+        {:error, message} ->
+          conn
+          |> put_status(422)
+          |> json(%{error: message})
+
+        {_, activity} ->
+          conn
+          |> put_view(StatusView)
+          |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+      end
     end
   end
 
+  defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do
+    idempotency_key =
+      case get_req_header(conn, "idempotency-key") do
+        [key] -> key
+        _ -> Ecto.UUID.generate()
+      end
+
+    Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
+      case CommonAPI.post(user, params) do
+        {:ok, activity} -> activity
+        {:error, message} -> {:ignore, message}
+      end
+    end)
+  end
+
   def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
       json(conn, %{})
@@ -1084,7 +1157,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       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,
-        limit: 20
+        limit: 40
       )
 
     q =
@@ -1346,8 +1419,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       accounts =
         Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
 
-      flavour = get_user_flavour(user)
-
       initial_state =
         %{
           meta: %{
@@ -1366,6 +1437,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
             max_toot_chars: limit,
             mascot: User.get_mascot(user)["url"]
           },
+          poll_limits: Config.get([:instance, :poll_limits]),
           rights: %{
             delete_others_notice: present?(user.info.is_moderator),
             admin: present?(user.info.is_admin)
@@ -1433,7 +1505,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       conn
       |> put_layout(false)
       |> put_view(MastodonView)
-      |> render("index.html", %{initial_state: initial_state, flavour: flavour})
+      |> render("index.html", %{initial_state: initial_state})
     else
       conn
       |> put_session(:return_to, conn.request_path)
@@ -1456,43 +1528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  @supported_flavours ["glitch", "vanilla"]
-
-  def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
-      when flavour in @supported_flavours do
-    flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
-
-    with changeset <- Ecto.Changeset.change(user),
-         changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
-         {:ok, user} <- User.update_and_set_cache(changeset),
-         flavour <- user.info.flavour do
-      json(conn, flavour)
-    else
-      e ->
-        conn
-        |> put_resp_content_type("application/json")
-        |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
-    end
-  end
-
-  def set_flavour(conn, _params) do
-    conn
-    |> put_status(400)
-    |> json(%{error: "Unsupported flavour"})
-  end
-
-  def get_flavour(%{assigns: %{user: user}} = conn, _params) do
-    json(conn, get_user_flavour(user))
-  end
-
-  defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
-    flavour
-  end
-
-  defp get_user_flavour(_) do
-    "glitch"
-  end
-
   def login(%{assigns: %{user: %User{}}} = conn, _params) do
     redirect(conn, to: local_mastodon_root_path(conn))
   end
@@ -1691,7 +1726,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> String.replace("{{user}}", user)
 
       with {:ok, %{status: 200, body: body}} <-
-             @httpoison.get(
+             HTTP.get(
                url,
                [],
                adapter: [
index b82d3319b4c4d40da6ef356ed61eb2b0e00dca91..dc32a152549f2e41f297504b16f78d77b655b5de 100644 (file)
@@ -130,6 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     |> maybe_put_role(user, opts[:for])
     |> maybe_put_settings(user, opts[:for], user_info)
     |> maybe_put_notification_settings(user, opts[:for])
+    |> maybe_put_settings_store(user, opts[:for], opts)
   end
 
   defp username_from_nickname(string) when is_binary(string) do
@@ -152,6 +153,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   defp maybe_put_settings(data, _, _, _), do: data
 
+  defp maybe_put_settings_store(data, %User{info: info, id: id}, %User{id: id}, %{
+         with_pleroma_settings: true
+       }) do
+    data
+    |> Kernel.put_in([:pleroma, :settings_store], info.pleroma_settings_store)
+  end
+
+  defp maybe_put_settings_store(data, _, _, _), do: data
+
   defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
     data
     |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
index 8e8f7cf31973d7b0ce1e8ab653adb4d55760d688..af1dcf66dd7588856551d2bee1e8f26b3ace7ca2 100644 (file)
@@ -22,9 +22,14 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
 
     last_status = StatusView.render("status.json", %{activity: activity, for: user})
 
+    # Conversations return all users except the current user.
+    users =
+      participation.conversation.users
+      |> Enum.reject(&(&1.id == user.id))
+
     accounts =
       AccountView.render("accounts.json", %{
-        users: participation.conversation.users,
+        users: users,
         as: :user
       })
 
index e55f9b96e5aa4a11dce6ebecdd1191ac42896213..6836d331a7ae89c5d2e58102f68ae55a26c5f698 100644 (file)
@@ -240,6 +240,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       spoiler_text: summary_html,
       visibility: get_visibility(object),
       media_attachments: attachments,
+      poll: render("poll.json", %{object: object, for: opts[:for]}),
       mentions: mentions,
       tags: build_tags(tags),
       application: %{
@@ -290,8 +291,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
       }
@@ -329,6 +330,64 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     }
   end
 
+  def render("poll.json", %{object: object} = opts) do
+    {multiple, options} =
+      case object.data do
+        %{"anyOf" => options} when is_list(options) -> {true, options}
+        %{"oneOf" => options} when is_list(options) -> {false, options}
+        _ -> {nil, nil}
+      end
+
+    if options do
+      end_time =
+        (object.data["closed"] || object.data["endTime"])
+        |> NaiveDateTime.from_iso8601!()
+
+      expired =
+        end_time
+        |> NaiveDateTime.compare(NaiveDateTime.utc_now())
+        |> case do
+          :lt -> true
+          _ -> false
+        end
+
+      voted =
+        if opts[:for] do
+          existing_votes =
+            Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
+
+          existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+        else
+          false
+        end
+
+      {options, votes_count} =
+        Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
+          current_count = option["replies"]["totalItems"] || 0
+
+          {%{
+             title: HTML.strip_tags(name),
+             votes_count: current_count
+           }, current_count + count}
+        end)
+
+      %{
+        # Mastodon uses separate ids for polls, but an object can't have
+        # more than one poll embedded so object id is fine
+        id: object.id,
+        expires_at: Utils.to_masto_date(end_time),
+        expired: expired,
+        multiple: multiple,
+        votes_count: votes_count,
+        options: options,
+        voted: voted,
+        emojis: build_emojis(object.data["emoji"])
+      }
+    else
+      nil
+    end
+  end
+
   def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
     object = Object.normalize(activity)
 
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)
 
index 3bf2a0fbcb262d59fa7f235c55f2fa4fa8470199..57f5b61bb3706b480c2a7d22084daaa2228e601e 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,23 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         "pleroma_api",
         "mastodon_api",
         "mastodon_api_streaming",
-        if Keyword.get(media_proxy, :enabled) do
+        "polls",
+        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 +130,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 +138,30 @@ 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),
+        pollLimits: Config.get([:instance, :poll_limits]),
+        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 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 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 352268b967b9938b198ca6afce767d346c07a00a..e699f6ae2e162fe1efcaeccd0f21c38555b3cbb0 100644 (file)
@@ -309,8 +309,6 @@ defmodule Pleroma.Web.Router do
       post("/conversations/:id/read", MastodonAPIController, :conversation_read)
 
       get("/endorsements", MastodonAPIController, :empty_array)
-
-      get("/pleroma/flavour", MastodonAPIController, :get_flavour)
     end
 
     scope [] do
@@ -335,6 +333,8 @@ defmodule Pleroma.Web.Router do
       put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
       delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
 
+      post("/polls/:id/votes", MastodonAPIController, :poll_vote)
+
       post("/media", MastodonAPIController, :upload)
       put("/media/:id", MastodonAPIController, :update_media)
 
@@ -350,8 +350,6 @@ defmodule Pleroma.Web.Router do
       put("/filters/:id", MastodonAPIController, :update_filter)
       delete("/filters/:id", MastodonAPIController, :delete_filter)
 
-      post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
-
       get("/pleroma/mascot", MastodonAPIController, :get_mascot)
       put("/pleroma/mascot", MastodonAPIController, :set_mascot)
 
@@ -426,6 +424,8 @@ defmodule Pleroma.Web.Router do
       get("/statuses/:id", MastodonAPIController, :get_status)
       get("/statuses/:id/context", MastodonAPIController, :get_context)
 
+      get("/polls/:id", MastodonAPIController, :get_poll)
+
       get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
       get("/accounts/:id/followers", MastodonAPIController, :followers)
       get("/accounts/:id/following", MastodonAPIController, :following)
index 9fefdbe2566e60d74d90a4e02280f97d5d547e8f..19e3ef401088d8dda75ebadb545d3364f523ea4d 100644 (file)
@@ -5,11 +5,10 @@
 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
@@ -153,7 +152,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"}]
index 3389c91cce11160ea89a8eec65f0100e7954f472..b3cf9ed1151a57cac52ee8d30ae76d39b31eedcb 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 {
 
       .scopes-input {
         display: flex;
+        flex-direction: column;
         margin-top: 1em;
         text-align: left;
         color: #89898a;
       }
 
       .scopes-input label:first-child {
-        flex-basis: 40%;
+        height: 2em;
       }
 
       .scopes {
       }
 
       .scope {
-        flex-basis: 100%;
         display: flex;
+        flex-basis: 100%;
         height: 2em;
         align-items: center;
       }
 
+      .scope:before {
+        color: #b9b9ba;
+        content: "✔\fe0e";
+        margin-left: 1em;
+        margin-right: 1em;
+      }
+
       [type="checkbox"] + label {
+        display: none;
+        cursor: pointer;
         margin: 0.5em;
       }
 
       }
 
       [type="checkbox"] + label:before {
+        cursor: pointer;
         display: inline-block;
         color: white;
         background-color: #121a24;
         border: 4px solid #121a24;
+        box-shadow: 0px 0px 1px 0 #d8a070;
         box-sizing: border-box;
         width: 1.2em;
         height: 1.2em;
         border-radius: 4px;
         border: none;
         padding: 10px;
-        margin-top: 30px;
+        margin-top: 20px;
+        margin-bottom: 20px;
         text-transform: uppercase;
         font-size: 16px;
         box-shadow: 0px 0px 2px 0px black,
         box-sizing: border-box;
         width: 100%;
         background-color: #931014;
+        border: 1px solid #a06060;
         border-radius: 4px;
-        border: none;
         padding: 10px;
         margin-top: 20px;
         font-weight: 500;
           margin-top: 0
         }
 
-        .scopes-input {
-          flex-direction: column;
+        .scope {
+          flex-basis: 0%;
         }
 
-        .scope {
-          flex-basis: 50%;
+        .scope:before {
+          content: "";
+          margin-left: 0em;
+          margin-right: 1em;
+        }
+
+        .scope:first-child:before {
+          margin-left: 1em;
+          content: "✔\fe0e";
+        }
+
+        .scope:after {
+          content: ",";
+        }
+
+        .scope:last-child:after {
+          content: "";
         }
       }
       .form-row {
   </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..3325beca149d532eb738cb7eadd6bd92ff781c9d 100644 (file)
@@ -4,11 +4,11 @@
 <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>
-<script crossorigin='anonymous' src="/packs/locales/<%= @flavour %>/en.js"></script>
+<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
 
 <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'>
 <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
 <script src="/packs/core/common.js"></script>
 <link rel="stylesheet" media="all" href="/packs/core/common.css" />
 
-<script src="/packs/flavours/<%= @flavour %>/common.js"></script>
-<link rel="stylesheet" media="all" href="/packs/flavours/<%= @flavour %>/common.css" />
+<script src="/packs/flavours/glitch/common.js"></script>
+<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" />
 
-<script src="/packs/flavours/<%= @flavour %>/home.js"></script>
+<script src="/packs/flavours/glitch/home.js"></script>
 </head>
 <body class='app-body no-reduce-motion system-font'>
   <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
index e6cfe108b9e18354cde7d9cc31c00481fff3fde3..c9ec1ecbfae18749f0c808e31d9b9e155a38c62e 100644 (file)
@@ -1,13 +1,19 @@
 <div class="scopes-input">
-  <%= label @form, :scope, "Permissions" %>
-
+  <%= label @form, :scope, "The following permissions will be granted" %>
   <div class="scopes">
     <%= for scope <- @available_scopes do %>
       <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
-      <div class="scope">
+      <%= if scope in @scopes do %>
+        <div class="scope">
+          <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
+          <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
+          <%= if scope in @scopes && scope do %>
+            <%= String.capitalize(scope) %>
+          <% end %>
+        </div>
+      <% else %>
         <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
-        <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
-      </div>
+      <% end %>
     <% end %>
   </div>
 </div>
index 4bcda7300e651a8625d79ac6394841b8a6715fe8..4a0718851a8dc21986c9e3df79e2f660891a399d 100644 (file)
@@ -1,7 +1,9 @@
 <h2>Sign in with external provider</h2>
 
 <%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
-  <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
+  <div style="display: none">
+    <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+  </div>
 
   <%= hidden_input f, :client_id, value: @client_id %>
   <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
index 3e360a52cc3bf75b575550c097254496dc9a9258..b17142ff8ffd066c8f6579d87de3616066fe1b3e 100644 (file)
@@ -6,26 +6,38 @@
 <% end %>
 
 <h2>OAuth Authorization</h2>
-
 <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
-<div class="input">
-  <%= label f, :name, "Name or email" %>
-  <%= text_input f, :name %>
-</div>
-<div class="input">
-  <%= label f, :password, "Password" %>
-  <%= password_input f, :password %>
-</div>
 
-<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+<%= if @params["registration"] in ["true", true] do %>
+  <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
+  <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+  <div class="input">
+    <%= label f, :nickname, "Pleroma Handle" %>
+    <%= text_input f, :nickname, placeholder: "lain" %>
+  </div>
+  <%= hidden_input f, :name, value: @params["name"] %>
+  <%= hidden_input f, :password, value: @params["password"] %>
+  <br>
+<% else %>
+  <div class="input">
+    <%= label f, :name, "Username" %>
+    <%= text_input f, :name %>
+  </div>
+  <div class="input">
+    <%= label f, :password, "Password" %>
+    <%= password_input f, :password %>
+  </div>
+  <%= submit "Log In" %>
+  <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+<% end %>
 
 <%= hidden_input f, :client_id, value: @client_id %>
 <%= hidden_input f, :response_type, value: @response_type %>
 <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
 <%= hidden_input f, :state, value: @state %>
-<%= submit "Authorize" %>
 <% end %>
 
 <%= if Pleroma.Config.oauth_consumer_enabled?() do %>
   <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
 <% end %>
+
index 31e86685a2b0172f073ee4438a33031dcd2f01c4..1b6b33e6973eebcc1b507cfee631e340156c9c37 100644 (file)
@@ -728,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 f0a4ddbd3be1964eeb2116682bb20e87da4387d5..550f35f5f796f688213279b3e4400c018813bc49 100644 (file)
@@ -121,6 +121,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
             "tags" => user.tags
           }
           |> maybe_with_activation_status(user, for_user)
+          |> with_notification_settings(user, for_user)
       }
       |> maybe_with_user_settings(user, for_user)
       |> maybe_with_role(user, for_user)
@@ -132,6 +133,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
     end
   end
 
+  defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
+    Map.put(data, "notification_settings", user.info.notification_settings)
+  end
+
+  defp with_notification_settings(data, _, _), do: data
+
   defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
     Map.put(data, "deactivated", user.info.deactivated)
   end
index c5b7d4acb1a59fcb59a9e1edef95a7c91888a04e..3fca72de84f9806eddf5547638a2503a46390fc1 100644 (file)
@@ -3,8 +3,7 @@
 # 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
@@ -176,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)}"}
@@ -209,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 b2017ef9bff5ca9006210ff16c949b81febd5cf3..df1a7ced44643f77795b9509dce787921d16115d 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -51,16 +51,27 @@ defmodule Pleroma.Mixfile do
   defp elixirc_paths(:test), do: ["lib", "test/support"]
   defp elixirc_paths(_), do: ["lib"]
 
+  # Specifies OAuth dependencies.
+  defp oauth_deps do
+    oauth_strategy_packages =
+      System.get_env("OAUTH_CONSUMER_STRATEGIES")
+      |> to_string()
+      |> String.split()
+      |> Enum.map(fn strategy_entry ->
+        with [_strategy, dependency] <- String.split(strategy_entry, ":") do
+          dependency
+        else
+          [strategy] -> "ueberauth_#{strategy}"
+        end
+      end)
+
+    for s <- oauth_strategy_packages, do: {String.to_atom(s), ">= 0.0.0"}
+  end
+
   # Specifies your project dependencies.
   #
   # Type `mix help deps` for examples and options.
   defp deps do
-    oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
-
-    oauth_deps =
-      for s <- oauth_strategies,
-          do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
-
     [
       {:phoenix, "~> 1.4.1"},
       {:plug_cowboy, "~> 2.0"},
@@ -121,7 +132,7 @@ defmodule Pleroma.Mixfile do
       {:ex_rated, "~> 1.2"},
       {:plug_static_index_html, "~> 1.0.0"},
       {:excoveralls, "~> 0.11.1", only: :test}
-    ] ++ oauth_deps
+    ] ++ oauth_deps()
   end
 
   # Aliases are shortcuts or tasks specific to the current project.
diff --git a/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs b/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs
new file mode 100644 (file)
index 0000000..a88b0ea
--- /dev/null
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.AddNonFollowsAndNonFollowersFieldsToNotificationSettings do
+  use Ecto.Migration
+
+  def change do
+    execute("""
+    update users set info = jsonb_set(info, '{notification_settings}', '{"local": true, "remote": true, "follows": true, "followers": true, "non_follows": true, "non_followers": true}')
+    where local=true
+    """)
+  end
+end
diff --git a/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs b/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs
new file mode 100644 (file)
index 0000000..df4ac77
--- /dev/null
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.AddObjectInReplyToIndex do
+  use Ecto.Migration
+
+  def change do
+    create index(:objects, ["(data->>'inReplyTo')"], name: :objects_in_reply_to_index)
+  end
+end
diff --git a/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs b/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs
new file mode 100644 (file)
index 0000000..c915a02
--- /dev/null
@@ -0,0 +1,8 @@
+defmodule Pleroma.Repo.Migrations.AddTagIndexToObjects do
+  use Ecto.Migration
+
+  def change do
+    drop_if_exists index(:activities, ["(data #> '{\"object\",\"tag\"}')"], using: :gin, name: :activities_tags)
+    create index(:objects, ["(data->'tag')"], using: :gin, name: :objects_tags)
+  end
+end
diff --git a/test/fixtures/httpoison_mock/emelie.json b/test/fixtures/httpoison_mock/emelie.json
new file mode 100644 (file)
index 0000000..592fc0e
--- /dev/null
@@ -0,0 +1 @@
+{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value","Hashtag":"as:Hashtag","Emoji":"toot:Emoji","IdentityProof":"toot:IdentityProof","focalPoint":{"@container":"@list","@id":"toot:focalPoint"}}],"id":"https://mastodon.social/users/emelie","type":"Person","following":"https://mastodon.social/users/emelie/following","followers":"https://mastodon.social/users/emelie/followers","inbox":"https://mastodon.social/users/emelie/inbox","outbox":"https://mastodon.social/users/emelie/outbox","featured":"https://mastodon.social/users/emelie/collections/featured","preferredUsername":"emelie","name":"emelie 🎨","summary":"\u003cp\u003e23 / \u003ca href=\"https://mastodon.social/tags/sweden\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eSweden\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/artist\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eArtist\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/equestrian\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eEquestrian\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/gamedev\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eGameDev\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003eIf I ain\u0026apos;t spending time with my pets, I\u0026apos;m probably drawing. 🐴 🐱 🐰\u003c/p\u003e","url":"https://mastodon.social/@emelie","manuallyApprovesFollowers":false,"publicKey":{"id":"https://mastodon.social/users/emelie#main-key","owner":"https://mastodon.social/users/emelie","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3CWs1oAJPE3ZJ9sj6Ut\n/Mu+mTE7MOijsQc8/6c73XVVuhIEomiozJIH7l8a7S1n5SYL4UuiwcubSOi7u1bb\nGpYnp5TYhN+Cxvq/P80V4/ncNIPSQzS49it7nSLeG5pA21lGPDA44huquES1un6p\n9gSmbTwngVX9oe4MYuUeh0Z7vijjU13Llz1cRq/ZgPQPgfz+2NJf+VeXnvyDZDYx\nZPVBBlrMl3VoGbu0M5L8SjY35559KCZ3woIvqRolcoHXfgvJMdPcJgSZVYxlCw3d\nA95q9jQcn6s87CPSUs7bmYEQCrDVn5m5NER5TzwBmP4cgJl9AaDVWQtRd4jFZNTx\nlQIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[{"type":"Hashtag","href":"https://mastodon.social/explore/sweden","name":"#sweden"},{"type":"Hashtag","href":"https://mastodon.social/explore/gamedev","name":"#gamedev"},{"type":"Hashtag","href":"https://mastodon.social/explore/artist","name":"#artist"},{"type":"Hashtag","href":"https://mastodon.social/explore/equestrian","name":"#equestrian"}],"attachment":[{"type":"PropertyValue","name":"Ko-fi","value":"\u003ca href=\"https://ko-fi.com/emeliepng\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eko-fi.com/emeliepng\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Instagram","value":"\u003ca href=\"https://www.instagram.com/emelie_png/\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003einstagram.com/emelie_png/\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Carrd","value":"\u003ca href=\"https://emelie.carrd.co/\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eemelie.carrd.co/\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Artstation","value":"\u003ca href=\"https://emiri.artstation.com\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eemiri.artstation.com\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"}],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"},"icon":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"},"image":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"}}
\ No newline at end of file
diff --git a/test/fixtures/httpoison_mock/rinpatch.json b/test/fixtures/httpoison_mock/rinpatch.json
new file mode 100644 (file)
index 0000000..59311ec
--- /dev/null
@@ -0,0 +1,64 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://w3id.org/security/v1",
+    {
+      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+      "toot": "http://joinmastodon.org/ns#",
+      "featured": {
+        "@id": "toot:featured",
+        "@type": "@id"
+      },
+      "alsoKnownAs": {
+        "@id": "as:alsoKnownAs",
+        "@type": "@id"
+      },
+      "movedTo": {
+        "@id": "as:movedTo",
+        "@type": "@id"
+      },
+      "schema": "http://schema.org#",
+      "PropertyValue": "schema:PropertyValue",
+      "value": "schema:value",
+      "Hashtag": "as:Hashtag",
+      "Emoji": "toot:Emoji",
+      "IdentityProof": "toot:IdentityProof",
+      "focalPoint": {
+        "@container": "@list",
+        "@id": "toot:focalPoint"
+      }
+    }
+  ],
+  "id": "https://mastodon.sdf.org/users/rinpatch",
+  "type": "Person",
+  "following": "https://mastodon.sdf.org/users/rinpatch/following",
+  "followers": "https://mastodon.sdf.org/users/rinpatch/followers",
+  "inbox": "https://mastodon.sdf.org/users/rinpatch/inbox",
+  "outbox": "https://mastodon.sdf.org/users/rinpatch/outbox",
+  "featured": "https://mastodon.sdf.org/users/rinpatch/collections/featured",
+  "preferredUsername": "rinpatch",
+  "name": "rinpatch",
+  "summary": "<p>umu</p>",
+  "url": "https://mastodon.sdf.org/@rinpatch",
+  "manuallyApprovesFollowers": false,
+  "publicKey": {
+    "id": "https://mastodon.sdf.org/users/rinpatch#main-key",
+    "owner": "https://mastodon.sdf.org/users/rinpatch",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vbhYKDopb5xzfJB2TZY\n0ZvgxqdAhbSKKkQC5Q2b0ofhvueDy2AuZTnVk1/BbHNlqKlwhJUSpA6LiTZVvtcc\nMn6cmSaJJEg30gRF5GARP8FMcuq8e2jmceiW99NnUX17MQXsddSf2JFUwD0rUE8H\nBsgD7UzE9+zlA/PJOTBO7fvBEz9PTQ3r4sRMTJVFvKz2MU/U+aRNTuexRKMMPnUw\nfp6VWh1F44VWJEQOs4tOEjGiQiMQh5OfBk1w2haT3vrDbQvq23tNpUP1cRomLUtx\nEBcGKi5DMMBzE1RTVT1YUykR/zLWlA+JSmw7P6cWtsHYZovs8dgn8Po3X//6N+ng\nTQIDAQAB\n-----END PUBLIC KEY-----\n"
+  },
+  "tag": [],
+  "attachment": [],
+  "endpoints": {
+    "sharedInbox": "https://mastodon.sdf.org/inbox"
+  },
+  "icon": {
+    "type": "Image",
+    "mediaType": "image/jpeg",
+    "url": "https://mastodon.sdf.org/system/accounts/avatars/000/067/580/original/bf05521bf711b7a0.jpg?1533238802"
+  },
+  "image": {
+    "type": "Image",
+    "mediaType": "image/gif",
+    "url": "https://mastodon.sdf.org/system/accounts/headers/000/067/580/original/a99b987e798f7063.gif?1533278217"
+  }
+}
diff --git a/test/fixtures/mastodon-question-activity.json b/test/fixtures/mastodon-question-activity.json
new file mode 100644 (file)
index 0000000..ac329c7
--- /dev/null
@@ -0,0 +1,99 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "Hashtag": "as:Hashtag",
+      "toot": "http://joinmastodon.org/ns#",
+      "Emoji": "toot:Emoji",
+      "focalPoint": {
+        "@container": "@list",
+        "@id": "toot:focalPoint"
+      }
+    }
+  ],
+  "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/activity",
+  "type": "Create",
+  "actor": "https://mastodon.sdf.org/users/rinpatch",
+  "published": "2019-05-10T09:03:36Z",
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "cc": [
+    "https://mastodon.sdf.org/users/rinpatch/followers"
+  ],
+  "object": {
+    "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304",
+    "type": "Question",
+    "summary": null,
+    "inReplyTo": null,
+    "published": "2019-05-10T09:03:36Z",
+    "url": "https://mastodon.sdf.org/@rinpatch/102070944809637304",
+    "attributedTo": "https://mastodon.sdf.org/users/rinpatch",
+    "to": [
+      "https://www.w3.org/ns/activitystreams#Public"
+    ],
+    "cc": [
+      "https://mastodon.sdf.org/users/rinpatch/followers"
+    ],
+    "sensitive": false,
+    "atomUri": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304",
+    "inReplyToAtomUri": null,
+    "conversation": "tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation",
+    "content": "<p>Why is Tenshi eating a corndog so cute?</p>",
+    "contentMap": {
+      "en": "<p>Why is Tenshi eating a corndog so cute?</p>"
+    },
+    "endTime": "2019-05-11T09:03:36Z",
+    "closed": "2019-05-11T09:03:36Z",
+    "attachment": [],
+    "tag": [],
+    "replies": {
+      "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies",
+      "type": "Collection",
+      "first": {
+        "type": "CollectionPage",
+        "partOf": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies",
+        "items": []
+      }
+    },
+    "oneOf": [
+      {
+        "type": "Note",
+        "name": "Dunno",
+        "replies": {
+          "type": "Collection",
+          "totalItems": 0
+        }
+      },
+      {
+        "type": "Note",
+        "name": "Everyone knows that!",
+        "replies": {
+          "type": "Collection",
+          "totalItems": 1
+        }
+      },
+      {
+        "type": "Note",
+        "name": "25 char limit is dumb",
+        "replies": {
+          "type": "Collection",
+          "totalItems": 0
+        }
+      },
+      {
+        "type": "Note",
+        "name": "I can't even fit a funny",
+        "replies": {
+          "type": "Collection",
+          "totalItems": 1
+        }
+      }
+    ]
+  }
+}
diff --git a/test/fixtures/mastodon-vote.json b/test/fixtures/mastodon-vote.json
new file mode 100644 (file)
index 0000000..c2c5f40
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "actor": "https://mastodon.sdf.org/users/rinpatch",
+  "id": "https://mastodon.sdf.org/users/rinpatch#votes/387/activity",
+  "nickname": "rin",
+  "object": {
+    "attributedTo": "https://mastodon.sdf.org/users/rinpatch",
+    "id": "https://mastodon.sdf.org/users/rinpatch#votes/387",
+    "inReplyTo": "https://testing.uguu.ltd/objects/9d300947-2dcb-445d-8978-9a3b4b84fa14",
+    "name": "suya..",
+    "to": "https://testing.uguu.ltd/users/rin",
+    "type": "Note"
+  },
+  "to": "https://testing.uguu.ltd/users/rin",
+  "type": "Create"
+}
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>
index 47b91b121fe944528f2a71b9fc569c353ea07885..bfa673049843bcc779d249cd6dc16925eb4ac04f 100644 (file)
@@ -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
index 581db58a80296f51817ed2260a0b874440bd6eb8..be292abd9ffce0e6cef082965aa1e1fce4dd2733 100644 (file)
@@ -78,33 +78,6 @@ defmodule Pleroma.NotificationTest do
       assert nil == Notification.create_notification(activity, muter)
     end
 
-    test "it disables notifications from people on remote instances" do
-      user = insert(:user, info: %{notification_settings: %{"remote" => false}})
-      other_user = insert(:user)
-
-      create_activity = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "type" => "Create",
-        "to" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "actor" => other_user.ap_id,
-        "object" => %{
-          "type" => "Note",
-          "content" => "Hi @#{user.nickname}",
-          "attributedTo" => other_user.ap_id
-        }
-      }
-
-      {:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity)
-      assert nil == Notification.create_notification(activity, user)
-    end
-
-    test "it disables notifications from people on the local instance" do
-      user = insert(:user, info: %{notification_settings: %{"local" => false}})
-      other_user = insert(:user)
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
-      assert nil == Notification.create_notification(activity, user)
-    end
-
     test "it disables notifications from followers" do
       follower = insert(:user)
       followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
@@ -113,6 +86,13 @@ defmodule Pleroma.NotificationTest do
       assert nil == Notification.create_notification(activity, followed)
     end
 
+    test "it disables notifications from non-followers" do
+      follower = insert(:user)
+      followed = insert(:user, info: %{notification_settings: %{"non_followers" => false}})
+      {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
+      assert nil == Notification.create_notification(activity, followed)
+    end
+
     test "it disables notifications from people the user follows" do
       follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
       followed = insert(:user)
@@ -122,6 +102,13 @@ defmodule Pleroma.NotificationTest do
       assert nil == Notification.create_notification(activity, follower)
     end
 
+    test "it disables notifications from people the user does not follow" do
+      follower = insert(:user, info: %{notification_settings: %{"non_follows" => false}})
+      followed = insert(:user)
+      {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
+      assert nil == Notification.create_notification(activity, follower)
+    end
+
     test "it doesn't create a notification for user if he is the activity author" do
       activity = insert(:note_activity)
       author = User.get_cached_by_ap_id(activity.data["actor"])
index 4520640939f7d72b26193be97fdc18af30a93558..a7a046203a5d24ef5b8aed51f0b2fed89654dde0 100644 (file)
@@ -6,6 +6,11 @@ defmodule Pleroma.Object.ContainmentTest do
 
   import Pleroma.Factory
 
+  setup_all do
+    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+    :ok
+  end
+
   describe "general origin containment" do
     test "contain_origin_from_id() catches obvious spoofing attempts" do
       data = %{
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 5b355bfe6088c8f87f214baa992116a88eed05e9..67ef0928a1afaf7392b2180faedd82a7c0fc2b96 100644 (file)
@@ -52,6 +52,14 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/httpoison_mock/rinpatch.json")
+     }}
+  end
+
   def get(
         "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
         _,
@@ -235,6 +243,14 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("https://n1u.moe/users/rye", _, _, Accept: "application/activity+json") do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/httpoison_mock/rye.json")
+     }}
+  end
+
   def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _, _) do
     {:ok,
      %Tesla.Env{
@@ -294,6 +310,10 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("http://mastodon.example.org/users/gargron", _, _, Accept: "application/activity+json") do
+    {:error, :nxdomain}
+  end
+
   def get(
         "http://mastodon.example.org/@admin/99541947525187367",
         _,
@@ -538,6 +558,15 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get(
+        "http://gs.example.org:4040/index.php/user/1",
+        _,
+        _,
+        Accept: "application/activity+json"
+      ) do
+    {:ok, %Tesla.Env{status: 406, body: ""}}
+  end
+
   def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _, _, _) do
     {:ok,
      %Tesla.Env{
@@ -728,6 +757,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
index 30adfda36ac6647bad317a420a2f42726a4b8098..8b323372954bf9be125ac1658631a39c02fe2183 100644 (file)
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectView
   alias Pleroma.Web.ActivityPub.UserView
+  alias Pleroma.Web.ActivityPub.Utils
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -234,13 +235,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
   end
 
   describe "/users/:nickname/inbox" do
-    test "it inserts an incoming activity into the database", %{conn: conn} do
-      user = insert(:user)
-
+    setup do
       data =
         File.read!("test/fixtures/mastodon-post-activity.json")
         |> Poison.decode!()
-        |> Map.put("bcc", [user.ap_id])
+
+      [data: data]
+    end
+
+    test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
+      user = insert(:user)
+      data = Map.put(data, "bcc", [user.ap_id])
 
       conn =
         conn
@@ -253,16 +258,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert Activity.get_by_ap_id(data["id"])
     end
 
-    test "it accepts messages from actors that are followed by the user", %{conn: conn} do
+    test "it accepts messages from actors that are followed by the user", %{
+      conn: conn,
+      data: data
+    } do
       recipient = insert(:user)
       actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
 
       {:ok, recipient} = User.follow(recipient, actor)
 
-      data =
-        File.read!("test/fixtures/mastodon-post-activity.json")
-        |> Poison.decode!()
-
       object =
         data["object"]
         |> Map.put("attributedTo", actor.ap_id)
@@ -309,13 +313,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert response(conn, 200) =~ note_activity.data["object"]["content"]
     end
 
-    test "it clears `unreachable` federation status of the sender", %{conn: conn} do
+    test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
       user = insert(:user)
-
-      data =
-        File.read!("test/fixtures/mastodon-post-activity.json")
-        |> Poison.decode!()
-        |> Map.put("bcc", [user.ap_id])
+      data = Map.put(data, "bcc", [user.ap_id])
 
       sender_host = URI.parse(data["actor"]).host
       Instances.set_consistently_unreachable(sender_host)
@@ -330,6 +330,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert "ok" == json_response(conn, 200)
       assert Instances.reachable?(sender_host)
     end
+
+    test "it removes all follower collections but actor's", %{conn: conn} do
+      [actor, recipient] = insert_pair(:user)
+
+      data =
+        File.read!("test/fixtures/activitypub-client-post-activity.json")
+        |> Poison.decode!()
+
+      object = Map.put(data["object"], "attributedTo", actor.ap_id)
+
+      data =
+        data
+        |> Map.put("id", Utils.generate_object_id())
+        |> Map.put("actor", actor.ap_id)
+        |> Map.put("object", object)
+        |> Map.put("cc", [
+          recipient.follower_address,
+          actor.follower_address
+        ])
+        |> Map.put("to", [
+          recipient.ap_id,
+          recipient.follower_address,
+          "https://www.w3.org/ns/activitystreams#Public"
+        ])
+
+      conn
+      |> assign(:valid_signature, true)
+      |> put_req_header("content-type", "application/activity+json")
+      |> post("/users/#{recipient.nickname}/inbox", data)
+      |> json_response(200)
+
+      activity = Activity.get_by_ap_id(data["id"])
+
+      assert activity.id
+      assert actor.follower_address in activity.recipients
+      assert actor.follower_address in activity.data["cc"]
+
+      refute recipient.follower_address in activity.recipients
+      refute recipient.follower_address in activity.data["cc"]
+      refute recipient.follower_address in activity.data["to"]
+    end
   end
 
   describe "/users/:nickname/outbox" do
index 23b11d0153129462ca2915d70c56941a5747353e..2bed1563937d3a3dd5f7d409bb212d2d84f6479e 100644 (file)
@@ -1201,4 +1201,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
index 3d1f26e60343c63f09e06c723350b5d67f894c35..0fd68e103814e73f57bdf0d8753cc8d0cf01ec17 100644 (file)
@@ -145,6 +145,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
 
       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
diff --git a/test/web/activity_pub/mrf/subchain_policy_test.exs b/test/web/activity_pub/mrf/subchain_policy_test.exs
new file mode 100644 (file)
index 0000000..f7cbcad
--- /dev/null
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Web.ActivityPub.MRF.DropPolicy
+  alias Pleroma.Web.ActivityPub.MRF.SubchainPolicy
+
+  @message %{
+    "actor" => "https://banned.com",
+    "type" => "Create",
+    "object" => %{"content" => "hi"}
+  }
+
+  test "it matches and processes subchains when the actor matches a configured target" do
+    Pleroma.Config.put([:mrf_subchain, :match_actor], %{
+      ~r/^https:\/\/banned.com/s => [DropPolicy]
+    })
+
+    {:reject, _} = SubchainPolicy.filter(@message)
+  end
+
+  test "it doesn't match and process subchains when the actor doesn't match a configured target" do
+    Pleroma.Config.put([:mrf_subchain, :match_actor], %{
+      ~r/^https:\/\/borked.com/s => [DropPolicy]
+    })
+
+    {:ok, _message} = SubchainPolicy.filter(@message)
+  end
+end
index e93189df65ac5a81ce5a85ab8558b55e216ebfcd..e6388f88af2671e9f9fc77dde4b89bf5934299ec 100644 (file)
@@ -113,6 +113,55 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert Enum.at(object.data["tag"], 2) == "moo"
     end
 
+    test "it works for incoming questions" do
+      data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
+
+      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+      object = Object.normalize(activity)
+
+      assert Enum.all?(object.data["oneOf"], fn choice ->
+               choice["name"] in [
+                 "Dunno",
+                 "Everyone knows that!",
+                 "25 char limit is dumb",
+                 "I can't even fit a funny"
+               ]
+             end)
+    end
+
+    test "it rewrites Note votes to Answers and increments vote counters on question activities" do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "suya...",
+          "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
+        })
+
+      object = Object.normalize(activity)
+
+      data =
+        File.read!("test/fixtures/mastodon-vote.json")
+        |> Poison.decode!()
+        |> Kernel.put_in(["to"], user.ap_id)
+        |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
+        |> Kernel.put_in(["object", "to"], user.ap_id)
+
+      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+      answer_object = Object.normalize(activity)
+      assert answer_object.data["type"] == "Answer"
+      object = Object.get_by_ap_id(object.data["id"])
+
+      assert Enum.any?(
+               object.data["oneOf"],
+               fn
+                 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
+                 _ -> false
+               end
+             )
+    end
+
     test "it works for incoming notices with contentMap" do
       data =
         File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
@@ -1221,4 +1270,85 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
     end
   end
+
+  test "Rewrites Answers to Notes" do
+    user = insert(:user)
+
+    {:ok, poll_activity} =
+      CommonAPI.post(user, %{
+        "status" => "suya...",
+        "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
+      })
+
+    poll_object = Object.normalize(poll_activity)
+    # TODO: Replace with CommonAPI vote creation when implemented
+    data =
+      File.read!("test/fixtures/mastodon-vote.json")
+      |> Poison.decode!()
+      |> Kernel.put_in(["to"], user.ap_id)
+      |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
+      |> Kernel.put_in(["object", "to"], user.ap_id)
+
+    {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+    assert data["object"]["type"] == "Note"
+  end
+
+  describe "fix_explicit_addressing" do
+    setup do
+      user = insert(:user)
+      [user: user]
+    end
+
+    test "moves non-explicitly mentioned actors to cc", %{user: user} do
+      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", %{user: user} do
+      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
+
+    test "removes recipient's follower collection from cc", %{user: user} do
+      recipient = insert(:user)
+
+      object = %{
+        "actor" => user.ap_id,
+        "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
+        "cc" => [user.follower_address, recipient.follower_address]
+      }
+
+      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+
+      assert user.follower_address in fixed_object["cc"]
+      refute recipient.follower_address in fixed_object["cc"]
+      refute recipient.follower_address in fixed_object["to"]
+    end
+  end
 end
index e2584f635f881e15d8c12a2cd59c7cadb76f85f9..466d980dccb269685e016b37371496627fca63d7 100644 (file)
@@ -117,4 +117,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
     assert Visibility.get_visibility(direct) == "direct"
     assert Visibility.get_visibility(unlisted) == "unlisted"
   end
+
+  test "get_visibility with directMessage flag" do
+    assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct"
+  end
 end
index c15c67e313e4c4c25be4164fd64417377aa3a679..43dcf945a6c93eabf99bcae408434b5cca66b6e8 100644 (file)
@@ -437,27 +437,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
 
@@ -659,35 +663,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
 
@@ -698,27 +706,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
 
@@ -753,27 +765,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
 
index aaf2261bbd2f6f98e76c73fca2edd744a2bbca13..1611d937e542720605950a5c610cf90b6a8f45f4 100644 (file)
@@ -78,10 +78,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     user = insert(:user)
 
     notification_settings = %{
-      "remote" => true,
-      "local" => true,
       "followers" => true,
-      "follows" => true
+      "follows" => true,
+      "non_follows" => true,
+      "non_followers" => true
     }
 
     privacy = user.info.default_scope
@@ -239,4 +239,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
     assert expected == AccountView.render("account.json", %{user: user, for: other_user})
   end
+
+  test "returns the settings store if the requesting user is the represented user and it's requested specifically" do
+    user = insert(:user, %{info: %User.Info{pleroma_settings_store: %{fe: "test"}}})
+
+    result =
+      AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
+
+    assert result.pleroma.settings_store == %{:fe => "test"}
+
+    result = AccountView.render("account.json", %{user: user, with_pleroma_settings: true})
+    assert result.pleroma[:settings_store] == nil
+
+    result = AccountView.render("account.json", %{user: user, for: user})
+    assert result.pleroma[:settings_store] == nil
+  end
 end
index 1d9f5a816c084551fe1a4399b0a6fbbd6a901203..b0cde649d8a9a61fae14ebdf26bce42b68a9b7dc 100644 (file)
@@ -146,6 +146,103 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     refute id == third_id
   end
 
+  describe "posting polls" do
+    test "posting a poll", %{conn: conn} do
+      user = insert(:user)
+      time = NaiveDateTime.utc_now()
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "Who is the #bestgrill?",
+          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
+        })
+
+      response = json_response(conn, 200)
+
+      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
+               title in ["Rei", "Asuka", "Misato"]
+             end)
+
+      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
+      refute response["poll"]["expred"]
+    end
+
+    test "option limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "desu~",
+          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Poll can't contain more than #{limit} options"
+    end
+
+    test "option character limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "...",
+          "poll" => %{
+            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
+            "expires_in" => 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Poll options cannot be longer than #{limit} characters each"
+    end
+
+    test "minimal date limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "imagine arbitrary limits",
+          "poll" => %{
+            "options" => ["this post was made by pleroma gang"],
+            "expires_in" => limit - 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Expiration date is too soon"
+    end
+
+    test "maximum date limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "imagine arbitrary limits",
+          "poll" => %{
+            "options" => ["this post was made by pleroma gang"],
+            "expires_in" => limit + 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Expiration date is too far in the future"
+    end
+  end
+
   test "posting a sensitive status", %{conn: conn} do
     user = insert(:user)
 
@@ -317,12 +414,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   test "Conversations", %{conn: conn} do
     user_one = insert(:user)
     user_two = insert(:user)
+    user_three = insert(:user)
 
     {:ok, user_two} = User.follow(user_two, user_one)
 
     {:ok, direct} =
       CommonAPI.post(user_one, %{
-        "status" => "Hi @#{user_two.nickname}!",
+        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
         "visibility" => "direct"
       })
 
@@ -348,7 +446,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
              }
            ] = response
 
+    account_ids = Enum.map(res_accounts, & &1["id"])
     assert length(res_accounts) == 2
+    assert user_two.id in account_ids
+    assert user_three.id in account_ids
     assert is_binary(res_id)
     assert unread == true
     assert res_last_status["id"] == direct.id
@@ -2322,6 +2423,66 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   end
 
   describe "updating credentials" do
+    test "sets user settings in a generic way", %{conn: conn} do
+      user = insert(:user)
+
+      res_conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{
+          "pleroma_settings_store" => %{
+            pleroma_fe: %{
+              theme: "bla"
+            }
+          }
+        })
+
+      assert user = json_response(res_conn, 200)
+      assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
+
+      user = Repo.get(User, user["id"])
+
+      res_conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{
+          "pleroma_settings_store" => %{
+            masto_fe: %{
+              theme: "bla"
+            }
+          }
+        })
+
+      assert user = json_response(res_conn, 200)
+
+      assert user["pleroma"]["settings_store"] ==
+               %{
+                 "pleroma_fe" => %{"theme" => "bla"},
+                 "masto_fe" => %{"theme" => "bla"}
+               }
+
+      user = Repo.get(User, user["id"])
+
+      res_conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{
+          "pleroma_settings_store" => %{
+            masto_fe: %{
+              theme: "blub"
+            }
+          }
+        })
+
+      assert user = json_response(res_conn, 200)
+
+      assert user["pleroma"]["settings_store"] ==
+               %{
+                 "pleroma_fe" => %{"theme" => "bla"},
+                 "masto_fe" => %{"theme" => "blub"}
+               }
+    end
+
     test "updates the user's bio", %{conn: conn} do
       user = insert(:user)
       user2 = insert(:user)
@@ -2538,7 +2699,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
              "stats" => _,
              "thumbnail" => _,
              "languages" => _,
-             "registrations" => _
+             "registrations" => _,
+             "poll_limits" => _
            } = result
 
     assert email == from_config_email
@@ -2684,33 +2846,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} =
@@ -2722,9 +2901,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"})
 
-      Pleroma.Config.put([:rich_media, :enabled], false)
+      response =
+        conn
+        |> get("/api/v1/statuses/#{activity.id}/card")
+        |> json_response(:ok)
+
+      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
 
@@ -2811,31 +3014,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
-  test "flavours switching (Pleroma Extension)", %{conn: conn} do
-    user = insert(:user)
-
-    get_old_flavour =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/pleroma/flavour")
-
-    assert "glitch" == json_response(get_old_flavour, 200)
-
-    set_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert "vanilla" == json_response(set_flavour, 200)
-
-    get_new_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
-  end
-
   describe "reports" do
     setup do
       reporter = insert(:user)
@@ -3421,4 +3599,124 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
     end
   end
+
+  describe "GET /api/v1/polls/:id" do
+    test "returns poll entity for object id", %{conn: conn} do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Pleroma does",
+          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/polls/#{object.id}")
+
+      response = json_response(conn, 200)
+      id = object.id
+      assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
+    end
+
+    test "does not expose polls for private statuses", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Pleroma does",
+          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
+          "visibility" => "private"
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/polls/#{object.id}")
+
+      assert json_response(conn, 404)
+    end
+  end
+
+  describe "POST /api/v1/polls/:id/votes" do
+    test "votes are added to the poll", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "A very delicious sandwich",
+          "poll" => %{
+            "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
+            "expires_in" => 20,
+            "multiple" => true
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, other_user)
+        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
+
+      assert json_response(conn, 200)
+      object = Object.get_by_id(object.id)
+
+      assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+               total_items == 1
+             end)
+    end
+
+    test "author can't vote", %{conn: conn} do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Am I cute?",
+          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      assert conn
+             |> assign(:user, user)
+             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
+             |> json_response(422) == %{"error" => "Poll's author can't vote"}
+
+      object = Object.get_by_id(object.id)
+
+      refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
+    end
+
+    test "does not allow multiple choices on a single-choice question", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "The glass is",
+          "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      assert conn
+             |> assign(:user, other_user)
+             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
+             |> json_response(422) == %{"error" => "Too many choices"}
+
+      object = Object.get_by_id(object.id)
+
+      refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+               total_items == 1
+             end)
+    end
+  end
 end
index d7c800e83a54d04a210d7f8894a6583af74bb66e..ec75150ab88c377099eaa7aefed0cef5a69ad732 100644 (file)
@@ -103,6 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       muted: false,
       pinned: false,
       sensitive: false,
+      poll: nil,
       spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),
       visibility: "public",
       media_attachments: [],
@@ -341,4 +342,106 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
         StatusView.render("card.json", %{page_url: page_url, rich_media: card})
     end
   end
+
+  describe "poll view" do
+    test "renders a poll" do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Is Tenshi eating a corndog cute?",
+          "poll" => %{
+            "options" => ["absolutely!", "sure", "yes", "why are you even asking?"],
+            "expires_in" => 20
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      expected = %{
+        emojis: [],
+        expired: false,
+        id: object.id,
+        multiple: false,
+        options: [
+          %{title: "absolutely!", votes_count: 0},
+          %{title: "sure", votes_count: 0},
+          %{title: "yes", votes_count: 0},
+          %{title: "why are you even asking?", votes_count: 0}
+        ],
+        voted: false,
+        votes_count: 0
+      }
+
+      result = StatusView.render("poll.json", %{object: object})
+      expires_at = result.expires_at
+      result = Map.delete(result, :expires_at)
+
+      assert result == expected
+
+      expires_at = NaiveDateTime.from_iso8601!(expires_at)
+      assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20
+    end
+
+    test "detects if it is multiple choice" do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Which Mastodon developer is your favourite?",
+          "poll" => %{
+            "options" => ["Gargron", "Eugen"],
+            "expires_in" => 20,
+            "multiple" => true
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      assert %{multiple: true} = StatusView.render("poll.json", %{object: object})
+    end
+
+    test "detects emoji" do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "What's with the smug face?",
+          "poll" => %{
+            "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"],
+            "expires_in" => 20
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      assert %{emojis: [%{shortcode: "blank"}]} =
+               StatusView.render("poll.json", %{object: object})
+    end
+
+    test "detects vote status" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Which input devices do you use?",
+          "poll" => %{
+            "options" => ["mouse", "trackball", "trackpoint"],
+            "multiple" => true,
+            "expires_in" => 20
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2])
+
+      result = StatusView.render("poll.json", %{object: object, for: other_user})
+
+      assert result[:voted] == true
+      assert Enum.at(result[:options], 1)[:votes_count] == 1
+      assert Enum.at(result[:options], 2)[:votes_count] == 1
+    end
+  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 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 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 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 2cd82b3e77839a2d9774030d0ca58db074304e8a..cab9e5d904f79d0725e4c71fdeee467f2878f0ea 100644 (file)
@@ -102,7 +102,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       conn
       |> assign(:user, user)
       |> put("/api/pleroma/notification_settings", %{
-        "remote" => false,
         "followers" => false,
         "bar" => 1
       })
@@ -110,8 +109,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
       user = Repo.get(User, user.id)
 
-      assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
-               user.info.notification_settings
+      assert %{
+               "followers" => false,
+               "follows" => true,
+               "non_follows" => true,
+               "non_followers" => true
+             } == user.info.notification_settings
     end
   end
 
index 74526673c2959034b72b84618bbc194be36fd1ad..a48fc9b784ddeb6207e411210178504ad926c510 100644 (file)
@@ -112,9 +112,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
     as_user = UserView.render("show.json", %{user: user, for: user})
     assert as_user["default_scope"] == user.info.default_scope
     assert as_user["no_rich_text"] == user.info.no_rich_text
+    assert as_user["pleroma"]["notification_settings"] == user.info.notification_settings
     as_stranger = UserView.render("show.json", %{user: user})
     refute as_stranger["default_scope"]
     refute as_stranger["no_rich_text"]
+    refute as_stranger["pleroma"]["notification_settings"]
   end
 
   test "A user for a given other follower", %{user: user} do
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