diff options
103 files changed, 3064 insertions, 721 deletions
| diff --git a/.buildpacks b/.buildpacks new file mode 100644 index 000000000..31dd57096 --- /dev/null +++ b/.buildpacks @@ -0,0 +1 @@ +https://github.com/hashnuke/heroku-buildpack-elixir   diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8b5131dc3..58c9de167 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b8907a23f..99b42e280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 index 000000000..7ac187baa --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: mix phx.server +release: mix ecto.migrate diff --git a/config/config.exs b/config/config.exs index e90821d66..7d70c1a5e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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 index 000000000..9ea0ec450 --- /dev/null +++ b/config/dokku.exs @@ -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" diff --git a/config/test.exs b/config/test.exs index 6100989c4..41cddb9bd 100644 --- a/config/test.exs +++ b/config/test.exs @@ -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 diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 946e0e885..27463f8f3 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -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 diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index 4d99a2d2b..edc62727a 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -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` diff --git a/docs/config.md b/docs/config.md index 67b062fe9..718a7912a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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 diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md index c493816d6..e1d69c873 100644 --- a/docs/installation/alpine_linux_en.md +++ b/docs/installation/alpine_linux_en.md @@ -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: diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md index 2b040cfbc..26e1ab86a 100644 --- a/docs/installation/arch_linux_en.md +++ b/docs/installation/arch_linux_en.md @@ -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: diff --git a/docs/installation/centos7_en.md b/docs/installation/centos7_en.md index 76de21ed8..19bff7461 100644 --- a/docs/installation/centos7_en.md +++ b/docs/installation/centos7_en.md @@ -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: diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md index 9c0ef92d4..7d39ca5f9 100644 --- a/docs/installation/debian_based_en.md +++ b/docs/installation/debian_based_en.md @@ -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: diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index 41cce6792..84b9666c8 100644 --- a/docs/installation/debian_based_jp.md +++ b/docs/installation/debian_based_jp.md @@ -69,7 +69,7 @@ cd ~  *  Gitリポジトリをクローンします。  ``` -git clone https://git.pleroma.social/pleroma/pleroma +git clone -b master https://git.pleroma.social/pleroma/pleroma  ```  *  新しいディレクトリに移動します。 diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md index fccaad378..b7c42a477 100644 --- a/docs/installation/gentoo_en.md +++ b/docs/installation/gentoo_en.md @@ -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: diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md index e0ac98359..a096d5354 100644 --- a/docs/installation/netbsd_en.md +++ b/docs/installation/netbsd_en.md @@ -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: diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md index 633b08e6c..fcba38b2c 100644 --- a/docs/installation/openbsd_en.md +++ b/docs/installation/openbsd_en.md @@ -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:   diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md index fa6faa62d..39819a8c8 100644 --- a/docs/installation/openbsd_fi.md +++ b/docs/installation/openbsd_fi.md @@ -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 index 000000000..c23b08fb8 --- /dev/null +++ b/elixir_buildpack.config @@ -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 index 000000000..d7567321f --- /dev/null +++ b/installation/pleroma-mongooseim.cfg @@ -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, "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"} +%   ]} +% ]}, + +  {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, "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"}, +%       {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: +%%%. diff --git a/installation/pleroma.vcl b/installation/pleroma.vcl index 92153d8ef..154747aa6 100644 --- a/installation/pleroma.vcl +++ b/installation/pleroma.vcl @@ -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); +} diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex index 646fb3b9d..32aafc210 100644 --- a/lib/healthcheck.ex +++ b/lib/healthcheck.ex @@ -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 -> diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index 238c1acf2..bc97b39ca 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -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) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 6390cce4c..7d12eff7f 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -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") ++ diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 3e3b9fe97..607843a5b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -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/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index d1da746de..e5e78ee4f 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -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 diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index 558005c19..c216cdcb1 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -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 diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index c5f720bc9..c96ee7353 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -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 diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 844264307..46f2107b1 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -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"] diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index cc6fc9c5d..4b181ec59 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -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 diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index bb9388d4f..ca980c629 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -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"}]             ), diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index effc154bf..4dc4e9279 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -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 diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index a3f177fec..285d57309 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -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)), diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 190ed9f3a..237544337 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -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}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 653dec95f..474cd8c1a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -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 diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 6397e2737..fb9ab92ab 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -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} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 48aaabe94..47115aa6e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index ad2ca1e54..0182bda46 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index 1aaa20050..10ceef715 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -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] diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 890d70a7a..433d23c5f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -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 index 000000000..765704389 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index fdebdf85c..f376e5618 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -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,                 [ diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 49d1610a7..d22d24479 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -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"] diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index ca8a0844b..b8159e9e5 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 93b50ee47..8965e3253 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -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" diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index e8199200e..85fb32669 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -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(          %{ diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d97a80dd5..9c92c6cea 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -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 diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 9ef30e885..bd76e4295 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -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, []}    ) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 6b0b75284..f4c9fe284 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 1ec0f30a1..fe2fdcea1 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -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: [ diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b82d3319b..dc32a1525 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -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) diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 8e8f7cf31..af1dcf66d 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -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        }) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e55f9b96e..6836d331a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -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) diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 5762e767b..cee6d8481 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -12,25 +12,27 @@ defmodule Pleroma.Web.MediaProxy do    def url("/" <> _ = url), do: url    def url(url) do -    config = Application.get_env(:pleroma, :media_proxy, []) -    domain = URI.parse(url).host +    if !enabled?() or local?(url) or whitelisted?(url) do +      url +    else +      encode_url(url) +    end +  end -    cond do -      !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) -> -        url +  defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false) -      Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> -        String.equivalent?(domain, pattern) -      end) -> -        url +  defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) -      true -> -        encode_url(url) -    end +  defp whitelisted?(url) do +    %{host: domain} = URI.parse(url) + +    Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> +      String.equivalent?(domain, pattern) +    end)    end    def encode_url(url) do -    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] +    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])      # Must preserve `%2F` for compatibility with S3      # https://git.pleroma.social/pleroma/pleroma/issues/580 @@ -52,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy do    end    def decode_url(sig, url) do -    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] +    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])      sig = Base.url_decode64!(sig, @base64_opts)      local_sig = :crypto.hmac(:sha, secret, url) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 3bf2a0fbc..57f5b61bb 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -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 diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 61515b31e..6ed089d84 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -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 diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 62e8fa610..e4595800c 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -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)}"} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 352268b96..e699f6ae2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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) diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 9fefdbe25..19e3ef401 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -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"}] diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 3389c91cc..b3cf9ed11 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -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 { @@ -63,13 +63,14 @@        .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 { @@ -80,13 +81,22 @@        }        .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;        } @@ -95,10 +105,12 @@        }        [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; @@ -128,7 +140,8 @@          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, @@ -147,8 +160,8 @@          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; @@ -171,12 +184,27 @@            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 { @@ -194,7 +222,7 @@    </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> diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex index 5659c7828..3325beca1 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex @@ -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'> @@ -19,10 +19,10 @@  <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='{"locale":"en"}' id='mastodon'> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex index e6cfe108b..c9ec1ecbf 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex @@ -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> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 4bcda7300..4a0718851 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -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 %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 3e360a52c..b17142ff8 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -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 %> + diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 31e86685a..1b6b33e69 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -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 diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index f0a4ddbd3..550f35f5f 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -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 diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index c5b7d4acb..3fca72de8 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -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"             ), diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index 7ad0414ab..b61f388b8 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -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,               [ @@ -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 index 000000000..a88b0ea61 --- /dev/null +++ b/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs @@ -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 index 000000000..df4ac7782 --- /dev/null +++ b/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs @@ -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 index 000000000..c915a0213 --- /dev/null +++ b/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs @@ -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 index 000000000..592fc0e4e --- /dev/null +++ b/test/fixtures/httpoison_mock/emelie.json @@ -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 index 000000000..59311ecb6 --- /dev/null +++ b/test/fixtures/httpoison_mock/rinpatch.json @@ -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 index 000000000..ac329c7d5 --- /dev/null +++ b/test/fixtures/mastodon-question-activity.json @@ -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 index 000000000..c2c5f40c0 --- /dev/null +++ b/test/fixtures/mastodon-vote.json @@ -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 index 000000000..5746dc2f4 --- /dev/null +++ b/test/fixtures/rich_media/ogp-missing-data.html @@ -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> diff --git a/test/fixtures/rich_media/ogp.html b/test/fixtures/rich_media/ogp.html index c886b5871..4b5a33595 100644 --- a/test/fixtures/rich_media/ogp.html +++ b/test/fixtures/rich_media/ogp.html @@ -5,5 +5,6 @@      <meta property="og:type" content="video.movie" />      <meta property="og:url" content="http://www.imdb.com/title/tt0117500/" />      <meta property="og:image" content="http://ia.media-imdb.com/images/rock.jpg" /> +    <meta property="og:description" content="Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.">    </head>  </html> diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 47b91b121..bfa673049 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -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 diff --git a/test/notification_test.exs b/test/notification_test.exs index 581db58a8..be292abd9 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -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"]) diff --git a/test/object/containment_test.exs b/test/object/containment_test.exs index 452064093..a7a046203 100644 --- a/test/object/containment_test.exs +++ b/test/object/containment_test.exs @@ -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 index 000000000..45151b289 --- /dev/null +++ b/test/plugs/cache_control_test.exs @@ -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 diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 5b355bfe6..67ef0928a 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -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 index 9c0f2f323..000000000 --- a/test/support/ostatus_mock.ex +++ /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 index e3d5a5b16..000000000 --- a/test/support/websub_mock.ex +++ /dev/null @@ -1,9 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.WebsubMock do -  def verify(sub) do -    {:ok, sub} -  end -end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 30adfda36..8b3233729 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -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 diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 23b11d015..2bed15639 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -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 diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 3d1f26e60..0fd68e103 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -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 index 000000000..f7cbcad48 --- /dev/null +++ b/test/web/activity_pub/mrf/subchain_policy_test.exs @@ -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 diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index e93189df6..e6388f88a 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -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 diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index e2584f635..466d980dc 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -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 diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index c15c67e31..43dcf945a 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -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 diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index aaf2261bb..1611d937e 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -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 diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 1d9f5a816..b0cde649d 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -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 diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index d7c800e83..ec75150ab 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -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 diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 2fc42b7cc..be1173513 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -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") diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs index 612db7e32..530562325 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/web/plugs/federating_plug_test.exs @@ -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 diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index 47b127cf9..3a9cc1854 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -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/"                }} diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index e194f14fb..bcd0f522d 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -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 diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 2cd82b3e7..cab9e5d90 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -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 diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 74526673c..a48fc9b78 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -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 diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index 1e69ed01a..f79745d58 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -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 | 
