diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..c5ef89b86
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,12 @@
+.*
+*.md
+AGPL-3
+CC-BY-SA-4.0
+COPYING
+*file
+elixir_buildpack.config
+docs/
+test/
+
+# Required to get version
+!.git
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..e4ca5f9b5
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,2 @@
+Ariadne Conill
+Ariadne Conill
diff --git a/CC-BY-NC-ND-4.0 b/CC-BY-NC-ND-4.0
deleted file mode 100644
index 486544290..000000000
--- a/CC-BY-NC-ND-4.0
+++ /dev/null
@@ -1,403 +0,0 @@
-Attribution-NonCommercial-NoDerivatives 4.0 International
-
-=======================================================================
-
-Creative Commons Corporation ("Creative Commons") is not a law firm and
-does not provide legal services or legal advice. Distribution of
-Creative Commons public licenses does not create a lawyer-client or
-other relationship. Creative Commons makes its licenses and related
-information available on an "as-is" basis. Creative Commons gives no
-warranties regarding its licenses, any material licensed under their
-terms and conditions, or any related information. Creative Commons
-disclaims all liability for damages resulting from their use to the
-fullest extent possible.
-
-Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and
-conditions that creators and other rights holders may use to share
-original works of authorship and other material subject to copyright
-and certain other rights specified in the public license below. The
-following considerations are for informational purposes only, are not
-exhaustive, and do not form part of our licenses.
-
- Considerations for licensors: Our public licenses are
- intended for use by those authorized to give the public
- permission to use material in ways otherwise restricted by
- copyright and certain other rights. Our licenses are
- irrevocable. Licensors should read and understand the terms
- and conditions of the license they choose before applying it.
- Licensors should also secure all rights necessary before
- applying our licenses so that the public can reuse the
- material as expected. Licensors should clearly mark any
- material not subject to the license. This includes other CC-
- licensed material, or material used under an exception or
- limitation to copyright. More considerations for licensors:
- wiki.creativecommons.org/Considerations_for_licensors
-
- Considerations for the public: By using one of our public
- licenses, a licensor grants the public permission to use the
- licensed material under specified terms and conditions. If
- the licensor's permission is not necessary for any reason--for
- example, because of any applicable exception or limitation to
- copyright--then that use is not regulated by the license. Our
- licenses grant only permissions under copyright and certain
- other rights that a licensor has authority to grant. Use of
- the licensed material may still be restricted for other
- reasons, including because others have copyright or other
- rights in the material. A licensor may make special requests,
- such as asking that all changes be marked or described.
- Although not required by our licenses, you are encouraged to
- respect those requests where reasonable. More considerations
- for the public:
- wiki.creativecommons.org/Considerations_for_licensees
-
-=======================================================================
-
-Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
-International Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree
-to be bound by the terms and conditions of this Creative Commons
-Attribution-NonCommercial-NoDerivatives 4.0 International Public
-License ("Public License"). To the extent this Public License may be
-interpreted as a contract, You are granted the Licensed Rights in
-consideration of Your acceptance of these terms and conditions, and the
-Licensor grants You such rights in consideration of benefits the
-Licensor receives from making the Licensed Material available under
-these terms and conditions.
-
-
-Section 1 -- Definitions.
-
- a. Adapted Material means material subject to Copyright and Similar
- Rights that is derived from or based upon the Licensed Material
- and in which the Licensed Material is translated, altered,
- arranged, transformed, or otherwise modified in a manner requiring
- permission under the Copyright and Similar Rights held by the
- Licensor. For purposes of this Public License, where the Licensed
- Material is a musical work, performance, or sound recording,
- Adapted Material is always produced where the Licensed Material is
- synched in timed relation with a moving image.
-
- b. Copyright and Similar Rights means copyright and/or similar rights
- closely related to copyright including, without limitation,
- performance, broadcast, sound recording, and Sui Generis Database
- Rights, without regard to how the rights are labeled or
- categorized. For purposes of this Public License, the rights
- specified in Section 2(b)(1)-(2) are not Copyright and Similar
- Rights.
-
- c. Effective Technological Measures means those measures that, in the
- absence of proper authority, may not be circumvented under laws
- fulfilling obligations under Article 11 of the WIPO Copyright
- Treaty adopted on December 20, 1996, and/or similar international
- agreements.
-
- d. Exceptions and Limitations means fair use, fair dealing, and/or
- any other exception or limitation to Copyright and Similar Rights
- that applies to Your use of the Licensed Material.
-
- e. Licensed Material means the artistic or literary work, database,
- or other material to which the Licensor applied this Public
- License.
-
- f. Licensed Rights means the rights granted to You subject to the
- terms and conditions of this Public License, which are limited to
- all Copyright and Similar Rights that apply to Your use of the
- Licensed Material and that the Licensor has authority to license.
-
- g. Licensor means the individual(s) or entity(ies) granting rights
- under this Public License.
-
- h. NonCommercial means not primarily intended for or directed towards
- commercial advantage or monetary compensation. For purposes of
- this Public License, the exchange of the Licensed Material for
- other material subject to Copyright and Similar Rights by digital
- file-sharing or similar means is NonCommercial provided there is
- no payment of monetary compensation in connection with the
- exchange.
-
- i. Share means to provide material to the public by any means or
- process that requires permission under the Licensed Rights, such
- as reproduction, public display, public performance, distribution,
- dissemination, communication, or importation, and to make material
- available to the public including in ways that members of the
- public may access the material from a place and at a time
- individually chosen by them.
-
- j. Sui Generis Database Rights means rights other than copyright
- resulting from Directive 96/9/EC of the European Parliament and of
- the Council of 11 March 1996 on the legal protection of databases,
- as amended and/or succeeded, as well as other essentially
- equivalent rights anywhere in the world.
-
- k. You means the individual or entity exercising the Licensed Rights
- under this Public License. Your has a corresponding meaning.
-
-
-Section 2 -- Scope.
-
- a. License grant.
-
- 1. Subject to the terms and conditions of this Public License,
- the Licensor hereby grants You a worldwide, royalty-free,
- non-sublicensable, non-exclusive, irrevocable license to
- exercise the Licensed Rights in the Licensed Material to:
-
- a. reproduce and Share the Licensed Material, in whole or
- in part, for NonCommercial purposes only; and
-
- b. produce and reproduce, but not Share, Adapted Material
- for NonCommercial purposes only.
-
- 2. Exceptions and Limitations. For the avoidance of doubt, where
- Exceptions and Limitations apply to Your use, this Public
- License does not apply, and You do not need to comply with
- its terms and conditions.
-
- 3. Term. The term of this Public License is specified in Section
- 6(a).
-
- 4. Media and formats; technical modifications allowed. The
- Licensor authorizes You to exercise the Licensed Rights in
- all media and formats whether now known or hereafter created,
- and to make technical modifications necessary to do so. The
- Licensor waives and/or agrees not to assert any right or
- authority to forbid You from making technical modifications
- necessary to exercise the Licensed Rights, including
- technical modifications necessary to circumvent Effective
- Technological Measures. For purposes of this Public License,
- simply making modifications authorized by this Section 2(a)
- (4) never produces Adapted Material.
-
- 5. Downstream recipients.
-
- a. Offer from the Licensor -- Licensed Material. Every
- recipient of the Licensed Material automatically
- receives an offer from the Licensor to exercise the
- Licensed Rights under the terms and conditions of this
- Public License.
-
- b. No downstream restrictions. You may not offer or impose
- any additional or different terms or conditions on, or
- apply any Effective Technological Measures to, the
- Licensed Material if doing so restricts exercise of the
- Licensed Rights by any recipient of the Licensed
- Material.
-
- 6. No endorsement. Nothing in this Public License constitutes or
- may be construed as permission to assert or imply that You
- are, or that Your use of the Licensed Material is, connected
- with, or sponsored, endorsed, or granted official status by,
- the Licensor or others designated to receive attribution as
- provided in Section 3(a)(1)(A)(i).
-
- b. Other rights.
-
- 1. Moral rights, such as the right of integrity, are not
- licensed under this Public License, nor are publicity,
- privacy, and/or other similar personality rights; however, to
- the extent possible, the Licensor waives and/or agrees not to
- assert any such rights held by the Licensor to the limited
- extent necessary to allow You to exercise the Licensed
- Rights, but not otherwise.
-
- 2. Patent and trademark rights are not licensed under this
- Public License.
-
- 3. To the extent possible, the Licensor waives any right to
- collect royalties from You for the exercise of the Licensed
- Rights, whether directly or through a collecting society
- under any voluntary or waivable statutory or compulsory
- licensing scheme. In all other cases the Licensor expressly
- reserves any right to collect such royalties, including when
- the Licensed Material is used other than for NonCommercial
- purposes.
-
-
-Section 3 -- License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the
-following conditions.
-
- a. Attribution.
-
- 1. If You Share the Licensed Material, You must:
-
- a. retain the following if it is supplied by the Licensor
- with the Licensed Material:
-
- i. identification of the creator(s) of the Licensed
- Material and any others designated to receive
- attribution, in any reasonable manner requested by
- the Licensor (including by pseudonym if
- designated);
-
- ii. a copyright notice;
-
- iii. a notice that refers to this Public License;
-
- iv. a notice that refers to the disclaimer of
- warranties;
-
- v. a URI or hyperlink to the Licensed Material to the
- extent reasonably practicable;
-
- b. indicate if You modified the Licensed Material and
- retain an indication of any previous modifications; and
-
- c. indicate the Licensed Material is licensed under this
- Public License, and include the text of, or the URI or
- hyperlink to, this Public License.
-
- For the avoidance of doubt, You do not have permission under
- this Public License to Share Adapted Material.
-
- 2. You may satisfy the conditions in Section 3(a)(1) in any
- reasonable manner based on the medium, means, and context in
- which You Share the Licensed Material. For example, it may be
- reasonable to satisfy the conditions by providing a URI or
- hyperlink to a resource that includes the required
- information.
-
- 3. If requested by the Licensor, You must remove any of the
- information required by Section 3(a)(1)(A) to the extent
- reasonably practicable.
-
-
-Section 4 -- Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that
-apply to Your use of the Licensed Material:
-
- a. for the avoidance of doubt, Section 2(a)(1) grants You the right
- to extract, reuse, reproduce, and Share all or a substantial
- portion of the contents of the database for NonCommercial purposes
- only and provided You do not Share Adapted Material;
-
- b. if You include all or a substantial portion of the database
- contents in a database in which You have Sui Generis Database
- Rights, then the database in which You have Sui Generis Database
- Rights (but not its individual contents) is Adapted Material; and
-
- c. You must comply with the conditions in Section 3(a) if You Share
- all or a substantial portion of the contents of the database.
-
-For the avoidance of doubt, this Section 4 supplements and does not
-replace Your obligations under this Public License where the Licensed
-Rights include other Copyright and Similar Rights.
-
-
-Section 5 -- Disclaimer of Warranties and Limitation of Liability.
-
- a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
- EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
- AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
- ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
- IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
- WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
- PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
- ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
- KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
- ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
-
- b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
- TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
- NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
- INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
- COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
- USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
- ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
- DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
- IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
-
- c. The disclaimer of warranties and limitation of liability provided
- above shall be interpreted in a manner that, to the extent
- possible, most closely approximates an absolute disclaimer and
- waiver of all liability.
-
-
-Section 6 -- Term and Termination.
-
- a. This Public License applies for the term of the Copyright and
- Similar Rights licensed here. However, if You fail to comply with
- this Public License, then Your rights under this Public License
- terminate automatically.
-
- b. Where Your right to use the Licensed Material has terminated under
- Section 6(a), it reinstates:
-
- 1. automatically as of the date the violation is cured, provided
- it is cured within 30 days of Your discovery of the
- violation; or
-
- 2. upon express reinstatement by the Licensor.
-
- For the avoidance of doubt, this Section 6(b) does not affect any
- right the Licensor may have to seek remedies for Your violations
- of this Public License.
-
- c. For the avoidance of doubt, the Licensor may also offer the
- Licensed Material under separate terms or conditions or stop
- distributing the Licensed Material at any time; however, doing so
- will not terminate this Public License.
-
- d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
- License.
-
-
-Section 7 -- Other Terms and Conditions.
-
- a. The Licensor shall not be bound by any additional or different
- terms or conditions communicated by You unless expressly agreed.
-
- b. Any arrangements, understandings, or agreements regarding the
- Licensed Material not stated herein are separate from and
- independent of the terms and conditions of this Public License.
-
-
-Section 8 -- Interpretation.
-
- a. For the avoidance of doubt, this Public License does not, and
- shall not be interpreted to, reduce, limit, restrict, or impose
- conditions on any use of the Licensed Material that could lawfully
- be made without permission under this Public License.
-
- b. To the extent possible, if any provision of this Public License is
- deemed unenforceable, it shall be automatically reformed to the
- minimum extent necessary to make it enforceable. If the provision
- cannot be reformed, it shall be severed from this Public License
- without affecting the enforceability of the remaining terms and
- conditions.
-
- c. No term or condition of this Public License will be waived and no
- failure to comply consented to unless expressly agreed to by the
- Licensor.
-
- d. Nothing in this Public License constitutes or may be interpreted
- as a limitation upon, or waiver of, any privileges and immunities
- that apply to the Licensor or You, including from the legal
- processes of any jurisdiction or authority.
-
-=======================================================================
-
-Creative Commons is not a party to its public
-licenses. Notwithstanding, Creative Commons may elect to apply one of
-its public licenses to material it publishes and in those instances
-will be considered the “Licensor.” The text of the Creative Commons
-public licenses is dedicated to the public domain under the CC0 Public
-Domain Dedication. Except for the limited purpose of indicating that
-material is shared under a Creative Commons public license or as
-otherwise permitted by the Creative Commons policies published at
-creativecommons.org/policies, Creative Commons does not authorize the
-use of the trademark "Creative Commons" or any other trademark or logo
-of Creative Commons without its prior written consent including,
-without limitation, in connection with any unauthorized modifications
-to any of its public licenses or any other arrangements,
-understandings, or agreements concerning use of licensed material. For
-the avoidance of doubt, this paragraph does not form part of the
-public licenses.
-
-Creative Commons may be contacted at creativecommons.org.
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dbbd8225..6f1a22359 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,21 +4,93 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
-### Added
-- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
-- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
-- Admin API: Return users' tags when querying reports
-- Admin API: Return avatar and display name when querying users
+### Security
+- OStatus: eliminate the possibility of a protocol downgrade attack.
+- OStatus: prevent following locked accounts, bypassing the approval process.
+
+### Changed
+- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
+- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
+- Configuration: OpenGraph and TwitterCard providers enabled by default
+- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
+- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
+- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
+- NodeInfo: Return `mailerEnabled` in `metadata`
+- Mastodon API: Unsubscribe followers when they unfollow a user
+- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
### Fixed
- Not being able to pin unlisted posts
+- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
+- Metadata rendering errors resulting in the entire page being inaccessible
+- Federation/MediaProxy not working with instances that have wrong certificate order
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
+- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
+- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
+- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
+- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
+- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
+- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
+- Existing user id not being preserved on insert conflict
+- Rich Media: Parser failing when no TTL can be found by image TTL setters
+- Rich Media: The crawled URL is now spliced into the rich media data.
+- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
+- ActivityPub S2S: remote user deletions now work the same as local user deletions.
+- Not being able to access the Mastodon FE login page on private instances
+- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
+- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
+- Report email not being sent to admins when the reporter is a remote user
+- MRF: ensure that subdomain_match calls are case-insensitive
+
+### Added
+- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
+- MRF: Support for excluding specific domains from Transparency.
+- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
+- MRF (Simple Policy): Support for wildcard domains.
+- Support for wildcard domains in user domain blocks setting.
+- Configuration: `quarantined_instances` support wildcard domains.
+- Configuration: `federation_incoming_replies_max_depth` option
+- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
+- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
+- Mastodon API, extension: Ability to reset avatar, profile banner, and background
+- Mastodon API: Add support for categories for custom emojis by reusing the group feature.
+- Mastodon API: Add support for muting/unmuting notifications
+- Mastodon API: Add support for the `blocked_by` attribute in the relationship API (`GET /api/v1/accounts/relationships`).
+- Mastodon API: Add support for the `domain_blocking` attribute in the relationship API (`GET /api/v1/accounts/relationships`).
+- Mastodon API: Add `pleroma.deactivated` to the Account entity
+- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
+- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
+- Admin API: Return users' tags when querying reports
+- Admin API: Return avatar and display name when querying users
+- Admin API: Allow querying user by ID
+- Admin API: Added support for `tuples`.
+- Admin API: Added endpoints to run mix tasks pleroma.config migrate_to_db & pleroma.config migrate_from_db
+- Added synchronization of following/followers counters for external users
+- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
+- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
+- Addressable lists
+- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
+- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
+- ActivityPub: Optional signing of ActivityPub object fetches.
+- Admin API: Endpoint for fetching latest user's statuses
+- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=` for resending account confirmation.
+- Relays: Added a task to list relay subscriptions.
+- Mix Tasks: `mix pleroma.database fix_likes_collections`
+- Federation: Remove `likes` from objects.
### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
+- Admin API: changed json structure for saving config settings.
+- RichMedia: parsers and their order are configured in `rich_media` config.
+- RichMedia: add the rich media ttl based on image expiration time.
-### Changed
-- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
+### Removed
+- Emoji: Remove longfox emojis.
+- Remove `Reply-To` header from report emails for admins.
+
+## [1.0.1] - 2019-07-14
+### Security
+- OStatus: fix an object spoofing vulnerability.
## [1.0.0] - 2019-06-29
### Security
@@ -26,6 +98,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich media: Do not crawl private IP ranges
### Added
+- Digest email for inactive users
- Add a generic settings store for frontends / clients to use.
- Explicit addressing option for posting.
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
@@ -52,6 +125,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `notify_email` option
- Configuration: Media proxy `whitelist` option
- Configuration: `report_uri` option
+- Configuration: `email_notifications` option
- Configuration: `limit_to_local_content` option
- Pleroma API: User subscriptions
- Pleroma API: Healthcheck endpoint
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..268ec61dc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,39 @@
+FROM rinpatch/elixir:1.9.0-rc.0-alpine as build
+
+COPY . .
+
+ENV MIX_ENV=prod
+
+RUN apk add git gcc g++ musl-dev make &&\
+ echo "import Mix.Config" > config/prod.secret.exs &&\
+ mix local.hex --force &&\
+ mix local.rebar --force &&\
+ mix deps.get --only prod &&\
+ mkdir release &&\
+ mix release --path release
+
+FROM alpine:latest
+
+ARG HOME=/opt/pleroma
+ARG DATA=/var/lib/pleroma
+
+RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
+ apk update &&\
+ apk add ncurses postgresql-client &&\
+ adduser --system --shell /bin/false --home ${HOME} pleroma &&\
+ mkdir -p ${DATA}/uploads &&\
+ mkdir -p ${DATA}/static &&\
+ chown -R pleroma ${DATA} &&\
+ mkdir -p /etc/pleroma &&\
+ chown -R pleroma /etc/pleroma
+
+USER pleroma
+
+COPY --from=build --chown=pleroma:0 /release ${HOME}
+
+COPY ./config/docker.exs /etc/pleroma/config.exs
+COPY ./docker-entrypoint.sh ${HOME}
+
+EXPOSE 4000
+
+ENTRYPOINT ["/opt/pleroma/docker-entrypoint.sh"]
diff --git a/README.md b/README.md
index 41d454a03..5aad34ccc 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ If you want to run your own server, feel free to contact us at @lain@pleroma.soy
Currently Pleroma is not packaged by any OS/Distros, but feel free to reach out to us at [#pleroma-dev on freenode](https://webchat.freenode.net/?channels=%23pleroma-dev) or via matrix at for assistance. If you want to change default options in your Pleroma package, please **discuss it with us first**.
### Docker
-While we don’t provide docker files, other people have written very good ones. Take a look at or .
+While we don’t provide docker files, other people have written very good ones. Take a look at or .
### Dependencies
diff --git a/config/config.exs b/config/config.exs
index 551d380f6..f1a08b6fc 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -194,6 +194,8 @@ config :pleroma, :http,
send_user_agent: true,
adapter: [
ssl_options: [
+ # Workaround for remote server certificate chain issues
+ partial_chain: &:hackney_connect.partial_chain/1,
# We don't support TLS v1.3 yet
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
]
@@ -218,6 +220,7 @@ config :pleroma, :instance,
},
registrations_open: true,
federating: true,
+ federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7,
federation_publisher_modules: [
Pleroma.Web.ActivityPub.Publisher,
@@ -239,6 +242,7 @@ config :pleroma, :instance,
"text/bbcode"
],
mrf_transparency: true,
+ mrf_transparency_exclusions: [],
autofollowed_nicknames: [],
max_pinned_statuses: 1,
no_attachment_links: false,
@@ -250,7 +254,8 @@ config :pleroma, :instance,
remote_post_retention_days: 90,
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
- dynamic_configuration: false
+ dynamic_configuration: false,
+ external_user_synchronization: true
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
@@ -304,7 +309,8 @@ config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
outgoing_blocks: true,
- follow_handshake_timeout: 500
+ follow_handshake_timeout: 500,
+ sign_object_fetches: true
config :pleroma, :user, deny_follow_blocked: true
@@ -347,7 +353,13 @@ config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
- ignore_tld: ["local", "localdomain", "lan"]
+ ignore_tld: ["local", "localdomain", "lan"],
+ parsers: [
+ Pleroma.Web.RichMedia.Parsers.TwitterCard,
+ Pleroma.Web.RichMedia.Parsers.OGP,
+ Pleroma.Web.RichMedia.Parsers.OEmbed
+ ],
+ ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
config :pleroma, :media_proxy,
enabled: false,
@@ -371,7 +383,11 @@ config :pleroma, :gopher,
port: 9999
config :pleroma, Pleroma.Web.Metadata,
- providers: [Pleroma.Web.Metadata.Providers.RelMe],
+ providers: [
+ Pleroma.Web.Metadata.Providers.OpenGraph,
+ Pleroma.Web.Metadata.Providers.TwitterCard,
+ Pleroma.Web.Metadata.Providers.RelMe
+ ],
unfurl_nsfw: false
config :pleroma, :suggestions,
@@ -502,7 +518,7 @@ config :ueberauth,
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
-config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail
+config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
@@ -511,6 +527,14 @@ config :pleroma, Pleroma.ScheduledActivity,
total_user_limit: 300,
enabled: true
+config :pleroma, :email_notifications,
+ digest: %{
+ active: false,
+ schedule: "0 0 * * 0",
+ interval: 7,
+ inactivity_threshold: 7
+ }
+
config :pleroma, :oauth2,
token_expires_in: 600,
issue_new_refresh_token: true,
@@ -526,7 +550,13 @@ config :http_signatures,
config :pleroma, :rate_limit,
search: [{1000, 10}, {1000, 30}],
- app_account_creation: {1_800_000, 25}
+ app_account_creation: {1_800_000, 25},
+ relations_actions: {10_000, 10},
+ relation_id_action: {60_000, 2},
+ statuses_actions: {10_000, 15},
+ status_id_action: {60_000, 3},
+ password_reset: {1_800_000, 5},
+ account_confirmation_resend: {8_640_000, 5}
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/config/docker.exs b/config/docker.exs
new file mode 100644
index 000000000..63ab4cdee
--- /dev/null
+++ b/config/docker.exs
@@ -0,0 +1,68 @@
+import Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+ url: [host: System.get_env("DOMAIN", "localhost"), scheme: "https", port: 443],
+ http: [ip: {0, 0, 0, 0}, port: 4000]
+
+config :pleroma, :instance,
+ name: System.get_env("INSTANCE_NAME", "Pleroma"),
+ email: System.get_env("ADMIN_EMAIL"),
+ notify_email: System.get_env("NOTIFY_EMAIL"),
+ limit: 5000,
+ registrations_open: false,
+ dynamic_configuration: true
+
+config :pleroma, Pleroma.Repo,
+ adapter: Ecto.Adapters.Postgres,
+ username: System.get_env("DB_USER", "pleroma"),
+ password: System.fetch_env!("DB_PASS"),
+ database: System.get_env("DB_NAME", "pleroma"),
+ hostname: System.get_env("DB_HOST", "db"),
+ pool_size: 10
+
+# Configure web push notifications
+config :web_push_encryption, :vapid_details, subject: "mailto:#{System.get_env("NOTIFY_EMAIL")}"
+
+config :pleroma, :database, rum_enabled: false
+config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
+config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
+
+# We can't store the secrets in this file, since this is baked into the docker image
+if not File.exists?("/var/lib/pleroma/secret.exs") do
+ secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
+ signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
+ {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
+
+ secret_file =
+ EEx.eval_string(
+ """
+ import Config
+
+ config :pleroma, Pleroma.Web.Endpoint,
+ secret_key_base: "<%= secret %>",
+ signing_salt: "<%= signing_salt %>"
+
+ config :web_push_encryption, :vapid_details,
+ public_key: "<%= web_push_public_key %>",
+ private_key: "<%= web_push_private_key %>"
+ """,
+ secret: secret,
+ signing_salt: signing_salt,
+ web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
+ web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
+ )
+
+ File.write("/var/lib/pleroma/secret.exs", secret_file)
+end
+
+import_config("/var/lib/pleroma/secret.exs")
+
+# For additional user config
+if File.exists?("/var/lib/pleroma/config.exs"),
+ do: import_config("/var/lib/pleroma/config.exs"),
+ else:
+ File.write("/var/lib/pleroma/config.exs", """
+ import Config
+
+ # For additional configuration outside of environmental variables
+ """)
diff --git a/config/emoji.txt b/config/emoji.txt
index 79246f239..200768ad1 100644
--- a/config/emoji.txt
+++ b/config/emoji.txt
@@ -1,30 +1,2 @@
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
-f_00b, /emoji/f_00b.png
-f_00b11b, /emoji/f_00b11b.png
-f_00b33b, /emoji/f_00b33b.png
-f_00h, /emoji/f_00h.png
-f_00t, /emoji/f_00t.png
-f_01b, /emoji/f_01b.png
-f_03b, /emoji/f_03b.png
-f_10b, /emoji/f_10b.png
-f_11b, /emoji/f_11b.png
-f_11b00b, /emoji/f_11b00b.png
-f_11b22b, /emoji/f_11b22b.png
-f_11h, /emoji/f_11h.png
-f_11t, /emoji/f_11t.png
-f_12b, /emoji/f_12b.png
-f_21b, /emoji/f_21b.png
-f_22b, /emoji/f_22b.png
-f_22b11b, /emoji/f_22b11b.png
-f_22b33b, /emoji/f_22b33b.png
-f_22h, /emoji/f_22h.png
-f_22t, /emoji/f_22t.png
-f_23b, /emoji/f_23b.png
-f_30b, /emoji/f_30b.png
-f_32b, /emoji/f_32b.png
-f_33b, /emoji/f_33b.png
-f_33b00b, /emoji/f_33b00b.png
-f_33b22b, /emoji/f_33b22b.png
-f_33h, /emoji/f_33h.png
-f_33t, /emoji/f_33t.png
diff --git a/config/test.exs b/config/test.exs
index 9d441a7f5..6f75f39b5 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -23,12 +23,16 @@ config :pleroma, Pleroma.Upload, filters: [], link_name: false
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
-config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test
+config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
config :pleroma, :instance,
email: "admin@example.com",
notify_email: "noreply@example.com",
- skip_thread_containment: false
+ skip_thread_containment: false,
+ federating: false,
+ external_user_synchronization: false
+
+config :pleroma, :activitypub, sign_object_fetches: false
# Configure your database
config :pleroma, Pleroma.Repo,
@@ -64,7 +68,10 @@ config :pleroma, Pleroma.ScheduledActivity,
total_user_limit: 3,
enabled: false
-config :pleroma, :rate_limit, app_account_creation: {10_000, 5}
+config :pleroma, :rate_limit,
+ search: [{1000, 30}, {1000, 30}],
+ app_account_creation: {10_000, 5},
+ password_reset: {1000, 30}
config :pleroma, :http_security, report_uri: "https://endpoint.com"
@@ -74,6 +81,10 @@ rum_enabled = System.get_env("RUM_ENABLED") == "true"
config :pleroma, :database, rum_enabled: rum_enabled
IO.puts("RUM enabled: #{rum_enabled}")
+config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
+
+config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
+
try do
import_config "test.secret.exs"
rescue
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 000000000..f56f8c50a
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,14 @@
+#!/bin/ash
+
+set -e
+
+echo "-- Waiting for database..."
+while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:5432/${DB_NAME:-pleroma} -t 1; do
+ sleep 1s
+done
+
+echo "-- Running migrations..."
+$HOME/bin/pleroma_ctl migrate
+
+echo "-- Starting!"
+exec $HOME/bin/pleroma start
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index 74bde3ece..7ccb90836 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -176,17 +176,30 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- `nickname`
- `status` BOOLEAN field, false value means deactivation.
-## `/api/pleroma/admin/users/:nickname`
+## `/api/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user
- Method: `GET`
- Params:
- - `nickname`
+ - `nickname` or `id`
- Response:
- On failure: `Not found`
- On success: JSON of the user
+## `/api/pleroma/admin/users/:nickname_or_id/statuses`
+
+### Retrive user's latest statuses
+
+- Method: `GET`
+- Params:
+ - `nickname` or `id`
+ - *optional* `page_size`: number of statuses to return (default is `20`)
+ - *optional* `godmode`: `true`/`false` – allows to see private statuses
+- Response:
+ - On failure: `Not found`
+ - On success: JSON array of user's latest statuses
+
## `/api/pleroma/admin/relay`
### Follow a Relay
@@ -562,8 +575,32 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- 404 Not Found `"Not found"`
- On success: 200 OK `{}`
+
+## `/api/pleroma/admin/config/migrate_to_db`
+### Run mix task pleroma.config migrate_to_db
+Copy settings on key `:pleroma` to DB.
+- Method `GET`
+- Params: none
+- Response:
+
+```json
+{}
+```
+
+## `/api/pleroma/admin/config/migrate_from_db`
+### Run mix task pleroma.config migrate_from_db
+Copy all settings from DB to `config/prod.exported_from_db.secret.exs` with deletion from DB.
+- Method `GET`
+- Params: none
+- Response:
+
+```json
+{}
+```
+
## `/api/pleroma/admin/config`
### List config settings
+List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
- Method `GET`
- Params: none
- Response:
@@ -573,7 +610,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
configs: [
{
"group": string,
- "key": string,
+ "key": string or string with leading `:` for atoms,
"value": string or {} or [] or {"tuple": []}
}
]
@@ -582,11 +619,16 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config`
### Update config settings
+Updating config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
-Atom or boolean value can be passed with `:` in the beginning, e.g. `":true"`, `":upload"`. For keys it is not needed.
-Integer with `i:`, e.g. `"i:150"`.
-Tuple with more than 2 values with `{"tuple": ["first_val", Pleroma.Module, []]}`.
+Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
+Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`.
+Keywords can be passed as lists with 2 child tuples, e.g.
+`[{"tuple": ["first_val", Pleroma.Module]}, {"tuple": ["second_val", true]}]`.
+
+If value contains list of settings `[subkey: val1, subkey2: val2, subkey3: val3]`, it's possible to remove only subkeys instead of all settings passing `subkeys` parameter. E.g.:
+{"group": "pleroma", "key": "some_key", "delete": "true", "subkeys": [":subkey", ":subkey3"]}.
Compile time settings (need instance reboot):
- all settings by this keys:
@@ -603,9 +645,10 @@ Compile time settings (need instance reboot):
- Params:
- `configs` => [
- `group` (string)
- - `key` (string)
+ - `key` (string or string with leading `:` for atoms)
- `value` (string, [], {} or {"tuple": []})
- `delete` = true (optional, if parameter must be deleted)
+ - `subkeys` [(string with leading `:` for atoms)] (optional, works only if `delete=true` parameter is passed, otherwise will be ignored)
]
- Request (example):
@@ -616,24 +659,25 @@ Compile time settings (need instance reboot):
{
"group": "pleroma",
"key": "Pleroma.Upload",
- "value": {
- "uploader": "Pleroma.Uploaders.Local",
- "filters": ["Pleroma.Upload.Filter.Dedupe"],
- "link_name": ":true",
- "proxy_remote": ":false",
- "proxy_opts": {
- "redirect_on_failure": ":false",
- "max_body_length": "i:1048576",
- "http": {
- "follow_redirect": ":true",
- "pool": ":upload"
- }
- },
- "dispatch": {
+ "value": [
+ {"tuple": [":uploader", "Pleroma.Uploaders.Local"]},
+ {"tuple": [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
+ {"tuple": [":link_name", true]},
+ {"tuple": [":proxy_remote", false]},
+ {"tuple": [":proxy_opts", [
+ {"tuple": [":redirect_on_failure", false]},
+ {"tuple": [":max_body_length", 1048576]},
+ {"tuple": [":http": [
+ {"tuple": [":follow_redirect", true]},
+ {"tuple": [":pool", ":upload"]},
+ ]]}
+ ]
+ ]},
+ {"tuple": [":dispatch", {
"tuple": ["/api/v1/streaming", "Pleroma.Web.MastodonAPI.WebsocketHandler", []]
- }
- }
- }
+ }]}
+ ]
+ }
]
}
@@ -644,7 +688,7 @@ Compile time settings (need instance reboot):
configs: [
{
"group": string,
- "key": string,
+ "key": string or string with leading `:` for atoms,
"value": string or {} or [] or {"tuple": []}
}
]
diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md
index 3ee7115cf..1907d70c8 100644
--- a/docs/api/differences_in_mastoapi_responses.md
+++ b/docs/api/differences_in_mastoapi_responses.md
@@ -16,9 +16,11 @@ Adding the parameter `with_muted=true` to the timeline queries will also return
## Statuses
+- `visibility`: has an additional possible value `list`
+
Has these additional fields under the `pleroma` object:
-- `local`: true if the post was made on the local instance.
+- `local`: true if the post was made on the local instance
- `conversation_id`: the ID of the conversation the status is associated with (if any)
- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any)
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
@@ -32,7 +34,10 @@ Has these additional fields under the `pleroma` object:
## Accounts
-- `/api/v1/accounts/:id`: The `id` parameter can also be the `nickname` of the user. This only works in this endpoint, not the deeper nested ones for following etc.
+The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc.
+
+- `/api/v1/accounts/:id`
+- `/api/v1/accounts/:id/statuses`
Has these additional fields under the `pleroma` object:
@@ -45,6 +50,7 @@ Has these additional fields under the `pleroma` object:
- `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`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
+- `deactivated`: boolean, true when the user is deactivated
### Source
@@ -72,6 +78,7 @@ Additional parameters can be added to the JSON body/Form data:
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
+- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
## PATCH `/api/v1/update_credentials`
diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md
index edc62727a..5698e88ac 100644
--- a/docs/api/pleroma_api.md
+++ b/docs/api/pleroma_api.md
@@ -238,6 +238,21 @@ See [Admin-API](Admin-API.md)
]
```
+## `/api/v1/pleroma/accounts/update_*`
+### Set and clear account avatar, banner, and background
+
+- PATCH `/api/v1/pleroma/accounts/update_avatar`: Set/clear user avatar image
+- PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image
+- PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image
+
+## `/api/v1/pleroma/accounts/confirmation_resend`
+### Resend confirmation email
+* Method `POST`
+* Params:
+ * `email`: email of that needs to be verified
+* Authentication: not required
+* Response: 204 No Content
+
## `/api/v1/pleroma/mascot`
### Gets user mascot image
* Method `GET`
diff --git a/docs/clients.md b/docs/clients.md
index 30358c210..9029361f8 100644
--- a/docs/clients.md
+++ b/docs/clients.md
@@ -31,10 +31,11 @@ Feel free to contact us to be added to this list!
- Features: No Streaming
### Fedilab
-- Source Code:
-- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
+- Homepage:
+- Source Code:
+- Contact: [@fedilab@framapiaf.org](https://framapiaf.org/users/fedilab)
- Platforms: Android
-- Features: Streaming Ready
+- Features: Streaming Ready, Moderation, Text Formatting
### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
diff --git a/docs/config.md b/docs/config.md
index 8afccb228..55311b76d 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
## Pleroma.Uploaders.S3
* `bucket`: S3 bucket name
+* `bucket_namespace`: S3 bucket namespace
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
For example, when using CDN to S3 virtual host format, set "".
@@ -25,7 +26,7 @@ At this time, write CNAME to CDN in public_endpoint.
## Pleroma.Upload.Filter.Mogrify
-* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"impode", "1"}]`.
+* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
## Pleroma.Upload.Filter.Dedupe
@@ -41,6 +42,7 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
## Pleroma.Emails.Mailer
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
+* `enabled`: Allows enable/disable send emails. Default: `false`.
An example for Sendgrid adapter:
@@ -87,6 +89,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
* `federating`: Enable federation with other instances
+* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
@@ -99,11 +102,13 @@ config :pleroma, Pleroma.Emails.Mailer,
* `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:.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
+ * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
+* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
* "email": Copy and preprend re:, as in email.
@@ -124,6 +129,8 @@ config :pleroma, Pleroma.Emails.Mailer,
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
+* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
+
## :logger
@@ -266,6 +273,9 @@ config :pleroma, :mrf_subchain,
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
+## :mrf_mention
+* `actors`: A list of actors, for which to drop any posts mentioning.
+
## :media_proxy
* `enabled`: Enables proxying of remote media to the instance’s proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
@@ -323,6 +333,7 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
+* ``sign_object_fetches``: Sign object fetches with HTTP signatures
## :http_security
* ``enabled``: Whether the managed content security policy is enabled
@@ -420,6 +431,7 @@ This config contains two queues: `federator_incoming` and `federator_outgoing`.
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"]
+* `parsers`: list of Rich Media parsers
## :fetch_initial_posts
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
@@ -525,6 +537,18 @@ Authentication / authorization settings.
* `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. Each entry in this space-delimited string should be of format `` or `:` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_`).
+## :email_notifications
+
+Email notifications settings.
+
+ - digest - emails of "what you've missed" for users who have been
+ inactive for a while.
+ - active: globally enable or disable digest emails
+ - schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
+ "0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
+ - interval: Minimum interval between digest emails to one user
+ - inactivity_threshold: Minimum user inactivity threshold
+
## OAuth consumer mode
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
@@ -636,3 +660,12 @@ A keyword list of rate limiters where a key is a limiter name and value is the l
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
See [`Pleroma.Plugs.RateLimiter`](Pleroma.Plugs.RateLimiter.html) documentation for examples.
+
+Supported rate limiters:
+
+* `:search` for the search requests (account & status search etc.)
+* `:app_account_creation` for registering user accounts from the same IP address
+* `:relations_actions` for actions on relations with all users (follow, unfollow)
+* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)
+* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
+* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user
diff --git a/docs/config/howto_mediaproxy.md b/docs/config/howto_mediaproxy.md
index fb731112b..16c40c5db 100644
--- a/docs/config/howto_mediaproxy.md
+++ b/docs/config/howto_mediaproxy.md
@@ -1,8 +1,8 @@
# How to activate mediaproxy
## Explanation
-Without the `mediaproxy` function, Pleroma don't store any remote content like pictures, video etc. locally. So every time you open Pleroma, the content is loaded from the source server, from where the post is coming. This can result in slowly loading content or/and increased bandwidth usage on the source server.
-With the `mediaproxy` function you can use the cache ability of nginx, to cache these content, so user can access it faster, cause it's loaded from your server.
+Without the `mediaproxy` function, Pleroma doesn't store any remote content like pictures, video etc. locally. So every time you open Pleroma, the content is loaded from the source server, from where the post is coming. This can result in slowly loading content or/and increased bandwidth usage on the source server.
+With the `mediaproxy` function you can use nginx to cache this content, so users can access it faster, because it's loaded from your server.
## Activate it
@@ -24,7 +24,9 @@ If you came here from one of the installation guides, take a look at the example
```
config :pleroma, :media_proxy,
enabled: true,
- redirect_on_failure: true
+ proxy_opts: [
+ redirect_on_failure: true
+ ]
#base_url: "https://cache.pleroma.social"
```
If you want to use a subdomain to serve the files, uncomment `base_url`, change the url and add a comma after `true` in the previous line.
diff --git a/docs/config/howto_set_richmedia_cache_ttl_based_on_image.md b/docs/config/howto_set_richmedia_cache_ttl_based_on_image.md
new file mode 100644
index 000000000..bfee5a9e6
--- /dev/null
+++ b/docs/config/howto_set_richmedia_cache_ttl_based_on_image.md
@@ -0,0 +1,33 @@
+# How to set rich media cache ttl based on image ttl
+## Explanation
+
+Richmedia are cached without the ttl but the rich media may have image which can expire, like aws signed url.
+In such cases the old image url (expired) is returned from the media cache.
+
+So to avoid such situation we can define a module that will set ttl based on image.
+The module must adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL`
+
+### Example
+
+```exs
+defmodule MyModule do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+
+ @impl Pleroma.Web.RichMedia.Parser.TTL
+ def ttl(data, url) do
+ image_url = Map.get(data, :image)
+ # do some parsing in the url and get the ttl of the image
+ # return ttl is unix time
+ parse_ttl_from_url(image_url)
+ end
+end
+```
+
+And update the config
+
+```exs
+config :pleroma, :rich_media,
+ ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl, MyModule]
+```
+
+> For reference there is a parser for AWS signed URL `Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl`, it's enabled by default.
diff --git a/docs/config/small_customizations.md b/docs/config/small_customizations.md
index 09e8d6041..f91657a4c 100644
--- a/docs/config/small_customizations.md
+++ b/docs/config/small_customizations.md
@@ -1,35 +1,12 @@
# Small customizations
-Replace `dev.secret.exs` with `prod.secret.exs` according to your setup.
-# Thumbnail
+See also static_dir.md for visual settings.
-Replace `priv/static/instance/thumbnail.jpeg` with your selfie or other neat picture. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html).
-
-# Instance-specific panel
-
-
-
-To show the instance specific panel, set `show_instance_panel` to `true` in `config/dev.secret.exs`. You can modify its content by editing `priv/static/instance/panel.html`.
-
-# Background
-
-You can change the background of your Pleroma instance by uploading it to `priv/static/static`, and then changing `"background"` in `config/dev.secret.exs` accordingly.
-
-# Logo
-
-
-
-If you want to give a brand to your instance, look no further. You can change the logo of your instance by uploading it to `priv/static/static`, and then changing `logo` in `config/dev.secret.exs` accordingly.
-
-# Theme
+## Theme
All users of your instance will be able to change the theme they use by going to the settings (the cog in the top-right hand corner). However, if you wish to change the default theme, you can do so by editing `theme` in `config/dev.secret.exs` accordingly.
-# Terms of Service
-
-Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by changing `priv/static/static/terms-of-service.html`.
-
-# Message Visibility
+## Message Visibility
To enable message visibility options when posting like in the Mastodon frontend, set
`scope_options_enabled` to `true` in `config/dev.secret.exs`.
diff --git a/docs/config/static_dir.md b/docs/config/static_dir.md
index 0cc52b99a..5fb38c3de 100644
--- a/docs/config/static_dir.md
+++ b/docs/config/static_dir.md
@@ -7,7 +7,13 @@ config :pleroma, :instance,
static_dir: "instance/static/",
```
-You can overwrite this value in your configuration to use a different static instance directory.
+For example, edit `instance/static/instance/panel.html` .
+
+Alternatively, you can overwrite this value in your configuration to use a different static instance directory.
+
+This document is written assuming `instance/static/`.
+
+Or, if you want to manage your custom file in git repository, basically remove the `instance/` entry from `.gitignore`.
## robots.txt
@@ -18,3 +24,46 @@ If you want to generate a restrictive `robots.txt`, you can run the following mi
```
mix pleroma.robots_txt disallow_all
```
+
+## Thumbnail
+
+Put on `instance/static/instance/thumbnail.jpeg` with your selfie or other neat picture. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html).
+
+## Instance-specific panel
+
+
+
+Create and Edit your file on `instance/static/instance/panel.html`.
+
+## Background
+
+You can change the background of your Pleroma instance by uploading it to `instance/static/`, and then changing `background` in `config/prod.secret.exs` accordingly.
+
+If you put `instance/static/images/background.jpg`
+
+```
+config :pleroma, :frontend_configurations,
+ pleroma_fe: %{
+ background: "/images/background.jpg"
+ }
+```
+
+## Logo
+
+
+
+If you want to give a brand to your instance, You can change the logo of your instance by uploading it to `instance/static/`.
+
+Alternatively, you can specify the path with config.
+If you put `instance/static/static/mylogo-file.png`
+
+```
+config :pleroma, :frontend_configurations,
+ pleroma_fe: %{
+ logo: "/static/mylogo-file.png"
+ }
+```
+
+## Terms of Service
+
+Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by changing `instance/static/static/terms-of-service.html`.
diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index a9b5afd33..1f300f353 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -202,7 +202,6 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Healthcheck do
@moduledoc """
Module collects metrics about app and assign healthy status.
diff --git a/lib/jason_types.ex b/lib/jason_types.ex
index d1a7bc7ac..c558aef57 100644
--- a/lib/jason_types.ex
+++ b/lib/jason_types.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
Postgrex.Types.define(
Pleroma.PostgresTypes,
[] ++ Ecto.Adapters.Postgres.extensions(),
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index d43db7b35..5222cce80 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Mix.Tasks.Pleroma.Benchmark do
import Mix.Pleroma
use Mix.Task
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index faa605d9b..462940e7e 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Mix.Tasks.Pleroma.Config do
use Mix.Task
import Mix.Pleroma
@@ -11,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Config do
mix pleroma.config migrate_to_db
- ## Transfers config from DB to file.
+ ## Transfers config from DB to file `config/env.exported_from_db.secret.exs`
mix pleroma.config migrate_from_db ENV
"""
@@ -24,6 +28,14 @@ defmodule Mix.Tasks.Pleroma.Config do
|> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
|> Enum.each(fn {k, v} ->
key = to_string(k) |> String.replace("Elixir.", "")
+
+ key =
+ if String.starts_with?(key, "Pleroma.") do
+ key
+ else
+ ":" <> key
+ end
+
{:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
Mix.shell().info("#{key} is migrated.")
end)
@@ -49,17 +61,9 @@ defmodule Mix.Tasks.Pleroma.Config do
Repo.all(Config)
|> Enum.each(fn config ->
- mark =
- if String.starts_with?(config.key, "Pleroma.") or
- String.starts_with?(config.key, "Ueberauth"),
- do: ",",
- else: ":"
-
IO.write(
file,
- "config :#{config.group}, #{config.key}#{mark} #{
- inspect(Config.from_binary(config.value))
- }\r\n"
+ "config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
)
if delete? do
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index e91fb31d1..bcc2052d6 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -8,6 +8,7 @@ defmodule Mix.Tasks.Pleroma.Database do
alias Pleroma.Repo
alias Pleroma.User
require Logger
+ require Pleroma.Constants
import Mix.Pleroma
use Mix.Task
@@ -35,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do
## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts
+
+ ## Fix the pre-existing "likes" collections for all objects
+
+ mix pleroma.database fix_likes_collections
"""
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
@@ -99,10 +104,15 @@ defmodule Mix.Tasks.Pleroma.Database do
NaiveDateTime.utc_now()
|> NaiveDateTime.add(-(deadline * 86_400))
- public = "https://www.w3.org/ns/activitystreams#Public"
-
from(o in Object,
- where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public),
+ where:
+ fragment(
+ "?->'to' \\? ? OR ?->'cc' \\? ?",
+ o.data,
+ ^Pleroma.Constants.as_public(),
+ o.data,
+ ^Pleroma.Constants.as_public()
+ ),
where: o.inserted_at < ^time_deadline,
where:
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
@@ -119,4 +129,36 @@ defmodule Mix.Tasks.Pleroma.Database do
)
end
end
+
+ def run(["fix_likes_collections"]) do
+ import Ecto.Query
+
+ start_pleroma()
+
+ from(object in Object,
+ where: fragment("(?)->>'likes' is not null", object.data),
+ select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
+ )
+ |> Pleroma.RepoStreamer.chunk_stream(100)
+ |> Stream.each(fn objects ->
+ ids =
+ objects
+ |> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
+ |> Enum.map(& &1.id)
+
+ Object
+ |> where([object], object.id in ^ids)
+ |> update([object],
+ set: [
+ data:
+ fragment(
+ "jsonb_set(?, '{likes}', '[]'::jsonb, true)",
+ object.data
+ )
+ ]
+ )
+ |> Repo.update_all([], timeout: :infinity)
+ end)
+ |> Stream.run()
+ end
end
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
new file mode 100644
index 000000000..81c207e10
--- /dev/null
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -0,0 +1,33 @@
+defmodule Mix.Tasks.Pleroma.Digest do
+ use Mix.Task
+
+ @shortdoc "Manages digest emails"
+ @moduledoc """
+ Manages digest emails
+
+ ## Send digest email since given date (user registration date by default)
+ ignoring user activity status.
+
+ ``mix pleroma.digest test ``
+
+ Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
+ """
+ def run(["test", nickname | opts]) do
+ Mix.Pleroma.start_pleroma()
+
+ user = Pleroma.User.get_by_nickname(nickname)
+
+ last_digest_emailed_at =
+ with [date] <- opts,
+ {:ok, datetime} <- Timex.parse(date, "{YYYY}-{0M}-{0D}") do
+ datetime
+ else
+ _ -> user.inserted_at
+ end
+
+ patched_user = %{user | last_digest_emailed_at: last_digest_emailed_at}
+
+ _user = Pleroma.DigestEmailWorker.perform(patched_user)
+ Mix.shell().info("Digest email have been sent to #{nickname} (#{user.email})")
+ end
+end
diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto/ecto.ex
index 324f57fdd..b66f63376 100644
--- a/lib/mix/tasks/pleroma/ecto/ecto.ex
+++ b/lib/mix/tasks/pleroma/ecto/ecto.ex
@@ -1,6 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-onl
+
defmodule Mix.Tasks.Pleroma.Ecto do
@doc """
Ensures the given repository's migrations path exists on the file system.
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 2ae16adc0..b9b1991c2 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
- `--uploads-dir` - the directory uploads go in when using a local uploader
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
+ - `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
+ - `--listen-port` - the port the app should listen to, defaults to 4000
"""
def run(["gen" | rest]) do
@@ -56,7 +58,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
indexable: :string,
db_configurable: :string,
uploads_dir: :string,
- static_dir: :string
+ static_dir: :string,
+ listen_ip: :string,
+ listen_port: :string
],
aliases: [
o: :output,
@@ -146,6 +150,22 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n"
) === "y"
+ listen_port =
+ get_option(
+ options,
+ :listen_port,
+ "What port will the app listen to (leave it if you are using the default setup with nginx)?",
+ 4000
+ )
+
+ listen_ip =
+ get_option(
+ options,
+ :listen_ip,
+ "What ip will the app listen to (leave it if you are using the default setup with nginx)?",
+ "127.0.0.1"
+ )
+
uploads_dir =
get_option(
options,
@@ -163,6 +183,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
+ jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
@@ -180,13 +201,16 @@ defmodule Mix.Tasks.Pleroma.Instance do
dbuser: dbuser,
dbpass: dbpass,
secret: secret,
+ jwt_secret: jwt_secret,
signing_salt: signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
db_configurable?: db_configurable?,
static_dir: static_dir,
uploads_dir: uploads_dir,
- rum_enabled: rum_enabled
+ rum_enabled: rum_enabled,
+ listen_ip: listen_ip,
+ listen_port: listen_port
)
result_psql =
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index 83ed0ed02..c7324fff6 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
import Mix.Pleroma
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@@ -22,6 +23,10 @@ defmodule Mix.Tasks.Pleroma.Relay do
``mix pleroma.relay unfollow ``
Example: ``mix pleroma.relay unfollow https://example.org/relay``
+
+ ## List relay subscriptions
+
+ ``mix pleroma.relay list``
"""
def run(["follow", target]) do
start_pleroma()
@@ -44,4 +49,19 @@ defmodule Mix.Tasks.Pleroma.Relay do
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
end
end
+
+ def run(["list"]) do
+ start_pleroma()
+
+ with %User{} = user <- Relay.get_actor() do
+ user.following
+ |> Enum.each(fn entry ->
+ URI.parse(entry)
+ |> Map.get(:host)
+ |> shell_info()
+ end)
+ else
+ e -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
+ end
+ end
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 8a78b4fe6..a3f8bc945 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -31,8 +31,8 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user invite [OPTION...]
Options:
- - `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05")
- - `--max_use NUMBER` - maximum numbers of token uses
+ - `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
+ - `--max-use NUMBER` - maximum numbers of token uses
## List generated invites
@@ -62,6 +62,10 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user unsubscribe NICKNAME
+ ## Unsubscribe local users from an entire instance and deactivate all accounts
+
+ mix pleroma.user unsubscribe_all_from_instance INSTANCE
+
## Create a password reset link.
mix pleroma.user reset_password NICKNAME
@@ -246,6 +250,20 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
+ def run(["unsubscribe_all_from_instance", instance]) do
+ start_pleroma()
+
+ Pleroma.User.Query.build(%{nickname: "@#{instance}"})
+ |> Pleroma.RepoStreamer.chunk_stream(500)
+ |> Stream.each(fn users ->
+ users
+ |> Enum.each(fn user ->
+ run(["unsubscribe", user.nickname])
+ end)
+ end)
+ |> Stream.run()
+ end
+
def run(["set", nickname | rest]) do
start_pleroma()
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 6db41fe6e..46552c7be 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -344,5 +344,5 @@ defmodule Pleroma.Activity do
)
end
- defdelegate search(user, query), to: Pleroma.Activity.Search
+ defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
end
diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex
index 0aa2aab23..f847ac238 100644
--- a/lib/pleroma/activity/search.ex
+++ b/lib/pleroma/activity/search.ex
@@ -5,14 +5,19 @@
defmodule Pleroma.Activity.Search do
alias Pleroma.Activity
alias Pleroma.Object.Fetcher
- alias Pleroma.Repo
+ alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility
+ require Pleroma.Constants
+
import Ecto.Query
- def search(user, search_query) do
+ def search(user, search_query, options \\ []) do
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
+ limit = Enum.min([Keyword.get(options, :limit), 40])
+ offset = Keyword.get(options, :offset, 0)
+ author = Keyword.get(options, :author)
Activity
|> Activity.with_preloaded_object()
@@ -20,15 +25,23 @@ defmodule Pleroma.Activity.Search do
|> restrict_public()
|> query_with(index_type, search_query)
|> maybe_restrict_local(user)
- |> Repo.all()
+ |> maybe_restrict_author(author)
+ |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|> maybe_fetch(user, search_query)
end
+ def maybe_restrict_author(query, %User{} = author) do
+ from([a, o] in query,
+ where: a.actor == ^author.ap_id
+ )
+ end
+
+ def maybe_restrict_author(query, _), do: query
+
defp restrict_public(q) do
from([a, o] in q,
where: fragment("?->>'type' = 'Create'", a.data),
- where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
- limit: 40
+ where: ^Pleroma.Constants.as_public() in a.recipients
)
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index ba4cf8486..00b06f723 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -140,6 +140,11 @@ defmodule Pleroma.Application do
id: :federator_init,
start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
restart: :temporary
+ },
+ %{
+ id: :internal_fetch_init,
+ start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
+ restart: :temporary
}
] ++
streamer_child() ++
@@ -157,7 +162,9 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
- Supervisor.start_link(children, opts)
+ result = Supervisor.start_link(children, opts)
+ :ok = after_supervisor_start()
+ result
end
defp setup_instrumenters do
@@ -222,4 +229,17 @@ defmodule Pleroma.Application do
:hackney_pool.child_spec(pool, options)
end
end
+
+ defp after_supervisor_start do
+ with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest],
+ true <- digest_config[:active] do
+ PleromaJobQueue.schedule(
+ digest_config[:schedule],
+ :digest_emails,
+ Pleroma.DigestEmailWorker
+ )
+ end
+
+ :ok
+ end
end
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
index a2c153720..79f133ea6 100644
--- a/lib/pleroma/bbs/authenticator.ex
+++ b/lib/pleroma/bbs/authenticator.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.BBS.Authenticator do
use Sshd.PasswordAuthenticator
alias Comeonin.Pbkdf2
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
index f34be961f..0a381f592 100644
--- a/lib/pleroma/bbs/handler.ex
+++ b/lib/pleroma/bbs/handler.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.BBS.Handler do
use Sshd.ShellHandler
alias Pleroma.Activity
diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex
index 7f8fd43b6..d976f949c 100644
--- a/lib/pleroma/bookmark.ex
+++ b/lib/pleroma/bookmark.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Bookmark do
use Ecto.Schema
diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex
index f105cbb25..a73b87251 100644
--- a/lib/pleroma/captcha/captcha.ex
+++ b/lib/pleroma/captcha/captcha.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do
+ import Pleroma.Web.Gettext
+
alias Calendar.DateTime
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor
@@ -83,10 +85,11 @@ defmodule Pleroma.Captcha do
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
try do
- if DateTime.before?(at, valid_if_after), do: throw({:error, "CAPTCHA expired"})
+ if DateTime.before?(at, valid_if_after),
+ do: throw({:error, dgettext("errors", "CAPTCHA expired")})
if not is_nil(Cachex.get!(:used_captcha_cache, token)),
- do: throw({:error, "CAPTCHA already used"})
+ do: throw({:error, dgettext("errors", "CAPTCHA already used")})
res = method().validate(token, captcha, answer_md5)
# Throw if an error occurs
@@ -101,7 +104,7 @@ defmodule Pleroma.Captcha do
:throw, e -> e
end
else
- _ -> {:error, "Invalid answer data"}
+ _ -> {:error, dgettext("errors", "Invalid answer data")}
end
{:reply, result, state}
diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex
index 18931d5a0..4e1a07c59 100644
--- a/lib/pleroma/captcha/kocaptcha.ex
+++ b/lib/pleroma/captcha/kocaptcha.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Kocaptcha do
+ import Pleroma.Web.Gettext
alias Pleroma.Captcha.Service
@behaviour Service
@@ -12,7 +13,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
case Tesla.get(endpoint <> "/new") do
{:error, _} ->
- %{error: "Kocaptcha service unavailable"}
+ %{error: dgettext("errors", "Kocaptcha service unavailable")}
{:ok, res} ->
json_resp = Jason.decode!(res.body)
@@ -32,6 +33,6 @@ defmodule Pleroma.Captcha.Kocaptcha do
if not is_nil(captcha) and
:crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data),
do: :ok,
- else: {:error, "Invalid CAPTCHA"}
+ else: {:error, dgettext("errors", "Invalid CAPTCHA")}
end
end
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index cf880aa22..7799b2a78 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Config.TransferTask do
use Task
alias Pleroma.Web.AdminAPI.Config
@@ -31,7 +35,7 @@ defmodule Pleroma.Config.TransferTask do
if String.starts_with?(setting.key, "Pleroma.") do
"Elixir." <> setting.key
else
- setting.key
+ String.trim_leading(setting.key, ":")
end
group = String.to_existing_atom(setting.group)
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
new file mode 100644
index 000000000..ef1418543
--- /dev/null
+++ b/lib/pleroma/constants.ex
@@ -0,0 +1,9 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Constants do
+ use Const
+
+ const(as_public, do: "https://www.w3.org/ns/activitystreams#Public")
+end
diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex
new file mode 100644
index 000000000..18e67d39b
--- /dev/null
+++ b/lib/pleroma/digest_email_worker.ex
@@ -0,0 +1,35 @@
+defmodule Pleroma.DigestEmailWorker do
+ import Ecto.Query
+
+ @queue_name :digest_emails
+
+ def perform do
+ config = Pleroma.Config.get([:email_notifications, :digest])
+ negative_interval = -Map.fetch!(config, :interval)
+ inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
+ inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
+
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+
+ from(u in inactive_users_query,
+ where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
+ where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
+ select: u
+ )
+ |> Pleroma.Repo.all()
+ |> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1]))
+ end
+
+ @doc """
+ Send digest email to the given user.
+ Updates `last_digest_emailed_at` field for the user and returns the updated user.
+ """
+ @spec perform(Pleroma.User.t()) :: Pleroma.User.t()
+ def perform(user) do
+ with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
+ Pleroma.Emails.Mailer.deliver_async(email)
+ end
+
+ Pleroma.User.touch_last_digest_emailed_at(user)
+ end
+end
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index d0e254362..c14be02dd 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -63,7 +63,6 @@ defmodule Pleroma.Emails.AdminEmail do
new()
|> to({to.name, to.email})
|> from({instance_name(), instance_notify_email()})
- |> reply_to({reporter.name, reporter.email})
|> subject("#{instance_name()} Report")
|> html_body(html_body)
end
diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex
index 53f5a661c..2e4657b7c 100644
--- a/lib/pleroma/emails/mailer.ex
+++ b/lib/pleroma/emails/mailer.ex
@@ -3,11 +3,58 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.Mailer do
- use Swoosh.Mailer, otp_app: :pleroma
+ @moduledoc """
+ Defines the Pleroma mailer.
+ The module contains functions to delivery email using Swoosh.Mailer.
+ """
+
+ alias Swoosh.DeliveryError
+
+ @otp_app :pleroma
+ @mailer_config [otp: :pleroma]
+
+ @spec enabled?() :: boolean()
+ def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled])
+
+ @doc "add email to queue"
def deliver_async(email, config \\ []) do
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
end
+ @doc "callback to perform send email from queue"
def perform(:deliver_async, email, config), do: deliver(email, config)
+
+ @spec deliver(Swoosh.Email.t(), Keyword.t()) :: {:ok, term} | {:error, term}
+ def deliver(email, config \\ [])
+
+ def deliver(email, config) do
+ case enabled?() do
+ true -> Swoosh.Mailer.deliver(email, parse_config(config))
+ false -> {:error, :deliveries_disabled}
+ end
+ end
+
+ @spec deliver!(Swoosh.Email.t(), Keyword.t()) :: term | no_return
+ def deliver!(email, config \\ [])
+
+ def deliver!(email, config) do
+ case deliver(email, config) do
+ {:ok, result} -> result
+ {:error, reason} -> raise DeliveryError, reason: reason
+ end
+ end
+
+ @on_load :validate_dependency
+
+ @doc false
+ def validate_dependency do
+ parse_config([])
+ |> Keyword.get(:adapter)
+ |> Swoosh.Mailer.validate_dependency()
+ end
+
+ defp parse_config(config) do
+ Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
+ end
end
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 934620765..49046bb8b 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -5,7 +5,7 @@
defmodule Pleroma.Emails.UserEmail do
@moduledoc "User emails"
- import Swoosh.Email
+ use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@@ -87,4 +87,73 @@ defmodule Pleroma.Emails.UserEmail do
|> subject("#{instance_name()} account confirmation")
|> html_body(html_body)
end
+
+ @doc """
+ Email used in digest email notifications
+ Includes Mentions and New Followers data
+ If there are no mentions (even when new followers exist), the function will return nil
+ """
+ @spec digest_email(Pleroma.User.t()) :: Swoosh.Email.t() | nil
+ def digest_email(user) do
+ new_notifications =
+ Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
+ |> Enum.reduce(%{followers: [], mentions: []}, fn
+ %{activity: %{data: %{"type" => "Create"}, actor: actor} = activity} = notification,
+ acc ->
+ new_mention = %{
+ data: notification,
+ object: Pleroma.Object.normalize(activity),
+ from: Pleroma.User.get_by_ap_id(actor)
+ }
+
+ %{acc | mentions: [new_mention | acc.mentions]}
+
+ %{activity: %{data: %{"type" => "Follow"}, actor: actor} = activity} = notification,
+ acc ->
+ new_follower = %{
+ data: notification,
+ object: Pleroma.Object.normalize(activity),
+ from: Pleroma.User.get_by_ap_id(actor)
+ }
+
+ %{acc | followers: [new_follower | acc.followers]}
+
+ _, acc ->
+ acc
+ end)
+
+ with [_ | _] = mentions <- new_notifications.mentions do
+ html_data = %{
+ instance: instance_name(),
+ user: user,
+ mentions: mentions,
+ followers: new_notifications.followers,
+ unsubscribe_link: unsubscribe_url(user, "digest")
+ }
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject("Your digest from #{instance_name()}")
+ |> render_body("digest.html", html_data)
+ else
+ _ ->
+ nil
+ end
+ end
+
+ @doc """
+ Generate unsubscribe link for given user and notifications type.
+ The link contains JWT token with the data, and subscription can be modified without
+ authorization.
+ """
+ @spec unsubscribe_url(Pleroma.User.t(), String.t()) :: String.t()
+ def unsubscribe_url(user, notifications_type) do
+ token =
+ %{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
+ |> Pleroma.JWT.generate_and_sign!()
+ |> Base.encode64()
+
+ Router.Helpers.subscription_url(Pleroma.Web.Endpoint, :unsubscribe, token)
+ end
end
diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex
index 58ab3650d..ca0610abc 100644
--- a/lib/pleroma/flake_id.ex
+++ b/lib/pleroma/flake_id.ex
@@ -66,6 +66,16 @@ defmodule Pleroma.FlakeId do
@spec get :: binary
def get, do: to_string(:gen_server.call(:flake, :get))
+ # checks that ID is is valid FlakeID
+ #
+ @spec is_flake_id?(String.t()) :: boolean
+ def is_flake_id?(id), do: is_flake_id?(String.to_charlist(id), true)
+ defp is_flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: is_flake_id?(cs, true)
+ defp is_flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: is_flake_id?(cs, true)
+ defp is_flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: is_flake_id?(cs, true)
+ defp is_flake_id?([], true), do: true
+ defp is_flake_id?(_, _), do: false
+
# -- Ecto.Type API
@impl Ecto.Type
def type, do: :uuid
diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex
index c216cdcb1..7e2c6f5e8 100644
--- a/lib/pleroma/http/connection.ex
+++ b/lib/pleroma/http/connection.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.HTTP.Connection do
connect_timeout: 10_000,
recv_timeout: 20_000,
follow_redirect: true,
+ force_redirect: true,
pool: :federation
]
@adapter Application.get_env(:tesla, :adapter)
@@ -29,7 +30,7 @@ defmodule Pleroma.HTTP.Connection do
# fetch Hackney options
#
- defp hackney_options(opts) do
+ def 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)
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index c96ee7353..dec24458a 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -65,10 +65,7 @@ defmodule Pleroma.HTTP do
end
def process_request_options(options) do
- case Pleroma.Config.get([:http, :proxy_url]) do
- nil -> options
- proxy -> options ++ [proxy: proxy]
- end
+ Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
end
@doc """
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index fa5043bc5..1b05d573c 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Instances do
@moduledoc "Instances context."
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 420803a8f..4d7ed4ca1 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Instances.Instance do
@moduledoc "Instance."
diff --git a/lib/pleroma/jwt.ex b/lib/pleroma/jwt.ex
new file mode 100644
index 000000000..10102ff5d
--- /dev/null
+++ b/lib/pleroma/jwt.ex
@@ -0,0 +1,9 @@
+defmodule Pleroma.JWT do
+ use Joken.Config
+
+ @impl true
+ def token_config do
+ default_claims(skip: [:aud])
+ |> add_claim("aud", &Pleroma.Web.Endpoint.url/0, &(&1 == Pleroma.Web.Endpoint.url()))
+ end
+end
diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex
index b7bc7a4da..6dd31d3bd 100644
--- a/lib/pleroma/keys.ex
+++ b/lib/pleroma/keys.ex
@@ -35,10 +35,12 @@ defmodule Pleroma.Keys do
end
def keys_from_pem(pem) do
- [private_key_code] = :public_key.pem_decode(pem)
- private_key = :public_key.pem_entry_decode(private_key_code)
- {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
- public_key = {:RSAPublicKey, modulus, exponent}
- {:ok, private_key, public_key}
+ with [private_key_code] <- :public_key.pem_decode(pem),
+ private_key <- :public_key.pem_entry_decode(private_key_code),
+ {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
+ {:ok, private_key, {:RSAPublicKey, modulus, exponent}}
+ else
+ error -> {:error, error}
+ end
end
end
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index a5b1cad68..1d320206e 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.List do
belongs_to(:user, User, type: Pleroma.FlakeId)
field(:title, :string)
field(:following, {:array, :string}, default: [])
+ field(:ap_id, :string)
timestamps()
end
@@ -55,6 +56,10 @@ defmodule Pleroma.List do
Repo.one(query)
end
+ def get_by_ap_id(ap_id) do
+ Repo.get_by(__MODULE__, ap_id: ap_id)
+ end
+
def get_following(%Pleroma.List{following: following} = _list) do
q =
from(
@@ -105,7 +110,14 @@ defmodule Pleroma.List do
def create(title, %User{} = creator) do
list = %Pleroma.List{user_id: creator.id, title: title}
- Repo.insert(list)
+
+ Repo.transaction(fn ->
+ list = Repo.insert!(list)
+
+ list
+ |> change(ap_id: "#{creator.ap_id}/lists/#{list.id}")
+ |> Repo.update!()
+ end)
end
def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
@@ -125,4 +137,19 @@ defmodule Pleroma.List do
|> follow_changeset(attrs)
|> Repo.update()
end
+
+ def memberships(%User{follower_address: follower_address}) do
+ Pleroma.List
+ |> where([l], ^follower_address in l.following)
+ |> select([l], l.ap_id)
+ |> Repo.all()
+ end
+
+ def memberships(_), do: []
+
+ def member?(%Pleroma.List{following: following}, %User{follower_address: follower_address}) do
+ Enum.member?(following, follower_address)
+ end
+
+ def member?(_, _), do: false
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index a414afbbf..5d29af853 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -11,7 +11,6 @@ defmodule Pleroma.Notification do
alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
@@ -19,6 +18,8 @@ defmodule Pleroma.Notification do
import Ecto.Query
import Ecto.Changeset
+ @type t :: %__MODULE__{}
+
schema "notifications" do
field(:seen, :boolean, default: false)
belongs_to(:user, User, type: Pleroma.FlakeId)
@@ -32,34 +33,69 @@ defmodule Pleroma.Notification do
|> cast(attrs, [:seen])
end
- def for_user_query(user) do
- Notification
- |> where(user_id: ^user.id)
- |> where(
- [n, a],
- fragment(
- "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
- a.actor
- )
- )
- |> join(:inner, [n], activity in assoc(n, :activity))
- |> join(:left, [n, a], object in Object,
- on:
+ def for_user_query(user, opts \\ []) do
+ query =
+ Notification
+ |> where(user_id: ^user.id)
+ |> where(
+ [n, a],
fragment(
- "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
- object.data,
- a.data
+ "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
+ a.actor
)
- )
- |> preload([n, a, o], activity: {a, object: o})
+ )
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> join(:left, [n, a], object in Object,
+ on:
+ fragment(
+ "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
+ object.data,
+ a.data
+ )
+ )
+ |> preload([n, a, o], activity: {a, object: o})
+
+ if opts[:with_muted] do
+ query
+ else
+ where(query, [n, a], a.actor not in ^user.info.muted_notifications)
+ |> where([n, a], a.actor not in ^user.info.blocks)
+ |> where(
+ [n, a],
+ fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
+ )
+ |> join(:left, [n, a], tm in Pleroma.ThreadMute,
+ on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+ )
+ |> where([n, a, o, tm], is_nil(tm.user_id))
+ end
end
def for_user(user, opts \\ %{}) do
user
- |> for_user_query()
+ |> for_user_query(opts)
|> Pagination.fetch_paginated(opts)
end
+ @doc """
+ Returns notifications for user received since given date.
+
+ ## Examples
+
+ iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
+ [%Pleroma.Notification{}, %Pleroma.Notification{}]
+
+ iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
+ []
+ """
+ @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
+ def for_user_since(user, date) do
+ from(n in for_user_query(user),
+ where: n.updated_at > ^date
+ )
+ |> Repo.all()
+ end
+
def set_read_up_to(%{id: user_id} = _user, id) do
query =
from(
@@ -67,7 +103,10 @@ defmodule Pleroma.Notification do
where: n.user_id == ^user_id,
where: n.id <= ^id,
update: [
- set: [seen: true]
+ set: [
+ seen: true,
+ updated_at: ^NaiveDateTime.utc_now()
+ ]
]
)
@@ -179,11 +218,10 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(_, _local_only), do: []
+ @spec skip?(Activity.t(), User.t()) :: boolean()
def skip?(activity, user) do
[
:self,
- :blocked,
- :muted,
:followers,
:follows,
:non_followers,
@@ -193,21 +231,11 @@ defmodule Pleroma.Notification do
|> Enum.any?(&skip?(&1, activity, user))
end
+ @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
def skip?(:self, activity, user) do
activity.data["actor"] == user.ap_id
end
- def skip?(:blocked, activity, user) do
- actor = activity.data["actor"]
- User.blocks?(user, %{ap_id: actor})
- end
-
- def skip?(:muted, activity, user) do
- actor = activity.data["actor"]
-
- User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
- end
-
def skip?(
:followers,
activity,
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 4b181ec59..c8d339c19 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -44,44 +44,46 @@ defmodule Pleroma.Object do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
- def normalize(_, fetch_remote \\ true)
+ defp warn_on_no_object_preloaded(ap_id) do
+ "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object"
+ |> Logger.debug()
+
+ Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
+ end
+
+ def normalize(_, fetch_remote \\ true, options \\ [])
+
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
- def normalize(%Object{} = object, _), do: object
- def normalize(%Activity{object: %Object{} = object}, _), do: object
+ def normalize(%Object{} = object, _, _), do: object
+ def normalize(%Activity{object: %Object{} = object}, _, _), do: object
# A hack for fake activities
- def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do
+ def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
%Object{id: "pleroma:fake_object_id", data: data}
end
- # Catch and log Object.normalize() calls where the Activity's child object is not
- # preloaded.
- def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do
- Logger.debug(
- "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
- )
-
- Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
-
+ # No preloaded object
+ def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
+ warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end
- def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do
- Logger.debug(
- "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
- )
-
- Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
-
+ # No preloaded object
+ def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
+ warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end
# Old way, try fetching the object through cache.
- def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote)
- def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
- def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id)
- def normalize(_, _), do: nil
+ def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
+ def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
+
+ def normalize(ap_id, true, options) when is_binary(ap_id) do
+ Fetcher.fetch_object_from_id!(ap_id, options)
+ end
+
+ def normalize(_, _, _), do: nil
# Owned objects can only be mutated by their owner
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index ada9da0bb..f077a9f32 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -48,6 +48,9 @@ defmodule Pleroma.Object.Containment do
end
end
+ def contain_origin(id, %{"attributedTo" => actor} = params),
+ do: contain_origin(id, Map.put(params, "actor", actor))
+
def contain_origin_from_id(_id, %{"id" => nil}), do: :error
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
@@ -60,4 +63,9 @@ defmodule Pleroma.Object.Containment do
:error
end
end
+
+ def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
+ do: contain_origin(id, object)
+
+ def contain_child(_), do: :ok
end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index c422490ac..8d79ddb1f 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -1,7 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
alias Pleroma.Object
alias Pleroma.Object.Containment
+ alias Pleroma.Signature
+ alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.OStatus
@@ -22,39 +28,45 @@ defmodule Pleroma.Object.Fetcher do
# TODO:
# This will create a Create activity, which we need internally at the moment.
- def fetch_object_from_id(id) do
+ def fetch_object_from_id(id, options \\ []) do
if object = Object.get_cached_by_ap_id(id) do
{:ok, object}
else
Logger.info("Fetching #{id} via AP")
- with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
- nil <- Object.normalize(data, false),
+ with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
+ {:normalize, nil} <- {:normalize, Object.normalize(data, false)},
params <- %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
+ # Should we seriously keep this attributedTo thing?
"actor" => data["actor"] || data["attributedTo"],
"object" => data
},
- :ok <- Containment.contain_origin(id, params),
- {:ok, activity} <- Transmogrifier.handle_incoming(params),
+ {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
+ {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
{:object, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do
{:ok, object}
else
+ {:containment, _} ->
+ {:error, "Object containment failed."}
+
{:error, {:reject, nil}} ->
{:reject, nil}
{:object, data, nil} ->
reinject_object(data)
- object = %Object{} ->
+ {:normalize, object = %Object{}} ->
{:ok, object}
_e ->
+ # Only fallback when receiving a fetch/normalization error with ActivityPub
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
+ # FIXME: OStatus Object Containment?
case OStatus.fetch_activity_from_url(id) do
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
e -> e
@@ -63,8 +75,8 @@ defmodule Pleroma.Object.Fetcher do
end
end
- def fetch_object_from_id!(id) do
- with {:ok, object} <- fetch_object_from_id(id) do
+ def fetch_object_from_id!(id, options \\ []) do
+ with {:ok, object} <- fetch_object_from_id(id, options) do
object
else
_e ->
@@ -72,15 +84,52 @@ defmodule Pleroma.Object.Fetcher do
end
end
- def fetch_and_contain_remote_object_from_id(id) do
+ defp make_signature(id, date) do
+ uri = URI.parse(id)
+
+ signature =
+ InternalFetchActor.get_actor()
+ |> Signature.sign(%{
+ "(request-target)": "get #{uri.path}",
+ host: uri.host,
+ date: date
+ })
+
+ [{:Signature, signature}]
+ end
+
+ defp sign_fetch(headers, id, date) do
+ if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
+ headers ++ make_signature(id, date)
+ else
+ headers
+ end
+ end
+
+ defp maybe_date_fetch(headers, date) do
+ if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
+ headers ++ [{:Date, date}]
+ else
+ headers
+ end
+ end
+
+ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.info("Fetching object #{id} via AP")
+ date =
+ NaiveDateTime.utc_now()
+ |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
+
+ headers =
+ [{:Accept, "application/activity+json"}]
+ |> maybe_date_fetch(date)
+ |> sign_fetch(id, date)
+
+ Logger.debug("Fetch headers: #{inspect(headers)}")
+
with true <- String.starts_with?(id, "http"),
- {:ok, %{body: body, status: code}} when code in 200..299 <-
- HTTP.get(
- id,
- [{:Accept, "application/activity+json"}]
- ),
+ {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers),
{:ok, data} <- Jason.decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
{:ok, data}
@@ -92,4 +141,9 @@ defmodule Pleroma.Object.Fetcher do
{:error, e}
end
end
+
+ def fetch_and_contain_remote_object_from_id(%{"id" => id}),
+ do: fetch_and_contain_remote_object_from_id(id)
+
+ def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"}
end
diff --git a/lib/pleroma/object_tombstone.ex b/lib/pleroma/object_tombstone.ex
index 64d836d3e..fe947ffd3 100644
--- a/lib/pleroma/object_tombstone.ex
+++ b/lib/pleroma/object_tombstone.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.ObjectTombstone do
@enforce_keys [:id, :formerType, :deleted]
defstruct [:id, :formerType, :deleted, type: "Tombstone"]
diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex
index f435e5c9c..2b869ccdc 100644
--- a/lib/pleroma/pagination.ex
+++ b/lib/pleroma/pagination.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Pagination do
@moduledoc """
Implements Mastodon-compatible pagination.
@@ -10,16 +14,28 @@ defmodule Pleroma.Pagination do
@default_limit 20
- def fetch_paginated(query, params) do
+ def fetch_paginated(query, params, type \\ :keyset)
+
+ def fetch_paginated(query, params, :keyset) do
options = cast_params(params)
query
- |> paginate(options)
+ |> paginate(options, :keyset)
|> Repo.all()
|> enforce_order(options)
end
- def paginate(query, options) do
+ def fetch_paginated(query, params, :offset) do
+ options = cast_params(params)
+
+ query
+ |> paginate(options, :offset)
+ |> Repo.all()
+ end
+
+ def paginate(query, options, method \\ :keyset)
+
+ def paginate(query, options, :keyset) do
query
|> restrict(:min_id, options)
|> restrict(:since_id, options)
@@ -28,11 +44,18 @@ defmodule Pleroma.Pagination do
|> restrict(:limit, options)
end
+ def paginate(query, options, :offset) do
+ query
+ |> restrict(:offset, options)
+ |> restrict(:limit, options)
+ end
+
defp cast_params(params) do
param_types = %{
min_id: :string,
since_id: :string,
max_id: :string,
+ offset: :integer,
limit: :integer
}
@@ -66,6 +89,10 @@ defmodule Pleroma.Pagination do
order_by(query, [u], fragment("? desc nulls last", u.id))
end
+ defp restrict(query, :offset, %{offset: offset}) do
+ offset(query, ^offset)
+ end
+
defp restrict(query, :limit, options) do
limit = Map.get(options, :limit, @default_limit)
diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/plugs/authentication_plug.ex
index da4ed4226..567674a0b 100644
--- a/lib/pleroma/plugs/authentication_plug.ex
+++ b/lib/pleroma/plugs/authentication_plug.ex
@@ -6,9 +6,21 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
alias Comeonin.Pbkdf2
import Plug.Conn
alias Pleroma.User
+ require Logger
- def init(options) do
- options
+ def init(options), do: options
+
+ def checkpw(password, "$6" <> _ = password_hash) do
+ :crypt.crypt(password, password_hash) == password_hash
+ end
+
+ def checkpw(password, "$pbkdf2" <> _ = password_hash) do
+ Pbkdf2.checkpw(password, password_hash)
+ end
+
+ def checkpw(_password, _password_hash) do
+ Logger.error("Password hash not recognized")
+ false
end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex
index 11c4342c4..27cd41aec 100644
--- a/lib/pleroma/plugs/ensure_authenticated_plug.ex
+++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
import Plug.Conn
+ import Pleroma.Web.TranslationHelpers
alias Pleroma.User
def init(options) do
@@ -16,8 +17,7 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
def call(conn, _) do
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{error: "Invalid credentials."}))
+ |> render_error(:forbidden, "Invalid credentials.")
|> halt
end
end
diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
index 317fd5445..a16f61435 100644
--- a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
+++ b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
+ import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.Config
alias Pleroma.User
@@ -23,8 +24,7 @@ defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
{false, _} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
+ |> render_error(:forbidden, "This resource requires authentication.")
|> halt
end
end
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex
index e2874c469..d87fa52fa 100644
--- a/lib/pleroma/plugs/http_signature.ex
+++ b/lib/pleroma/plugs/http_signature.ex
@@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
- alias Pleroma.Web.ActivityPub.Utils
import Plug.Conn
require Logger
@@ -16,38 +15,30 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
end
def call(conn, _opts) do
- user = Utils.get_ap_id(conn.params["actor"])
- Logger.debug("Checking sig for #{user}")
[signature | _] = get_req_header(conn, "signature")
- cond do
- signature && String.contains?(signature, user) ->
- # set (request-target) header to the appropriate value
- # we also replace the digest header with the one we computed
- conn =
- conn
- |> put_req_header(
- "(request-target)",
- String.downcase("#{conn.method}") <> " #{conn.request_path}"
- )
-
- conn =
- if conn.assigns[:digest] do
- conn
- |> put_req_header("digest", conn.assigns[:digest])
- else
- conn
- end
-
- assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
-
- signature ->
- Logger.debug("Signature not from actor")
- assign(conn, :valid_signature, false)
-
- true ->
- Logger.debug("No signature header!")
+ if signature do
+ # set (request-target) header to the appropriate value
+ # we also replace the digest header with the one we computed
+ conn =
conn
+ |> put_req_header(
+ "(request-target)",
+ String.downcase("#{conn.method}") <> " #{conn.request_path}"
+ )
+
+ conn =
+ if conn.assigns[:digest] do
+ conn
+ |> put_req_header("digest", conn.assigns[:digest])
+ else
+ conn
+ end
+
+ assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+ else
+ Logger.debug("No signature header!")
+ conn
end
end
end
diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
new file mode 100644
index 000000000..ce8494b9d
--- /dev/null
+++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
@@ -0,0 +1,70 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
+ alias Pleroma.Signature
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Utils
+
+ import Plug.Conn
+ require Logger
+
+ def init(options), do: options
+
+ defp key_id_from_conn(conn) do
+ with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do
+ Signature.key_id_to_actor_id(key_id)
+ else
+ _ ->
+ nil
+ end
+ end
+
+ defp user_from_key_id(conn) do
+ with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
+ user
+ else
+ _ ->
+ nil
+ end
+ end
+
+ def call(%{assigns: %{user: _}} = conn, _opts), do: conn
+
+ # if this has payload make sure it is signed by the same actor that made it
+ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
+ with actor_id <- Utils.get_ap_id(actor),
+ {:user, %User{} = user} <- {:user, user_from_key_id(conn)},
+ {:user_match, true} <- {:user_match, user.ap_id == actor_id} do
+ assign(conn, :user, user)
+ else
+ {:user_match, false} ->
+ Logger.debug("Failed to map identity from signature (payload actor mismatch)")
+ Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
+ assign(conn, :valid_signature, false)
+
+ # remove me once testsuite uses mapped capabilities instead of what we do now
+ {:user, nil} ->
+ Logger.debug("Failed to map identity from signature (lookup failure)")
+ Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
+ conn
+ end
+ end
+
+ # no payload, probably a signed fetch
+ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
+ with %User{} = user <- user_from_key_id(conn) do
+ assign(conn, :user, user)
+ else
+ _ ->
+ Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
+ Logger.debug("key_id=#{key_id_from_conn(conn)}")
+ assign(conn, :valid_signature, false)
+ end
+ end
+
+ # no signature at all
+ def call(conn, _opts), do: conn
+end
diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex
index f2bfa2b1a..b508628a9 100644
--- a/lib/pleroma/plugs/oauth_scopes_plug.ex
+++ b/lib/pleroma/plugs/oauth_scopes_plug.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Plugs.OAuthScopesPlug do
import Plug.Conn
+ import Pleroma.Web.Gettext
@behaviour Plug
@@ -30,11 +31,14 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
true ->
missing_scopes = scopes -- token.scopes
- error_message = "Insufficient permissions: #{Enum.join(missing_scopes, " #{op} ")}."
+ permissions = Enum.join(missing_scopes, " #{op} ")
+
+ error_message =
+ dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
conn
|> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{error: error_message}))
+ |> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
|> halt()
end
end
diff --git a/lib/pleroma/plugs/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter.ex
index 9ba5875fa..31388f574 100644
--- a/lib/pleroma/plugs/rate_limiter.ex
+++ b/lib/pleroma/plugs/rate_limiter.ex
@@ -31,12 +31,28 @@ defmodule Pleroma.Plugs.RateLimiter do
## Usage
+ AllowedSyntax:
+
+ plug(Pleroma.Plugs.RateLimiter, :limiter_name)
+ plug(Pleroma.Plugs.RateLimiter, {:limiter_name, options})
+
+ Allowed options:
+
+ * `bucket_name` overrides bucket name (e.g. to have a separate limit for a set of actions)
+ * `params` appends values of specified request params (e.g. ["id"]) to bucket name
+
Inside a controller:
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
- or inside a router pipiline:
+ plug(
+ Pleroma.Plugs.RateLimiter,
+ {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
+ when action in ~w(fav_status unfav_status)a
+ )
+
+ or inside a router pipeline:
pipeline :api do
...
@@ -44,39 +60,61 @@ defmodule Pleroma.Plugs.RateLimiter do
...
end
"""
-
- import Phoenix.Controller, only: [json: 2]
+ import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.User
- def init(limiter_name) do
+ def init(limiter_name) when is_atom(limiter_name) do
+ init({limiter_name, []})
+ end
+
+ def init({limiter_name, opts}) do
case Pleroma.Config.get([:rate_limit, limiter_name]) do
nil -> nil
- config -> {limiter_name, config}
+ config -> {limiter_name, config, opts}
end
end
- # do not limit if there is no limiter configuration
+ # Do not limit if there is no limiter configuration
def call(conn, nil), do: conn
- def call(conn, opts) do
- case check_rate(conn, opts) do
- {:ok, _count} -> conn
- {:error, _count} -> render_error(conn)
+ def call(conn, settings) do
+ case check_rate(conn, settings) do
+ {:ok, _count} ->
+ conn
+
+ {:error, _count} ->
+ render_throttled_error(conn)
end
end
- defp check_rate(%{assigns: %{user: %User{id: user_id}}}, {limiter_name, [_, {scale, limit}]}) do
- ExRated.check_rate("#{limiter_name}:#{user_id}", scale, limit)
+ defp bucket_name(conn, limiter_name, opts) do
+ bucket_name = opts[:bucket_name] || limiter_name
+
+ if params_names = opts[:params] do
+ params_values = for p <- Enum.sort(params_names), do: conn.params[p]
+ Enum.join([bucket_name] ++ params_values, ":")
+ else
+ bucket_name
+ end
end
- defp check_rate(conn, {limiter_name, [{scale, limit} | _]}) do
- ExRated.check_rate("#{limiter_name}:#{ip(conn)}", scale, limit)
+ defp check_rate(
+ %{assigns: %{user: %User{id: user_id}}} = conn,
+ {limiter_name, [_, {scale, limit}], opts}
+ ) do
+ bucket_name = bucket_name(conn, limiter_name, opts)
+ ExRated.check_rate("#{bucket_name}:#{user_id}", scale, limit)
end
- defp check_rate(conn, {limiter_name, {scale, limit}}) do
- check_rate(conn, {limiter_name, [{scale, limit}]})
+ defp check_rate(conn, {limiter_name, [{scale, limit} | _], opts}) do
+ bucket_name = bucket_name(conn, limiter_name, opts)
+ ExRated.check_rate("#{bucket_name}:#{ip(conn)}", scale, limit)
+ end
+
+ defp check_rate(conn, {limiter_name, {scale, limit}, opts}) do
+ check_rate(conn, {limiter_name, [{scale, limit}, {scale, limit}], opts})
end
def ip(%{remote_ip: remote_ip}) do
@@ -85,10 +123,9 @@ defmodule Pleroma.Plugs.RateLimiter do
|> Enum.join(".")
end
- defp render_error(conn) do
+ defp render_throttled_error(conn) do
conn
- |> put_status(:too_many_requests)
- |> json(%{error: "Throttled"})
+ |> render_error(:too_many_requests, "Throttled")
|> halt()
end
end
diff --git a/lib/pleroma/plugs/set_format_plug.ex b/lib/pleroma/plugs/set_format_plug.ex
new file mode 100644
index 000000000..5ca741c64
--- /dev/null
+++ b/lib/pleroma/plugs/set_format_plug.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.SetFormatPlug do
+ import Plug.Conn, only: [assign: 3, fetch_query_params: 1]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ case get_format(conn) do
+ nil -> conn
+ format -> assign(conn, :format, format)
+ end
+ end
+
+ defp get_format(conn) do
+ conn.private[:phoenix_format] ||
+ case fetch_query_params(conn) do
+ %{query_params: %{"_format" => format}} -> format
+ _ -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/plugs/set_locale_plug.ex b/lib/pleroma/plugs/set_locale_plug.ex
new file mode 100644
index 000000000..8646cb30d
--- /dev/null
+++ b/lib/pleroma/plugs/set_locale_plug.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# NOTE: this module is based on https://github.com/smeevil/set_locale
+defmodule Pleroma.Plugs.SetLocalePlug do
+ import Plug.Conn, only: [get_req_header: 2, assign: 3]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ locale = get_locale_from_header(conn) || Gettext.get_locale()
+ Gettext.put_locale(locale)
+ assign(conn, :locale, locale)
+ end
+
+ defp get_locale_from_header(conn) do
+ conn
+ |> extract_accept_language()
+ |> Enum.find(&supported_locale?/1)
+ end
+
+ defp extract_accept_language(conn) do
+ case get_req_header(conn, "accept-language") do
+ [value | _] ->
+ value
+ |> String.split(",")
+ |> Enum.map(&parse_language_option/1)
+ |> Enum.sort(&(&1.quality > &2.quality))
+ |> Enum.map(& &1.tag)
+ |> Enum.reject(&is_nil/1)
+ |> ensure_language_fallbacks()
+
+ _ ->
+ []
+ end
+ end
+
+ defp supported_locale?(locale) do
+ Pleroma.Web.Gettext
+ |> Gettext.known_locales()
+ |> Enum.member?(locale)
+ end
+
+ defp parse_language_option(string) do
+ captures = Regex.named_captures(~r/^\s?(?[\w\-]+)(?:;q=(?[\d\.]+))?$/i, string)
+
+ quality =
+ case Float.parse(captures["quality"] || "1.0") do
+ {val, _} -> val
+ :error -> 1.0
+ end
+
+ %{tag: captures["tag"], quality: quality}
+ end
+
+ defp ensure_language_fallbacks(tags) do
+ Enum.flat_map(tags, fn tag ->
+ [language | _] = String.split(tag, "-")
+ if Enum.member?(tags, language), do: [tag], else: [tag, language]
+ end)
+ end
+end
diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex
index 8d0fac7ee..69c1ab942 100644
--- a/lib/pleroma/plugs/uploaded_media.ex
+++ b/lib/pleroma/plugs/uploaded_media.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
"""
import Plug.Conn
+ import Pleroma.Web.Gettext
require Logger
@behaviour Plug
@@ -45,7 +46,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
else
_ ->
conn
- |> send_resp(500, "Failed")
+ |> send_resp(:internal_server_error, dgettext("errors", "Failed"))
|> halt()
end
end
@@ -64,7 +65,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
conn
else
conn
- |> send_resp(404, "Not found")
+ |> send_resp(:not_found, dgettext("errors", "Not found"))
|> halt()
end
end
@@ -84,7 +85,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
conn
- |> send_resp(500, "Internal Error")
+ |> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
|> halt()
end
end
diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex
index 04329e919..4c4b3d610 100644
--- a/lib/pleroma/plugs/user_is_admin_plug.ex
+++ b/lib/pleroma/plugs/user_is_admin_plug.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserIsAdminPlug do
+ import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.User
@@ -16,8 +17,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
def call(conn, _) do
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{error: "User is not admin."}))
+ |> render_error(:forbidden, "User is not admin.")
|> halt
end
end
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
new file mode 100644
index 000000000..776c4794c
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client do
+ @callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
+ {:ok, pos_integer(), [tuple()], reference() | map()}
+ | {:ok, pos_integer(), [tuple()]}
+ | {:ok, reference()}
+ | {:error, term()}
+
+ @callback stream_body(reference() | pid() | map()) ::
+ {:ok, binary()} | :done | {:error, String.t()}
+
+ @callback close(reference() | pid() | map()) :: :ok
+
+ def request(method, url, headers, "", opts \\ []) do
+ client().request(method, url, headers, "", opts)
+ end
+
+ def stream_body(ref), do: client().stream_body(ref)
+
+ def close(ref), do: client().close(ref)
+
+ defp client do
+ Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney)
+ end
+end
diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
similarity index 96%
rename from lib/pleroma/reverse_proxy.ex
rename to lib/pleroma/reverse_proxy/reverse_proxy.ex
index de0f6e1bc..1f98f215c 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -61,7 +61,7 @@ defmodule Pleroma.ReverseProxy do
* `http`: options for [hackney](https://github.com/benoitc/hackney).
"""
- @default_hackney_options []
+ @default_hackney_options [pool: :media]
@inline_content_types [
"image/gif",
@@ -94,7 +94,8 @@ defmodule Pleroma.ReverseProxy do
def call(conn = %{method: method}, url, opts) when method in @methods do
hackney_opts =
- @default_hackney_options
+ Pleroma.HTTP.Connection.hackney_options([])
+ |> Keyword.merge(@default_hackney_options)
|> Keyword.merge(Keyword.get(opts, :http, []))
|> HTTP.process_request_options()
@@ -146,7 +147,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 client().request(method, url, headers, "", hackney_opts) do
{:ok, code, headers, client} when code in @valid_resp_codes ->
{:ok, code, downcase_headers(headers), client}
@@ -173,7 +174,7 @@ defmodule Pleroma.ReverseProxy do
halt(conn)
{:error, :closed, conn} ->
- :hackney.close(client)
+ client().close(client)
halt(conn)
{:error, error, conn} ->
@@ -181,7 +182,7 @@ defmodule Pleroma.ReverseProxy do
"#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}"
)
- :hackney.close(client)
+ client().close(client)
halt(conn)
end
end
@@ -196,7 +197,7 @@ defmodule Pleroma.ReverseProxy do
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
- {:ok, data} <- hackney().stream_body(client),
+ {:ok, data} <- client().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)),
@@ -378,5 +379,5 @@ defmodule Pleroma.ReverseProxy do
{:ok, :no_duration_limit, :no_duration_limit}
end
- defp hackney, do: Pleroma.Config.get(:hackney, :hackney)
+ defp client, do: Pleroma.ReverseProxy.Client
end
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 1a4d54c62..15bf3c317 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -8,10 +8,25 @@ defmodule Pleroma.Signature do
alias Pleroma.Keys
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Utils
+
+ def key_id_to_actor_id(key_id) do
+ uri =
+ URI.parse(key_id)
+ |> Map.put(:fragment, nil)
+
+ uri =
+ if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do
+ Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
+ else
+ uri
+ end
+
+ URI.to_string(uri)
+ end
def fetch_public_key(conn) do
- with actor_id <- Utils.get_ap_id(conn.params["actor"]),
+ with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
+ actor_id <- key_id_to_actor_id(kid),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
@@ -21,7 +36,8 @@ defmodule Pleroma.Signature do
end
def refetch_public_key(conn) do
- with actor_id <- Utils.get_ap_id(conn.params["actor"]),
+ with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
+ actor_id <- key_id_to_actor_id(kid),
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index c47d65241..9f0adde5b 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -228,7 +228,14 @@ defmodule Pleroma.Upload do
""
end
- [base_url, "media", path]
+ prefix =
+ if is_nil(Pleroma.Config.get([__MODULE__, :base_url])) do
+ "media"
+ else
+ ""
+ end
+
+ [base_url, prefix, path]
|> Path.join()
end
diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex
index e4c225833..14928c355 100644
--- a/lib/pleroma/upload/filter/dedupe.ex
+++ b/lib/pleroma/upload/filter/dedupe.ex
@@ -6,10 +6,19 @@ defmodule Pleroma.Upload.Filter.Dedupe do
@behaviour Pleroma.Upload.Filter
alias Pleroma.Upload
- def filter(%Upload{name: name} = upload) do
- extension = String.split(name, ".") |> List.last()
- shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
+ def filter(%Upload{name: name, tempfile: tempfile} = upload) do
+ extension =
+ name
+ |> String.split(".")
+ |> List.last()
+
+ shasum =
+ :crypto.hash(:sha256, File.read!(tempfile))
+ |> Base.encode16(case: :lower)
+
filename = shasum <> "." <> extension
{:ok, %Upload{upload | id: shasum, path: filename}}
end
+
+ def filter(_), do: :ok
end
diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex
index 35a5a1381..fee49fb51 100644
--- a/lib/pleroma/upload/filter/mogrifun.ex
+++ b/lib/pleroma/upload/filter/mogrifun.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Upload.Filter.Mogrifun do
@behaviour Pleroma.Upload.Filter
+ alias Pleroma.Upload.Filter
@filters [
{"implode", "1"},
@@ -34,31 +35,10 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
]
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
- filter = Enum.random(@filters)
-
- file
- |> Mogrify.open()
- |> mogrify_filter(filter)
- |> Mogrify.save(in_place: true)
+ Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
:ok
end
def filter(_), do: :ok
-
- defp mogrify_filter(mogrify, [filter | rest]) do
- mogrify
- |> mogrify_filter(filter)
- |> mogrify_filter(rest)
- end
-
- defp mogrify_filter(mogrify, []), do: mogrify
-
- defp mogrify_filter(mogrify, {action, options}) do
- Mogrify.custom(mogrify, action, options)
- end
-
- defp mogrify_filter(mogrify, string) when is_binary(string) do
- Mogrify.custom(mogrify, string)
- end
end
diff --git a/lib/pleroma/upload/filter/mogrify.ex b/lib/pleroma/upload/filter/mogrify.ex
index f459eeecb..91bfdd4f5 100644
--- a/lib/pleroma/upload/filter/mogrify.ex
+++ b/lib/pleroma/upload/filter/mogrify.ex
@@ -11,16 +11,19 @@ defmodule Pleroma.Upload.Filter.Mogrify do
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
filters = Pleroma.Config.get!([__MODULE__, :args])
- file
- |> Mogrify.open()
- |> mogrify_filter(filters)
- |> Mogrify.save(in_place: true)
-
+ do_filter(file, filters)
:ok
end
def filter(_), do: :ok
+ def do_filter(file, filters) do
+ file
+ |> Mogrify.open()
+ |> mogrify_filter(filters)
+ |> Mogrify.save(in_place: true)
+ end
+
defp mogrify_filter(mogrify, nil), do: mogrify
defp mogrify_filter(mogrify, [filter | rest]) do
diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex
index fc533da23..36b3c35ec 100644
--- a/lib/pleroma/uploaders/local.ex
+++ b/lib/pleroma/uploaders/local.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Uploaders.Local do
def put_file(upload) do
{local_path, file} =
- case Enum.reverse(String.split(upload.path, "/", trim: true)) do
+ case Enum.reverse(Path.split(upload.path)) do
[file] ->
{upload_path(), file}
@@ -23,7 +23,7 @@ defmodule Pleroma.Uploaders.Local do
result_file = Path.join(local_path, file)
- unless File.exists?(result_file) do
+ if not File.exists?(result_file) do
File.cp!(upload.tempfile, result_file)
end
diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex
index 237544337..c36f3d61d 100644
--- a/lib/pleroma/uploaders/mdii.ex
+++ b/lib/pleroma/uploaders/mdii.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do
+ @moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
+
alias Pleroma.Config
alias Pleroma.HTTP
diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex
index 521daa93b..8c353bed3 100644
--- a/lib/pleroma/uploaders/s3.ex
+++ b/lib/pleroma/uploaders/s3.ex
@@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader
require Logger
+ alias Pleroma.Config
+
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
def get_file(file) do
- config = Pleroma.Config.get([__MODULE__])
+ config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)
bucket_with_namespace =
@@ -34,15 +36,15 @@ defmodule Pleroma.Uploaders.S3 do
end
def put_file(%Pleroma.Upload{} = upload) do
- config = Pleroma.Config.get([__MODULE__])
+ config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
- {:ok, file_data} = File.read(upload.tempfile)
-
s3_name = strict_encode(upload.path)
op =
- ExAws.S3.put_object(bucket, s3_name, file_data, [
+ upload.tempfile
+ |> ExAws.S3.Upload.stream_file()
+ |> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
diff --git a/lib/pleroma/uploaders/swift/keystone.ex b/lib/pleroma/uploaders/swift/keystone.ex
deleted file mode 100644
index dd44c7561..000000000
--- a/lib/pleroma/uploaders/swift/keystone.ex
+++ /dev/null
@@ -1,51 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift.Keystone do
- use HTTPoison.Base
-
- def process_url(url) do
- Enum.join(
- [Pleroma.Config.get!([Pleroma.Uploaders.Swift, :auth_url]), url],
- "/"
- )
- end
-
- def process_response_body(body) do
- body
- |> Jason.decode!()
- end
-
- def get_token do
- settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
- username = Keyword.fetch!(settings, :username)
- password = Keyword.fetch!(settings, :password)
- tenant_id = Keyword.fetch!(settings, :tenant_id)
-
- case post(
- "/tokens",
- make_auth_body(username, password, tenant_id),
- ["Content-Type": "application/json"],
- hackney: [:insecure]
- ) do
- {:ok, %Tesla.Env{status: 200, body: body}} ->
- body["access"]["token"]["id"]
-
- {:ok, %Tesla.Env{status: _}} ->
- ""
- end
- end
-
- def make_auth_body(username, password, tenant) do
- Jason.encode!(%{
- :auth => %{
- :passwordCredentials => %{
- :username => username,
- :password => password
- },
- :tenantId => tenant
- }
- })
- end
-end
diff --git a/lib/pleroma/uploaders/swift/swift.ex b/lib/pleroma/uploaders/swift/swift.ex
deleted file mode 100644
index 2b0f2ad04..000000000
--- a/lib/pleroma/uploaders/swift/swift.ex
+++ /dev/null
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift.Client do
- use HTTPoison.Base
-
- def process_url(url) do
- Enum.join(
- [Pleroma.Config.get!([Pleroma.Uploaders.Swift, :storage_url]), url],
- "/"
- )
- end
-
- def upload_file(filename, body, content_type) do
- token = Pleroma.Uploaders.Swift.Keystone.get_token()
-
- case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do
- {:ok, %Tesla.Env{status: 201}} ->
- {:ok, {:file, filename}}
-
- {:ok, %Tesla.Env{status: 401}} ->
- {:error, "Unauthorized, Bad Token"}
-
- {:error, _} ->
- {:error, "Swift Upload Error"}
- end
- end
-end
diff --git a/lib/pleroma/uploaders/swift/uploader.ex b/lib/pleroma/uploaders/swift/uploader.ex
deleted file mode 100644
index d122b09e7..000000000
--- a/lib/pleroma/uploaders/swift/uploader.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift do
- @behaviour Pleroma.Uploaders.Uploader
-
- def get_file(name) do
- {:ok, {:url, Path.join([Pleroma.Config.get!([__MODULE__, :object_url]), name])}}
- end
-
- def put_file(upload) do
- Pleroma.Uploaders.Swift.Client.upload_file(
- upload.path,
- File.read!(upload.tmpfile),
- upload.content_type
- )
- end
-end
diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
index bf15389fc..c0b22c28a 100644
--- a/lib/pleroma/uploaders/uploader.ex
+++ b/lib/pleroma/uploaders/uploader.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Uploader do
+ import Pleroma.Web.Gettext
+
@moduledoc """
Defines the contract to put and get an uploaded file to any backend.
"""
@@ -66,7 +68,14 @@ defmodule Pleroma.Uploaders.Uploader do
{:error, error}
end
after
- 30_000 -> {:error, "Uploader callback timeout"}
+ callback_timeout() -> {:error, dgettext("errors", "Uploader callback timeout")}
+ end
+ end
+
+ defp callback_timeout do
+ case Mix.env() do
+ :test -> 1_000
+ _ -> 30_000
end
end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 09f86aaa2..7d18f099e 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -52,10 +52,12 @@ defmodule Pleroma.User do
field(:avatar, :map)
field(:local, :boolean, default: true)
field(:follower_address, :string)
+ field(:following_address, :string)
field(:search_rank, :float, virtual: true)
field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec)
+ field(:last_digest_emailed_at, :naive_datetime)
has_many(:notifications, Notification)
has_many(:registrations, Registration)
embeds_one(:info, User.Info)
@@ -107,17 +109,34 @@ defmodule Pleroma.User do
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
- def user_info(%User{} = user) do
+ @spec ap_following(User.t()) :: Sring.t()
+ def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
+ def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
+
+ def user_info(%User{} = user, args \\ %{}) do
+ following_count =
+ if args[:following_count],
+ do: args[:following_count],
+ else: user.info.following_count || following_count(user)
+
+ follower_count =
+ if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
+
%{
- following_count: following_count(user),
note_count: user.info.note_count,
- follower_count: user.info.follower_count,
locked: user.info.locked,
confirmation_pending: user.info.confirmation_pending,
default_scope: user.info.default_scope
}
+ |> Map.put(:following_count, following_count)
+ |> Map.put(:follower_count, follower_count)
end
+ def set_info_cache(user, args) do
+ Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
+ end
+
+ @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
from(u in query,
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
@@ -152,9 +171,10 @@ defmodule Pleroma.User do
if changes.valid? do
case info_cng.changes[:source_data] do
- %{"followers" => followers} ->
+ %{"followers" => followers, "following" => following} ->
changes
|> put_change(:follower_address, followers)
+ |> put_change(:following_address, following)
_ ->
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
@@ -186,7 +206,14 @@ defmodule Pleroma.User do
|> User.Info.user_upgrade(params[:info])
struct
- |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
+ |> cast(params, [
+ :bio,
+ :name,
+ :follower_address,
+ :following_address,
+ :avatar,
+ :last_refreshed_at
+ ])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000)
@@ -202,6 +229,7 @@ defmodule Pleroma.User do
|> put_password_hash
end
+ @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def reset_password(%User{id: user_id} = user, data) do
multi =
Multi.new()
@@ -306,6 +334,7 @@ defmodule Pleroma.User do
def needs_update?(_), do: true
+ @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
{:ok, follower}
end
@@ -380,6 +409,8 @@ defmodule Pleroma.User do
{1, [follower]} = Repo.update_all(q, [])
+ follower = maybe_update_following_count(follower)
+
{:ok, _} = update_follower_count(followed)
set_cache(follower)
@@ -399,6 +430,8 @@ defmodule Pleroma.User do
{1, [follower]} = Repo.update_all(q, [])
+ follower = maybe_update_following_count(follower)
+
{:ok, followed} = update_follower_count(followed)
set_cache(follower)
@@ -447,7 +480,7 @@ defmodule Pleroma.User do
end
def update_and_set_cache(changeset) do
- with {:ok, user} <- Repo.update(changeset) do
+ with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
else
e -> e
@@ -562,12 +595,23 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
+ @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
q = get_followers_query(user, page)
{:ok, Repo.all(q)}
end
+ @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ def get_external_followers(user, page \\ nil) do
+ q =
+ user
+ |> get_followers_query(page)
+ |> User.Query.build(%{external: true})
+
+ {:ok, Repo.all(q)}
+ end
+
def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page)
@@ -672,32 +716,73 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def update_follower_count(%User{} = user) do
- follower_count_query =
- User.Query.build(%{followers: user, deactivated: false})
- |> select([u], %{count: count(u.id)})
+ def maybe_fetch_follow_information(user) do
+ with {:ok, user} <- fetch_follow_information(user) do
+ user
+ else
+ e ->
+ Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
- User
- |> where(id: ^user.id)
- |> join(:inner, [u], s in subquery(follower_count_query))
- |> update([u, s],
- set: [
- info:
- fragment(
- "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
- u.info,
- s.count
- )
- ]
- )
- |> select([u], u)
- |> Repo.update_all([])
- |> case do
- {1, [user]} -> set_cache(user)
- _ -> {:error, user}
+ user
end
end
+ def fetch_follow_information(user) do
+ with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
+ info_cng = User.Info.follow_information_update(user.info, info)
+
+ changeset =
+ user
+ |> change()
+ |> put_embed(:info, info_cng)
+
+ update_and_set_cache(changeset)
+ else
+ {:error, _} = e -> e
+ e -> {:error, e}
+ end
+ end
+
+ def update_follower_count(%User{} = user) do
+ if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
+ follower_count_query =
+ User.Query.build(%{followers: user, deactivated: false})
+ |> select([u], %{count: count(u.id)})
+
+ User
+ |> where(id: ^user.id)
+ |> join(:inner, [u], s in subquery(follower_count_query))
+ |> update([u, s],
+ set: [
+ info:
+ fragment(
+ "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
+ u.info,
+ s.count
+ )
+ ]
+ )
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ else
+ {:ok, maybe_fetch_follow_information(user)}
+ end
+ end
+
+ def maybe_update_following_count(%User{local: false} = user) do
+ if Pleroma.Config.get([:instance, :external_user_synchronization]) do
+ {:ok, maybe_fetch_follow_information(user)}
+ else
+ user
+ end
+ end
+
+ def maybe_update_following_count(user), do: user
+
def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following)
@@ -725,10 +810,13 @@ defmodule Pleroma.User do
|> Repo.all()
end
- def mute(muter, %User{ap_id: ap_id}) do
+ @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
+ def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
+ info = muter.info
+
info_cng =
- muter.info
- |> User.Info.add_to_mutes(ap_id)
+ User.Info.add_to_mutes(info, ap_id)
+ |> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
cng =
change(muter)
@@ -738,9 +826,11 @@ defmodule Pleroma.User do
end
def unmute(muter, %{ap_id: ap_id}) do
+ info = muter.info
+
info_cng =
- muter.info
- |> User.Info.remove_from_mutes(ap_id)
+ User.Info.remove_from_mutes(info, ap_id)
+ |> User.Info.remove_from_muted_notifications(info, ap_id)
cng =
change(muter)
@@ -836,14 +926,32 @@ defmodule Pleroma.User do
def mutes?(nil, _), do: false
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
- def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
- blocks = info.blocks
- domain_blocks = info.domain_blocks
- %{host: host} = URI.parse(ap_id)
+ @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
+ def muted_notifications?(nil, _), do: false
- Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host))
+ def muted_notifications?(user, %{ap_id: ap_id}),
+ do: Enum.member?(user.info.muted_notifications, ap_id)
+
+ def blocks?(%User{} = user, %User{} = target) do
+ blocks_ap_id?(user, target) || blocks_domain?(user, target)
end
+ def blocks?(nil, _), do: false
+
+ def blocks_ap_id?(%User{} = user, %User{} = target) do
+ Enum.member?(user.info.blocks, target.ap_id)
+ end
+
+ def blocks_ap_id?(_, _), do: false
+
+ def blocks_domain?(%User{} = user, %User{} = target) do
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+ %{host: host} = URI.parse(target.ap_id)
+ Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
+ end
+
+ def blocks_domain?(_, _), do: false
+
def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id)
@@ -927,6 +1035,8 @@ defmodule Pleroma.User do
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
+ {:ok, _user} = ActivityPub.delete(user)
+
# Remove all relationships
{:ok, followers} = User.get_followers(user)
@@ -943,8 +1053,8 @@ defmodule Pleroma.User do
end)
delete_user_activities(user)
-
- {:ok, _user} = Repo.delete(user)
+ invalidate_cache(user)
+ Repo.delete(user)
end
@spec perform(atom(), User.t()) :: {:ok, User.t()}
@@ -1000,6 +1110,34 @@ defmodule Pleroma.User do
)
end
+ @spec external_users_query() :: Ecto.Query.t()
+ def external_users_query do
+ User.Query.build(%{
+ external: true,
+ active: true,
+ order_by: :id
+ })
+ end
+
+ @spec external_users(keyword()) :: [User.t()]
+ def external_users(opts \\ []) do
+ query =
+ external_users_query()
+ |> select([u], struct(u, [:id, :ap_id, :info]))
+
+ query =
+ if opts[:max_id],
+ do: where(query, [u], u.id > ^opts[:max_id]),
+ else: query
+
+ query =
+ if opts[:limit],
+ do: limit(query, ^opts[:limit]),
+ else: query
+
+ Repo.all(query)
+ end
+
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
do:
PleromaJobQueue.enqueue(:background, __MODULE__, [
@@ -1092,19 +1230,18 @@ defmodule Pleroma.User do
end
end
- def get_or_create_instance_user do
- relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
-
- if user = get_cached_by_ap_id(relay_uri) do
+ @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
+ def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
+ if user = get_cached_by_ap_id(uri) do
user
else
changes =
%User{info: %User.Info{}}
|> cast(%{}, [:ap_id, :nickname, :local])
- |> put_change(:ap_id, relay_uri)
- |> put_change(:nickname, nil)
+ |> put_change(:ap_id, uri)
+ |> put_change(:nickname, nickname)
|> put_change(:local, true)
- |> put_change(:follower_address, relay_uri <> "/followers")
+ |> put_change(:follower_address, uri <> "/followers")
{:ok, user} = Repo.insert(changes)
user
@@ -1125,10 +1262,12 @@ defmodule Pleroma.User do
end
# OStatus Magic Key
- def public_key_from_info(%{magic_key: magic_key}) do
+ def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
end
+ def public_key_from_info(_), do: {:error, "not found key"}
+
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
{:ok, public_key} <- public_key_from_info(user.info) do
@@ -1145,7 +1284,7 @@ defmodule Pleroma.User do
data
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation()
- |> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
+ |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
|> set_cache()
end
@@ -1281,6 +1420,80 @@ defmodule Pleroma.User do
target.ap_id not in user.info.muted_reblogs
end
+ @doc """
+ The function returns a query to get users with no activity for given interval of days.
+ Inactive users are those who didn't read any notification, or had any activity where
+ the user is the activity's actor, during `inactivity_threshold` days.
+ Deactivated users will not appear in this list.
+
+ ## Examples
+
+ iex> Pleroma.User.list_inactive_users()
+ %Ecto.Query{}
+ """
+ @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
+ def list_inactive_users_query(inactivity_threshold \\ 7) do
+ negative_inactivity_threshold = -inactivity_threshold
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+ # Subqueries are not supported in `where` clauses, join gets too complicated.
+ has_read_notifications =
+ from(n in Pleroma.Notification,
+ where: n.seen == true,
+ group_by: n.id,
+ having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
+ select: n.user_id
+ )
+ |> Pleroma.Repo.all()
+
+ from(u in Pleroma.User,
+ left_join: a in Pleroma.Activity,
+ on: u.ap_id == a.actor,
+ where: not is_nil(u.nickname),
+ where: fragment("not (?->'deactivated' @> 'true')", u.info),
+ where: u.id not in ^has_read_notifications,
+ group_by: u.id,
+ having:
+ max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
+ is_nil(max(a.inserted_at))
+ )
+ end
+
+ @doc """
+ Enable or disable email notifications for user
+
+ ## Examples
+
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
+ Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
+
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
+ Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
+ """
+ @spec switch_email_notifications(t(), String.t(), boolean()) ::
+ {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def switch_email_notifications(user, type, status) do
+ info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
+
+ change(user)
+ |> put_embed(:info, info)
+ |> update_and_set_cache()
+ end
+
+ @doc """
+ Set `last_digest_emailed_at` value for the user to current time
+ """
+ @spec touch_last_digest_emailed_at(t()) :: t()
+ def touch_last_digest_emailed_at(user) do
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+
+ {:ok, updated_user} =
+ user
+ |> change(%{last_digest_emailed_at: now})
+ |> update_and_set_cache()
+
+ updated_user
+ end
+
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending
@@ -1314,23 +1527,16 @@ defmodule Pleroma.User do
}
end
- def ensure_keys_present(user) do
- info = user.info
-
+ def ensure_keys_present(%User{info: info} = user) do
if info.keys do
{:ok, user}
else
{:ok, pem} = Keys.generate_rsa_pem()
- info_cng =
- info
- |> User.Info.set_keys(pem)
-
- cng =
- Ecto.Changeset.change(user)
- |> Ecto.Changeset.put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ user
+ |> Ecto.Changeset.change()
+ |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
+ |> update_and_set_cache()
end
end
@@ -1351,4 +1557,8 @@ defmodule Pleroma.User do
end
defp put_password_hash(changeset), do: changeset
+
+ def is_internal_user?(%User{nickname: nil}), do: true
+ def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
+ def is_internal_user?(_), do: false
end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 08e43ff0f..22eb9a182 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -16,6 +16,8 @@ defmodule Pleroma.User.Info do
field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
+ # Should be filled in only for remote users
+ field(:following_count, :integer, default: nil)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil)
@@ -24,6 +26,7 @@ defmodule Pleroma.User.Info do
field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:muted_reblogs, {:array, :string}, default: [])
+ field(:muted_notifications, {:array, :string}, default: [])
field(:subscribers, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false)
@@ -42,6 +45,7 @@ defmodule Pleroma.User.Info do
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: [])
+ field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
field(:emoji, {:array, :map}, default: [])
field(:pleroma_settings_store, :map, default: %{})
@@ -92,6 +96,30 @@ defmodule Pleroma.User.Info do
|> validate_required([:notification_settings])
end
+ @doc """
+ Update email notifications in the given User.Info struct.
+
+ Examples:
+
+ iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
+ %Pleroma.User.Info{email_notifications: %{"digest" => true}}
+
+ """
+ @spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
+ def update_email_notifications(info, settings) do
+ email_notifications =
+ info.email_notifications
+ |> Map.merge(settings)
+ |> Map.take(["digest"])
+
+ params = %{email_notifications: email_notifications}
+ fields = [:email_notifications]
+
+ info
+ |> cast(params, fields)
+ |> validate_required(fields)
+ end
+
def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number)
end
@@ -120,6 +148,16 @@ defmodule Pleroma.User.Info do
|> validate_required([:mutes])
end
+ @spec set_notification_mutes(Changeset.t(), [String.t()], boolean()) :: Changeset.t()
+ def set_notification_mutes(changeset, muted_notifications, notifications?) do
+ if notifications? do
+ put_change(changeset, :muted_notifications, muted_notifications)
+ |> validate_required([:muted_notifications])
+ else
+ changeset
+ end
+ end
+
def set_blocks(info, blocks) do
params = %{blocks: blocks}
@@ -136,14 +174,31 @@ defmodule Pleroma.User.Info do
|> validate_required([:subscribers])
end
+ @spec add_to_mutes(Info.t(), String.t()) :: Changeset.t()
def add_to_mutes(info, muted) do
set_mutes(info, Enum.uniq([muted | info.mutes]))
end
+ @spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
+ Changeset.t()
+ def add_to_muted_notifications(changeset, info, muted, notifications?) do
+ set_notification_mutes(
+ changeset,
+ Enum.uniq([muted | info.muted_notifications]),
+ notifications?
+ )
+ end
+
+ @spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
def remove_from_mutes(info, muted) do
set_mutes(info, List.delete(info.mutes, muted))
end
+ @spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
+ def remove_from_muted_notifications(changeset, info, muted) do
+ set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
+ end
+
def add_to_block(info, blocked) do
set_blocks(info, Enum.uniq([blocked | info.blocks]))
end
@@ -195,7 +250,11 @@ defmodule Pleroma.User.Info do
:uri,
:hub,
:topic,
- :salmon
+ :salmon,
+ :hide_followers,
+ :hide_follows,
+ :follower_count,
+ :following_count
])
end
@@ -206,7 +265,11 @@ defmodule Pleroma.User.Info do
:source_data,
:banner,
:locked,
- :magic_key
+ :magic_key,
+ :follower_count,
+ :following_count,
+ :hide_follows,
+ :hide_followers
])
end
@@ -320,4 +383,14 @@ defmodule Pleroma.User.Info do
cast(info, params, [:muted_reblogs])
end
+
+ def follow_information_update(info, params) do
+ info
+ |> cast(params, [
+ :hide_followers,
+ :hide_follows,
+ :follower_count,
+ :following_count
+ ])
+ end
end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index ace9c05f2..f9bcc9e19 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.User.Query do
User query builder module. Builds query from new query or another user query.
## Example:
- query = Pleroma.User.Query(%{nickname: "nickname"})
+ query = Pleroma.User.Query.build(%{nickname: "nickname"})
another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
Pleroma.Repo.all(query)
Pleroma.Repo.all(another_query)
@@ -47,7 +47,10 @@ defmodule Pleroma.User.Query do
friends: User.t(),
recipients_from_activity: [String.t()],
nickname: [String.t()],
- ap_id: [String.t()]
+ ap_id: [String.t()],
+ order_by: term(),
+ select: term(),
+ limit: pos_integer()
}
| %{}
@@ -141,6 +144,18 @@ defmodule Pleroma.User.Query do
where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
end
+ defp compose_query({:order_by, key}, query) do
+ order_by(query, [u], field(u, ^key))
+ end
+
+ defp compose_query({:select, keys}, query) do
+ select(query, [u], ^keys)
+ end
+
+ defp compose_query({:limit, limit}, query) do
+ limit(query, ^limit)
+ end
+
defp compose_query(_unsupported_param, query), do: query
defp prepare_tag_criteria(tag, query) do
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 7680c2afd..6fb2c2352 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do
+ alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
@@ -18,8 +19,7 @@ defmodule Pleroma.User.Search do
for_user = Keyword.get(opts, :for_user)
- # Strip the beginning @ off if there is a query
- query_string = String.trim_leading(query_string, "@")
+ query_string = format_query(query_string)
maybe_resolve(resolve, for_user, query_string)
@@ -33,13 +33,24 @@ defmodule Pleroma.User.Search do
query_string
|> search_query(for_user, following)
- |> paginate(result_limit, offset)
- |> Repo.all()
+ |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
end)
results
end
+ defp format_query(query_string) do
+ # Strip the beginning @ off if there is a query
+ query_string = String.trim_leading(query_string, "@")
+
+ with [name, domain] <- String.split(query_string, "@"),
+ formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do
+ name <> "@" <> to_string(:idna.encode(formatted_domain))
+ else
+ _ -> query_string
+ end
+ end
+
defp search_query(query_string, for_user, following) do
for_user
|> base_query(following)
@@ -76,10 +87,6 @@ defmodule Pleroma.User.Search do
defp filter_blocked_domains(query, _), do: query
- defp paginate(query, limit, offset) do
- from(q in query, limit: ^limit, offset: ^offset)
- end
-
defp union_subqueries({fts_subquery, trigram_subquery}) do
from(s in trigram_subquery, union_all: ^fts_subquery)
end
@@ -150,8 +157,8 @@ defmodule Pleroma.User.Search do
@spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
defp fts_search_subquery(query, term) do
processed_query =
- term
- |> String.replace(~r/\W+/, " ")
+ String.trim_trailing(term, "@" <> local_domain())
+ |> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ")
|> String.trim()
|> String.split()
|> Enum.map(&(&1 <> ":*"))
@@ -192,6 +199,8 @@ defmodule Pleroma.User.Search do
@spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
defp trigram_search_subquery(query, term) do
+ term = String.trim_trailing(term, "@" <> local_domain())
+
from(
u in query,
select_merge: %{
@@ -209,4 +218,6 @@ defmodule Pleroma.User.Search do
)
|> User.restrict_deactivated()
end
+
+ defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
end
diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex
index 2ba65b75a..99fba729e 100644
--- a/lib/pleroma/user/welcome_message.ex
+++ b/lib/pleroma/user/welcome_message.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.User.WelcomeMessage do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
index fadc89891..b9e80acdd 100644
--- a/lib/pleroma/user_invite_token.ex
+++ b/lib/pleroma/user_invite_token.ex
@@ -74,7 +74,7 @@ defmodule Pleroma.UserInviteToken do
@spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil
def find_by_token(token) do
- with invite <- Repo.get_by(UserInviteToken, token: token) do
+ with %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, token: token) do
{:ok, invite}
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 55315d66e..1a279a7df 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Conversation
alias Pleroma.Notification
alias Pleroma.Object
+ alias Pleroma.Object.Containment
alias Pleroma.Object.Fetcher
alias Pleroma.Pagination
alias Pleroma.Repo
@@ -22,23 +23,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
import Pleroma.Web.ActivityPub.Visibility
require Logger
+ require Pleroma.Constants
# For Announce activities, we filter the recipients based on following status for any actors
# that match actual users. See issue #164 for more information about why this is necessary.
defp get_recipients(%{"type" => "Announce"} = data) do
- to = data["to"] || []
- cc = data["cc"] || []
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
actor = User.get_cached_by_ap_id(data["actor"])
recipients =
- (to ++ cc)
- |> Enum.filter(fn recipient ->
+ Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
case User.get_cached_by_ap_id(recipient) do
- nil ->
- true
-
- user ->
- User.following?(user, actor)
+ nil -> true
+ user -> User.following?(user, actor)
end
end)
@@ -46,17 +45,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp get_recipients(%{"type" => "Create"} = data) do
- to = data["to"] || []
- cc = data["cc"] || []
- actor = data["actor"] || []
- recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
+ actor = Map.get(data, "actor", [])
+ recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
{recipients, to, cc}
end
defp get_recipients(data) do
- to = data["to"] || []
- cc = data["cc"] || []
- recipients = to ++ cc
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
+ recipients = Enum.concat([to, cc, bcc])
{recipients, to, cc}
end
@@ -126,6 +127,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
+ :ok <- Containment.contain_child(map),
{:ok, map, object} <- insert_full_object(map) do
{:ok, activity} =
Repo.insert(%Activity{
@@ -206,8 +208,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def stream_out_participations(_, _), do: :noop
def stream_out(activity) do
- public = "https://www.w3.org/ns/activitystreams#Public"
-
if activity.data["type"] in ["Create", "Announce", "Delete"] do
object = Object.normalize(activity)
# Do not stream out poll replies
@@ -215,7 +215,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.Web.Streamer.stream("user", activity)
Pleroma.Web.Streamer.stream("list", activity)
- if Enum.member?(activity.data["to"], public) do
+ if get_visibility(activity) == "public" do
Pleroma.Web.Streamer.stream("public", activity)
if activity.local do
@@ -237,13 +237,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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)
+ if get_visibility(activity) == "direct",
+ do: Pleroma.Web.Streamer.stream("direct", activity)
end
end
end
@@ -272,6 +267,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
else
{:fake, true, activity} ->
{:ok, activity}
+
+ {:error, message} ->
+ {:error, message}
end
end
@@ -405,6 +403,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
+ with data <- %{
+ "to" => [follower_address],
+ "type" => "Delete",
+ "actor" => ap_id,
+ "object" => %{"type" => "Person", "id" => ap_id}
+ },
+ {:ok, activity} <- insert(data, true, true),
+ :ok <- maybe_federate(activity) do
+ {:ok, user}
+ end
+ end
+
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor)
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
@@ -500,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp fetch_activities_for_context_query(context, opts) do
- public = ["https://www.w3.org/ns/activitystreams#Public"]
+ public = [Pleroma.Constants.as_public()]
recipients =
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
@@ -541,7 +552,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def fetch_public_activities(opts \\ %{}) do
- q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
+ q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
q
|> restrict_unlisted()
@@ -617,17 +628,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
recipients =
- if reading_user do
- ["https://www.w3.org/ns/activitystreams#Public"] ++
- [reading_user.ap_id | reading_user.following]
- else
- ["https://www.w3.org/ns/activitystreams#Public"]
- end
+ user_activities_recipients(%{
+ "godmode" => params["godmode"],
+ "reading_user" => reading_user
+ })
fetch_activities(recipients, params)
|> Enum.reverse()
end
+ defp user_activities_recipients(%{"godmode" => true}) do
+ []
+ end
+
+ defp user_activities_recipients(%{"reading_user" => reading_user}) do
+ if reading_user do
+ [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
+ else
+ [Pleroma.Constants.as_public()]
+ end
+ end
+
defp restrict_since(query, %{"since_id" => ""}), do: query
defp restrict_since(query, %{"since_id" => since_id}) do
@@ -728,8 +749,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
from(
- activity in query,
- where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
+ [_activity, object] in query,
+ where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
)
end
@@ -809,7 +830,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
fragment(
"not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
activity.data,
- ^["https://www.w3.org/ns/activitystreams#Public"]
+ ^[Pleroma.Constants.as_public()]
)
)
end
@@ -883,13 +904,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp maybe_order(query, _), do: query
def fetch_activities_query(recipients, opts \\ %{}) do
- base_query = from(activity in Activity)
-
config = %{
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
}
- base_query
+ Activity
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
@@ -918,17 +937,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def fetch_activities(recipients, opts \\ %{}) do
- fetch_activities_query(recipients, opts)
+ list_memberships = Pleroma.List.memberships(opts["user"])
+
+ fetch_activities_query(recipients ++ list_memberships, opts)
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()
+ |> maybe_update_cc(list_memberships, opts["user"])
end
+ defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
+ when is_list(list_memberships) and length(list_memberships) > 0 do
+ Enum.map(activities, fn
+ %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
+ if Enum.any?(bcc, &(&1 in list_memberships)) do
+ update_in(activity.data["cc"], &[user_ap_id | &1])
+ else
+ activity
+ end
+
+ activity ->
+ activity
+ end)
+ end
+
+ defp maybe_update_cc(activities, _, _), do: activities
+
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)
+ ^Pleroma.Constants.as_public() in activity.recipients)
)
end
@@ -973,14 +1012,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
user_data = %{
ap_id: data["id"],
info: %{
- "ap_enabled" => true,
- "source_data" => data,
- "banner" => banner,
- "locked" => locked
+ ap_enabled: true,
+ source_data: data,
+ banner: banner,
+ locked: locked
},
avatar: avatar,
name: data["name"],
follower_address: data["followers"],
+ following_address: data["following"],
bio: data["summary"]
}
@@ -999,6 +1039,71 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, user_data}
end
+ def fetch_follow_information_for_user(user) do
+ with {:ok, following_data} <-
+ Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
+ following_count when is_integer(following_count) <- following_data["totalItems"],
+ {:ok, hide_follows} <- collection_private(following_data),
+ {:ok, followers_data} <-
+ Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
+ followers_count when is_integer(followers_count) <- followers_data["totalItems"],
+ {:ok, hide_followers} <- collection_private(followers_data) do
+ {:ok,
+ %{
+ hide_follows: hide_follows,
+ follower_count: followers_count,
+ following_count: following_count,
+ hide_followers: hide_followers
+ }}
+ else
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
+ end
+ end
+
+ defp maybe_update_follow_information(data) do
+ with {:enabled, true} <-
+ {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
+ {:ok, info} <- fetch_follow_information_for_user(data) do
+ info = Map.merge(data.info, info)
+ Map.put(data, :info, info)
+ else
+ {:enabled, false} ->
+ data
+
+ e ->
+ Logger.error(
+ "Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e)
+ )
+
+ data
+ end
+ end
+
+ defp collection_private(data) do
+ if is_map(data["first"]) and
+ data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
+ {:ok, false}
+ else
+ with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
+ Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
+ {:ok, false}
+ else
+ {:error, {:ok, %{status: code}}} when code in [401, 403] ->
+ {:ok, true}
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
+ end
+ end
+ end
+
def user_data_from_user_object(data) do
with {:ok, data} <- MRF.filter(data),
{:ok, data} <- object_to_user_data(data) do
@@ -1010,7 +1115,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_user_from_ap_id(ap_id) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
- {:ok, data} <- user_data_from_user_object(data) do
+ {:ok, data} <- user_data_from_user_object(data),
+ data <- maybe_update_follow_information(data) do
{:ok, data}
else
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 0182bda46..133a726c5 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Object.Fetcher
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -31,9 +32,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
else
conn
- |> put_status(404)
- |> json(%{error: "not found"})
- |> halt
+ |> render_error(:not_found, "not found")
+ |> halt()
end
end
@@ -104,43 +104,57 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
- def following(conn, %{"nickname" => nickname, "page" => page}) do
+ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
+ {:show_follows, true} <-
+ {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("following.json", %{user: user, page: page}))
+ |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
+ else
+ {:show_follows, _} ->
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> send_resp(403, "")
end
end
- def following(conn, %{"nickname" => nickname}) do
+ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("following.json", %{user: user}))
+ |> json(UserView.render("following.json", %{user: user, for: for_user}))
end
end
- def followers(conn, %{"nickname" => nickname, "page" => page}) do
+ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
+ {:show_followers, true} <-
+ {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("followers.json", %{user: user, page: page}))
+ |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
+ else
+ {:show_followers, _} ->
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> send_resp(403, "")
end
end
- def followers(conn, %{"nickname" => nickname}) do
+ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("followers.json", %{user: user}))
+ |> json(UserView.render("followers.json", %{user: user, for: for_user}))
end
end
@@ -190,12 +204,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
Logger.info(inspect(conn.req_headers))
end
- json(conn, "error")
+ json(conn, dgettext("errors", "error"))
end
- def relay(conn, _params) do
- with %User{} = user <- Relay.get_actor(),
- {:ok, user} <- User.ensure_keys_present(user) do
+ defp represent_service_actor(%User{} = user, conn) do
+ with {:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("user.json", %{user: user}))
@@ -204,6 +217,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
+ defp represent_service_actor(nil, _), do: {:error, :not_found}
+
+ def relay(conn, _params) do
+ Relay.get_actor()
+ |> represent_service_actor(conn)
+ end
+
+ def internal_fetch(conn, _params) do
+ InternalFetchActor.get_actor()
+ |> represent_service_actor(conn)
+ end
+
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn
|> put_resp_header("content-type", "application/activity+json")
@@ -218,9 +243,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
else
+ err =
+ dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
+ nickname: nickname,
+ as_nickname: user.nickname
+ )
+
conn
|> put_status(:forbidden)
- |> json("can't read inbox of #{nickname} as #{user.nickname}")
+ |> json(err)
end
end
@@ -246,7 +277,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
else
- _ -> {:error, "Can't delete object"}
+ _ -> {:error, dgettext("errors", "Can't delete object")}
end
end
@@ -255,12 +286,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity}
else
- _ -> {:error, "Can't like object"}
+ _ -> {:error, dgettext("errors", "Can't like object")}
end
end
def handle_user_activity(_, _) do
- {:error, "Unhandled activity type"}
+ {:error, dgettext("errors", "Unhandled activity type")}
end
def update_outbox(
@@ -288,22 +319,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(message)
end
else
+ err =
+ dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
+ nickname: nickname,
+ as_nickname: user.nickname
+ )
+
conn
|> put_status(:forbidden)
- |> json("can't update outbox of #{nickname} as #{user.nickname}")
+ |> json(err)
end
end
def errors(conn, {:error, :not_found}) do
conn
- |> put_status(404)
- |> json("Not found")
+ |> put_status(:not_found)
+ |> json(dgettext("errors", "Not found"))
end
def errors(conn, _e) do
conn
- |> put_status(500)
- |> json("error")
+ |> put_status(:internal_server_error)
+ |> json(dgettext("errors", "error"))
end
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
@@ -314,4 +351,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
+
+ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ {:ok, new_user} = User.ensure_keys_present(user)
+
+ for_user =
+ if new_user != user and match?(%User{}, for_user) do
+ User.get_cached_by_nickname(for_user.nickname)
+ else
+ for_user
+ end
+
+ {new_user, for_user}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
new file mode 100644
index 000000000..9213ddde7
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.InternalFetchActor do
+ alias Pleroma.User
+
+ require Logger
+
+ def init do
+ # Wait for everything to settle.
+ Process.sleep(1000 * 5)
+ get_actor()
+ end
+
+ def get_actor do
+ "#{Pleroma.Web.Endpoint.url()}/internal/fetch"
+ |> User.get_or_create_service_actor_by_ap_id("internal.fetch")
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 10ceef715..caa2a3231 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -25,4 +25,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do
defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: []
+
+ @spec subdomains_regex([String.t()]) :: [Regex.t()]
+ def subdomains_regex(domains) when is_list(domains) do
+ for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
+ end
+
+ @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
+ def subdomain_match?(domains, host) do
+ Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 15d8514be..2d03df68a 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -9,8 +9,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
@behaviour Pleroma.Web.ActivityPub.MRF
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
+
def filter_by_summary(
- %{"summary" => parent_summary} = _parent,
+ %{data: %{"summary" => parent_summary}} = _in_reply_to,
%{"summary" => child_summary} = child
)
when not is_nil(child_summary) and byte_size(child_summary) > 0 and
@@ -24,17 +25,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
end
end
- def filter_by_summary(_parent, child), do: child
-
- def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
- child = object["object"]
- in_reply_to = Object.normalize(child["inReplyTo"])
+ def filter_by_summary(_in_reply_to, child), do: child
+ def filter(%{"type" => "Create", "object" => child_object} = object) do
child =
- if(in_reply_to,
- do: filter_by_summary(in_reply_to.data, child),
- else: child
- )
+ child_object["inReplyTo"]
+ |> Object.normalize(child_object["inReplyTo"])
+ |> filter_by_summary(child_object)
object = Map.put(object, "object", child)
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index a699f6a7e..377987cf2 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -4,6 +4,9 @@
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
alias Pleroma.User
+
+ require Pleroma.Constants
+
@moduledoc "Block messages with too much mentions (configurable)"
@behaviour Pleroma.Web.ActivityPub.MRF
@@ -19,12 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
when follower_collection? and recipients > threshold ->
message
|> Map.put("to", [follower_collection])
- |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [Pleroma.Constants.as_public()])
{:public, recipients} when recipients > threshold ->
message
|> Map.put("to", [])
- |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [Pleroma.Constants.as_public()])
_ ->
message
@@ -51,10 +54,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
recipients = (message["to"] || []) ++ (message["cc"] || [])
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
- if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do
+ if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients =
recipients
- |> List.delete("https://www.w3.org/ns/activitystreams#Public")
+ |> List.delete(Pleroma.Constants.as_public())
|> List.delete(follower_collection)
{:public, length(recipients)}
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index d5c341433..4eec8b916 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
+ require Pleroma.Constants
+
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF
@@ -31,12 +33,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do
- if "https://www.w3.org/ns/activitystreams#Public" in to and
+ if Pleroma.Constants.as_public() in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
- to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
- cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
+ to = List.delete(to, Pleroma.Constants.as_public())
+ cc = [Pleroma.Constants.as_public() | message["cc"] || []]
message =
message
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
new file mode 100644
index 000000000..1842e1aeb
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
+ @moduledoc "Block messages which mention a user"
+
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ @impl true
+ def filter(%{"type" => "Create"} = message) do
+ reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
+ recipients = (message["to"] || []) ++ (message["cc"] || [])
+
+ if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
+ {:reject, nil}
+ else
+ {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index f30fee0d5..86a48bda5 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -10,19 +10,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
def filter(
%{
"type" => "Create",
- "object" => %{"content" => content, "attachment" => _attachment} = child_object
+ "object" => %{"content" => content, "attachment" => _} = _child_object
} = object
)
when content in [".", ".
"] do
- child_object =
- child_object
- |> Map.put("content", "")
-
- object =
- object
- |> Map.put("object", child_object)
-
- {:ok, object}
+ {:ok, put_in(object, ["object", "content"], "")}
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index 9c87c6963..c269d0f89 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -8,18 +8,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@behaviour Pleroma.Web.ActivityPub.MRF
- def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
+ def filter(%{"type" => "Create", "object" => child_object} = object) do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
- child = object["object"]
-
content =
- child["content"]
+ child_object["content"]
|> HTML.filter_tags(scrub_policy)
- child = Map.put(child, "content", content)
-
- object = Map.put(object, "object", child)
+ object = put_in(object, ["object", "content"], content)
{:ok, object}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index ea3df1b4d..457b6ee10 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -3,46 +3,42 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
- alias Pleroma.User
@moduledoc "Rejects non-public (followers-only, direct) activities"
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
@behaviour Pleroma.Web.ActivityPub.MRF
+ require Pleroma.Constants
+
@impl true
def filter(%{"type" => "Create"} = object) do
user = User.get_cached_by_ap_id(object["actor"])
- public = "https://www.w3.org/ns/activitystreams#Public"
# Determine visibility
visibility =
cond do
- public in object["to"] -> "public"
- public in object["cc"] -> "unlisted"
+ Pleroma.Constants.as_public() in object["to"] -> "public"
+ Pleroma.Constants.as_public() in object["cc"] -> "unlisted"
user.follower_address in object["to"] -> "followers"
true -> "direct"
end
- policy = Pleroma.Config.get(:mrf_rejectnonpublic)
+ policy = Config.get(:mrf_rejectnonpublic)
- case visibility do
- "public" ->
+ cond do
+ visibility in ["public", "unlisted"] ->
{:ok, object}
- "unlisted" ->
+ visibility == "followers" and Keyword.get(policy, :allow_followersonly) ->
{:ok, object}
- "followers" ->
- with true <- Keyword.get(policy, :allow_followersonly) do
- {:ok, object}
- else
- _e -> {:reject, nil}
- end
+ visibility == "direct" and Keyword.get(policy, :allow_direct) ->
+ {:ok, object}
- "direct" ->
- with true <- Keyword.get(policy, :allow_direct) do
- {:ok, object}
- else
- _e -> {:reject, nil}
- end
+ true ->
+ {:reject, nil}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 433d23c5f..f266457e3 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,22 +4,31 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour MRF
+
+ require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do
- accepts = Pleroma.Config.get([:mrf_simple, :accept])
+ accepts =
+ Pleroma.Config.get([:mrf_simple, :accept])
+ |> MRF.subdomains_regex()
cond do
accepts == [] -> {:ok, object}
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
- Enum.member?(accepts, actor_host) -> {:ok, object}
+ MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
end
end
defp check_reject(%{host: actor_host} = _actor_info, object) do
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do
+ rejects =
+ Pleroma.Config.get([:mrf_simple, :reject])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -31,8 +40,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
)
when length(child_attachment) > 0 do
+ media_removal =
+ Pleroma.Config.get([:mrf_simple, :media_removal])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do
+ if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object)
else
@@ -51,8 +64,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
"object" => child_object
} = object
) do
+ media_nsfw =
+ Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
+ if MRF.subdomain_match?(media_nsfw, actor_host) do
tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true)
@@ -67,21 +84,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
- object =
- with true <-
- Enum.member?(
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
- actor_host
- ),
- user <- User.get_cached_by_ap_id(object["actor"]),
- 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]
+ timeline_removal =
+ Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ |> MRF.subdomains_regex()
- cc =
- List.delete(object["cc"], user.follower_address) ++
- ["https://www.w3.org/ns/activitystreams#Public"]
+ object =
+ with true <- MRF.subdomain_match?(timeline_removal, actor_host),
+ user <- User.get_cached_by_ap_id(object["actor"]),
+ true <- Pleroma.Constants.as_public() in object["to"] do
+ to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
+
+ cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
object
|> Map.put("to", to)
@@ -94,7 +107,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do
+ report_removal =
+ Pleroma.Config.get([:mrf_simple, :report_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -104,7 +121,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do
+ avatar_removal =
+ Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")}
else
{:ok, object}
@@ -114,7 +135,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do
+ banner_removal =
+ Pleroma.Config.get([:mrf_simple, :banner_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")}
else
{:ok, object}
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index 6683b8d8e..70edf4f7f 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -19,12 +19,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
- `mrf_tag:disable-any-subscription`: Reject any follow requests
"""
+ require Pleroma.Constants
+
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
defp get_tags(_), do: []
defp process_tag(
"mrf_tag:media-force-nsfw",
- %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
+ %{
+ "type" => "Create",
+ "object" => %{"attachment" => child_attachment} = object
+ } = message
)
when length(child_attachment) > 0 do
tags = (object["tag"] || []) ++ ["nsfw"]
@@ -41,7 +46,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:media-strip",
- %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
+ %{
+ "type" => "Create",
+ "object" => %{"attachment" => child_attachment} = object
+ } = message
)
when length(child_attachment) > 0 do
object = Map.delete(object, "attachment")
@@ -52,19 +60,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:force-unlisted",
- %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
) do
user = User.get_cached_by_ap_id(actor)
- if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do
- to =
- List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
-
- cc =
- List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"]
+ if Enum.member?(to, Pleroma.Constants.as_public()) do
+ to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
+ cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
object =
- message["object"]
+ object
|> Map.put("to", to)
|> Map.put("cc", cc)
@@ -82,19 +93,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:sandbox",
- %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
) do
user = User.get_cached_by_ap_id(actor)
- if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or
- Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do
- to =
- List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
-
- cc = List.delete(cc, "https://www.w3.org/ns/activitystreams#Public")
+ if Enum.member?(to, Pleroma.Constants.as_public()) or
+ Enum.member?(cc, Pleroma.Constants.as_public()) do
+ to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
+ cc = List.delete(cc, Pleroma.Constants.as_public())
object =
- message["object"]
+ object
|> Map.put("to", to)
|> Map.put("cc", cc)
@@ -123,7 +138,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
end
end
- defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil}
+ defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
+ do: {:reject, nil}
defp process_tag(_, message), do: {:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
similarity index 86%
rename from lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
rename to lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
index 47663414a..e35d2c422 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
@@ -21,7 +21,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
@impl true
def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(actor)
- allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
+
+ allow_list =
+ Config.get(
+ [:mrf_user_allowlist, String.to_atom(actor_info.host)],
+ []
+ )
filter_by_list(object, allow_list)
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index a05e03263..46edab0bd 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
+ require Pleroma.Constants
+
import Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.Federator.Publisher
@@ -87,23 +89,115 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
if public do
true
else
- inbox_info = URI.parse(inbox)
- !Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
+ %{host: host} = URI.parse(inbox)
+
+ quarantined_instances =
+ Config.get([:instance, :quarantined_instances], [])
+ |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+
+ !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
end
+ @spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
+ defp recipients(actor, activity) do
+ {:ok, followers} =
+ if actor.follower_address in activity.recipients do
+ User.get_external_followers(actor)
+ else
+ {:ok, []}
+ end
+
+ Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
+ end
+
+ defp get_cc_ap_ids(ap_id, recipients) do
+ host = Map.get(URI.parse(ap_id), :host)
+
+ recipients
+ |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
+ |> Enum.map(& &1.ap_id)
+ end
+
+ defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
+ do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
+
+ @doc """
+ Determine a user inbox to use based on heuristics. These heuristics
+ are based on an approximation of the ``sharedInbox`` rules in the
+ [ActivityPub specification][ap-sharedinbox].
+
+ Please do not edit this function (or its children) without reading
+ the spec, as editing the code is likely to introduce some breakage
+ without some familiarity.
+
+ [ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
+ """
+ def determine_inbox(
+ %Activity{data: activity_data},
+ %User{info: %{source_data: data}} = user
+ ) do
+ to = activity_data["to"] || []
+ cc = activity_data["cc"] || []
+ type = activity_data["type"]
+
+ cond do
+ type == "Delete" ->
+ maybe_use_sharedinbox(user)
+
+ Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
+ maybe_use_sharedinbox(user)
+
+ length(to) + length(cc) > 1 ->
+ maybe_use_sharedinbox(user)
+
+ true ->
+ data["inbox"]
+ end
+ end
+
+ @doc """
+ Publishes an activity with BCC to all relevant peers.
+ """
+
+ def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
+ public = is_public?(activity)
+ {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+ recipients = recipients(actor, activity)
+
+ recipients
+ |> Enum.filter(&User.ap_enabled?/1)
+ |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
+ |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+ |> Instances.filter_reachable()
+ |> Enum.each(fn {inbox, unreachable_since} ->
+ %User{ap_id: ap_id} =
+ Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
+
+ # Get all the recipients on the same host and add them to cc. Otherwise, a remote
+ # instance would only accept a first message for the first recipient and ignore the rest.
+ cc = get_cc_ap_ids(ap_id, recipients)
+
+ json =
+ data
+ |> Map.put("cc", cc)
+ |> Jason.encode!()
+
+ Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
+ inbox: inbox,
+ json: json,
+ actor: actor,
+ id: activity.data["id"],
+ unreachable_since: unreachable_since
+ })
+ end)
+ end
+
@doc """
Publishes an activity to all relevant peers.
"""
def publish(%User{} = actor, %Activity{} = activity) do
- remote_followers =
- if actor.follower_address in activity.recipients do
- {:ok, followers} = User.get_followers(actor)
- followers |> Enum.filter(&(!&1.local))
- else
- []
- end
-
public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do
@@ -114,10 +208,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
- (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
+ recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
- |> Enum.map(fn %{info: %{source_data: data}} ->
- (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
+ |> Enum.map(fn %User{} = user ->
+ determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 93808517b..1ebfcdd86 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,7 +10,8 @@ defmodule Pleroma.Web.ActivityPub.Relay do
require Logger
def get_actor do
- User.get_or_create_instance_user()
+ "#{Pleroma.Web.Endpoint.url()}/relay"
+ |> User.get_or_create_service_actor_by_ap_id()
end
def follow(target_instance) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 3bb8b40b5..b7bc48f0a 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -14,28 +14,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.Federator
import Ecto.Query
require Logger
+ require Pleroma.Constants
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
- def fix_object(object) do
+ def fix_object(object, options \\ []) do
object
+ |> strip_internal_fields
|> fix_actor
|> fix_url
|> fix_attachments
|> fix_context
- |> fix_in_reply_to
+ |> fix_in_reply_to(options)
|> fix_emoji
|> fix_tag
|> fix_content_map
- |> fix_likes
|> fix_addressing
|> fix_summary
- |> fix_type
+ |> fix_type(options)
end
def fix_summary(%{"summary" => nil} = object) do
@@ -101,8 +103,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
- explicit_mentions =
- explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
+ explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection]
fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
@@ -114,11 +115,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if followers_collection not in recipients do
cond do
- "https://www.w3.org/ns/activitystreams#Public" in cc ->
+ Pleroma.Constants.as_public() in cc ->
to = to ++ [followers_collection]
Map.put(object, "to", to)
- "https://www.w3.org/ns/activitystreams#Public" in to ->
+ Pleroma.Constants.as_public() in to ->
cc = cc ++ [followers_collection]
Map.put(object, "cc", cc)
@@ -150,21 +151,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
end
- # Check for standardisation
- # This is what Peertube does
- # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
- # Prismo returns only an integer (count) as "likes"
- def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
- object
- |> Map.put("likes", [])
- |> Map.put("like_count", 0)
- end
+ def fix_in_reply_to(object, options \\ [])
- def fix_likes(object) do
- object
- end
-
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
+ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id =
cond do
@@ -182,28 +171,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
""
end
- case get_obj_helper(in_reply_to_id) do
- {:ok, replied_object} ->
- with %Activity{} = _activity <-
- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
- object
- |> Map.put("inReplyTo", replied_object.data["id"])
- |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
- |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
- |> Map.put("context", replied_object.data["context"] || object["conversation"])
- else
- e ->
- Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
- object
- end
+ object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
- e ->
- Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
- object
+ if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+ case get_obj_helper(in_reply_to_id, options) do
+ {:ok, replied_object} ->
+ with %Activity{} = _activity <-
+ Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
+ object
+ |> Map.put("inReplyTo", replied_object.data["id"])
+ |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
+ |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
+ |> Map.put("context", replied_object.data["context"] || object["conversation"])
+ else
+ e ->
+ Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+ object
+ end
+
+ e ->
+ Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+ object
+ end
+ else
+ object
end
end
- def fix_in_reply_to(object), do: object
+ def fix_in_reply_to(object, _options), do: object
def fix_context(object) do
context = object["context"] || object["conversation"] || Utils.generate_context_id()
@@ -336,8 +331,13 @@ 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)
+ def fix_type(object, options \\ [])
+
+ def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
+ reply =
+ if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+ Object.normalize(reply_id, true)
+ end
if reply && (reply.data["type"] == "Question" and object["name"]) do
Map.put(object, "type", "Answer")
@@ -346,7 +346,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def fix_type(object), do: object
+ def fix_type(object, _), do: object
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
with true <- id =~ "follows",
@@ -374,9 +374,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
+ def handle_incoming(data, options \\ [])
+
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
- def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do
+ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -409,15 +411,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
# disallow objects with bogus IDs
- def handle_incoming(%{"id" => nil}), do: :error
- def handle_incoming(%{"id" => ""}), do: :error
+ def handle_incoming(%{"id" => nil}, _options), do: :error
+ def handle_incoming(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now.
- def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error
+ def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
+ do: :error
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+ def handle_incoming(
+ %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+ options
+ )
when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
actor = Containment.get_actor(data)
@@ -427,7 +433,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
- object = fix_object(data["object"])
+ options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+ object = fix_object(data["object"], options)
params = %{
to: data["to"],
@@ -452,14 +459,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
+ %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
+ _options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
- {_, false} <-
- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
+ {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{_, false} <- {:user_locked, User.locked?(followed)},
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <-
@@ -503,7 +510,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@@ -524,7 +532,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@@ -548,7 +557,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data
+ %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -561,7 +571,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
+ %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -576,19 +587,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
- data
+ data,
+ _options
)
when object_type in ["Person", "Application", "Service", "Organization"] do
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
- banner = new_user_data[:info]["banner"]
- locked = new_user_data[:info]["locked"] || false
+ banner = new_user_data[:info][:banner]
+ locked = new_user_data[:info][:locked] || false
update_data =
new_user_data
|> Map.take([:name, :bio, :avatar])
- |> Map.put(:info, %{"banner" => banner, "locked" => locked})
+ |> Map.put(:info, %{banner: banner, locked: locked})
actor
|> User.upgrade_changeset(update_data)
@@ -614,7 +626,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# an error or a tombstone. This would allow us to verify that a deletion actually took
# place.
def handle_incoming(
- %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
+ _options
) do
object_id = Utils.get_ap_id(object_id)
@@ -625,7 +638,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
else
- _e -> :error
+ nil ->
+ case User.get_cached_by_ap_id(object_id) do
+ %User{ap_id: ^actor} = user ->
+ User.delete(user)
+
+ nil ->
+ :error
+ end
+
+ _e ->
+ :error
end
end
@@ -635,7 +658,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Announce", "object" => object_id},
"actor" => _actor,
"id" => id
- } = data
+ } = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -653,7 +677,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
- } = _data
+ } = _data,
+ _options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@@ -671,7 +696,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Block", "object" => blocked},
"actor" => blocker,
"id" => id
- } = _data
+ } = _data,
+ _options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
@@ -685,7 +711,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data
+ %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
+ _options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
@@ -705,7 +732,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Like", "object" => object_id},
"actor" => _actor,
"id" => id
- } = data
+ } = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -717,10 +745,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(_), do: :error
+ def handle_incoming(_, _), do: :error
- def get_obj_helper(id) do
- if object = Object.normalize(id), do: {:ok, object}, else: nil
+ def get_obj_helper(id, options \\ []) do
+ if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
@@ -742,7 +770,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> add_mention_tags
|> add_emoji_tags
|> add_attributed_to
- |> add_likes
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
@@ -758,13 +785,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
object =
- Object.normalize(object_id).data
+ object_id
+ |> Object.normalize()
+ |> Map.get(:data)
|> prepare_object
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
+ |> Map.delete("bcc")
{:ok, data}
end
@@ -926,22 +956,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("attributedTo", attributed_to)
end
- def add_likes(%{"id" => id, "like_count" => likes} = object) do
- likes = %{
- "id" => "#{id}/likes",
- "first" => "#{id}/likes?page=1",
- "type" => "OrderedCollection",
- "totalItems" => likes
- }
-
- object
- |> Map.put("likes", likes)
- end
-
- def add_likes(object) do
- object
- end
-
def prepare_attachments(object) do
attachments =
(object["attachment"] || [])
@@ -957,6 +971,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_fields(object) do
object
|> Map.drop([
+ "likes",
"like_count",
"announcements",
"announcement_count",
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 514266cee..fc5305c58 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
import Ecto.Query
require Logger
+ require Pleroma.Constants
@supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
@supported_report_states ~w(open closed resolved)
@@ -25,12 +26,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
# Some implementations send the actor URI as the actor field, others send the entire actor object,
# so figure out what the actor's URI is based on what we have.
- def get_ap_id(object) do
- case object do
- %{"id" => id} -> id
- id -> id
- end
- end
+ def get_ap_id(%{"id" => id} = _), do: id
+ def get_ap_id(id), do: id
def normalize_params(params) do
Map.put(params, "actor", get_ap_id(params["actor"]))
@@ -170,14 +167,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Enqueues an activity for federation if it's local
"""
def maybe_federate(%Activity{local: true} = activity) do
- priority =
- case activity.data["type"] do
- "Delete" -> 10
- "Create" -> 1
- _ -> 5
- end
+ if Pleroma.Config.get!([:instance, :federating]) do
+ priority =
+ case activity.data["type"] do
+ "Delete" -> 10
+ "Create" -> 1
+ _ -> 5
+ end
+
+ Pleroma.Web.Federator.publish(activity, priority)
+ end
- Pleroma.Web.Federator.publish(activity, priority)
:ok
end
@@ -251,20 +251,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def insert_full_object(map), do: {:ok, map, nil}
- def update_object_in_activities(%{data: %{"id" => id}} = object) do
- # TODO
- # Update activities that already had this. Could be done in a seperate process.
- # Alternatively, just don't do this and fetch the current object each time. Most
- # could probably be taken from cache.
- relevant_activities = Activity.get_all_create_by_object_ap_id(id)
-
- Enum.map(relevant_activities, fn activity ->
- new_activity_data = activity.data |> Map.put("object", object.data)
- changeset = Changeset.change(activity, data: new_activity_data)
- Repo.update(changeset)
- end)
- end
-
#### Like-related helpers
@doc """
@@ -347,8 +333,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put("#{property}_count", length(element))
|> Map.put("#{property}s", element),
changeset <- Changeset.change(object, data: new_data),
- {:ok, object} <- Object.update_and_set_cache(changeset),
- _ <- update_object_in_activities(object) do
+ {:ok, object} <- Object.update_and_set_cache(changeset) do
{:ok, object}
end
end
@@ -419,7 +404,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"type" => "Follow",
"actor" => follower_id,
"to" => [followed_id],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [Pleroma.Constants.as_public()],
"object" => followed_id,
"state" => "pending"
}
@@ -511,7 +496,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"actor" => ap_id,
"object" => id,
"to" => [user.follower_address, object.data["actor"]],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [Pleroma.Constants.as_public()],
"context" => object.data["context"]
}
@@ -531,7 +516,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"actor" => ap_id,
"object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@@ -548,7 +533,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"actor" => ap_id,
"object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@@ -557,7 +542,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def add_announce_to_object(
%Activity{
- data: %{"actor" => actor, "cc" => ["https://www.w3.org/ns/activitystreams#Public"]}
+ data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}
},
object
) do
@@ -766,7 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
) do
cc = Map.get(data, "cc", [])
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
- public = "https://www.w3.org/ns/activitystreams#Public"
+ public = Pleroma.Constants.as_public()
case visibility do
"public" ->
diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex
index 6028b773c..94d05f49b 100644
--- a/lib/pleroma/web/activity_pub/views/object_view.ex
+++ b/lib/pleroma/web/activity_pub/views/object_view.ex
@@ -66,8 +66,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
"orderedItems" => items
}
- if offset < total do
+ if offset + length(items) < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
+ else
+ map
end
end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 327e0e05b..06c9e1c71 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -31,8 +31,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("endpoints.json", _), do: %{}
- # the instance itself is not a Person, but instead an Application
- def render("user.json", %{user: %{nickname: nil} = user}) do
+ def render("service.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
@@ -47,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox",
"name" => "Pleroma",
- "summary" => "Virtual actor for Pleroma relay",
+ "summary" =>
+ "An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
"url" => user.ap_id,
"manuallyApprovesFollowers" => false,
"publicKey" => %{
@@ -60,6 +60,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Map.merge(Utils.make_json_ld_header())
end
+ # the instance itself is not a Person, but instead an Application
+ def render("user.json", %{user: %User{nickname: nil} = user}),
+ do: render("service.json", %{user: user})
+
+ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
+ do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
+
def render("user.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
@@ -98,29 +105,31 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Map.merge(Utils.make_json_ld_header())
end
- def render("following.json", %{user: user, page: page}) do
+ def render("following.json", %{user: user, page: page} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
total =
- if !user.info.hide_follows do
+ if showing do
length(following)
else
0
end
- collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
+ collection(following, "#{user.ap_id}/following", page, showing, total)
|> Map.merge(Utils.make_json_ld_header())
end
- def render("following.json", %{user: user}) do
+ def render("following.json", %{user: user} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
total =
- if !user.info.hide_follows do
+ if showing do
length(following)
else
0
@@ -130,34 +139,43 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{user.ap_id}/following",
"type" => "OrderedCollection",
"totalItems" => total,
- "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+ "first" =>
+ if showing do
+ collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+ else
+ "#{user.ap_id}/following?page=1"
+ end
}
|> Map.merge(Utils.make_json_ld_header())
end
- def render("followers.json", %{user: user, page: page}) do
+ def render("followers.json", %{user: user, page: page} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
+
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
total =
- if !user.info.hide_followers do
+ if showing do
length(followers)
else
0
end
- collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
+ collection(followers, "#{user.ap_id}/followers", page, showing, total)
|> Map.merge(Utils.make_json_ld_header())
end
- def render("followers.json", %{user: user}) do
+ def render("followers.json", %{user: user} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
+
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
total =
- if !user.info.hide_followers do
+ if showing do
length(followers)
else
0
@@ -168,7 +186,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"type" => "OrderedCollection",
"totalItems" => total,
"first" =>
- collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
+ if showing do
+ collection(followers, "#{user.ap_id}/followers", 1, showing, total)
+ else
+ "#{user.ap_id}/followers?page=1"
+ end
}
|> Map.merge(Utils.make_json_ld_header())
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 8965e3253..dfb166b65 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -1,17 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+ require Pleroma.Constants
+
+ @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
-
- def is_public?(data) do
- "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
- end
+ def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || []))
def is_private?(activity) do
with false <- is_public?(activity),
@@ -30,6 +34,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
!is_public?(activity) && !is_private?(activity)
end
+ def is_list?(%{data: %{"listMessage" => _}}), do: true
+ def is_list?(_), do: false
+
+ def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+
+ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+ user.ap_id in activity.data["to"] ||
+ list_ap_id
+ |> Pleroma.List.get_by_ap_id()
+ |> Pleroma.List.member?(user)
+ end
+
+ def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+
def visible_for_user?(activity, nil) do
is_public?(activity)
end
@@ -51,15 +69,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
def get_visibility(object) do
- public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
- public in to ->
+ Pleroma.Constants.as_public() in to ->
"public"
- public in cc ->
+ Pleroma.Constants.as_public() in cc ->
"unlisted"
# this should use the sql for the object's activity
@@ -69,6 +86,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
object.data["directMessage"] == true ->
"direct"
+ is_binary(object.data["listMessage"]) ->
+ "list"
+
length(cc) > 0 ->
"private"
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 498beb56a..2d3d0adc4 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
def user_show(conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
conn
|> json(AccountView.render("show.json", %{user: user}))
else
@@ -82,6 +82,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
+ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
+ godmode = params["godmode"] == "true" || params["godmode"] == true
+
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
+ {_, page_size} = page_params(params)
+
+ activities =
+ ActivityPub.fetch_user_activities(user, nil, %{
+ "limit" => page_size,
+ "godmode" => godmode
+ })
+
+ conn
+ |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
@@ -160,9 +179,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
def right_add(conn, _) do
- conn
- |> put_status(404)
- |> json(%{error: "No such permission_group"})
+ render_error(conn, :not_found, "No such permission_group")
end
def right_get(conn, %{"nickname" => nickname}) do
@@ -184,9 +201,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
)
when permission_group in ["moderator", "admin"] do
if admin_nickname == nickname do
- conn
- |> put_status(403)
- |> json(%{error: "You can't revoke your own admin status."})
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
else
user = User.get_cached_by_nickname(nickname)
@@ -207,9 +222,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
def right_delete(conn, _) do
- conn
- |> put_status(404)
- |> json(%{error: "No such permission_group"})
+ render_error(conn, :not_found, "No such permission_group")
end
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
@@ -278,11 +291,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@doc "Revokes invite by token"
def revoke_invite(conn, %{"token" => token}) do
- invite = UserInviteToken.find_by_token!(token)
- {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true})
-
- conn
- |> json(AccountView.render("invite.json", %{invite: updated_invite}))
+ with {:ok, invite} <- UserInviteToken.find_by_token(token),
+ {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
+ conn
+ |> json(AccountView.render("invite.json", %{invite: updated_invite}))
+ else
+ nil -> {:error, :not_found}
+ end
end
@doc "Get a password reset token (base64 string) for given nickname"
@@ -364,6 +379,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
+ def migrate_to_db(conn, _params) do
+ Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
+ json(conn, %{})
+ end
+
+ def migrate_from_db(conn, _params) do
+ Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
+ json(conn, %{})
+ end
+
def config_show(conn, _params) do
configs = Pleroma.Repo.all(Config)
@@ -377,13 +402,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
updated =
Enum.map(configs, fn
+ %{"group" => group, "key" => key, "delete" => "true"} = params ->
+ {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
+ config
+
%{"group" => group, "key" => key, "value" => value} ->
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
config
-
- %{"group" => group, "key" => key, "delete" => "true"} ->
- {:ok, _} = Config.delete(%{group: group, key: key})
- nil
end)
|> Enum.reject(&is_nil(&1))
@@ -401,26 +426,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
def errors(conn, {:error, :not_found}) do
conn
- |> put_status(404)
- |> json("Not found")
+ |> put_status(:not_found)
+ |> json(dgettext("errors", "Not found"))
end
def errors(conn, {:error, reason}) do
conn
- |> put_status(400)
+ |> put_status(:bad_request)
|> json(reason)
end
def errors(conn, {:param_cast, _}) do
conn
- |> put_status(400)
- |> json("Invalid parameters")
+ |> put_status(:bad_request)
+ |> json(dgettext("errors", "Invalid parameters"))
end
def errors(conn, _) do
conn
- |> put_status(500)
- |> json("Something went wrong")
+ |> put_status(:internal_server_error)
+ |> json(dgettext("errors", "Something went wrong"))
end
defp page_params(params) do
diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex
index 8b9b658a9..a10cc779b 100644
--- a/lib/pleroma/web/admin_api/config.ex
+++ b/lib/pleroma/web/admin_api/config.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.AdminAPI.Config do
use Ecto.Schema
import Ecto.Changeset
+ import Pleroma.Web.Gettext
alias __MODULE__
alias Pleroma.Repo
@@ -54,107 +55,119 @@ defmodule Pleroma.Web.AdminAPI.Config do
@spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
def delete(params) do
- with %Config{} = config <- Config.get_by_params(params) do
- Repo.delete(config)
+ with %Config{} = config <- Config.get_by_params(Map.delete(params, :subkeys)) do
+ if params[:subkeys] do
+ updated_value =
+ Keyword.drop(
+ :erlang.binary_to_term(config.value),
+ Enum.map(params[:subkeys], &do_transform_string(&1))
+ )
+
+ Config.update(config, %{value: updated_value})
+ else
+ Repo.delete(config)
+ {:ok, nil}
+ end
else
- nil -> {:error, "Config with params #{inspect(params)} not found"}
+ nil ->
+ err =
+ dgettext("errors", "Config with params %{params} not found", params: inspect(params))
+
+ {:error, err}
end
end
@spec from_binary(binary()) :: term()
- def from_binary(value), do: :erlang.binary_to_term(value)
+ def from_binary(binary), do: :erlang.binary_to_term(binary)
- @spec from_binary_to_map(binary()) :: any()
- def from_binary_to_map(binary) do
+ @spec from_binary_with_convert(binary()) :: any()
+ def from_binary_with_convert(binary) do
from_binary(binary)
|> do_convert()
end
- defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
- do: %{k => do_convert(v)}
+ defp do_convert(entity) when is_list(entity) do
+ for v <- entity, into: [], do: do_convert(v)
+ end
- defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
+ defp do_convert(entity) when is_map(entity) do
+ for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
+ end
- defp do_convert({k, v} = value) when is_tuple(value),
- do: %{k => do_convert(v)}
+ defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
+ defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
- defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
+ defp do_convert(entity) when is_tuple(entity),
+ do: %{"tuple" => do_convert(Tuple.to_list(entity))}
- defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
+ defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity),
+ do: entity
- defp do_convert(value) when is_atom(value) do
- string = to_string(value)
+ defp do_convert(entity) when is_atom(entity) do
+ string = to_string(entity)
if String.starts_with?(string, "Elixir."),
- do: String.trim_leading(string, "Elixir."),
- else: value
+ do: do_convert(string),
+ else: ":" <> string
end
+ defp do_convert("Elixir." <> module_name), do: module_name
+
+ defp do_convert(entity) when is_binary(entity), do: entity
+
@spec transform(any()) :: binary()
- def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
-
- def transform(entity) when is_map(entity) do
- tuples =
- for {k, v} <- entity,
- into: [],
- do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
-
- Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
- |> Enum.sort()
- |> :erlang.term_to_binary()
- end
-
- def transform(entity) when is_list(entity) do
- list = Enum.map(entity, &do_transform(&1))
- :erlang.term_to_binary(list)
+ def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
+ :erlang.term_to_binary(do_transform(entity))
end
def transform(entity), do: :erlang.term_to_binary(entity)
- defp do_transform(%Regex{} = value) when is_map(value), do: value
+ defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
- defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
- {do_transform(k), do_transform(values)}
+ defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
+ {dispatch_settings, []} = do_eval(entity)
+ {:dispatch, [dispatch_settings]}
end
- defp do_transform(%{"tuple" => values}) do
- Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
+ defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
+ {partial_chain, []} = do_eval(entity)
+ {:partial_chain, partial_chain}
end
- defp do_transform(value) when is_map(value) do
- values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
-
- Enum.sort(values)
+ defp do_transform(%{"tuple" => entity}) do
+ Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end
- defp do_transform(value) when is_list(value) do
- Enum.map(value, &do_transform(&1))
+ defp do_transform(entity) when is_map(entity) do
+ for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
end
- defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
+ defp do_transform(entity) when is_list(entity) do
+ for v <- entity, into: [], do: do_transform(v)
+ end
- defp do_transform(value) when is_binary(value) do
- String.trim(value)
+ defp do_transform(entity) when is_binary(entity) do
+ String.trim(entity)
|> do_transform_string()
end
- defp do_transform(value), do: value
+ defp do_transform(entity), do: entity
- defp do_transform_string(value) when byte_size(value) == 0, do: nil
+ defp do_transform_string("~r/" <> pattern) do
+ pattern = String.trim_trailing(pattern, "/")
+ ~r/#{pattern}/
+ end
+
+ defp do_transform_string(":" <> atom), do: String.to_atom(atom)
defp do_transform_string(value) do
- cond do
- String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
- String.to_existing_atom("Elixir." <> value)
+ if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"),
+ do: String.to_existing_atom("Elixir." <> value),
+ else: value
+ end
- String.starts_with?(value, ":") ->
- String.replace(value, ":", "") |> String.to_existing_atom()
-
- String.starts_with?(value, "i:") ->
- String.replace(value, "i:", "") |> String.to_integer()
-
- true ->
- value
- end
+ defp do_eval(entity) do
+ cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
+ Code.eval_string(cleaned_string, [], requires: [], macros: [])
end
end
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
index 3ccc9ca46..49add0b6e 100644
--- a/lib/pleroma/web/admin_api/views/config_view.ex
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view
@@ -11,7 +15,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do
%{
key: config.key,
group: config.group,
- value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
+ value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value)
}
end
end
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index d4e0ffa80..dd49987f7 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -21,8 +21,7 @@ defmodule Pleroma.Web.Auth.Authenticator do
def create_from_registration(plug, registration),
do: implementation().create_from_registration(plug, registration)
- @callback get_registration(Plug.Conn.t()) ::
- {:ok, Registration.t()} | {:error, any()}
+ @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()}
def get_registration(plug), do: implementation().get_registration(plug)
@callback handle_error(Plug.Conn.t(), any()) :: any()
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index a9164ad98..f4234b743 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
- alias Comeonin.Pbkdf2
+ alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
def get_user(%Plug.Conn{} = conn) do
with {:ok, {name, password}} <- fetch_credentials(conn),
{_, %User{} = user} <- {:user, fetch_user(name)},
- {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
+ {_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)} do
{:ok, user}
else
error ->
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index f71c67a3d..2db58324b 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -4,7 +4,6 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
- alias Pleroma.Bookmark
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
@@ -13,6 +12,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
def follow(follower, followed) do
@@ -30,7 +30,8 @@ defmodule Pleroma.Web.CommonAPI do
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
- {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
+ {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
+ {:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
{:ok, follower}
end
end
@@ -74,7 +75,7 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, delete}
else
_ ->
- {:error, "Could not delete"}
+ {:error, dgettext("errors", "Could not delete")}
end
end
@@ -85,7 +86,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.announce(user, object)
else
_ ->
- {:error, "Could not repeat"}
+ {:error, dgettext("errors", "Could not repeat")}
end
end
@@ -95,7 +96,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.unannounce(user, object)
else
_ ->
- {:error, "Could not unrepeat"}
+ {:error, dgettext("errors", "Could not unrepeat")}
end
end
@@ -106,7 +107,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.like(user, object)
else
_ ->
- {:error, "Could not favorite"}
+ {:error, dgettext("errors", "Could not favorite")}
end
end
@@ -116,7 +117,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.unlike(user, object)
else
_ ->
- {:error, "Could not unfavorite"}
+ {:error, dgettext("errors", "Could not unfavorite")}
end
end
@@ -148,10 +149,10 @@ defmodule Pleroma.Web.CommonAPI do
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"}
+ {:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")}
+ {:existing_votes, _} -> {:error, dgettext("errors", "Already voted")}
+ {:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")}
+ {:count_check, false} -> {:error, dgettext("errors", "Too many choices")}
end
end
@@ -174,6 +175,11 @@ defmodule Pleroma.Web.CommonAPI do
when visibility in ~w{public unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)}
+ def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
+ visibility = {:list, String.to_integer(list_id)}
+ {visibility, get_replied_to_visibility(in_reply_to)}
+ end
+
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
@@ -234,23 +240,27 @@ defmodule Pleroma.Web.CommonAPI do
"emoji",
Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
) do
- res =
- ActivityPub.create(
- %{
- to: to,
- actor: user,
- context: context,
- object: object,
- additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
- },
- Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
- )
+ preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
+ direct? = visibility == "direct"
- res
+ %{
+ to: to,
+ actor: user,
+ context: context,
+ object: object,
+ additional: %{"cc" => cc, "directMessage" => direct?}
+ }
+ |> maybe_add_list_data(user, visibility)
+ |> ActivityPub.create(preview?)
else
- {:private_to_public, true} -> {:error, "The message visibility must be direct"}
- {:error, _} = e -> e
- e -> {:error, e}
+ {:private_to_public, true} ->
+ {:error, dgettext("errors", "The message visibility must be direct")}
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
end
end
@@ -290,8 +300,7 @@ defmodule Pleroma.Web.CommonAPI do
}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity),
- %{valid?: true} = info_changeset <-
- User.Info.add_pinnned_activity(user.info, activity),
+ %{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
@@ -301,7 +310,7 @@ defmodule Pleroma.Web.CommonAPI do
{:error, err}
_ ->
- {:error, "Could not pin"}
+ {:error, dgettext("errors", "Could not pin")}
end
end
@@ -318,7 +327,7 @@ defmodule Pleroma.Web.CommonAPI do
{:error, err}
_ ->
- {:error, "Could not unpin"}
+ {:error, dgettext("errors", "Could not unpin")}
end
end
@@ -326,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
{:ok, activity}
else
- {:error, _} -> {:error, "conversation is already muted"}
+ {:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
end
end
@@ -345,15 +354,6 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def bookmarked?(user, activity) do
- with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
- true
- else
- _ ->
- false
- end
- end
-
def report(user, data) do
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
@@ -371,8 +371,8 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, activity}
else
{:error, err} -> {:error, err}
- {:account_id, %{}} -> {:error, "Valid `account_id` required"}
- {:account, nil} -> {:error, "Account not found"}
+ {:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")}
+ {:account, nil} -> {:error, dgettext("errors", "Account not found")}
end
end
@@ -381,14 +381,9 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, activity} <- Utils.update_report_state(activity, state) do
{:ok, activity}
else
- nil ->
- {:error, :not_found}
-
- {:error, reason} ->
- {:error, reason}
-
- _ ->
- {:error, "Could not update state"}
+ nil -> {:error, :not_found}
+ {:error, reason} -> {:error, reason}
+ _ -> {:error, dgettext("errors", "Could not update state")}
end
end
@@ -398,11 +393,8 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, activity} <- set_visibility(activity, opts) do
{:ok, activity}
else
- nil ->
- {:error, :not_found}
-
- {:error, reason} ->
- {:error, reason}
+ nil -> {:error, :not_found}
+ {:error, reason} -> {:error, reason}
end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 8b9477927..22c44a0a3 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -3,12 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.Utils do
+ import Pleroma.Web.Gettext
+
alias Calendar.Strftime
- alias Comeonin.Pbkdf2
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Formatter
alias Pleroma.Object
+ alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
@@ -17,11 +19,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Web.MediaProxy
require Logger
+ require Pleroma.Constants
# This is a hack for twidere.
def get_by_id_or_ap_id(id) do
activity =
- Activity.get_by_id_with_object(id) || Activity.get_create_by_object_ap_id_with_object(id)
+ with true <- Pleroma.FlakeId.is_flake_id?(id),
+ %Activity{} = activity <- Activity.get_by_id_with_object(id) do
+ activity
+ else
+ _ -> Activity.get_create_by_object_ap_id_with_object(id)
+ end
activity &&
if activity.data["type"] == "Create" do
@@ -39,32 +47,49 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def get_replied_to_activity(_), do: nil
- def attachments_from_ids(data) do
- if Map.has_key?(data, "descriptions") do
- attachments_from_ids_descs(data["media_ids"], data["descriptions"])
- else
- attachments_from_ids_no_descs(data["media_ids"])
- end
+ def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
+ attachments_from_ids_descs(ids, desc)
end
- def attachments_from_ids_no_descs(ids) do
- Enum.map(ids || [], fn media_id ->
- Repo.get(Object, media_id).data
- end)
+ def attachments_from_ids(%{"media_ids" => ids} = _) do
+ attachments_from_ids_no_descs(ids)
end
+ def attachments_from_ids(_), do: []
+
+ def attachments_from_ids_no_descs([]), do: []
+
+ def attachments_from_ids_no_descs(ids) do
+ Enum.map(ids, fn media_id ->
+ case Repo.get(Object, media_id) do
+ %Object{data: data} = _ -> data
+ _ -> nil
+ end
+ end)
+ |> Enum.filter(& &1)
+ end
+
+ def attachments_from_ids_descs([], _), do: []
+
def attachments_from_ids_descs(ids, descs_str) do
{_, descs} = Jason.decode(descs_str)
- Enum.map(ids || [], fn media_id ->
- Map.put(Repo.get(Object, media_id).data, "name", descs[media_id])
+ Enum.map(ids, fn media_id ->
+ case Repo.get(Object, media_id) do
+ %Object{data: data} = _ ->
+ Map.put(data, "name", descs[media_id])
+
+ _ ->
+ nil
+ end
end)
+ |> Enum.filter(& &1)
end
@spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
{list(String.t()), list(String.t())}
def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
- to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
+ to = [Pleroma.Constants.as_public() | mentioned_users]
cc = [user.follower_address]
if inReplyTo do
@@ -76,7 +101,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
to = [user.follower_address | mentioned_users]
- cc = ["https://www.w3.org/ns/activitystreams#Public"]
+ cc = [Pleroma.Constants.as_public()]
if inReplyTo do
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
@@ -98,12 +123,29 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
+ def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []}
+
def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to)
end
def get_addressed_users(mentioned_users, _), do: mentioned_users
+ def maybe_add_list_data(activity_params, user, {:list, list_id}) do
+ case Pleroma.List.get(list_id, user) do
+ %Pleroma.List{} = list ->
+ activity_params
+ |> put_in([:additional, "bcc"], [list.ap_id])
+ |> put_in([:additional, "listMessage"], list.ap_id)
+ |> put_in([:object, "listMessage"], list.ap_id)
+
+ _ ->
+ activity_params
+ end
+ end
+
+ def maybe_add_list_data(activity_params, _, _), do: activity_params
+
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} =
@@ -222,20 +264,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
def add_attachments(text, attachments) do
- attachment_text =
- Enum.map(attachments, fn
- %{"url" => [%{"href" => href} | _]} = attachment ->
- name = attachment["name"] || URI.decode(Path.basename(href))
- href = MediaProxy.url(href)
- "#{shortname(name)} "
-
- _ ->
- ""
- end)
-
+ attachment_text = Enum.map(attachments, &build_attachment_link/1)
Enum.join([text | attachment_text], " ")
end
+ defp build_attachment_link(%{"url" => [%{"href" => href} | _]} = attachment) do
+ name = attachment["name"] || URI.decode(Path.basename(href))
+ href = MediaProxy.url(href)
+ "#{shortname(name)} "
+ end
+
+ defp build_attachment_link(_), do: ""
+
def format_input(text, format, options \\ [])
@doc """
@@ -295,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
sensitive \\ false,
merge \\ %{}
) do
- object = %{
+ %{
"type" => "Note",
"to" => to,
"cc" => cc,
@@ -305,18 +345,20 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"context" => context,
"attachment" => attachments,
"actor" => actor,
- "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
+ "tag" => Keyword.values(tags) |> Enum.uniq()
}
+ |> add_in_reply_to(in_reply_to)
+ |> Map.merge(merge)
+ 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
+ defp add_in_reply_to(object, nil), do: object
- Map.merge(object, merge)
+ defp add_in_reply_to(object, in_reply_to) do
+ with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
+ Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
+ else
+ _ -> object
+ end
end
def format_naive_asctime(date) do
@@ -348,17 +390,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
end
- def to_masto_date(date) do
- try do
- date
- |> NaiveDateTime.from_iso8601!()
- |> NaiveDateTime.to_iso8601()
- |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
- rescue
- _e -> ""
+ def to_masto_date(date) when is_binary(date) do
+ with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
+ to_masto_date(date)
+ else
+ _ -> ""
end
end
+ def to_masto_date(_), do: ""
+
defp shortname(name) do
if String.length(name) < 30 do
name
@@ -369,10 +410,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def confirm_current_password(user, password) do
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
- true <- Pbkdf2.checkpw(password, db_user.password_hash) do
+ true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
{:ok, db_user}
else
- _ -> {:error, "Invalid password."}
+ _ -> {:error, dgettext("errors", "Invalid password.")}
end
end
@@ -403,7 +444,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
object_data =
cond do
- !is_nil(object) ->
+ not is_nil(object) ->
object.data
is_map(data["object"]) ->
@@ -420,6 +461,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
+ # Do not notify subscribers if author is making a reply
+ def maybe_notify_subscribers(recipients, %Activity{
+ object: %Object{data: %{"inReplyTo" => _ap_id}}
+ }) do
+ recipients
+ end
+
def maybe_notify_subscribers(
recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity
@@ -440,9 +488,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_extract_mentions(%{"tag" => tag}) do
tag
- |> Enum.filter(fn x -> is_map(x) end)
- |> Enum.filter(fn x -> x["type"] == "Mention" end)
+ |> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)
|> Enum.map(fn x -> x["href"] end)
+ |> Enum.uniq()
end
def maybe_extract_mentions(_), do: []
@@ -455,7 +503,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
else
- {:error, "Comment must be up to #{max_size} characters"}
+ {:error,
+ dgettext("errors", "Comment must be up to %{max_size} characters", max_size: max_size)}
end
end
@@ -490,7 +539,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
context
else
_e ->
- {:error, "No such conversation"}
+ {:error, dgettext("errors", "No such conversation")}
end
end
@@ -512,10 +561,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do
if length > 0 or Enum.count(attachments) > 0 do
:ok
else
- {:error, "Cannot post an empty status without attachments"}
+ {:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
else
- {:error, "The status is over the character limit"}
+ {:error, dgettext("errors", "The status is over the character limit")}
end
end
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index ddaf88f1d..c123530dc 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -7,13 +7,9 @@ defmodule Pleroma.Web.Endpoint do
socket("/socket", Pleroma.Web.UserSocket)
- # Serve at "/" the static files from "priv/static" directory.
- #
- # You should set gzip to true if you are running phoenix.digest
- # when deploying your static files in production.
+ plug(Pleroma.Plugs.SetLocalePlug)
plug(CORSPlug)
plug(Pleroma.Plugs.HTTPSecurityPlug)
-
plug(Pleroma.Plugs.UploadedMedia)
@static_cache_control "public, no-cache"
@@ -30,6 +26,10 @@ defmodule Pleroma.Web.Endpoint do
}
)
+ # Serve at "/" the static files from "priv/static" directory.
+ #
+ # You should set gzip to true if you are running phoenix.digest
+ # when deploying your static files in production.
plug(
Plug.Static,
at: "/",
diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex
new file mode 100644
index 000000000..5fbf3695f
--- /dev/null
+++ b/lib/pleroma/web/fallback_redirect_controller.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Fallback.RedirectController do
+ use Pleroma.Web, :controller
+ require Logger
+ alias Pleroma.User
+ alias Pleroma.Web.Metadata
+
+ def api_not_implemented(conn, _params) do
+ conn
+ |> put_status(404)
+ |> json(%{error: "Not implemented"})
+ end
+
+ def redirector(conn, _params, code \\ 200)
+
+ # redirect to admin section
+ # /pleroma/admin -> /pleroma/admin/
+ #
+ def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do
+ redirect(conn, to: "/pleroma/admin/")
+ end
+
+ def redirector(conn, _params, code) do
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_file(code, index_file_path())
+ end
+
+ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
+ redirector_with_meta(conn, %{user: user})
+ else
+ nil ->
+ redirector(conn, params)
+ end
+ end
+
+ def redirector_with_meta(conn, params) do
+ {:ok, index_content} = File.read(index_file_path())
+
+ tags =
+ try do
+ Metadata.build_tags(params)
+ rescue
+ e ->
+ Logger.error(
+ "Metadata rendering for #{conn.request_path} failed.\n" <>
+ Exception.format(:error, e, __STACKTRACE__)
+ )
+
+ ""
+ end
+
+ response = String.replace(index_content, "", tags)
+
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, response)
+ end
+
+ def index_file_path do
+ Pleroma.Plugs.InstanceStatic.file_path("index.html")
+ end
+
+ def registration_page(conn, params) do
+ redirector(conn, params)
+ end
+
+ def empty(conn, _params) do
+ conn
+ |> put_status(204)
+ |> text("")
+ end
+end
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index f4c9fe284..f4f9e83e0 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -22,6 +22,18 @@ defmodule Pleroma.Web.Federator do
refresh_subscriptions()
end
+ @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
+ # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+ def allowed_incoming_reply_depth?(depth) do
+ max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
+
+ if max_replies_depth do
+ (depth || 1) <= max_replies_depth
+ else
+ true
+ end
+ end
+
# Client API
def incoming_doc(doc) do
diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex
new file mode 100644
index 000000000..478a83518
--- /dev/null
+++ b/lib/pleroma/web/mailer/subscription_controller.ex
@@ -0,0 +1,20 @@
+defmodule Pleroma.Web.Mailer.SubscriptionController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.JWT
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ def unsubscribe(conn, %{"token" => encoded_token}) do
+ with {:ok, token} <- Base.decode64(encoded_token),
+ {:ok, claims} <- JWT.verify_and_validate(token),
+ %{"act" => %{"unsubscribe" => type}, "sub" => uid} <- claims,
+ %User{} = user <- Repo.get(User, uid),
+ {:ok, _user} <- User.switch_email_notifications(user, type, false) do
+ render(conn, "unsubscribe_success.html", email: user.email)
+ else
+ _err ->
+ render(conn, "unsubscribe_failure.html")
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 3a3ec7c2a..46944dcbc 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset
@@ -49,7 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
options = cast_params(params)
user
- |> Notification.for_user_query()
+ |> Notification.for_user_query(options)
|> restrict(:exclude_types, options)
|> Pagination.fetch_paginated(params)
end
@@ -63,7 +67,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
- reblogs: :boolean
+ reblogs: :boolean,
+ with_muted: :boolean
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index ceb88511b..174e93468 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -4,6 +4,9 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Bookmark
@@ -15,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Pagination
+ alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.Stats
@@ -45,9 +49,36 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
import Ecto.Query
require Logger
+ require Pleroma.Constants
- plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
- plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
+ @rate_limited_relations_actions ~w(follow unfollow)a
+
+ @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
+ post_status delete_status)a
+
+ plug(
+ RateLimiter,
+ {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
+ when action in ~w(reblog_status unreblog_status)a
+ )
+
+ plug(
+ RateLimiter,
+ {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
+ when action in ~w(fav_status unfav_status)a
+ )
+
+ plug(
+ RateLimiter,
+ {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
+ )
+
+ plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
+ plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
+ plug(RateLimiter, :app_account_creation when action == :account_register)
+ plug(RateLimiter, :search when action in [:search, :search2, :account_search])
+ plug(RateLimiter, :password_reset when action == :password_reset)
+ plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
@local_mastodon_name "Mastodon-Local"
@@ -160,10 +191,70 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
)
else
- _e ->
- conn
- |> put_status(403)
- |> json(%{error: "Invalid request"})
+ _e -> render_error(conn, :forbidden, "Invalid request")
+ end
+ end
+
+ def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ change = Changeset.change(user, %{avatar: nil})
+ {:ok, user} = User.update_and_set_cache(change)
+ CommonAPI.update(user)
+
+ json(conn, %{url: nil})
+ end
+
+ def update_avatar(%{assigns: %{user: user}} = conn, params) do
+ {:ok, object} = ActivityPub.upload(params, type: :avatar)
+ change = Changeset.change(user, %{avatar: object.data})
+ {:ok, user} = User.update_and_set_cache(change)
+ CommonAPI.update(user)
+ %{"url" => [%{"href" => href} | _]} = object.data
+
+ json(conn, %{url: href})
+ end
+
+ def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
+ with new_info <- %{"banner" => %{}},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, user} <- User.update_and_set_cache(changeset) do
+ CommonAPI.update(user)
+
+ json(conn, %{url: nil})
+ end
+ end
+
+ def update_banner(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
+ new_info <- %{"banner" => object.data},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, user} <- User.update_and_set_cache(changeset) do
+ CommonAPI.update(user)
+ %{"url" => [%{"href" => href} | _]} = object.data
+
+ json(conn, %{url: href})
+ end
+ end
+
+ def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ with new_info <- %{"background" => %{}},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, _user} <- User.update_and_set_cache(changeset) do
+ json(conn, %{url: nil})
+ end
+ end
+
+ def update_background(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, object} <- ActivityPub.upload(params, type: :background),
+ new_info <- %{"background" => object.data},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, _user} <- User.update_and_set_cache(changeset) do
+ %{"url" => [%{"href" => href} | _]} = object.data
+
+ json(conn, %{url: href})
end
end
@@ -195,10 +286,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
account = AccountView.render("account.json", %{user: user, for: for_user})
json(conn, account)
else
- _e ->
- conn
- |> put_status(404)
- |> json(%{error: "Can't find user"})
+ _e -> render_error(conn, :not_found, "Can't find user")
end
end
@@ -242,7 +330,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
"static_url" => url,
"visible_in_picker" => true,
"url" => url,
- "tags" => tags
+ "tags" => tags,
+ # Assuming that a comma is authorized in the category name
+ "category" => (tags -- ["Custom"]) |> Enum.join(",")
}
end)
end
@@ -355,7 +445,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_id(params["id"]) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do
params =
params
|> Map.put("tag", params["tagged"])
@@ -446,15 +536,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> 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"})
+ nil -> render_error(conn, :not_found, "Record not found")
+ false -> render_error(conn, :not_found, "Record not found")
end
end
@@ -483,18 +566,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> try_render("poll.json", %{object: object, for: user})
else
nil ->
- conn
- |> put_status(404)
- |> json(%{error: "Record not found"})
+ render_error(conn, :not_found, "Record not found")
false ->
- conn
- |> put_status(404)
- |> json(%{error: "Record not found"})
+ render_error(conn, :not_found, "Record not found")
{:error, message} ->
conn
- |> put_status(422)
+ |> put_status(:unprocessable_entity)
|> json(%{error: message})
end
end
@@ -583,10 +662,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
else
- _e ->
- conn
- |> put_status(403)
- |> json(%{error: "Can't delete this post"})
+ _e -> render_error(conn, :forbidden, "Can't delete this post")
end
end
@@ -631,11 +707,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
- else
- {:error, reason} ->
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
end
end
@@ -676,11 +747,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
- else
- {:error, reason} ->
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
end
end
@@ -711,8 +777,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, reason} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => reason}))
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
end
end
@@ -727,8 +793,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, reason} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => reason}))
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
end
end
@@ -806,9 +872,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> json(rendered)
else
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"}))
+ render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end
end
@@ -821,10 +885,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
+ with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^likes)
- users = Repo.all(q)
+
+ users =
+ Repo.all(q)
+ |> Enum.filter(&(not User.blocks?(user, &1)))
conn
|> put_view(AccountView)
@@ -835,10 +902,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
+ with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^announces)
- users = Repo.all(q)
+
+ users =
+ Repo.all(q)
+ |> Enum.filter(&(not User.blocks?(user, &1)))
conn
|> put_view(AccountView)
@@ -937,8 +1007,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -951,8 +1021,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -969,8 +1039,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -987,8 +1057,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1008,17 +1078,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
+ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
+ notifications =
+ if Map.has_key?(params, "notifications"),
+ do: params["notifications"] in [true, "True", "true", "1"],
+ else: true
+
with %User{} = muted <- User.get_cached_by_id(id),
- {:ok, muter} <- User.mute(muter, muted) do
+ {:ok, muter} <- User.mute(muter, muted, notifications) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: muter, target: muted})
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1031,8 +1106,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1053,8 +1128,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1068,8 +1143,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1103,8 +1178,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1117,8 +1192,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, message} ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ |> put_status(:forbidden)
+ |> json(%{error: message})
end
end
@@ -1150,10 +1225,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
recipients =
if for_user do
- ["https://www.w3.org/ns/activitystreams#Public"] ++
- [for_user.ap_id | for_user.following]
+ [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
else
- ["https://www.w3.org/ns/activitystreams#Public"]
+ [Pleroma.Constants.as_public()]
end
activities =
@@ -1166,13 +1240,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
else
- nil ->
- {:error, :not_found}
-
- true ->
- conn
- |> put_status(403)
- |> json(%{error: "Can't get favorites"})
+ nil -> {:error, :not_found}
+ true -> render_error(conn, :forbidden, "Can't get favorites")
end
end
@@ -1204,10 +1273,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
res = ListView.render("list.json", list: list)
json(conn, res)
else
- _e ->
- conn
- |> put_status(404)
- |> json(%{error: "Record not found"})
+ _e -> render_error(conn, :not_found, "Record not found")
end
end
@@ -1223,7 +1289,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
else
_e ->
- json(conn, "error")
+ json(conn, dgettext("errors", "error"))
end
end
@@ -1274,7 +1340,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, res)
else
_e ->
- json(conn, "error")
+ json(conn, dgettext("errors", "error"))
end
end
@@ -1298,10 +1364,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
else
- _e ->
- conn
- |> put_status(403)
- |> json(%{error: "Error."})
+ _e -> render_error(conn, :forbidden, "Error.")
end
end
@@ -1420,8 +1483,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
e ->
conn
- |> put_resp_content_type("application/json")
- |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
+ |> put_status(:internal_server_error)
+ |> json(%{error: inspect(e)})
end
end
@@ -1589,20 +1652,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> Enum.map_join(", ", fn {_k, v} -> v end)
conn
- |> put_status(422)
+ |> put_status(:unprocessable_entity)
|> json(%{error: error_message})
end
def errors(conn, {:error, :not_found}) do
+ render_error(conn, :not_found, "Record not found")
+ end
+
+ def errors(conn, {:error, error_message}) do
conn
- |> put_status(404)
- |> json(%{error: "Record not found"})
+ |> put_status(:bad_request)
+ |> json(%{error: error_message})
end
def errors(conn, _) do
conn
- |> put_status(500)
- |> json("Something went wrong")
+ |> put_status(:internal_server_error)
+ |> json(dgettext("errors", "Something went wrong"))
end
def suggestions(%{assigns: %{user: user}} = conn, _) do
@@ -1722,21 +1789,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
{:error, errors} ->
conn
- |> put_status(400)
- |> json(Jason.encode!(errors))
+ |> put_status(:bad_request)
+ |> json(errors)
end
end
def account_register(%{assigns: %{app: _app}} = conn, _params) do
- conn
- |> put_status(400)
- |> json(%{error: "Missing parameters"})
+ render_error(conn, :bad_request, "Missing parameters")
end
def account_register(conn, _) do
- conn
- |> put_status(403)
- |> json(%{error: "Invalid credentials"})
+ render_error(conn, :forbidden, "Invalid credentials")
end
def conversations(%{assigns: %{user: user}} = conn, params) do
@@ -1764,23 +1827,42 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ def password_reset(conn, params) do
+ nickname_or_email = params["email"] || params["nickname"]
+
+ with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
+ conn
+ |> put_status(:no_content)
+ |> json("")
+ else
+ {:error, "unknown user"} ->
+ send_resp(conn, :not_found, "")
+
+ {:error, _} ->
+ send_resp(conn, :bad_request, "")
+ end
+ end
+
+ def account_confirmation_resend(conn, params) do
+ nickname_or_email = params["email"] || params["nickname"]
+
+ with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
+ {:ok, _} <- User.try_send_confirmation_email(user) do
+ conn
+ |> json_response(:no_content, "")
+ end
+ end
+
def try_render(conn, target, params)
when is_binary(target) do
- res = render(conn, target, params)
-
- if res == nil do
- conn
- |> put_status(501)
- |> json(%{error: "Can't display this activity"})
- else
- res
+ case render(conn, target, params) do
+ nil -> render_error(conn, :not_implemented, "Can't display this activity")
+ res -> res
end
end
def try_render(conn, _, _) do
- conn
- |> put_status(501)
- |> json(%{error: "Can't display this activity"})
+ render_error(conn, :not_implemented, "Can't display this activity")
end
defp present?(nil), do: false
diff --git a/lib/pleroma/web/mastodon_api/search_controller.ex b/lib/pleroma/web/mastodon_api/search_controller.ex
index efa9cc788..9072aa7a4 100644
--- a/lib/pleroma/web/mastodon_api/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/search_controller.ex
@@ -4,61 +4,18 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
+
alias Pleroma.Activity
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
+ alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.ControllerHelper
-
require Logger
-
- plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
-
- def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
- statuses = with_fallback(fn -> Activity.search(user, query) end, [])
- tags_path = Web.base_url() <> "/tag/"
-
- tags =
- query
- |> String.split()
- |> Enum.uniq()
- |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
- |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
- |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
-
- res = %{
- "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
- "statuses" =>
- StatusView.render("index.json", activities: statuses, for: user, as: :activity),
- "hashtags" => tags
- }
-
- json(conn, res)
- end
-
- def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
- statuses = with_fallback(fn -> Activity.search(user, query) end, [])
-
- tags =
- query
- |> String.split()
- |> Enum.uniq()
- |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
- |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
-
- res = %{
- "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
- "statuses" =>
- StatusView.render("index.json", activities: statuses, for: user, as: :activity),
- "hashtags" => tags
- }
-
- json(conn, res)
- end
+ plug(RateLimiter, :search when action in [:search, :search2, :account_search])
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
@@ -67,17 +24,86 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
json(conn, res)
end
+ def search2(conn, params), do: do_search(:v2, conn, params)
+ def search(conn, params), do: do_search(:v1, conn, params)
+
+ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ options = search_options(params, user)
+ timeout = Keyword.get(Repo.config(), :timeout, 15_000)
+ default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
+
+ result =
+ default_values
+ |> Enum.map(fn {resource, default_value} ->
+ if params["type"] == nil or params["type"] == resource do
+ {resource, fn -> resource_search(version, resource, query, options) end}
+ else
+ {resource, fn -> default_value end}
+ end
+ end)
+ |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
+ timeout: timeout,
+ on_timeout: :kill_task
+ )
+ |> Enum.reduce(default_values, fn
+ {:ok, {resource, result}}, acc ->
+ Map.put(acc, resource, result)
+
+ _error, acc ->
+ acc
+ end)
+
+ json(conn, result)
+ end
+
defp search_options(params, user) do
[
resolve: params["resolve"] == "true",
following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"),
+ type: params["type"],
+ author: get_author(params),
for_user: user
]
+ |> Enum.filter(&elem(&1, 1))
end
- defp with_fallback(f, fallback) do
+ defp resource_search(_, "accounts", query, options) do
+ accounts = with_fallback(fn -> User.search(query, options) end)
+ AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user)
+ end
+
+ defp resource_search(_, "statuses", query, options) do
+ statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
+ StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
+ end
+
+ defp resource_search(:v2, "hashtags", query, _options) do
+ tags_path = Web.base_url() <> "/tag/"
+
+ query
+ |> prepare_tags()
+ |> Enum.map(fn tag ->
+ tag = String.trim_leading(tag, "#")
+ %{name: tag, url: tags_path <> tag}
+ end)
+ end
+
+ defp resource_search(:v1, "hashtags", query, _options) do
+ query
+ |> prepare_tags()
+ |> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
+ end
+
+ defp prepare_tags(query) do
+ query
+ |> String.split()
+ |> Enum.uniq()
+ |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+ end
+
+ defp with_fallback(f, fallback \\ []) do
try do
f.()
rescue
@@ -86,4 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
fallback
end
end
+
+ defp get_author(%{"account_id" => account_id}) when is_binary(account_id),
+ do: User.get_cached_by_id(account_id)
+
+ defp get_author(_params), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/subscription_controller.ex b/lib/pleroma/web/mastodon_api/subscription_controller.ex
index b6c8ff808..255ee2f18 100644
--- a/lib/pleroma/web/mastodon_api/subscription_controller.ex
+++ b/lib/pleroma/web/mastodon_api/subscription_controller.ex
@@ -59,13 +59,13 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
#
def errors(conn, {:error, :not_found}) do
conn
- |> put_status(404)
- |> json("Not found")
+ |> put_status(:not_found)
+ |> json(dgettext("errors", "Not found"))
end
def errors(conn, _) do
conn
- |> put_status(500)
- |> json("Something went wrong")
+ |> put_status(:internal_server_error)
+ |> json(dgettext("errors", "Something went wrong"))
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 62c516f8e..72c092f25 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(user.id),
acct: user.nickname,
username: username_from_nickname(user.nickname),
- url: user.ap_id
+ url: User.profile_url(user)
}
end
@@ -50,12 +50,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(target.id),
following: User.following?(user, target),
followed_by: User.following?(target, user),
- blocking: User.blocks?(user, target),
+ blocking: User.blocks_ap_id?(user, target),
+ blocked_by: User.blocks_ap_id?(target, user),
muting: User.mutes?(user, target),
- muting_notifications: false,
+ muting_notifications: User.muted_notifications?(user, target),
subscribing: User.subscribed_to?(user, target),
requested: requested,
- domain_blocking: false,
+ domain_blocking: User.blocks_domain?(user, target),
showing_reblogs: User.showing_reblogs?(user, target),
endorsed: false
}
@@ -71,6 +72,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)
+
+ following_count =
+ ((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
+
+ followers_count =
+ ((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
+
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
emojis =
@@ -101,11 +109,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
display_name: display_name,
locked: user_info.locked,
created_at: Utils.to_masto_date(user.inserted_at),
- followers_count: user_info.follower_count,
- following_count: user_info.following_count,
+ followers_count: followers_count,
+ following_count: following_count,
statuses_count: user_info.note_count,
note: bio || "",
- url: user.ap_id,
+ url: User.profile_url(user),
avatar: image,
avatar_static: image,
header: header,
@@ -136,6 +144,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts)
+ |> maybe_put_activation_status(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -196,6 +205,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_notification_settings(data, _, _), do: data
+ defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
+ Kernel.put_in(data, [:pleroma, :deactivated], user.info.deactivated)
+ end
+
+ defp maybe_put_activation_status(data, _, _), do: data
+
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index af1dcf66d..38bdec737 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.MastodonAPI.ConversationView do
use Pleroma.Web, :view
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 6836d331a..02819e116 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
# TODO: Add cached version.
+ defp get_replied_to_activities([]), do: %{}
+
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
@@ -104,7 +106,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(activity.id),
uri: activity_object.data["id"],
url: activity_object.data["id"],
- account: AccountView.render("account.json", %{user: user}),
+ account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
reblog: reblogged,
@@ -147,8 +149,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
tags = object.data["tag"] || []
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
+ tag_mentions =
+ tags
+ |> Enum.filter(fn tag -> is_map(tag) and tag["type"] == "Mention" end)
+ |> Enum.map(fn tag -> tag["href"] end)
+
mentions =
- activity.recipients
+ (object.data["to"] ++ tag_mentions)
+ |> Enum.uniq()
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
@@ -160,7 +168,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
- nil -> CommonAPI.thread_muted?(user, activity)
+ nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
end
attachment_data = object.data["attachment"] || []
@@ -214,14 +222,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
if user.local do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
else
- object.data["external_url"] || object.data["id"]
+ object.data["url"] || object.data["external_url"] || object.data["id"]
end
%{
id: to_string(activity.id),
uri: object.data["id"],
url: url,
- account: AccountView.render("account.json", %{user: user}),
+ account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
@@ -374,7 +382,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
%{
# 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,
+ id: to_string(object.id),
expires_at: Utils.to_masto_date(end_time),
expired: expired,
multiple: multiple,
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index 3299e1721..dbd3542ea 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -29,9 +29,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
def init(%{qs: qs} = req, state) do
with params <- :cow_qs.parse_qs(qs),
+ sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil),
access_token <- List.keyfind(params, "access_token", 0),
{_, stream} <- List.keyfind(params, "stream", 0),
- {:ok, user} <- allow_request(stream, access_token),
+ {:ok, user} <- allow_request(stream, [access_token, sec_websocket]),
topic when is_binary(topic) <- expand_topic(stream, params) do
{:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}}
else
@@ -84,13 +85,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
end
# Public streams without authentication.
- defp allow_request(stream, nil) when stream in @anonymous_streams do
+ defp allow_request(stream, [nil, nil]) when stream in @anonymous_streams do
{:ok, nil}
end
# Authenticated streams.
- defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
- with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
+ defp allow_request(stream, [access_token, sec_websocket]) when stream in @streams do
+ token =
+ with {"access_token", token} <- access_token do
+ token
+ else
+ _ -> sec_websocket
+ end
+
+ with true <- is_bitstring(token),
+ %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
user = %User{} <- User.get_cached_by_id(user_id) do
{:ok, user}
else
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index cee6d8481..1725ab071 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -3,89 +3,83 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy do
+ alias Pleroma.Config
+ alias Pleroma.Upload
+ alias Pleroma.Web
+
@base64_opts [padding: false]
- def url(nil), do: nil
-
- def url(""), do: nil
-
+ def url(url) when is_nil(url) or url == "", do: nil
def url("/" <> _ = url), do: url
def url(url) do
- if !enabled?() or local?(url) or whitelisted?(url) do
+ if disabled?() or local?(url) or whitelisted?(url) do
url
else
encode_url(url)
end
end
- defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false)
+ defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
defp whitelisted?(url) do
%{host: domain} = URI.parse(url)
- Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
+ mediaproxy_whitelist = Config.get([:media_proxy, :whitelist])
+
+ upload_base_url_domain =
+ if !is_nil(Config.get([Upload, :base_url])) do
+ [URI.parse(Config.get([Upload, :base_url])).host]
+ else
+ []
+ end
+
+ whitelist = mediaproxy_whitelist ++ upload_base_url_domain
+
+ Enum.any?(whitelist, fn pattern ->
String.equivalent?(domain, pattern)
end)
end
def encode_url(url) do
- secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
+ base64 = Base.url_encode64(url, @base64_opts)
- # Must preserve `%2F` for compatibility with S3
- # https://git.pleroma.social/pleroma/pleroma/issues/580
- replacement = get_replacement(url, ":2F:")
-
- # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
- base64 =
- url
- |> String.replace("%2F", replacement)
- |> URI.decode()
- |> URI.encode()
- |> String.replace(replacement, "%2F")
+ sig64 =
+ base64
+ |> signed_url
|> Base.url_encode64(@base64_opts)
- sig = :crypto.hmac(:sha, secret, base64)
- sig64 = sig |> Base.url_encode64(@base64_opts)
-
build_url(sig64, base64, filename(url))
end
def decode_url(sig, url) do
- secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
- sig = Base.url_decode64!(sig, @base64_opts)
- local_sig = :crypto.hmac(:sha, secret, url)
-
- if local_sig == sig do
+ with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
+ signature when signature == sig <- signed_url(url) do
{:ok, Base.url_decode64!(url, @base64_opts)}
else
- {:error, :invalid_signature}
+ _ -> {:error, :invalid_signature}
end
end
+ defp signed_url(url) do
+ :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
+ end
+
def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
def build_url(sig_base64, url_base64, filename \\ nil) do
[
- Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()),
+ Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
"proxy",
sig_base64,
url_base64,
filename
]
- |> Enum.filter(fn value -> value end)
+ |> Enum.filter(& &1)
|> Path.join()
end
-
- defp get_replacement(url, replacement) do
- if String.contains?(url, replacement) do
- get_replacement(url, replacement <> replacement)
- else
- replacement
- end
- end
end
diff --git a/lib/pleroma/web/media_proxy/controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
similarity index 71%
rename from lib/pleroma/web/media_proxy/controller.ex
rename to lib/pleroma/web/media_proxy/media_proxy_controller.ex
index c0552d89f..8403850ff 100644
--- a/lib/pleroma/web/media_proxy/controller.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
with config <- Pleroma.Config.get([:media_proxy], []),
true <- Keyword.get(config, :enabled, false),
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
- :ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
+ :ok <- filename_matches(params, conn.request_path, url) do
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
else
false ->
@@ -27,18 +27,20 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
end
end
- def filename_matches(has_filename, path, url) do
- filename =
- url
- |> MediaProxy.filename()
- |> URI.decode()
+ def filename_matches(%{"filename" => _} = _, path, url) do
+ filename = MediaProxy.filename(url)
- path = URI.decode(path)
-
- if has_filename && filename && Path.basename(path) != filename do
+ if filename && does_not_match(path, filename) do
{:wrong_filename, filename}
else
:ok
end
end
+
+ def filename_matches(_, _, _), do: :ok
+
+ defp does_not_match(path, filename) do
+ basename = Path.basename(path)
+ basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename
+ end
end
diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex
index 357b80a2d..e7fa7f408 100644
--- a/lib/pleroma/web/metadata/opengraph.ex
+++ b/lib/pleroma/web/metadata/opengraph.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
alias Pleroma.Web.Metadata.Utils
@behaviour Provider
+ @media_types ["image", "audio", "video"]
@impl Provider
def build_tags(%{
@@ -81,26 +82,19 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags =
Enum.reduce(attachment["url"], [], fn url, acc ->
- media_type =
- Enum.find(["image", "audio", "video"], fn media_type ->
- String.starts_with?(url["mediaType"], media_type)
- end)
-
# TODO: Add additional properties to objects when we have the data available.
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
- case media_type do
+ case Utils.fetch_media_type(@media_types, url["mediaType"]) do
"audio" ->
[
- {:meta,
- [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
+ {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []}
| acc
]
"image" ->
[
- {:meta,
- [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []},
+ {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
| acc
@@ -108,8 +102,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
"video" ->
[
- {:meta,
- [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
+ {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
| acc
]
@@ -121,4 +114,6 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
acc ++ rendered_tags
end)
end
+
+ defp build_attachments(_), do: []
end
diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex
index e9a8cfc8d..4289ebdbd 100644
--- a/lib/pleroma/web/metadata/player_view.ex
+++ b/lib/pleroma/web/metadata/player_view.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Metadata.PlayerView do
use Pleroma.Web, :view
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]
diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex
index 03af899c4..f87fc1973 100644
--- a/lib/pleroma/web/metadata/rel_me.ex
+++ b/lib/pleroma/web/metadata/rel_me.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Metadata.Providers.RelMe do
alias Pleroma.Web.Metadata.Providers.Provider
@behaviour Provider
diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex
index 040b872e7..d6a6049b3 100644
--- a/lib/pleroma/web/metadata/twitter_card.ex
+++ b/lib/pleroma/web/metadata/twitter_card.ex
@@ -1,4 +1,5 @@
# Pleroma: A lightweight social networking server
+
# Copyright © 2017-2019 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
@@ -9,13 +10,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
alias Pleroma.Web.Metadata.Utils
@behaviour Provider
+ @media_types ["image", "audio", "video"]
@impl Provider
- def build_tags(%{
- activity_id: id,
- object: object,
- user: user
- }) do
+ def build_tags(%{activity_id: id, object: object, user: user}) do
attachments = build_attachments(id, object)
scrubbed_content = Utils.scrub_html_and_truncate(object)
# Zero width space
@@ -27,21 +25,12 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end
[
- {:meta,
- [
- property: "twitter:title",
- content: Utils.user_name_string(user)
- ], []},
- {:meta,
- [
- property: "twitter:description",
- content: content
- ], []}
+ title_tag(user),
+ {:meta, [property: "twitter:description", content: content], []}
] ++
if attachments == [] or Metadata.activity_nsfw?(object) do
[
- {:meta,
- [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []},
+ image_tag(user),
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
]
else
@@ -53,30 +42,28 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
def build_tags(%{user: user}) do
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
[
- {:meta,
- [
- property: "twitter:title",
- content: Utils.user_name_string(user)
- ], []},
+ title_tag(user),
{:meta, [property: "twitter:description", content: truncated_bio], []},
- {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))],
- []},
+ image_tag(user),
{:meta, [property: "twitter:card", content: "summary"], []}
]
end
end
+ defp title_tag(user) do
+ {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
+ end
+
+ def image_tag(user) do
+ {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}
+ end
+
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags =
Enum.reduce(attachment["url"], [], fn url, acc ->
- media_type =
- Enum.find(["image", "audio", "video"], fn media_type ->
- String.starts_with?(url["mediaType"], media_type)
- end)
-
# TODO: Add additional properties to objects when we have the data available.
- case media_type do
+ case Utils.fetch_media_type(@media_types, url["mediaType"]) do
"audio" ->
[
{:meta, [property: "twitter:card", content: "player"], []},
@@ -117,6 +104,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end)
end
+ defp build_attachments(_id, _object), do: []
+
defp player_url(id) do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
end
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index 58385a3d1..720bd4519 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -39,4 +39,11 @@ defmodule Pleroma.Web.Metadata.Utils do
"(@#{user.nickname})"
end
end
+
+ @spec fetch_media_type(list(String.t()), String.t()) :: String.t() | nil
+ def fetch_media_type(supported_types, media_type) do
+ Enum.find(supported_types, fn support_type ->
+ String.starts_with?(media_type, support_type)
+ end)
+ end
end
diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index 489d5d3a5..b786a521b 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
else
false ->
conn
- |> put_status(403)
+ |> put_status(:forbidden)
|> json(false)
_ ->
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 869dda5c5..54f89e65c 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -34,8 +34,11 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
def raw_nodeinfo do
stats = Stats.get_stats()
+ exclusions = Config.get([:instance, :mrf_transparency_exclusions])
+
mrf_simple =
Config.get(:mrf_simple)
+ |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
# This horror is needed to convert regex sigils to strings
@@ -86,7 +89,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
mrf_simple: mrf_simple,
mrf_keyword: mrf_keyword,
mrf_user_allowlist: mrf_user_allowlist,
- quarantined_instances: quarantined
+ quarantined_instances: quarantined,
+ exclusions: length(exclusions) > 0
}
else
%{}
@@ -161,6 +165,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
},
accountActivationRequired: Config.get([:instance, :account_activation_required], false),
invitesEnabled: Config.get([:instance, :invites_enabled], false),
+ mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
@@ -201,8 +206,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
end
def nodeinfo(conn, _) do
- conn
- |> put_status(404)
- |> json(%{error: "Nodeinfo schema version not handled"})
+ render_error(conn, :not_found, "Nodeinfo schema version not handled")
end
end
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex
index e3984f009..dd7f08bf1 100644
--- a/lib/pleroma/web/oauth/fallback_controller.ex
+++ b/lib/pleroma/web/oauth/fallback_controller.ex
@@ -9,21 +9,24 @@ defmodule Pleroma.Web.OAuth.FallbackController do
def call(conn, {:register, :generic_error}) do
conn
|> put_status(:internal_server_error)
- |> put_flash(:error, "Unknown error, please check the details and try again.")
+ |> put_flash(
+ :error,
+ dgettext("errors", "Unknown error, please check the details and try again.")
+ )
|> OAuthController.registration_details(conn.params)
end
def call(conn, {:register, _error}) do
conn
|> put_status(:unauthorized)
- |> put_flash(:error, "Invalid Username/Password")
+ |> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|> OAuthController.registration_details(conn.params)
end
def call(conn, _error) do
conn
|> put_status(:unauthorized)
- |> put_flash(:error, "Invalid Username/Password")
+ |> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|> OAuthController.authorize(conn.params)
end
end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 3f8e3b074..81eae2c8b 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -90,7 +90,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect(conn, external: url)
else
conn
- |> put_flash(:error, "Unlisted redirect_uri.")
+ |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|> redirect(external: redirect_uri(conn, redirect_uri))
end
end
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect(conn, external: url)
else
conn
- |> put_flash(:error, "Unlisted redirect_uri.")
+ |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|> redirect(external: redirect_uri(conn, redirect_uri))
end
end
@@ -142,7 +142,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
conn
- |> put_flash(:error, "This action is outside the authorized scopes")
+ |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
|> put_status(:unauthorized)
|> authorize(params)
end
@@ -155,7 +155,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
conn
- |> put_flash(:error, "Your login is missing a confirmed e-mail address")
+ |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|> put_status(:forbidden)
|> authorize(params)
end
@@ -176,9 +176,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
json(conn, Token.Response.build(user, token, response_attrs))
else
- _error ->
- put_status(conn, 400)
- |> json(%{error: "Invalid credentials"})
+ _error -> render_invalid_credentials_error(conn)
end
end
@@ -192,9 +190,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
json(conn, Token.Response.build(user, token, response_attrs))
else
- _error ->
- put_status(conn, 400)
- |> json(%{error: "Invalid credentials"})
+ _error -> render_invalid_credentials_error(conn)
end
end
@@ -214,18 +210,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
- conn
- |> put_status(:forbidden)
- |> json(%{error: "Your login is missing a confirmed e-mail address"})
+ render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address")
{:user_active, false} ->
- conn
- |> put_status(:forbidden)
- |> json(%{error: "Your account is currently disabled"})
+ render_error(conn, :forbidden, "Your account is currently disabled")
_error ->
- put_status(conn, 400)
- |> json(%{error: "Invalid credentials"})
+ render_invalid_credentials_error(conn)
end
end
@@ -247,9 +238,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build_for_client_credentials(token))
else
- _error ->
- put_status(conn, 400)
- |> json(%{error: "Invalid credentials"})
+ _error -> render_invalid_credentials_error(conn)
end
end
@@ -271,9 +260,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# Response for bad request
defp bad_request(%Plug.Conn{} = conn, _) do
- conn
- |> put_status(500)
- |> json(%{error: "Bad request"})
+ render_error(conn, :internal_server_error, "Bad request")
end
@doc "Prepares OAuth request to provider for Ueberauth"
@@ -304,9 +291,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def request(%Plug.Conn{} = conn, params) do
message =
if params["provider"] do
- "Unsupported OAuth provider: #{params["provider"]}."
+ dgettext("errors", "Unsupported OAuth provider: %{provider}.",
+ provider: params["provider"]
+ )
else
- "Bad OAuth request."
+ dgettext("errors", "Bad OAuth request.")
end
conn
@@ -320,7 +309,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
message = Enum.join(messages, "; ")
conn
- |> put_flash(:error, "Failed to authenticate: #{message}.")
+ |> put_flash(
+ :error,
+ dgettext("errors", "Failed to authenticate: %{message}.", message: message)
+ )
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
@@ -350,7 +342,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
conn
- |> put_flash(:error, "Failed to set up user account.")
+ |> put_flash(:error, dgettext("errors", "Failed to set up user account."))
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
end
@@ -373,8 +365,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
%Registration{} = registration <- Repo.get(Registration, registration_id),
- {_, {:ok, auth}} <-
- {:create_authorization, do_create_authorization(conn, params)},
+ {_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)},
%User{} = user <- Repo.preload(auth, :user).user,
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
conn
@@ -468,4 +459,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> String.split()
|> Enum.at(0)
end
+
+ defp render_invalid_credentials_error(conn) do
+ render_error(conn, :bad_request, "Invalid credentials")
+ end
end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index 90c304487..40f131b57 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -44,8 +44,7 @@ defmodule Pleroma.Web.OAuth.Token do
|> Repo.find_resource()
end
- @spec exchange_token(App.t(), Authorization.t()) ::
- {:ok, Token.t()} | {:error, Changeset.t()}
+ @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()}
def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do
diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex
index 2648571ad..266110814 100644
--- a/lib/pleroma/web/oauth/token/response.ex
+++ b/lib/pleroma/web/oauth/token/response.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.Token.Response do
@moduledoc false
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
index 7df0be14e..c620050c8 100644
--- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
+++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
@moduledoc """
Functions for dealing with refresh token strategy.
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex
index dea63ca54..983f095b4 100644
--- a/lib/pleroma/web/oauth/token/strategy/revoke.ex
+++ b/lib/pleroma/web/oauth/token/strategy/revoke.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
@moduledoc """
Functions for dealing with revocation.
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex
index 7a4fddafd..1e8765e93 100644
--- a/lib/pleroma/web/oauth/token/utils.ex
+++ b/lib/pleroma/web/oauth/token/utils.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OAuth.Token.Utils do
@moduledoc """
Auxiliary functions for dealing with tokens.
diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex
index 95037125d..8e55b9f0b 100644
--- a/lib/pleroma/web/ostatus/activity_representer.ex
+++ b/lib/pleroma/web/ostatus/activity_representer.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.Web.OStatus.UserRepresenter
require Logger
+ require Pleroma.Constants
defp get_href(id) do
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
@@ -34,7 +35,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
Enum.map(to, fn id ->
cond do
# Special handling for the AP/Ostatus public collections
- "https://www.w3.org/ns/activitystreams#Public" == id ->
+ Pleroma.Constants.as_public() == id ->
{:link,
[
rel: "mentioned",
@@ -182,6 +183,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
+ retweeted_object = Object.normalize(retweeted_activity)
retweeted_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"])
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
@@ -196,7 +198,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
{:id, h.(activity.data["id"])},
{:title, ['#{user.nickname} repeated a notice']},
- {:content, [type: 'html'], ['RT #{retweeted_activity.data["object"]["content"]}']},
+ {:content, [type: 'html'], ['RT #{retweeted_object.data["content"]}']},
{:published, h.(inserted_at)},
{:updated, h.(updated_at)},
{:"ostatus:conversation", [ref: h.(activity.data["context"])],
diff --git a/lib/pleroma/web/ostatus/handlers/follow_handler.ex b/lib/pleroma/web/ostatus/handlers/follow_handler.ex
index 263d3b2dc..24513972e 100644
--- a/lib/pleroma/web/ostatus/handlers/follow_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/follow_handler.ex
@@ -9,14 +9,18 @@ defmodule Pleroma.Web.OStatus.FollowHandler do
alias Pleroma.Web.XML
def handle(entry, doc) do
- with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
+ with {:ok, actor} <- OStatus.find_make_or_update_actor(doc),
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
followed_uri when not is_nil(followed_uri) <-
XML.string_from_xpath("/entry/activity:object/id", entry),
{:ok, followed} <- OStatus.find_or_make_user(followed_uri),
+ {:locked, false} <- {:locked, followed.info.locked},
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
User.follow(actor, followed)
{:ok, activity}
+ else
+ {:locked, true} ->
+ {:error, "It's not possible to follow locked accounts over OStatus"}
end
end
end
diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex
index ec6e5cfaf..7fae14f7b 100644
--- a/lib/pleroma/web/ostatus/handlers/note_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex
@@ -4,12 +4,14 @@
defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger
+ require Pleroma.Constants
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
@@ -48,7 +50,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
def get_collection_mentions(entry) do
transmogrify = fn
"http://activityschema.org/collection/public" ->
- "https://www.w3.org/ns/activitystreams#Public"
+ Pleroma.Constants.as_public()
group ->
group
@@ -88,14 +90,15 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
Map.put(note, "external_url", url)
end
- def fetch_replied_to_activity(entry, in_reply_to) do
+ def fetch_replied_to_activity(entry, in_reply_to, options \\ []) do
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
activity
else
_e ->
- with in_reply_to_href when not is_nil(in_reply_to_href) <-
+ with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
+ in_reply_to_href when not is_nil(in_reply_to_href) <-
XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
- {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do
+ {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href, options) do
activity
else
_e -> nil
@@ -104,15 +107,16 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
end
# TODO: Clean this up a bit.
- def handle_note(entry, doc \\ nil) do
+ def handle_note(entry, doc \\ nil, options \\ []) do
with id <- XML.string_from_xpath("//id", entry),
activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
[author] <- :xmerl_xpath.string('//author[1]', doc),
- {:ok, actor} <- OStatus.find_make_or_update_user(author),
+ {:ok, actor} <- OStatus.find_make_or_update_actor(author),
content_html <- OStatus.get_content(entry),
cw <- OStatus.get_cw(entry),
in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
- in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to),
+ options <- Keyword.put(options, :depth, (options[:depth] || 0) + 1),
+ in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to, options),
in_reply_to_object <-
(in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,
@@ -123,7 +127,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
to <- make_to_list(actor, mentions),
date <- XML.string_from_xpath("//published", entry),
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
- cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []),
+ cc <- if(unlisted, do: [Pleroma.Constants.as_public()], else: []),
note <-
CommonAPI.Utils.make_note_data(
actor.ap_id,
diff --git a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
index 6596ada3b..2062432e3 100644
--- a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.OStatus.UnfollowHandler do
alias Pleroma.Web.XML
def handle(entry, doc) do
- with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
+ with {:ok, actor} <- OStatus.find_make_or_update_actor(doc),
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
followed_uri when not is_nil(followed_uri) <-
XML.string_from_xpath("/entry/activity:object/id", entry),
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index 6ed089d84..331cbc0b7 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -54,9 +54,9 @@ defmodule Pleroma.Web.OStatus do
"#{Web.base_url()}/ostatus_subscribe?acct={uri}"
end
- def handle_incoming(xml_string) do
+ def handle_incoming(xml_string, options \\ []) do
with doc when doc != :error <- parse_document(xml_string) do
- with {:ok, actor_user} <- find_make_or_update_user(doc),
+ with {:ok, actor_user} <- find_make_or_update_actor(doc),
do: Pleroma.Instances.set_reachable(actor_user.ap_id)
entries = :xmerl_xpath.string('//entry', doc)
@@ -91,10 +91,12 @@ defmodule Pleroma.Web.OStatus do
_ ->
case object_type do
'http://activitystrea.ms/schema/1.0/note' ->
- with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
+ with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
+ do: activity
'http://activitystrea.ms/schema/1.0/comment' ->
- with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
+ with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
+ do: activity
_ ->
Logger.error("Couldn't parse incoming document")
@@ -118,7 +120,7 @@ defmodule Pleroma.Web.OStatus do
end
def make_share(entry, doc, retweeted_activity) do
- with {:ok, actor} <- find_make_or_update_user(doc),
+ with {:ok, actor} <- find_make_or_update_actor(doc),
%Object{} = object <- Object.normalize(retweeted_activity),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
@@ -136,7 +138,7 @@ defmodule Pleroma.Web.OStatus do
end
def make_favorite(entry, doc, favorited_activity) do
- with {:ok, actor} <- find_make_or_update_user(doc),
+ with {:ok, actor} <- find_make_or_update_actor(doc),
%Object{} = object <- Object.normalize(favorited_activity),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
@@ -262,11 +264,18 @@ defmodule Pleroma.Web.OStatus do
end
end
- def find_make_or_update_user(doc) do
+ def find_make_or_update_actor(doc) do
uri = string_from_xpath("//author/uri[1]", doc)
- with {:ok, user} <- find_or_make_user(uri) do
+ with {:ok, %User{} = user} <- find_or_make_user(uri),
+ {:ap_enabled, false} <- {:ap_enabled, User.ap_enabled?(user)} do
maybe_update(doc, user)
+ else
+ {:ap_enabled, true} ->
+ {:error, :invalid_protocol}
+
+ _ ->
+ {:error, :unknown_user}
end
end
@@ -359,7 +368,7 @@ defmodule Pleroma.Web.OStatus do
end
end
- def fetch_activity_from_atom_url(url) do
+ def fetch_activity_from_atom_url(url, options \\ []) do
with true <- String.starts_with?(url, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <-
HTTP.get(
@@ -367,7 +376,7 @@ defmodule Pleroma.Web.OStatus do
[{:Accept, "application/atom+xml"}]
) do
Logger.debug("Got document from #{url}, handling...")
- handle_incoming(body)
+ handle_incoming(body, options)
else
e ->
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@@ -375,13 +384,13 @@ defmodule Pleroma.Web.OStatus do
end
end
- def fetch_activity_from_html_url(url) do
+ def fetch_activity_from_html_url(url, options \\ []) do
Logger.debug("Trying to fetch #{url}")
with true <- String.starts_with?(url, "http"),
{:ok, %{body: body}} <- HTTP.get(url, []),
{:ok, atom_url} <- get_atom_url(body) do
- fetch_activity_from_atom_url(atom_url)
+ fetch_activity_from_atom_url(atom_url, options)
else
e ->
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@@ -389,11 +398,11 @@ defmodule Pleroma.Web.OStatus do
end
end
- def fetch_activity_from_url(url) do
- with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url) do
+ def fetch_activity_from_url(url, options \\ []) do
+ with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do
{:ok, activities}
else
- _e -> fetch_activity_from_html_url(url)
+ _e -> fetch_activity_from_html_url(url, options)
end
rescue
e ->
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 2fb6ce41b..c70063b84 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller
+ alias Fallback.RedirectController
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
@@ -12,42 +13,44 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator
+ alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
+ alias Pleroma.Web.Router
alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
+ plug(
+ Pleroma.Plugs.SetFormatPlug
+ when action in [:feed_redirect, :object, :activity, :notice]
+ )
+
action_fallback(:errors)
+ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
+ with {_, %User{} = user} <-
+ {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
+ RedirectController.redirector_with_meta(conn, %{user: user})
+ end
+ end
+
+ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :user)
+ end
+
def feed_redirect(conn, %{"nickname" => nickname}) do
- case get_format(conn) do
- "html" ->
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
- Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
- else
- nil -> {:error, :not_found}
- end
-
- "activity+json" ->
- ActivityPubController.call(conn, :user)
-
- "json" ->
- ActivityPubController.call(conn, :user)
-
- _ ->
- with %User{} = user <- User.get_cached_by_nickname(nickname) do
- redirect(conn, external: OStatus.feed_path(user))
- else
- nil -> {:error, :not_found}
- end
+ with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ redirect(conn, external: OStatus.feed_path(user))
end
end
def feed(conn, %{"nickname" => nickname} = params) do
- with %User{} = user <- User.get_cached_by_nickname(nickname) do
+ with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
query_params =
Map.take(params, ["max_id"])
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
@@ -65,8 +68,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, response)
- else
- nil -> {:error, :not_found}
end
end
@@ -97,93 +98,82 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|> send_resp(200, "")
end
- def object(conn, %{"uuid" => uuid}) do
- if get_format(conn) in ["activity+json", "json"] do
- ActivityPubController.call(conn, :object)
- else
- with id <- o_status_url(conn, :object, uuid),
- {_, %Activity{} = activity} <-
- {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
- {_, true} <- {:public?, Visibility.is_public?(activity)},
- %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case get_format(conn) do
- "html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, nil, activity, user)
- end
- else
- {:public?, false} ->
- {:error, :not_found}
+ def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :object)
+ end
- {:activity, nil} ->
- {:error, :not_found}
-
- e ->
- e
+ def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
+ with id <- o_status_url(conn, :object, uuid),
+ {_, %Activity{} = activity} <-
+ {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
+ case format do
+ "html" -> redirect(conn, to: "/notice/#{activity.id}")
+ _ -> represent_activity(conn, nil, activity, user)
end
+ else
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
+ {:error, :not_found}
+
+ e ->
+ e
end
end
- def activity(conn, %{"uuid" => uuid}) do
- if get_format(conn) in ["activity+json", "json"] do
- ActivityPubController.call(conn, :activity)
- else
- with id <- o_status_url(conn, :activity, uuid),
- {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
- {_, true} <- {:public?, Visibility.is_public?(activity)},
- %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case format = get_format(conn) do
- "html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, format, activity, user)
- end
- else
- {:public?, false} ->
- {:error, :not_found}
+ def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :activity)
+ end
- {:activity, nil} ->
- {:error, :not_found}
-
- e ->
- e
+ def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
+ with id <- o_status_url(conn, :activity, uuid),
+ {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
+ case format do
+ "html" -> redirect(conn, to: "/notice/#{activity.id}")
+ _ -> represent_activity(conn, format, activity, user)
end
+ else
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
+ {:error, :not_found}
+
+ e ->
+ e
end
end
- def notice(conn, %{"id" => id}) do
+ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case format = get_format(conn) do
- "html" ->
- if activity.data["type"] == "Create" do
- %Object{} = object = Object.normalize(activity)
+ cond do
+ format == "html" && activity.data["type"] == "Create" ->
+ %Object{} = object = Object.normalize(activity)
- Fallback.RedirectController.redirector_with_meta(conn, %{
+ RedirectController.redirector_with_meta(
+ conn,
+ %{
activity_id: activity.id,
object: object,
- url:
- Pleroma.Web.Router.Helpers.o_status_url(
- Pleroma.Web.Endpoint,
- :notice,
- activity.id
- ),
+ url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
user: user
- })
- else
- Fallback.RedirectController.redirector(conn, nil)
- end
+ }
+ )
- _ ->
+ format == "html" ->
+ RedirectController.redirector(conn, nil)
+
+ true ->
represent_activity(conn, format, activity, user)
end
else
- {:public?, false} ->
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
conn
|> put_status(404)
- |> Fallback.RedirectController.redirector(nil, 404)
-
- {:activity, nil} ->
- conn
- |> Fallback.RedirectController.redirector(nil, 404)
+ |> RedirectController.redirector(nil, 404)
e ->
e
@@ -204,13 +194,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
"content-security-policy",
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
)
- |> put_view(Pleroma.Web.Metadata.PlayerView)
+ |> put_view(PlayerView)
|> render("player.html", url)
else
_error ->
conn
|> put_status(404)
- |> Fallback.RedirectController.redirector(nil, 404)
+ |> RedirectController.redirector(nil, 404)
end
end
@@ -245,14 +235,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end
def errors(conn, {:error, :not_found}) do
- conn
- |> put_status(404)
- |> text("Not found")
+ render_error(conn, :not_found, "Not found")
end
+ def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+
def errors(conn, _) do
- conn
- |> put_status(500)
- |> text("Something went wrong")
+ render_error(conn, :internal_server_error, "Something went wrong")
end
end
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 21cd47890..f5f9e358c 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -3,12 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
- @parsers [
- Pleroma.Web.RichMedia.Parsers.OGP,
- Pleroma.Web.RichMedia.Parsers.TwitterCard,
- Pleroma.Web.RichMedia.Parsers.OEmbed
- ]
-
@hackney_options [
pool: :media,
recv_timeout: 2_000,
@@ -16,6 +10,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
with_body: true
]
+ defp parsers do
+ Pleroma.Config.get([:rich_media, :parsers])
+ end
+
def parse(nil), do: {:error, "No URL provided"}
if Pleroma.Config.get(:env) == :test do
@@ -26,6 +24,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
Cachex.fetch!(:rich_media_cache, url, fn _ ->
{:commit, parse_url(url)}
end)
+ |> set_ttl_based_on_image(url)
rescue
e ->
{:error, "Cachex error: #{inspect(e)}"}
@@ -33,12 +32,57 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
end
+ @doc """
+ Set the rich media cache based on the expiration time of image.
+
+ Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL`
+
+ ## Example
+
+ defmodule MyModule do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+ def ttl(data, url) do
+ image_url = Map.get(data, :image)
+ # do some parsing in the url and get the ttl of the image
+ # and return ttl is unix time
+ parse_ttl_from_url(image_url)
+ end
+ end
+
+ Define the module in the config
+
+ config :pleroma, :rich_media,
+ ttl_setters: [MyModule]
+ """
+ def set_ttl_based_on_image({:ok, data}, url) do
+ with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url),
+ ttl when is_number(ttl) <- get_ttl_from_image(data, url) do
+ Cachex.expire_at(:rich_media_cache, url, ttl * 1000)
+ {:ok, data}
+ else
+ _ ->
+ {:ok, data}
+ end
+ end
+
+ defp get_ttl_from_image(data, url) do
+ Pleroma.Config.get([:rich_media, :ttl_setters])
+ |> Enum.reduce({:ok, nil}, fn
+ module, {:ok, _ttl} ->
+ module.ttl(data, url)
+
+ _, error ->
+ error
+ end)
+ end
+
defp parse_url(url) do
try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
html
|> maybe_parse()
+ |> Map.put(:url, url)
|> clean_parsed_data()
|> check_parsed_data()
rescue
@@ -48,7 +92,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp maybe_parse(html) do
- Enum.reduce_while(@parsers, %{}, fn parser, acc ->
+ Enum.reduce_while(parsers(), %{}, fn parser, acc ->
case parser.parse(html, acc) do
{:ok, data} -> {:halt, data}
{:error, _msg} -> {:cont, acc}
diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
index fb79630e4..913975616 100644
--- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
meta_data =
diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
index 2530b8c9d..875637c4d 100644
--- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
def parse(html, _data) do
with elements = [_ | _] <- get_discovery_data(html),
diff --git a/lib/pleroma/web/rich_media/parsers/ogp.ex b/lib/pleroma/web/rich_media/parsers/ogp.ex
index 0e1a0e719..d40fa009f 100644
--- a/lib/pleroma/web/rich_media/parsers/ogp.ex
+++ b/lib/pleroma/web/rich_media/parsers/ogp.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
def parse(html, data) do
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
new file mode 100644
index 000000000..0dc1efdaf
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
@@ -0,0 +1,51 @@
+defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+
+ @impl Pleroma.Web.RichMedia.Parser.TTL
+ def ttl(data, _url) do
+ image = Map.get(data, :image)
+
+ if is_aws_signed_url(image) do
+ image
+ |> parse_query_params()
+ |> format_query_params()
+ |> get_expiration_timestamp()
+ end
+ end
+
+ defp is_aws_signed_url(""), do: nil
+ defp is_aws_signed_url(nil), do: nil
+
+ defp is_aws_signed_url(image) when is_binary(image) do
+ %URI{host: host, query: query} = URI.parse(image)
+
+ if String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") do
+ image
+ else
+ nil
+ end
+ end
+
+ defp is_aws_signed_url(_), do: nil
+
+ defp parse_query_params(image) do
+ %URI{query: query} = URI.parse(image)
+ query
+ end
+
+ defp format_query_params(query) do
+ query
+ |> String.split(~r/&|=/)
+ |> Enum.chunk_every(2)
+ |> Map.new(fn [k, v] -> {k, v} end)
+ end
+
+ defp get_expiration_timestamp(params) when is_map(params) do
+ {:ok, date} =
+ params
+ |> Map.get("X-Amz-Date")
+ |> Timex.parse("{ISO:Basic:Z}")
+
+ Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))
+ end
+end
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
new file mode 100644
index 000000000..6b3ec6d30
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
@@ -0,0 +1,3 @@
+defmodule Pleroma.Web.RichMedia.Parser.TTL do
+ @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()}
+end
diff --git a/lib/pleroma/web/rich_media/parsers/twitter_card.ex b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
index a317c3e78..afaa98f3d 100644
--- a/lib/pleroma/web/rich_media/parsers/twitter_card.ex
+++ b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
@@ -1,11 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
+ alias Pleroma.Web.RichMedia.Parsers.MetaTagsParser
+
+ @spec parse(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def parse(html, data) do
- Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
- html,
- data,
- "twitter",
- "No twitter card metadata found",
- "name"
- )
+ data
+ |> parse_name_attrs(html)
+ |> parse_property_attrs(html)
+ end
+
+ defp parse_name_attrs(data, html) do
+ MetaTagsParser.parse(html, data, "twitter", %{}, "name")
+ end
+
+ defp parse_property_attrs({_, data}, html) do
+ MetaTagsParser.parse(html, data, "twitter", "No twitter card metadata found", "property")
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 055289dc5..c8c1c22dd 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -154,22 +154,12 @@ defmodule Pleroma.Web.Router do
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
- # TODO: to be removed at version 1.0
- delete("/user", AdminAPIController, :user_delete)
- post("/user", AdminAPIController, :user_create)
-
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :user_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
- # TODO: to be removed at version 1.0
- get("/permission_group/:nickname", AdminAPIController, :right_get)
- get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
- post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
- delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
-
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
@@ -190,13 +180,11 @@ defmodule Pleroma.Web.Router do
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
post("/users/email_invite", AdminAPIController, :email_invite)
- # TODO: to be removed at version 1.0
- get("/password_reset", AdminAPIController, :get_password_reset)
-
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
+ get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/reports", AdminAPIController, :list_reports)
get("/reports/:id", AdminAPIController, :report_show)
@@ -208,6 +196,8 @@ defmodule Pleroma.Web.Router do
get("/config", AdminAPIController, :config_show)
post("/config", AdminAPIController, :config_update)
+ get("/config/migrate_to_db", AdminAPIController, :migrate_to_db)
+ get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
end
scope "/", Pleroma.Web.TwitterAPI do
@@ -356,6 +346,10 @@ defmodule Pleroma.Web.Router do
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
+ patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
+ patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
+ patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background)
+
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
@@ -420,6 +414,12 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", SearchController, :account_search)
+ post(
+ "/pleroma/accounts/confirmation_resend",
+ MastodonAPIController,
+ :account_confirmation_resend
+ )
+
scope [] do
pipe_through(:oauth_read_or_public)
@@ -582,7 +582,7 @@ defmodule Pleroma.Web.Router do
end
end
- pipeline :ap_relay do
+ pipeline :ap_service_actor do
plug(:accepts, ["activity+json", "json"])
end
@@ -608,19 +608,20 @@ defmodule Pleroma.Web.Router do
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
+
+ get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
pipeline :activitypub do
plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
+ plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
end
scope "/", Pleroma.Web.ActivityPub do
# XXX: not really ostatus
pipe_through(:ostatus)
- get("/users/:nickname/followers", ActivityPubController, :followers)
- get("/users/:nickname/following", ActivityPubController, :following)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
end
@@ -652,11 +653,12 @@ defmodule Pleroma.Web.Router do
pipe_through(:oauth_write)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
end
- end
- scope "/relay", Pleroma.Web.ActivityPub do
- pipe_through(:ap_relay)
- get("/", ActivityPubController, :relay)
+ scope [] do
+ pipe_through(:oauth_read_or_public)
+ get("/users/:nickname/followers", ActivityPubController, :followers)
+ get("/users/:nickname/following", ActivityPubController, :following)
+ end
end
scope "/", Pleroma.Web.ActivityPub do
@@ -665,6 +667,20 @@ defmodule Pleroma.Web.Router do
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
+ scope "/relay", Pleroma.Web.ActivityPub do
+ pipe_through(:ap_service_actor)
+
+ get("/", ActivityPubController, :relay)
+ post("/inbox", ActivityPubController, :inbox)
+ end
+
+ scope "/internal/fetch", Pleroma.Web.ActivityPub do
+ pipe_through(:ap_service_actor)
+
+ get("/", ActivityPubController, :internal_fetch)
+ post("/inbox", ActivityPubController, :inbox)
+ end
+
scope "/.well-known", Pleroma.Web do
pipe_through(:well_known)
@@ -683,8 +699,10 @@ defmodule Pleroma.Web.Router do
get("/web/login", MastodonAPIController, :login)
delete("/auth/sign_out", MastodonAPIController, :logout)
+ post("/auth/password", MastodonAPIController, :password_reset)
+
scope [] do
- pipe_through(:oauth_read_or_public)
+ pipe_through(:oauth_read)
get("/web/*path", MastodonAPIController, :index)
end
end
@@ -721,54 +739,3 @@ defmodule Pleroma.Web.Router do
options("/*path", RedirectController, :empty)
end
end
-
-defmodule Fallback.RedirectController do
- use Pleroma.Web, :controller
- alias Pleroma.User
- alias Pleroma.Web.Metadata
-
- def api_not_implemented(conn, _params) do
- conn
- |> put_status(404)
- |> json(%{error: "Not implemented"})
- end
-
- def redirector(conn, _params, code \\ 200) do
- conn
- |> put_resp_content_type("text/html")
- |> send_file(code, index_file_path())
- end
-
- def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
- redirector_with_meta(conn, %{user: user})
- else
- nil ->
- redirector(conn, params)
- end
- end
-
- def redirector_with_meta(conn, params) do
- {:ok, index_content} = File.read(index_file_path())
- tags = Metadata.build_tags(params)
- response = String.replace(index_content, "", tags)
-
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, response)
- end
-
- def index_file_path do
- Pleroma.Plugs.InstanceStatic.file_path("index.html")
- end
-
- def registration_page(conn, params) do
- redirector(conn, params)
- end
-
- def empty(conn, _params) do
- conn
- |> put_status(204)
- |> text("")
- end
-end
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
index e96e4e1e4..9b01ebcc6 100644
--- a/lib/pleroma/web/salmon/salmon.ex
+++ b/lib/pleroma/web/salmon/salmon.ex
@@ -123,11 +123,26 @@ defmodule Pleroma.Web.Salmon do
{:ok, salmon}
end
- def remote_users(%{data: %{"to" => to} = data}) do
- to = to ++ (data["cc"] || [])
+ def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
+ cc = Map.get(data, "cc", [])
- to
- |> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
+ bcc =
+ data
+ |> Map.get("bcc", [])
+ |> Enum.reduce([], fn ap_id, bcc ->
+ case Pleroma.List.get_by_ap_id(ap_id) do
+ %Pleroma.List{user_id: ^user_id} = list ->
+ {:ok, following} = Pleroma.List.get_following(list)
+ bcc ++ Enum.map(following, & &1.ap_id)
+
+ _ ->
+ bcc
+ end
+ end)
+
+ [to, cc, bcc]
+ |> Enum.concat()
+ |> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.filter(fn user -> user && !user.local end)
end
@@ -191,7 +206,7 @@ defmodule Pleroma.Web.Salmon do
{:ok, private, _} = Keys.keys_from_pem(keys)
{:ok, feed} = encode(private, feed)
- remote_users = remote_users(activity)
+ remote_users = remote_users(user, activity)
salmon_urls = Enum.map(remote_users, & &1.info.salmon)
reachable_urls_metadata = Instances.filter_reachable(salmon_urls)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index 4f325113a..9ee331030 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.Streamer do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.NotificationView
@keepalive_interval :timer.seconds(30)
@@ -118,10 +119,14 @@ defmodule Pleroma.Web.Streamer do
topics
|> Map.get("#{topic}:#{item.user_id}", [])
|> Enum.each(fn socket ->
- send(
- socket.transport_pid,
- {:text, represent_notification(socket.assigns[:user], item)}
- )
+ with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id),
+ true <- should_send?(user, item),
+ false <- CommonAPI.thread_muted?(user, item.activity) do
+ send(
+ socket.transport_pid,
+ {:text, represent_notification(socket.assigns[:user], item)}
+ )
+ end
end)
{:noreply, topics}
@@ -225,19 +230,37 @@ defmodule Pleroma.Web.Streamer do
|> Jason.encode!()
end
+ defp should_send?(%User{} = user, %Activity{} = item) do
+ blocks = user.info.blocks || []
+ mutes = user.info.mutes || []
+ reblog_mutes = user.info.muted_reblogs || []
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+
+ with parent when not is_nil(parent) <- Object.normalize(item),
+ true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
+ true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
+ %{host: item_host} <- URI.parse(item.actor),
+ %{host: parent_host} <- URI.parse(parent.data["actor"]),
+ false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
+ false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
+ true <- thread_containment(item, user) do
+ true
+ else
+ _ -> false
+ end
+ end
+
+ defp should_send?(%User{} = user, %Notification{activity: activity}) do
+ should_send?(user, activity)
+ end
+
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc.
if socket.assigns[:user] do
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
- blocks = user.info.blocks || []
- mutes = user.info.mutes || []
- reblog_mutes = user.info.muted_reblogs || []
- with parent when not is_nil(parent) <- Object.normalize(item),
- true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
- true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
- true <- thread_containment(item, user) do
+ if should_send?(user, item) do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
else
diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex
new file mode 100644
index 000000000..c9dd699fd
--- /dev/null
+++ b/lib/pleroma/web/templates/email/digest.html.eex
@@ -0,0 +1,20 @@
+Hey <%= @user.nickname %>, here is what you've missed!
+
+New Mentions:
+
+<%= for %{data: mention, object: object, from: from} <- @mentions do %>
+ <%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %>
+<% end %>
+
+
+<%= if @followers != [] do %>
+<%= length(@followers) %> New Followers:
+
+<%= for %{data: follow, from: from} <- @followers do %>
+ <%= link from.nickname, to: follow.activity.actor %>
+<% end %>
+
+<% end %>
+
+You have received this email because you have signed up to receive digest emails from <%= @instance %> Pleroma instance.
+The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.
\ No newline at end of file
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index b3cf9ed11..5836ec1e0 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -36,6 +36,11 @@
margin-bottom: 20px;
}
+ a {
+ color: color: #d8a070;
+ text-decoration: none;
+ }
+
form {
width: 100%;
}
diff --git a/lib/pleroma/web/templates/layout/email.html.eex b/lib/pleroma/web/templates/layout/email.html.eex
new file mode 100644
index 000000000..f6dcd7f0f
--- /dev/null
+++ b/lib/pleroma/web/templates/layout/email.html.eex
@@ -0,0 +1,10 @@
+
+
+
+
+ <%= @email.subject %>
+
+
+ <%= render @view_module, @view_template, assigns %>
+
+
\ No newline at end of file
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
new file mode 100644
index 000000000..7b476f02d
--- /dev/null
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
@@ -0,0 +1 @@
+UNSUBSCRIBE FAILURE
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
new file mode 100644
index 000000000..6dfa2c185
--- /dev/null
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
@@ -0,0 +1 @@
+UNSUBSCRIBE SUCCESSFUL
diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex
new file mode 100644
index 000000000..8f5a43bf6
--- /dev/null
+++ b/lib/pleroma/web/translation_helpers.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TranslationHelpers do
+ defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do
+ quote do
+ require Pleroma.Web.Gettext
+
+ unquote(conn)
+ |> Plug.Conn.put_status(unquote(status))
+ |> Phoenix.Controller.json(%{
+ error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings))
+ })
+ end
+ end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index b1863528f..3405bd3b7 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -7,23 +7,26 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger
- alias Comeonin.Pbkdf2
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Emoji
+ alias Pleroma.Healthcheck
alias Pleroma.Notification
+ alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
alias Pleroma.Web
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
+ plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
+
def help_test(conn, _params) do
json(conn, "ok")
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
- with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
+ with %User{} = user <- User.get_cached_by_nickname(nick),
+ avatar = User.avatar_url(user) do
conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else
@@ -57,27 +60,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
%Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
redirect(conn, to: "/notice/#{activity_id}")
else
- {err, followee} = OStatus.find_or_make_user(acct)
- avatar = User.avatar_url(followee)
- name = followee.nickname
- id = followee.id
-
- if !!user do
+ with {:ok, followee} <- User.get_or_fetch(acct) do
conn
- |> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
- else
- conn
- |> render("follow_login.html", %{
+ |> render(follow_template(user), %{
error: false,
acct: acct,
- avatar: avatar,
- name: name,
- id: id
+ avatar: User.avatar_url(followee),
+ name: followee.nickname,
+ id: followee.id
})
+ else
+ {:error, _reason} ->
+ render(conn, follow_template(user), %{error: :error})
end
end
end
+ defp follow_template(%User{} = _user), do: "follow.html"
+ defp follow_template(_), do: "follow_login.html"
+
defp is_status?(acct) do
case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
{:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
@@ -91,50 +92,53 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
def do_remote_follow(conn, %{
"authorization" => %{"name" => username, "password" => password, "id" => id}
}) do
- followee = User.get_cached_by_id(id)
- avatar = User.avatar_url(followee)
- name = followee.nickname
-
- with %User{} = user <- User.get_cached_by_nickname(username),
- true <- Pbkdf2.checkpw(password, user.password_hash),
- %User{} = _followed <- User.get_cached_by_id(id),
- {:ok, follower} <- User.follow(user, followee),
- {:ok, _activity} <- ActivityPub.follow(follower, followee) do
+ with %User{} = followee <- User.get_cached_by_id(id),
+ {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
+ {_, true, _} <- {
+ :auth,
+ AuthenticationPlug.checkpw(password, user.password_hash),
+ followee
+ },
+ {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
- render(conn, "followed.html", %{error: false})
+ render(conn, "followed.html", %{error: "Error following account"})
- _e ->
+ {:auth, _, followee} ->
conn
|> render("follow_login.html", %{
error: "Wrong username or password",
id: id,
- name: name,
- avatar: avatar
+ name: followee.nickname,
+ avatar: User.avatar_url(followee)
})
+
+ e ->
+ Logger.debug("Remote follow failed with error #{inspect(e)}")
+ render(conn, "followed.html", %{error: "Something went wrong."})
end
end
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
- with %User{} = followee <- User.get_cached_by_id(id),
- {:ok, follower} <- User.follow(user, followee),
- {:ok, _activity} <- ActivityPub.follow(follower, followee) do
+ with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
+ {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
- conn
- |> render("followed.html", %{error: false})
+ render(conn, "followed.html", %{error: "Error following account"})
+
+ {:fetch_user, error} ->
+ Logger.debug("Remote follow failed with error #{inspect(error)}")
+ render(conn, "followed.html", %{error: "Could not find user"})
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
-
- conn
- |> render("followed.html", %{error: inspect(e)})
+ render(conn, "followed.html", %{error: "Something went wrong."})
end
end
@@ -149,67 +153,70 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
+ def config(%{assigns: %{format: "xml"}} = conn, _params) do
+ instance = Pleroma.Config.get(:instance)
+
+ response = """
+
+
+ #{Keyword.get(instance, :name)}
+ #{Web.base_url()}
+ #{Keyword.get(instance, :limit)}
+ #{!Keyword.get(instance, :registrations_open)}
+
+
+ """
+
+ conn
+ |> put_resp_content_type("application/xml")
+ |> send_resp(200, response)
+ end
+
def config(conn, _params) do
instance = Pleroma.Config.get(:instance)
- case get_format(conn) do
- "xml" ->
- response = """
-
-
- #{Keyword.get(instance, :name)}
- #{Web.base_url()}
- #{Keyword.get(instance, :limit)}
- #{!Keyword.get(instance, :registrations_open)}
-
-
- """
+ vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
- conn
- |> put_resp_content_type("application/xml")
- |> send_resp(200, response)
+ uploadlimit = %{
+ uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
+ avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
+ backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
+ bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
+ }
- _ ->
- vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
+ data = %{
+ name: Keyword.get(instance, :name),
+ description: Keyword.get(instance, :description),
+ server: Web.base_url(),
+ textlimit: to_string(Keyword.get(instance, :limit)),
+ uploadlimit: uploadlimit,
+ closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
+ private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
+ vapidPublicKey: vapid_public_key,
+ accountActivationRequired:
+ bool_to_val(Keyword.get(instance, :account_activation_required, false)),
+ invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
+ safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
+ }
- uploadlimit = %{
- uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
- avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
- backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
- bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
- }
-
- data = %{
- name: Keyword.get(instance, :name),
- description: Keyword.get(instance, :description),
- server: Web.base_url(),
- textlimit: to_string(Keyword.get(instance, :limit)),
- uploadlimit: uploadlimit,
- closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
- private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
- vapidPublicKey: vapid_public_key,
- accountActivationRequired:
- if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"),
- invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0"),
- safeDMMentionsEnabled:
- if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
- }
+ managed_config = Keyword.get(instance, :managed_config)
+ data =
+ if managed_config do
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
+ Map.put(data, "pleromafe", pleroma_fe)
+ else
+ data
+ end
- managed_config = Keyword.get(instance, :managed_config)
-
- data =
- if managed_config do
- data |> Map.put("pleromafe", pleroma_fe)
- else
- data
- end
-
- json(conn, %{site: data})
- end
+ json(conn, %{site: data})
end
+ defp bool_to_val(true), do: "1"
+ defp bool_to_val(_), do: "0"
+ defp bool_to_val(true, val, _), do: val
+ defp bool_to_val(_, _, val), do: val
+
def frontend_configurations(conn, _params) do
config =
Pleroma.Config.get(:frontend_configurations, %{})
@@ -218,20 +225,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
json(conn, config)
end
- def version(conn, _params) do
+ def version(%{assigns: %{format: "xml"}} = conn, _params) do
version = Pleroma.Application.named_version()
- case get_format(conn) do
- "xml" ->
- response = "#{version} "
+ conn
+ |> put_resp_content_type("application/xml")
+ |> send_resp(200, "#{version} ")
+ end
- conn
- |> put_resp_content_type("application/xml")
- |> send_resp(200, response)
-
- _ ->
- json(conn, version)
- end
+ def version(conn, _params) do
+ json(conn, Pleroma.Application.named_version())
end
def emoji(conn, _params) do
@@ -338,20 +341,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def healthcheck(conn, _params) do
- info =
- if Pleroma.Config.get([:instance, :healthcheck]) do
- Pleroma.Healthcheck.system_info()
- else
- %{}
- end
+ with true <- Config.get([:instance, :healthcheck]),
+ %{healthy: true} = info <- Healthcheck.system_info() do
+ json(conn, info)
+ else
+ %{healthy: false} = info ->
+ service_unavailable(conn, info)
- conn =
- if info[:healthy] do
- conn
- else
- Plug.Conn.put_status(conn, :service_unavailable)
- end
+ _ ->
+ service_unavailable(conn, %{})
+ end
+ end
- json(conn, info)
+ defp service_unavailable(conn, info) do
+ conn
+ |> put_status(:service_unavailable)
+ |> json(info)
end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 41e1c2877..80082ea84 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
import Ecto.Query
+ require Pleroma.Constants
+
def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data)
end
@@ -221,6 +223,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
user
|> UserEmail.password_reset_email(token_record.token)
|> Mailer.deliver_async()
+
+ {:ok, :enqueued}
else
false ->
{:error, "bad user identifier"}
@@ -284,7 +288,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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,
+ where: ^Pleroma.Constants.as_public() in a.recipients,
where:
fragment(
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 6cf107d17..5dfab6a6c 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
require Logger
+ plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
action_fallback(:errors)
@@ -192,6 +193,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
def notifications(%{assigns: %{user: user}} = conn, params) do
+ params =
+ if Map.has_key?(params, "with_muted") do
+ Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
+ else
+ params
+ end
+
notifications = Notification.for_user(user, params)
conn
@@ -430,6 +438,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "")
+ else
+ {:error, "unknown user"} ->
+ send_resp(conn, :not_found, "")
+
+ {:error, _} ->
+ send_resp(conn, :bad_request, "")
end
end
@@ -456,6 +470,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
+ def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ change = Changeset.change(user, %{avatar: nil})
+ {:ok, user} = User.update_and_set_cache(change)
+ CommonAPI.update(user)
+
+ conn
+ |> put_view(UserView)
+ |> render("show.json", %{user: user, for: user})
+ end
+
def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params, type: :avatar)
change = Changeset.change(user, %{avatar: object.data})
@@ -467,6 +491,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|> render("show.json", %{user: user, for: user})
end
+ def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
+ with new_info <- %{"banner" => %{}},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, user} <- User.update_and_set_cache(changeset) do
+ CommonAPI.update(user)
+ response = %{url: nil} |> Jason.encode!()
+
+ conn
+ |> json_reply(200, response)
+ end
+ end
+
def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
new_info <- %{"banner" => object.data},
@@ -482,6 +519,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
+ def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ with new_info <- %{"background" => %{}},
+ info_cng <- User.Info.profile_update(user.info, new_info),
+ changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+ {:ok, _user} <- User.update_and_set_cache(changeset) do
+ response = %{url: nil} |> Jason.encode!()
+
+ conn
+ |> json_reply(200, response)
+ end
+ end
+
def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
new_info <- %{"background" => object.data},
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index e84af84dc..abae63877 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
import Ecto.Query
require Logger
+ require Pleroma.Constants
defp query_context_ids([]), do: []
@@ -91,7 +92,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
String.ends_with?(ap_id, "/followers") ->
nil
- ap_id == "https://www.w3.org/ns/activitystreams#Public" ->
+ ap_id == Pleroma.Constants.as_public() ->
nil
user = User.get_cached_by_ap_id(ap_id) ->
diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex
index e7c7a7496..085cd5aa3 100644
--- a/lib/pleroma/web/twitter_api/views/notification_view.ex
+++ b/lib/pleroma/web/twitter_api/views/notification_view.ex
@@ -10,6 +10,8 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView
+ require Pleroma.Constants
+
defp get_user(ap_id, opts) do
cond do
user = opts[:users][ap_id] ->
@@ -18,7 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
String.ends_with?(ap_id, "/followers") ->
nil
- ap_id == "https://www.w3.org/ns/activitystreams#Public" ->
+ ap_id == Pleroma.Constants.as_public() ->
nil
true ->
diff --git a/lib/pleroma/web/uploader_controller.ex b/lib/pleroma/web/uploader_controller.ex
index 5d8a77346..0cc172698 100644
--- a/lib/pleroma/web/uploader_controller.ex
+++ b/lib/pleroma/web/uploader_controller.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.UploaderController do
use Pleroma.Web, :controller
@@ -7,10 +11,6 @@ defmodule Pleroma.Web.UploaderController do
process_callback(conn, :global.whereis_name({Uploader, upload_path}), params)
end
- def callbacks(conn, _) do
- send_resp(conn, 400, "bad request")
- end
-
defp process_callback(conn, pid, params) when is_pid(pid) do
send(pid, {Uploader, self(), conn, params})
@@ -20,6 +20,6 @@ defmodule Pleroma.Web.UploaderController do
end
defp process_callback(conn, _, _) do
- send_resp(conn, 400, "bad request")
+ render_error(conn, :bad_request, "bad request")
end
end
diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex
new file mode 100644
index 000000000..b63eb162c
--- /dev/null
+++ b/lib/pleroma/web/views/email_view.ex
@@ -0,0 +1,5 @@
+defmodule Pleroma.Web.EmailView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML
+ import Phoenix.HTML.Link
+end
diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex
new file mode 100644
index 000000000..fc3d20816
--- /dev/null
+++ b/lib/pleroma/web/views/mailer/subscription_view.ex
@@ -0,0 +1,3 @@
+defmodule Pleroma.Web.Mailer.SubscriptionView do
+ use Pleroma.Web, :view
+end
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index 66813e4dd..b42f6887e 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -23,9 +23,11 @@ defmodule Pleroma.Web do
def controller do
quote do
use Phoenix.Controller, namespace: Pleroma.Web
+
import Plug.Conn
import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
+ import Pleroma.Web.TranslationHelpers
plug(:set_put_layout)
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 3fca72de8..ecb39ee50 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.WebFinger do
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
host = Pleroma.Web.Endpoint.host()
- regex = ~r/(acct:)?(?\w+)@#{host}/
+ regex = ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@#{host}/
with %{"username" => username} <- Regex.named_captures(regex, resource),
%User{} = user <- User.get_cached_by_nickname(username) do
@@ -86,11 +86,17 @@ defmodule Pleroma.Web.WebFinger do
|> XmlBuilder.to_doc()
end
- defp get_magic_key(magic_key) do
- "data:application/magic-public-key," <> magic_key = magic_key
+ defp get_magic_key("data:application/magic-public-key," <> magic_key) do
{:ok, magic_key}
- rescue
- MatchError -> {:error, "Missing magic key data."}
+ end
+
+ defp get_magic_key(nil) do
+ Logger.debug("Undefined magic key.")
+ {:ok, nil}
+ end
+
+ defp get_magic_key(_) do
+ {:error, "Missing magic key data."}
end
defp webfinger_from_xml(doc) do
@@ -187,6 +193,7 @@ defmodule Pleroma.Web.WebFinger do
end
end
+ @spec finger(String.t()) :: {:ok, map()} | {:error, any()}
def finger(account) do
account = String.trim_leading(account, "@")
@@ -220,8 +227,6 @@ defmodule Pleroma.Web.WebFinger do
else
with {:ok, doc} <- Jason.decode(body) do
webfinger_from_json(doc)
- else
- {:error, e} -> e
end
end
else
diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex
index b77c75ec5..896eb15f9 100644
--- a/lib/pleroma/web/web_finger/web_finger_controller.ex
+++ b/lib/pleroma/web/web_finger/web_finger_controller.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
alias Pleroma.Web.WebFinger
+ plug(Pleroma.Plugs.SetFormatPlug)
plug(Pleroma.Web.FederatingPlug)
def host_meta(conn, _params) do
@@ -17,30 +18,28 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|> send_resp(200, xml)
end
- def webfinger(conn, %{"resource" => resource}) do
- case get_format(conn) do
- n when n in ["xml", "xrd+xml"] ->
- with {:ok, response} <- WebFinger.webfinger(resource, "XML") do
- conn
- |> put_resp_content_type("application/xrd+xml")
- |> send_resp(200, response)
- else
- _e -> send_resp(conn, 404, "Couldn't find user")
- end
-
- n when n in ["json", "jrd+json"] ->
- with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
- json(conn, response)
- else
- _e -> send_resp(conn, 404, "Couldn't find user")
- end
-
- _ ->
- send_resp(conn, 404, "Unsupported format")
+ def webfinger(%{assigns: %{format: format}} = conn, %{"resource" => resource})
+ when format in ["xml", "xrd+xml"] do
+ with {:ok, response} <- WebFinger.webfinger(resource, "XML") do
+ conn
+ |> put_resp_content_type("application/xrd+xml")
+ |> send_resp(200, response)
+ else
+ _e -> send_resp(conn, 404, "Couldn't find user")
end
end
- def webfinger(conn, _params) do
- send_resp(conn, 400, "Bad Request")
+ def webfinger(%{assigns: %{format: format}} = conn, %{"resource" => resource})
+ when format in ["json", "jrd+json"] do
+ with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
+ json(conn, response)
+ else
+ _e ->
+ conn
+ |> put_status(404)
+ |> json("Couldn't find user")
+ end
end
+
+ def webfinger(conn, _params), do: send_resp(conn, 400, "Bad Request")
end
diff --git a/lib/transports.ex b/lib/transports.ex
index 42f645b21..9f3fc535d 100644
--- a/lib/transports.ex
+++ b/lib/transports.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Phoenix.Transports.WebSocket.Raw do
import Plug.Conn,
only: [
diff --git a/lib/xml_builder.ex b/lib/xml_builder.ex
index b58602c7b..ceeef2755 100644
--- a/lib/xml_builder.ex
+++ b/lib/xml_builder.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.XmlBuilder do
def to_xml({tag, attributes, content}) do
open_tag = make_open_tag(tag, attributes)
diff --git a/mix.exs b/mix.exs
index 22d3d50df..334fabb33 100644
--- a/mix.exs
+++ b/mix.exs
@@ -14,7 +14,7 @@ defmodule Pleroma.Mixfile do
aliases: aliases(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
-
+ preferred_cli_env: ["coveralls.html": :test],
# Docs
name: "Pleroma",
homepage_url: "https://pleroma.social/",
@@ -95,6 +95,7 @@ defmodule Pleroma.Mixfile do
defp deps do
[
{:phoenix, "~> 1.4.8"},
+ {:tzdata, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
@@ -109,13 +110,13 @@ defmodule Pleroma.Mixfile do
{:phoenix_html, "~> 2.10"},
{:calendar, "~> 0.17.4"},
{:cachex, "~> 3.0.2"},
- {:httpoison, "~> 1.2.0"},
{:poison, "~> 3.0", override: true},
{:tesla, "~> 1.2"},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
- {:ex_aws, "~> 2.0"},
+ {:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
+ {:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.3"},
{:bbcode, "~> 0.1.1"},
{:ex_machina, "~> 2.3", only: :test},
@@ -126,7 +127,8 @@ defmodule Pleroma.Mixfile do
{:cors_plug, "~> 1.5"},
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"},
- {:swoosh, "~> 0.20"},
+ {:swoosh, "~> 0.23.2"},
+ {:phoenix_swoosh, "~> 0.2"},
{:gen_smtp, "~> 0.13"},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
{:floki, "~> 0.20.0"},
@@ -138,20 +140,23 @@ defmodule Pleroma.Mixfile do
ref: "95e8188490e97505c56636c1379ffdf036c1fdde"},
{:http_signatures,
git: "https://git.pleroma.social/pleroma/http_signatures.git",
- ref: "9789401987096ead65646b52b5a2ca6bf52fc531"},
- {:pleroma_job_queue, "~> 0.2.0"},
+ ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
+ {:pleroma_job_queue, "~> 0.3"},
{:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"},
- {:prometheus_phoenix, "~> 1.2"},
+ {:prometheus_phoenix, "~> 1.3"},
{:prometheus_ecto, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"},
+ {:joken, "~> 2.0"},
{:benchee, "~> 1.0"},
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_rated, "~> 1.3"},
+ {:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"},
- {:excoveralls, "~> 0.11.1", only: :test}
+ {:excoveralls, "~> 0.11.1", only: :test},
+ {:mox, "~> 0.5", only: :test}
] ++ oauth_deps()
end
@@ -188,12 +193,13 @@ defmodule Pleroma.Mixfile do
tag = String.trim(tag),
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
describe = String.trim(describe),
- ahead <- String.replace(describe, tag, "") do
+ ahead <- String.replace(describe, tag, ""),
+ ahead <- String.trim_leading(ahead, "-") do
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
else
_ ->
{commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
- {nil, "-0-g" <> String.trim(commit_hash)}
+ {nil, "0-g" <> String.trim(commit_hash)}
end
if git_tag && version != git_tag do
@@ -205,14 +211,15 @@ defmodule Pleroma.Mixfile do
# Branch name as pre-release version component, denoted with a dot
branch_name =
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
+ branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
- true <- branch_name != "master" do
+ true <- branch_name not in ["master", "HEAD"] do
branch_name =
branch_name
|> String.trim()
|> String.replace(identifier_filter, "-")
- "." <> branch_name
+ branch_name
end
build_name =
@@ -232,6 +239,17 @@ defmodule Pleroma.Mixfile do
env_override -> env_override
end
+ # Pre-release version, denoted by appending a hyphen
+ # and a series of dot separated identifiers
+ pre_release =
+ [git_pre_release, branch_name]
+ |> Enum.filter(fn string -> string && string != "" end)
+ |> Enum.join(".")
+ |> (fn
+ "" -> nil
+ string -> "-" <> String.replace(string, identifier_filter, "-")
+ end).()
+
# Build metadata, denoted with a plus sign
build_metadata =
[build_name, env_name]
@@ -242,7 +260,7 @@ defmodule Pleroma.Mixfile do
string -> "+" <> String.replace(string, identifier_filter, "-")
end).()
- [version, git_pre_release, branch_name, build_metadata]
+ [version, pre_release, build_metadata]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join()
end
diff --git a/mix.lock b/mix.lock
index cae8d7d84..f8ee80c83 100644
--- a/mix.lock
+++ b/mix.lock
@@ -5,19 +5,20 @@
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
- "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
- "calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+ "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
+ "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
- "comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
+ "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
+ "crontab": {:hex, :crontab, "1.1.7", "b9219f0bdc8678b94143655a8f229716c5810c0636a4489f98c0956137e53985", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
- "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
+ "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
@@ -27,66 +28,69 @@
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
+ "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
"excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
- "gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
- "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
+ "gen_smtp": {:hex, :gen_smtp, "0.14.0", "39846a03522456077c6429b4badfd1d55e5e7d0fdfb65e935b7c5e38549d9202", [:rebar3], [], "hexpm"},
+ "gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
- "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]},
+ "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
- "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
+ "joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
+ "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
- "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
+ "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
+ "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
- "phoenix": {:hex, :phoenix, "1.4.8", "c72dc3adeb49c70eb963a0ea24f7a064ec1588e651e84e1b7ad5ed8253c0b4a2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
+ "phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
- "pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
+ "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
+ "pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.0.2", "6055f16868cc4882b24b6e1d63d2bada94fb4978413377a3b32ac16c18dffba2", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
- "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
- "prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
+ "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
- "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
+ "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
- "prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
- "swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
+ "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
+ "swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
- "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+ "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
- "tzdata": {:hex, :tzdata, "0.5.20", "304b9e98a02840fb32a43ec111ffbe517863c8566eb04a061f1c4dbb90b4d84c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+ "tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
- "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
+ "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
}
diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po
index 2211c98e3..25a2f73e4 100644
--- a/priv/gettext/en/LC_MESSAGES/errors.po
+++ b/priv/gettext/en/LC_MESSAGES/errors.po
@@ -91,3 +91,375 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:381
+msgid "Account not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:153
+msgid "Already voted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:263
+msgid "Bad request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
+msgid "Can't delete object"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
+msgid "Can't delete this post"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
+msgid "Can't display this activity"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
+msgid "Can't find user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
+msgid "Can't get favorites"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
+msgid "Can't like object"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:518
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:461
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/config.ex:63
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:78
+msgid "Could not delete"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:110
+msgid "Could not favorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:310
+msgid "Could not pin"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:89
+msgid "Could not repeat"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:120
+msgid "Could not unfavorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:327
+msgid "Could not unpin"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:99
+msgid "Could not unrepeat"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:392
+msgid "Could not update state"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
+msgid "Error."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:36
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
+#: lib/pleroma/web/oauth/oauth_controller.ex:465
+msgid "Invalid credentials"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
+msgid "Invalid credentials."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:154
+msgid "Invalid indices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
+msgid "Invalid parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:377
+msgid "Invalid password."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
+msgid "Invalid request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:16
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
+msgid "Missing parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:496
+msgid "No such conversation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
+msgid "No such permission_group"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:69
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
+msgid "Not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:152
+msgid "Poll's author can't vote"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
+msgid "Record not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
+msgid "Something went wrong"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:253
+msgid "The message visibility must be direct"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:521
+msgid "The status is over the character limit"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
+msgid "This resource requires authentication."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/rate_limiter.ex:89
+msgid "Throttled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:155
+msgid "Too many choices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
+msgid "Unhandled activity type"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/user_is_admin_plug.ex:20
+msgid "User is not admin."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:380
+msgid "Valid `account_id` required"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:216
+msgid "Your account is currently disabled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#: lib/pleroma/web/oauth/oauth_controller.ex:213
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:335
+msgid "conversation is already muted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
+msgid "error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
+msgid "mascots can only be images"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
+msgid "not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:298
+msgid "Bad OAuth request."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:92
+msgid "CAPTCHA already used"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:89
+msgid "CAPTCHA expired"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:50
+msgid "Failed"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:314
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:345
+msgid "Failed to set up user account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:89
+msgid "Internal Error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+msgid "Invalid Username/Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:107
+msgid "Invalid answer data"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:145
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:93
+#: lib/pleroma/web/oauth/oauth_controller.ex:131
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:294
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/uploaders/uploader.ex:71
+msgid "Uploader callback timeout"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/uploader_controller.ex:11
+#: lib/pleroma/web/uploader_controller.ex:23
+msgid "bad request"
+msgstr ""
diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot
index a964f84ec..2fd9c42e3 100644
--- a/priv/gettext/errors.pot
+++ b/priv/gettext/errors.pot
@@ -7,7 +7,6 @@
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
-
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
@@ -89,3 +88,375 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:381
+msgid "Account not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:153
+msgid "Already voted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:263
+msgid "Bad request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
+msgid "Can't delete object"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
+msgid "Can't delete this post"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
+msgid "Can't display this activity"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
+msgid "Can't find user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
+msgid "Can't get favorites"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
+msgid "Can't like object"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:518
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:461
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/config.ex:63
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:78
+msgid "Could not delete"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:110
+msgid "Could not favorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:310
+msgid "Could not pin"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:89
+msgid "Could not repeat"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:120
+msgid "Could not unfavorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:327
+msgid "Could not unpin"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:99
+msgid "Could not unrepeat"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:392
+msgid "Could not update state"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
+msgid "Error."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:36
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
+#: lib/pleroma/web/oauth/oauth_controller.ex:465
+msgid "Invalid credentials"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
+msgid "Invalid credentials."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:154
+msgid "Invalid indices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
+msgid "Invalid parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:377
+msgid "Invalid password."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
+msgid "Invalid request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:16
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
+msgid "Missing parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:496
+msgid "No such conversation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
+msgid "No such permission_group"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:69
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
+msgid "Not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:152
+msgid "Poll's author can't vote"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
+msgid "Record not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
+msgid "Something went wrong"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:253
+msgid "The message visibility must be direct"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:521
+msgid "The status is over the character limit"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
+msgid "This resource requires authentication."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/rate_limiter.ex:89
+msgid "Throttled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:155
+msgid "Too many choices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
+msgid "Unhandled activity type"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/user_is_admin_plug.ex:20
+msgid "User is not admin."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:380
+msgid "Valid `account_id` required"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:216
+msgid "Your account is currently disabled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#: lib/pleroma/web/oauth/oauth_controller.ex:213
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:335
+msgid "conversation is already muted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
+msgid "error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
+msgid "mascots can only be images"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
+msgid "not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:298
+msgid "Bad OAuth request."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:92
+msgid "CAPTCHA already used"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:89
+msgid "CAPTCHA expired"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:50
+msgid "Failed"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:314
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:345
+msgid "Failed to set up user account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:89
+msgid "Internal Error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+msgid "Invalid Username/Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:107
+msgid "Invalid answer data"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:145
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:93
+#: lib/pleroma/web/oauth/oauth_controller.ex:131
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:294
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/uploaders/uploader.ex:71
+msgid "Uploader callback timeout"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/uploader_controller.ex:11
+#: lib/pleroma/web/uploader_controller.ex:23
+msgid "bad request"
+msgstr ""
diff --git a/priv/gettext/ru/LC_MESSAGES/errors.po b/priv/gettext/ru/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..39f83e8a6
--- /dev/null
+++ b/priv/gettext/ru/LC_MESSAGES/errors.po
@@ -0,0 +1,463 @@
+## `msgid`s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove `msgid`s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use `mix gettext.extract --merge` or `mix gettext.merge`
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: ru\n"
+"Plural-Forms: nplurals=3\n"
+
+msgid "can't be blank"
+msgstr "не может быть пустым"
+
+msgid "has already been taken"
+msgstr "уже занято"
+
+msgid "is invalid"
+msgstr "неверный"
+
+msgid "has invalid format"
+msgstr "неверный формат"
+
+msgid "has an invalid entry"
+msgstr "содержит неверную запись"
+
+msgid "is reserved"
+msgstr "занято"
+
+msgid "does not match confirmation"
+msgstr "не совпадает"
+
+msgid "is still associated with this entry"
+msgstr "по прежнему связан с этой записью"
+
+msgid "are still associated with this entry"
+msgstr "по прежнему связаны с этой записью"
+
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] "должен состоять из %{count} символа"
+msgstr[1] "должен состоять из %{count} символов"
+msgstr[2] "должен состоять из %{count} символов"
+
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] "должен содержать %{count} элемент"
+msgstr[1] "должен содержать %{count} элемента"
+msgstr[2] "должен содержать %{count} элементов"
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] "должен быть не менее чем %{count} символа"
+msgstr[1] "должен быть не менее чем %{count} символов"
+msgstr[2] "должен быть не менее чем %{count} символов"
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] "должен быть не менее %{count} элемента"
+msgstr[1] "должен быть не менее %{count} элементов"
+msgstr[2] "должен быть не менее %{count} элементов"
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] "должен быть не более %{count} символа"
+msgstr[1] "должен быть не более %{count} символов"
+msgstr[2] "должен быть не более %{count} символов"
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] "должен содержать не менее %{count} элемента"
+msgstr[1] "должен содержать не менее %{count} элемента"
+msgstr[2] "должен содержать не менее %{count} элементов"
+
+msgid "must be less than %{number}"
+msgstr "должен быть меньше %{number}"
+
+msgid "must be greater than %{number}"
+msgstr "должен быть больше %{number}"
+
+msgid "must be less than or equal to %{number}"
+msgstr "должен быть меньше или равен %{number}"
+
+msgid "must be greater than or equal to %{number}"
+msgstr "должен быть больше или равен %{number}"
+
+msgid "must be equal to %{number}"
+msgstr "должен быть равным %{number}"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:381
+msgid "Account not found"
+msgstr "Учетная запись не найдена"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:153
+msgid "Already voted"
+msgstr "Уже проголосовал(а)"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:263
+msgid "Bad request"
+msgstr "Неверный запрос"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
+msgid "Can't delete object"
+msgstr "Произошла ошибка при удалении объекта"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
+msgid "Can't delete this post"
+msgstr "Произошла ошибка при удалении этой записи"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
+msgid "Can't display this activity"
+msgstr "Произошла ошибка при показе этой записи"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
+msgid "Can't find user"
+msgstr "Пользователь не найден"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
+msgid "Can't get favorites"
+msgstr "Не в состоянии получить избранное"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
+msgid "Can't like object"
+msgstr "Не могу поставить лайк"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:518
+msgid "Cannot post an empty status without attachments"
+msgstr "Нельзя отправить пустой статус без приложений"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:461
+msgid "Comment must be up to %{max_size} characters"
+msgstr "Комментарий должен быть не более %{max_size} символов"
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/config.ex:63
+msgid "Config with params %{params} not found"
+msgstr "Параметры конфигурации %{params} не найдены"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:78
+msgid "Could not delete"
+msgstr "Не в силах удалить"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:110
+msgid "Could not favorite"
+msgstr "Не в силах добавить в избранное"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:310
+msgid "Could not pin"
+msgstr "Не в силах прикрепить"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:89
+msgid "Could not repeat"
+msgstr "Не в силах повторить"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:120
+msgid "Could not unfavorite"
+msgstr "Не в силах удалить из избранного"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:327
+msgid "Could not unpin"
+msgstr "Не в силах открепить"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:99
+msgid "Could not unrepeat"
+msgstr "Не в силах отменить повтор"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:392
+msgid "Could not update state"
+msgstr "Не в силах обновить состояние"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
+msgid "Error."
+msgstr "Ошибка"
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:36
+msgid "Invalid CAPTCHA"
+msgstr "Неверная CAPTCHA"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
+#: lib/pleroma/web/oauth/oauth_controller.ex:465
+msgid "Invalid credentials"
+msgstr "Неверные учетные данные"
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
+msgid "Invalid credentials."
+msgstr "Неверные учетные данные"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:154
+msgid "Invalid indices"
+msgstr "Неверные индексы"
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
+msgid "Invalid parameters"
+msgstr "Неверны параметры"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:377
+msgid "Invalid password."
+msgstr "Неверный пароль"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
+msgid "Invalid request"
+msgstr "Неверный запрос"
+
+#, elixir-format
+#: lib/pleroma/captcha/kocaptcha.ex:16
+msgid "Kocaptcha service unavailable"
+msgstr "Kocaptcha недоступен"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
+msgid "Missing parameters"
+msgstr "Не хватает параметров"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:496
+msgid "No such conversation"
+msgstr "Разговор не найден"
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
+msgid "No such permission_group"
+msgstr "Такой группы полномочий не существует"
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:69
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
+msgid "Not found"
+msgstr "Не найден"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:152
+msgid "Poll's author can't vote"
+msgstr "Автор опроса не может голосовать"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
+msgid "Record not found"
+msgstr "Запись не найдена"
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
+#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
+msgid "Something went wrong"
+msgstr "Что-то пошло не так"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:253
+msgid "The message visibility must be direct"
+msgstr "Видимость у сообщения должна быть `Личное`"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:521
+msgid "The status is over the character limit"
+msgstr "Превышена длина статуса"
+
+#, elixir-format
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
+msgid "This resource requires authentication."
+msgstr "Для этого ресурса требуется аутентификация"
+
+#, elixir-format
+#: lib/pleroma/plugs/rate_limiter.ex:89
+msgid "Throttled"
+msgstr "Ограничено. Превышен лимит запросов."
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:155
+msgid "Too many choices"
+msgstr "Слишком много ответов"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
+msgid "Unhandled activity type"
+msgstr "Неизвестный тип activity"
+
+#, elixir-format
+#: lib/pleroma/plugs/user_is_admin_plug.ex:20
+msgid "User is not admin."
+msgstr "Пользователь не обладает правами администратора"
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:380
+msgid "Valid `account_id` required"
+msgstr "Требуется корректный `account_id`"
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
+msgid "You can't revoke your own admin status."
+msgstr "Вы не можете отозвать статус администратора у вашей учетной записи"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:216
+msgid "Your account is currently disabled"
+msgstr "Ваша учетная запись отключена"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#: lib/pleroma/web/oauth/oauth_controller.ex:213
+msgid "Your login is missing a confirmed e-mail address"
+msgstr "Ваш e-mail адрес не подтвержден"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/common_api.ex:335
+msgid "conversation is already muted"
+msgstr "разговор уже игнорируется"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
+msgid "error"
+msgstr "ошибка"
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
+msgid "mascots can only be images"
+msgstr "маскоты должны быть картинками"
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
+msgid "not found"
+msgstr "не найдено"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:298
+msgid "Bad OAuth request."
+msgstr "Неверный OAuth запрос"
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:92
+msgid "CAPTCHA already used"
+msgstr "CAPTCHA уже использована"
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:89
+msgid "CAPTCHA expired"
+msgstr "CAPTCHA устарела"
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:50
+msgid "Failed"
+msgstr "Ошибка"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:314
+msgid "Failed to authenticate: %{message}."
+msgstr "Ошибка при входе: %{message}"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:345
+msgid "Failed to set up user account."
+msgstr "Ошибка при создании учетной записи"
+
+#, elixir-format
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
+msgid "Insufficient permissions: %{permissions}."
+msgstr "Недостаточно полномочий: %{permissions}"
+
+#, elixir-format
+#: lib/pleroma/plugs/uploaded_media.ex:89
+msgid "Internal Error"
+msgstr "Внутренняя ошибка"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+msgid "Invalid Username/Password"
+msgstr "Неверное имя пользователя или пароль"
+
+#, elixir-format
+#: lib/pleroma/captcha/captcha.ex:107
+msgid "Invalid answer data"
+msgstr "Неверный ответ"
+
+#, elixir-format
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
+msgid "Nodeinfo schema version not handled"
+msgstr "Версия схемы Nodeinfo не учитывается"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:145
+msgid "This action is outside the authorized scopes"
+msgstr "Это действие выходит за рамки доступных полномочий"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+msgid "Unknown error, please check the details and try again."
+msgstr "Неизвестная ошибка. Пожалуйста, проверьте данные и попробуйте снова."
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:93
+#: lib/pleroma/web/oauth/oauth_controller.ex:131
+msgid "Unlisted redirect_uri."
+msgstr "Неизвестный redirect_uri"
+
+#, elixir-format
+#: lib/pleroma/web/oauth/oauth_controller.ex:294
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr "Неизвестный OAuth провайдер: %{provider}"
+
+#, elixir-format
+#: lib/pleroma/uploaders/uploader.ex:71
+msgid "Uploader callback timeout"
+msgstr "Тайм-аут при загрузке"
+
+#, elixir-format
+#: lib/pleroma/web/uploader_controller.ex:11
+#: lib/pleroma/web/uploader_controller.ex:23
+msgid "bad request"
+msgstr "неправильный запрос"
diff --git a/priv/repo/migrations/20190412052952_add_user_info_fields.exs b/priv/repo/migrations/20190412052952_add_user_info_fields.exs
new file mode 100644
index 000000000..646c91f32
--- /dev/null
+++ b/priv/repo/migrations/20190412052952_add_user_info_fields.exs
@@ -0,0 +1,20 @@
+defmodule Pleroma.Repo.Migrations.AddEmailNotificationsToUserInfo do
+ use Ecto.Migration
+
+ def up do
+ execute("
+ UPDATE users
+ SET info = info || '{
+ \"email_notifications\": {
+ \"digest\": false
+ }
+ }'")
+ end
+
+ def down do
+ execute("
+ UPDATE users
+ SET info = info - 'email_notifications'
+ ")
+ end
+end
diff --git a/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs b/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs
new file mode 100644
index 000000000..4312b171f
--- /dev/null
+++ b/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddSigninAndLastDigestDatesToUser do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add(:last_digest_emailed_at, :naive_datetime, default: fragment("now()"))
+ end
+ end
+end
diff --git a/priv/repo/migrations/20190516112144_add_ap_id_to_lists.exs b/priv/repo/migrations/20190516112144_add_ap_id_to_lists.exs
new file mode 100644
index 000000000..3c32bc355
--- /dev/null
+++ b/priv/repo/migrations/20190516112144_add_ap_id_to_lists.exs
@@ -0,0 +1,26 @@
+defmodule Pleroma.Repo.Migrations.AddApIdToLists do
+ use Ecto.Migration
+
+ def up do
+ alter table(:lists) do
+ add(:ap_id, :string)
+ end
+
+ execute("""
+ UPDATE lists
+ SET ap_id = u.ap_id || '/lists/' || lists.id
+ FROM users AS u
+ WHERE lists.user_id = u.id
+ """)
+
+ create(unique_index(:lists, :ap_id))
+ end
+
+ def down do
+ drop(index(:lists, [:ap_id]))
+
+ alter table(:lists) do
+ remove(:ap_id)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20190710115833_add_following_address_to_user.exs b/priv/repo/migrations/20190710115833_add_following_address_to_user.exs
new file mode 100644
index 000000000..fe30472a1
--- /dev/null
+++ b/priv/repo/migrations/20190710115833_add_following_address_to_user.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddFollowingAddressToUser do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add(:following_address, :string, unique: true)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20190710125051_add_following_address_index_to_user.exs b/priv/repo/migrations/20190710125051_add_following_address_index_to_user.exs
new file mode 100644
index 000000000..0cbfb71f4
--- /dev/null
+++ b/priv/repo/migrations/20190710125051_add_following_address_index_to_user.exs
@@ -0,0 +1,8 @@
+defmodule Pleroma.Repo.Migrations.AddFollowingAddressIndexToUser do
+ use Ecto.Migration
+
+ @disable_ddl_transaction true
+ def change do
+ create(index(:users, [:following_address], concurrently: true))
+ end
+end
diff --git a/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs b/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs
new file mode 100644
index 000000000..779aa382e
--- /dev/null
+++ b/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs
@@ -0,0 +1,20 @@
+defmodule Pleroma.Repo.Migrations.AddFollowingAddressFromSourceData do
+ use Ecto.Migration
+ import Ecto.Query
+ alias Pleroma.User
+
+ def change do
+ query =
+ User.external_users_query()
+ |> select([u], struct(u, [:id, :ap_id, :info]))
+
+ Pleroma.Repo.stream(query)
+ |> Enum.each(fn
+ %{info: %{source_data: source_data}} = user ->
+ Ecto.Changeset.cast(user, %{following_address: source_data["following"]}, [
+ :following_address
+ ])
+ |> Pleroma.Repo.update()
+ end)
+ end
+end
diff --git a/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs b/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
new file mode 100644
index 000000000..50669902e
--- /dev/null
+++ b/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
@@ -0,0 +1,24 @@
+defmodule Pleroma.Repo.Migrations.CopyMutedToMutedNotifications do
+ use Ecto.Migration
+ alias Pleroma.User
+
+ def change do
+ query =
+ User.Query.build(%{
+ local: true,
+ active: true,
+ order_by: :id
+ })
+
+ Pleroma.Repo.stream(query)
+ |> Enum.each(fn
+ %{info: %{mutes: mutes} = info} = user ->
+ info_cng =
+ Ecto.Changeset.cast(info, %{muted_notifications: mutes}, [:muted_notifications])
+
+ Ecto.Changeset.change(user)
+ |> Ecto.Changeset.put_embed(:info, info_cng)
+ |> Pleroma.Repo.update()
+ end)
+ end
+end
diff --git a/priv/static/emoji/f_00b.png b/priv/static/emoji/f_00b.png
deleted file mode 100644
index 3d00b89b0..000000000
Binary files a/priv/static/emoji/f_00b.png and /dev/null differ
diff --git a/priv/static/emoji/f_00b11b.png b/priv/static/emoji/f_00b11b.png
deleted file mode 100644
index 3e99ce464..000000000
Binary files a/priv/static/emoji/f_00b11b.png and /dev/null differ
diff --git a/priv/static/emoji/f_00b33b.png b/priv/static/emoji/f_00b33b.png
deleted file mode 100644
index 8f4929297..000000000
Binary files a/priv/static/emoji/f_00b33b.png and /dev/null differ
diff --git a/priv/static/emoji/f_00h.png b/priv/static/emoji/f_00h.png
deleted file mode 100644
index ba3da57c6..000000000
Binary files a/priv/static/emoji/f_00h.png and /dev/null differ
diff --git a/priv/static/emoji/f_00t.png b/priv/static/emoji/f_00t.png
deleted file mode 100644
index 31d98b433..000000000
Binary files a/priv/static/emoji/f_00t.png and /dev/null differ
diff --git a/priv/static/emoji/f_01b.png b/priv/static/emoji/f_01b.png
deleted file mode 100644
index 7bd2582c5..000000000
Binary files a/priv/static/emoji/f_01b.png and /dev/null differ
diff --git a/priv/static/emoji/f_03b.png b/priv/static/emoji/f_03b.png
deleted file mode 100644
index 9e4ff1bf7..000000000
Binary files a/priv/static/emoji/f_03b.png and /dev/null differ
diff --git a/priv/static/emoji/f_10b.png b/priv/static/emoji/f_10b.png
deleted file mode 100644
index 67c6493fc..000000000
Binary files a/priv/static/emoji/f_10b.png and /dev/null differ
diff --git a/priv/static/emoji/f_11b.png b/priv/static/emoji/f_11b.png
deleted file mode 100644
index b53328ba9..000000000
Binary files a/priv/static/emoji/f_11b.png and /dev/null differ
diff --git a/priv/static/emoji/f_11b00b.png b/priv/static/emoji/f_11b00b.png
deleted file mode 100644
index c4c30e11f..000000000
Binary files a/priv/static/emoji/f_11b00b.png and /dev/null differ
diff --git a/priv/static/emoji/f_11b22b.png b/priv/static/emoji/f_11b22b.png
deleted file mode 100644
index 47425e06e..000000000
Binary files a/priv/static/emoji/f_11b22b.png and /dev/null differ
diff --git a/priv/static/emoji/f_11h.png b/priv/static/emoji/f_11h.png
deleted file mode 100644
index 28342363a..000000000
Binary files a/priv/static/emoji/f_11h.png and /dev/null differ
diff --git a/priv/static/emoji/f_11t.png b/priv/static/emoji/f_11t.png
deleted file mode 100644
index dca67dc70..000000000
Binary files a/priv/static/emoji/f_11t.png and /dev/null differ
diff --git a/priv/static/emoji/f_12b.png b/priv/static/emoji/f_12b.png
deleted file mode 100644
index 9925adb7c..000000000
Binary files a/priv/static/emoji/f_12b.png and /dev/null differ
diff --git a/priv/static/emoji/f_21b.png b/priv/static/emoji/f_21b.png
deleted file mode 100644
index aa56d2cb2..000000000
Binary files a/priv/static/emoji/f_21b.png and /dev/null differ
diff --git a/priv/static/emoji/f_22b.png b/priv/static/emoji/f_22b.png
deleted file mode 100644
index 426878986..000000000
Binary files a/priv/static/emoji/f_22b.png and /dev/null differ
diff --git a/priv/static/emoji/f_22b11b.png b/priv/static/emoji/f_22b11b.png
deleted file mode 100644
index 4bdfb3107..000000000
Binary files a/priv/static/emoji/f_22b11b.png and /dev/null differ
diff --git a/priv/static/emoji/f_22b33b.png b/priv/static/emoji/f_22b33b.png
deleted file mode 100644
index adf94f811..000000000
Binary files a/priv/static/emoji/f_22b33b.png and /dev/null differ
diff --git a/priv/static/emoji/f_22h.png b/priv/static/emoji/f_22h.png
deleted file mode 100644
index 3b27e2de8..000000000
Binary files a/priv/static/emoji/f_22h.png and /dev/null differ
diff --git a/priv/static/emoji/f_22t.png b/priv/static/emoji/f_22t.png
deleted file mode 100644
index addd9fec7..000000000
Binary files a/priv/static/emoji/f_22t.png and /dev/null differ
diff --git a/priv/static/emoji/f_23b.png b/priv/static/emoji/f_23b.png
deleted file mode 100644
index beb69ab36..000000000
Binary files a/priv/static/emoji/f_23b.png and /dev/null differ
diff --git a/priv/static/emoji/f_30b.png b/priv/static/emoji/f_30b.png
deleted file mode 100644
index 41dbb2a5d..000000000
Binary files a/priv/static/emoji/f_30b.png and /dev/null differ
diff --git a/priv/static/emoji/f_32b.png b/priv/static/emoji/f_32b.png
deleted file mode 100644
index d8261e8a8..000000000
Binary files a/priv/static/emoji/f_32b.png and /dev/null differ
diff --git a/priv/static/emoji/f_33b.png b/priv/static/emoji/f_33b.png
deleted file mode 100644
index 71b8b914a..000000000
Binary files a/priv/static/emoji/f_33b.png and /dev/null differ
diff --git a/priv/static/emoji/f_33b00b.png b/priv/static/emoji/f_33b00b.png
deleted file mode 100644
index 65b6e24b8..000000000
Binary files a/priv/static/emoji/f_33b00b.png and /dev/null differ
diff --git a/priv/static/emoji/f_33b22b.png b/priv/static/emoji/f_33b22b.png
deleted file mode 100644
index d71a8ddd4..000000000
Binary files a/priv/static/emoji/f_33b22b.png and /dev/null differ
diff --git a/priv/static/emoji/f_33h.png b/priv/static/emoji/f_33h.png
deleted file mode 100644
index e141c5184..000000000
Binary files a/priv/static/emoji/f_33h.png and /dev/null differ
diff --git a/priv/static/emoji/f_33t.png b/priv/static/emoji/f_33t.png
deleted file mode 100644
index d5a23073d..000000000
Binary files a/priv/static/emoji/f_33t.png and /dev/null differ
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index f36b231c5..57ed05eba 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -20,6 +20,10 @@
"sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#",
"directMessage": "litepub:directMessage",
+ "listMessage": {
+ "@id": "litepub:listMessage",
+ "@type": "@id"
+ },
"oauthRegistrationEndpoint": {
"@id": "litepub:oauthRegistrationEndpoint",
"@type": "@id"
diff --git a/priv/templates/sample_config.eex b/priv/templates/sample_config.eex
index 2d4a49328..dc75d4008 100644
--- a/priv/templates/sample_config.eex
+++ b/priv/templates/sample_config.eex
@@ -11,6 +11,7 @@ end %>
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
+ http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>],
secret_key_base: "<%= secret %>",
signing_salt: "<%= signing_salt %>"
@@ -68,19 +69,4 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>"
# config :ex_aws, :s3,
# host: "s3.wasabisys.com"
-
-# Configure Openstack Swift support if desired.
-#
-# Many openstack deployments are different, so config is left very open with
-# no assumptions made on which provider you're using. This should allow very
-# wide support without needing separate handlers for OVH, Rackspace, etc.
-#
-# config :pleroma, Pleroma.Uploaders.Swift,
-# container: "some-container",
-# username: "api-username-yyyy",
-# password: "api-key-xxxx",
-# tenant_id: "",
-# auth_url: "https://keystone-endpoint.provider.com",
-# storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_/",
-# object_url: "https://cdn-endpoint.provider.com/"
-#
+config :joken, default_signer: "<%= jwt_secret %>"
diff --git a/test/activity_test.exs b/test/activity_test.exs
index 7ba4363c8..b27f6fd36 100644
--- a/test/activity_test.exs
+++ b/test/activity_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.ActivityTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Bookmark
+ alias Pleroma.Object
alias Pleroma.ThreadMute
import Pleroma.Factory
@@ -18,15 +19,18 @@ defmodule Pleroma.ActivityTest do
test "returns activities by it's objects AP ids" do
activity = insert(:note_activity)
- [found_activity] = Activity.get_all_create_by_object_ap_id(activity.data["object"]["id"])
+ object_data = Object.normalize(activity).data
+
+ [found_activity] = Activity.get_all_create_by_object_ap_id(object_data["id"])
assert activity == found_activity
end
test "returns the activity that created an object" do
activity = insert(:note_activity)
+ object_data = Object.normalize(activity).data
- found_activity = Activity.get_create_by_object_ap_id(activity.data["object"]["id"])
+ found_activity = Activity.get_create_by_object_ap_id(object_data["id"])
assert activity == found_activity
end
diff --git a/test/bbs/handler_test.exs b/test/bbs/handler_test.exs
index 7d5d68d11..4f0c13417 100644
--- a/test/bbs/handler_test.exs
+++ b/test/bbs/handler_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.BBS.HandlerTest do
use Pleroma.DataCase
alias Pleroma.Activity
@@ -59,6 +63,7 @@ defmodule Pleroma.BBS.HandlerTest do
another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "this is a test post"})
+ activity_object = Object.normalize(activity)
output =
capture_io(fn ->
@@ -76,8 +81,9 @@ defmodule Pleroma.BBS.HandlerTest do
)
assert reply.actor == user.ap_id
- object = Object.normalize(reply)
- assert object.data["content"] == "this is a reply"
- assert object.data["inReplyTo"] == activity.data["object"]
+
+ reply_object_data = Object.normalize(reply).data
+ assert reply_object_data["content"] == "this is a reply"
+ assert reply_object_data["inReplyTo"] == activity_object.data["id"]
end
end
diff --git a/test/bookmark_test.exs b/test/bookmark_test.exs
index b81c102ef..e54bd359c 100644
--- a/test/bookmark_test.exs
+++ b/test/bookmark_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.BookmarkTest do
use Pleroma.DataCase
import Pleroma.Factory
diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs
index c0e433263..dbeadbe87 100644
--- a/test/config/transfer_task_test.exs
+++ b/test/config/transfer_task_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Config.TransferTaskTest do
use Pleroma.DataCase
diff --git a/test/conversation_test.exs b/test/conversation_test.exs
index 5903d10ff..aa193e0d4 100644
--- a/test/conversation_test.exs
+++ b/test/conversation_test.exs
@@ -11,6 +11,16 @@ defmodule Pleroma.ConversationTest do
import Pleroma.Factory
+ setup_all do
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+ :ok
+ end
+
test "it goes through old direct conversations" do
user = insert(:user)
other_user = insert(:user)
diff --git a/test/emails/admin_email_test.exs b/test/emails/admin_email_test.exs
new file mode 100644
index 000000000..9e83c73c6
--- /dev/null
+++ b/test/emails/admin_email_test.exs
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Emails.AdminEmailTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Emails.AdminEmail
+ alias Pleroma.Web.Router.Helpers
+
+ test "build report email" do
+ config = Pleroma.Config.get(:instance)
+ to_user = insert(:user)
+ reporter = insert(:user)
+ account = insert(:user)
+
+ res =
+ AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
+
+ status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
+ reporter_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.nickname)
+ account_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, account.nickname)
+
+ assert res.to == [{to_user.name, to_user.email}]
+ assert res.from == {config[:name], config[:notify_email]}
+ assert res.subject == "#{config[:name]} Report"
+
+ assert res.html_body ==
+ "Reported by: #{reporter.nickname}
\nReported Account: #{account.nickname}
\nComment: Test comment\n
Statuses:\n
\n
\n\n"
+ end
+
+ test "it works when the reporter is a remote user without email" do
+ config = Pleroma.Config.get(:instance)
+ to_user = insert(:user)
+ reporter = insert(:user, email: nil, local: false)
+ account = insert(:user)
+
+ res =
+ AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
+
+ assert res.to == [{to_user.name, to_user.email}]
+ assert res.from == {config[:name], config[:notify_email]}
+ end
+end
diff --git a/test/emails/mailer_test.exs b/test/emails/mailer_test.exs
new file mode 100644
index 000000000..450bb09c7
--- /dev/null
+++ b/test/emails/mailer_test.exs
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Emails.MailerTest do
+ use Pleroma.DataCase
+ alias Pleroma.Emails.Mailer
+
+ import Swoosh.TestAssertions
+
+ @email %Swoosh.Email{
+ from: {"Pleroma", "noreply@example.com"},
+ html_body: "Test email",
+ subject: "Pleroma test email",
+ to: [{"Test User", "user1@example.com"}]
+ }
+
+ setup do
+ value = Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled])
+ on_exit(fn -> Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], value) end)
+ :ok
+ end
+
+ test "not send email when mailer is disabled" do
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+ Mailer.deliver(@email)
+
+ refute_email_sent(
+ from: {"Pleroma", "noreply@example.com"},
+ to: [{"Test User", "user1@example.com"}],
+ html_body: "Test email",
+ subject: "Pleroma test email"
+ )
+ end
+
+ test "send email" do
+ Mailer.deliver(@email)
+
+ assert_email_sent(
+ from: {"Pleroma", "noreply@example.com"},
+ to: [{"Test User", "user1@example.com"}],
+ html_body: "Test email",
+ subject: "Pleroma test email"
+ )
+ end
+
+ test "perform" do
+ Mailer.perform(:deliver_async, @email, [])
+
+ assert_email_sent(
+ from: {"Pleroma", "noreply@example.com"},
+ to: [{"Test User", "user1@example.com"}],
+ html_body: "Test email",
+ subject: "Pleroma test email"
+ )
+ end
+end
diff --git a/test/emails/user_email_test.exs b/test/emails/user_email_test.exs
new file mode 100644
index 000000000..7d8df6abc
--- /dev/null
+++ b/test/emails/user_email_test.exs
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Emails.UserEmailTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Emails.UserEmail
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Router
+
+ import Pleroma.Factory
+
+ test "build password reset email" do
+ config = Pleroma.Config.get(:instance)
+ user = insert(:user)
+ email = UserEmail.password_reset_email(user, "test_token")
+ assert email.from == {config[:name], config[:notify_email]}
+ assert email.to == [{user.name, user.email}]
+ assert email.subject == "Password reset"
+ assert email.html_body =~ Router.Helpers.reset_password_url(Endpoint, :reset, "test_token")
+ end
+
+ test "build user invitation email" do
+ config = Pleroma.Config.get(:instance)
+ user = insert(:user)
+ token = %Pleroma.UserInviteToken{token: "test-token"}
+ email = UserEmail.user_invitation_email(user, token, "test@test.com", "Jonh")
+ assert email.from == {config[:name], config[:notify_email]}
+ assert email.subject == "Invitation to Pleroma"
+ assert email.to == [{"Jonh", "test@test.com"}]
+
+ assert email.html_body =~
+ Router.Helpers.redirect_url(Endpoint, :registration_page, token.token)
+ end
+
+ test "build account confirmation email" do
+ config = Pleroma.Config.get(:instance)
+ user = insert(:user, info: %Pleroma.User.Info{confirmation_token: "conf-token"})
+ email = UserEmail.account_confirmation_email(user)
+ assert email.from == {config[:name], config[:notify_email]}
+ assert email.to == [{user.name, user.email}]
+ assert email.subject == "#{config[:name]} account confirmation"
+
+ assert email.html_body =~
+ Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token")
+ end
+end
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
index 2eaa26be6..07ac6ff1d 100644
--- a/test/emoji_test.exs
+++ b/test/emoji_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.EmojiTest do
use ExUnit.Case, async: true
alias Pleroma.Emoji
diff --git a/test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml b/test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml
new file mode 100644
index 000000000..df64d44b0
--- /dev/null
+++ b/test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/test/fixtures/lain.xml b/test/fixtures/lain.xml
new file mode 100644
index 000000000..332b3b28d
--- /dev/null
+++ b/test/fixtures/lain.xml
@@ -0,0 +1,12 @@
+
+
+ acct:lain@zetsubou.xn--q9jyb4c
+ https://zetsubou.xn--q9jyb4c/users/lain
+
+
+
+
+
+
+
diff --git a/test/fixtures/mastodon-delete-user.json b/test/fixtures/mastodon-delete-user.json
new file mode 100644
index 000000000..f19088fec
--- /dev/null
+++ b/test/fixtures/mastodon-delete-user.json
@@ -0,0 +1,24 @@
+{
+ "type": "Delete",
+ "object": {
+ "type": "Person",
+ "id": "http://mastodon.example.org/users/admin",
+ "atomUri": "http://mastodon.example.org/users/admin"
+ },
+ "id": "http://mastodon.example.org/users/admin#delete",
+ "actor": "http://mastodon.example.org/users/admin",
+ "@context": [
+ {
+ "toot": "http://joinmastodon.org/ns#",
+ "sensitive": "as:sensitive",
+ "ostatus": "http://ostatus.org#",
+ "movedTo": "as:movedTo",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "atomUri": "ostatus:atomUri",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji"
+ }
+ ]
+}
diff --git a/test/fixtures/nypd-facial-recognition-children-teenagers.html b/test/fixtures/nypd-facial-recognition-children-teenagers.html
new file mode 100644
index 000000000..5702c4484
--- /dev/null
+++ b/test/fixtures/nypd-facial-recognition-children-teenagers.html
@@ -0,0 +1,227 @@
+
+
+
+ She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New York | She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.
She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.
Image “It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, who pleaded guilty to an assault that occurred when she was 14. Credit Credit Sarah Blesener for The New York Times [What you need to know to start the day: Get New York Today in your inbox .]
The New York Police Department has been loading thousands of arrest photos of children and teenagers into a facial recognition database despite evidence the technology has a higher risk of false matches in younger faces.
For about four years, internal records show, the department has used the technology to compare crime scene images with its collection of juvenile mug shots, the photos that are taken at an arrest. Most of the photos are of teenagers, largely 13 to 16 years old, but children as young as 11 have been included.
Elected officials and civil rights groups said the disclosure that the city was deploying a powerful surveillance tool on adolescents — whose privacy seems sacrosanct and whose status is protected in the criminal justice system — was a striking example of the Police Department’s ability to adopt advancing technology with little public scrutiny.
Several members of the City Council as well as a range of civil liberties groups said they were unaware of the policy until they were contacted by The New York Times.
Police Department officials defended the decision, saying it was just the latest evolution of a longstanding policing technique: using arrest photos to identify suspects.
“I don’t think this is any secret decision that’s made behind closed doors,” the city’s chief of detectives, Dermot F. Shea, said in an interview. “This is just process, and making sure we’re doing everything to fight crime.”
Other cities have begun to debate whether law enforcement should use facial recognition, which relies on an algorithm to quickly pore through images and suggest matches. In May, San Francisco blocked city agencies, including the police , from using the tool amid unease about potential government abuse. Detroit is facing public resistance to a technology that has been shown to have lower accuracy with people with darker skin.
In New York, the state Education Department recently told the Lockport, N.Y., school district to delay a plan to use facial recognition on students, citing privacy concerns.
“At the end of the day, it should be banned — no young people,” said Councilman Donovan Richards, a Queens Democrat who heads the Public Safety Committee, which oversees the Police Department.
Image Dermot Shea, the city’s chief of detectives, said investigators would not arrest anyone based solely on a facial recognition match. Credit Chang W. Lee/The New York Times Still, facial recognition has not been widely tested on children. Most algorithms are taught to “think” based on adult faces, and there is growing evidence that they do not work as well on children.
The National Institute of Standards and Technology, which is part of the Commerce Department and evaluates facial recognition algorithms for accuracy, recently found the vast majority of more than 100 facial recognition algorithms had a higher rate of mistaken matches among children. The error rate was most pronounced in young children but was also seen in those aged 10 to 16.
Aging poses another problem: The appearance of children and adolescents can change drastically as bones stretch and shift, altering the underlying facial structure.
“I would use extreme caution in using those algorithms,” said Karl Ricanek Jr., a computer science professor and co-founder of the Face Aging Group at the University of North Carolina-Wilmington.
Technology that can match an image of a younger teenager to a recent arrest photo may be less effective at finding the same person even one or two years later, he said.
“The systems do not have the capacity to understand the dynamic changes that occur to a child’s face,” Dr. Ricanek said.
Idemia and DataWorks Plus, the two companies that provide facial recognition software to the Police Department, did not respond to requests for comment.
The New York Police Department can take arrest photos of minors as young as 11 who are charged with a felony, depending on the severity of the charge.
And in many cases, the department keeps the photos for years, making facial recognition comparisons to what may have effectively become outdated images. There are photos of 5,500 individuals in the juvenile database, 4,100 of whom are no longer 16 or under, the department said. Teenagers 17 and older are considered adults in the criminal justice system.
Police officials declined to provide statistics on how often their facial recognition systems provide false matches, or to explain how they evaluate the system’s effectiveness.
“We are comfortable with this technology because it has proved to be a valuable investigative method,” Chief Shea said. Facial recognition has helped lead to thousands of arrests of both adults and juveniles, the department has said.
Mayor Bill de Blasio had been aware the department was using the technology on minors, said Freddi Goldstein, a spokeswoman for the mayor.
She said the Police Department followed “strict guidelines” in applying the technology and City Hall monitored the agency’s compliance with the policies.
The Times learned details of the department’s use of facial recognition by reviewing documents posted online earlier this year by Clare Garvie, a senior associate at the Center on Privacy and Technology at Georgetown Law . Ms. Garvie received the documents as part of an open records lawsuit.
It could not be determined whether other large police departments used facial recognition with juveniles because very few have written policies governing the use of the technology, Ms. Garvie said.
New York detectives rely on a vast network of surveillance cameras throughout the city to provide images of people believed to have committed a crime. Since 2011, the department has had a dedicated unit of officers who use facial recognition to compare those images against millions of photos, usually mug shots. The software proposes matches, which have led to thousands of arrests, the department said.
By 2013, top police officials were meeting to discuss including juveniles in the program, the documents reviewed by The Times showed.
The documents showed that the juvenile database had been integrated into the system by 2015.
“We have these photos. It makes sense,” Chief Shea said in the interview.
State law requires that arrest photos be destroyed if the case is resolved in the juvenile’s favor, or if the child is found to have committed only a misdemeanor, rather than a felony. The photos also must be destroyed if a person reaches age 21 without a criminal record.
When children are charged with crimes, the court system usually takes some steps to prevent their acts from defining them in later years. Children who are 16 and under, for instance, are generally sent to Family Court, where records are not public.
Yet including their photos in a facial recognition database runs the risk that an imperfect algorithm identifies them as possible suspects in later crimes, civil rights advocates said. A mistaken match could lead investigators to focus on the wrong person from the outset, they said.
“It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, a 17-year-old Brooklyn girl who had admitted guilt in Family Court to a group attack that happened when she was 14. She said she was present at the attack but did not participate.
Bailey, who asked that she be identified only by her last name because she did not want her juvenile arrest to be public, has not been arrested again and is now a student at John Jay College of Criminal Justice.
Recent studies indicate that people of color, as well as children and women, have a greater risk of misidentification than their counterparts, said Joy Buolamwini, the founder of the Algorithmic Justice League and graduate researcher at the M.I.T. Media Lab, who has examined how human biases are built into artificial intelligence.
The racial disparities in the juvenile justice system are stark: In New York, black and Latino juveniles were charged with crimes at far higher rates than whites in 2017, the most recent year for which numbers were available. Black juveniles outnumbered white juveniles more than 15 to 1 .
“If the facial recognition algorithm has a negative bias toward a black population, that will get magnified more toward children,” Dr. Ricanek said, adding that in terms of diminished accuracy, “you’re now putting yourself in unknown territory.”
Joseph Goldstein writes about policing and the criminal justice system. He has been a reporter at The Times since 2011, and is based in New York. He also worked for a year in the Kabul bureau, reporting on Afghanistan. @ JoeKGoldstein
Ali Watkins is a reporter on the Metro Desk, covering courts and social services. Previously, she covered national security in Washington for The Times, BuzzFeed and McClatchy Newspapers. @ AliWatkins
A version of this article appears in print on , Section A, Page 1 of the New York edition with the headline: In New York, Police Computers Scan Faces, Some as Young as 11
. Order Reprints | Today’s Paper | Subscribe
Site Information Navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/nypd-facial-recognition-children-teenagers2.html b/test/fixtures/nypd-facial-recognition-children-teenagers2.html
new file mode 100644
index 000000000..ae8b26aff
--- /dev/null
+++ b/test/fixtures/nypd-facial-recognition-children-teenagers2.html
@@ -0,0 +1,226 @@
+
+
+
+ She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New York | She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.
She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.
Image “It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, who pleaded guilty to an assault that occurred when she was 14. Credit Credit Sarah Blesener for The New York Times [What you need to know to start the day: Get New York Today in your inbox .]
The New York Police Department has been loading thousands of arrest photos of children and teenagers into a facial recognition database despite evidence the technology has a higher risk of false matches in younger faces.
For about four years, internal records show, the department has used the technology to compare crime scene images with its collection of juvenile mug shots, the photos that are taken at an arrest. Most of the photos are of teenagers, largely 13 to 16 years old, but children as young as 11 have been included.
Elected officials and civil rights groups said the disclosure that the city was deploying a powerful surveillance tool on adolescents — whose privacy seems sacrosanct and whose status is protected in the criminal justice system — was a striking example of the Police Department’s ability to adopt advancing technology with little public scrutiny.
Several members of the City Council as well as a range of civil liberties groups said they were unaware of the policy until they were contacted by The New York Times.
Police Department officials defended the decision, saying it was just the latest evolution of a longstanding policing technique: using arrest photos to identify suspects.
“I don’t think this is any secret decision that’s made behind closed doors,” the city’s chief of detectives, Dermot F. Shea, said in an interview. “This is just process, and making sure we’re doing everything to fight crime.”
Other cities have begun to debate whether law enforcement should use facial recognition, which relies on an algorithm to quickly pore through images and suggest matches. In May, San Francisco blocked city agencies, including the police , from using the tool amid unease about potential government abuse. Detroit is facing public resistance to a technology that has been shown to have lower accuracy with people with darker skin.
In New York, the state Education Department recently told the Lockport, N.Y., school district to delay a plan to use facial recognition on students, citing privacy concerns.
“At the end of the day, it should be banned — no young people,” said Councilman Donovan Richards, a Queens Democrat who heads the Public Safety Committee, which oversees the Police Department.
Image Dermot Shea, the city’s chief of detectives, said investigators would not arrest anyone based solely on a facial recognition match. Credit Chang W. Lee/The New York Times Still, facial recognition has not been widely tested on children. Most algorithms are taught to “think” based on adult faces, and there is growing evidence that they do not work as well on children.
The National Institute of Standards and Technology, which is part of the Commerce Department and evaluates facial recognition algorithms for accuracy, recently found the vast majority of more than 100 facial recognition algorithms had a higher rate of mistaken matches among children. The error rate was most pronounced in young children but was also seen in those aged 10 to 16.
Aging poses another problem: The appearance of children and adolescents can change drastically as bones stretch and shift, altering the underlying facial structure.
“I would use extreme caution in using those algorithms,” said Karl Ricanek Jr., a computer science professor and co-founder of the Face Aging Group at the University of North Carolina-Wilmington.
Technology that can match an image of a younger teenager to a recent arrest photo may be less effective at finding the same person even one or two years later, he said.
“The systems do not have the capacity to understand the dynamic changes that occur to a child’s face,” Dr. Ricanek said.
Idemia and DataWorks Plus, the two companies that provide facial recognition software to the Police Department, did not respond to requests for comment.
The New York Police Department can take arrest photos of minors as young as 11 who are charged with a felony, depending on the severity of the charge.
And in many cases, the department keeps the photos for years, making facial recognition comparisons to what may have effectively become outdated images. There are photos of 5,500 individuals in the juvenile database, 4,100 of whom are no longer 16 or under, the department said. Teenagers 17 and older are considered adults in the criminal justice system.
Police officials declined to provide statistics on how often their facial recognition systems provide false matches, or to explain how they evaluate the system’s effectiveness.
“We are comfortable with this technology because it has proved to be a valuable investigative method,” Chief Shea said. Facial recognition has helped lead to thousands of arrests of both adults and juveniles, the department has said.
Mayor Bill de Blasio had been aware the department was using the technology on minors, said Freddi Goldstein, a spokeswoman for the mayor.
She said the Police Department followed “strict guidelines” in applying the technology and City Hall monitored the agency’s compliance with the policies.
The Times learned details of the department’s use of facial recognition by reviewing documents posted online earlier this year by Clare Garvie, a senior associate at the Center on Privacy and Technology at Georgetown Law . Ms. Garvie received the documents as part of an open records lawsuit.
It could not be determined whether other large police departments used facial recognition with juveniles because very few have written policies governing the use of the technology, Ms. Garvie said.
New York detectives rely on a vast network of surveillance cameras throughout the city to provide images of people believed to have committed a crime. Since 2011, the department has had a dedicated unit of officers who use facial recognition to compare those images against millions of photos, usually mug shots. The software proposes matches, which have led to thousands of arrests, the department said.
By 2013, top police officials were meeting to discuss including juveniles in the program, the documents reviewed by The Times showed.
The documents showed that the juvenile database had been integrated into the system by 2015.
“We have these photos. It makes sense,” Chief Shea said in the interview.
State law requires that arrest photos be destroyed if the case is resolved in the juvenile’s favor, or if the child is found to have committed only a misdemeanor, rather than a felony. The photos also must be destroyed if a person reaches age 21 without a criminal record.
When children are charged with crimes, the court system usually takes some steps to prevent their acts from defining them in later years. Children who are 16 and under, for instance, are generally sent to Family Court, where records are not public.
Yet including their photos in a facial recognition database runs the risk that an imperfect algorithm identifies them as possible suspects in later crimes, civil rights advocates said. A mistaken match could lead investigators to focus on the wrong person from the outset, they said.
“It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, a 17-year-old Brooklyn girl who had admitted guilt in Family Court to a group attack that happened when she was 14. She said she was present at the attack but did not participate.
Bailey, who asked that she be identified only by her last name because she did not want her juvenile arrest to be public, has not been arrested again and is now a student at John Jay College of Criminal Justice.
Recent studies indicate that people of color, as well as children and women, have a greater risk of misidentification than their counterparts, said Joy Buolamwini, the founder of the Algorithmic Justice League and graduate researcher at the M.I.T. Media Lab, who has examined how human biases are built into artificial intelligence.
The racial disparities in the juvenile justice system are stark: In New York, black and Latino juveniles were charged with crimes at far higher rates than whites in 2017, the most recent year for which numbers were available. Black juveniles outnumbered white juveniles more than 15 to 1 .
“If the facial recognition algorithm has a negative bias toward a black population, that will get magnified more toward children,” Dr. Ricanek said, adding that in terms of diminished accuracy, “you’re now putting yourself in unknown territory.”
Joseph Goldstein writes about policing and the criminal justice system. He has been a reporter at The Times since 2011, and is based in New York. He also worked for a year in the Kabul bureau, reporting on Afghanistan. @ JoeKGoldstein
Ali Watkins is a reporter on the Metro Desk, covering courts and social services. Previously, she covered national security in Washington for The Times, BuzzFeed and McClatchy Newspapers. @ AliWatkins
A version of this article appears in print on , Section A, Page 1 of the New York edition with the headline: In New York, Police Computers Scan Faces, Some as Young as 11
. Order Reprints | Today’s Paper | Subscribe
Site Information Navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/nypd-facial-recognition-children-teenagers3.html b/test/fixtures/nypd-facial-recognition-children-teenagers3.html
new file mode 100644
index 000000000..53454d23e
--- /dev/null
+++ b/test/fixtures/nypd-facial-recognition-children-teenagers3.html
@@ -0,0 +1,227 @@
+
+
+
+ She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New York | She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.
She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.
Image “It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, who pleaded guilty to an assault that occurred when she was 14. Credit Credit Sarah Blesener for The New York Times [What you need to know to start the day: Get New York Today in your inbox .]
The New York Police Department has been loading thousands of arrest photos of children and teenagers into a facial recognition database despite evidence the technology has a higher risk of false matches in younger faces.
For about four years, internal records show, the department has used the technology to compare crime scene images with its collection of juvenile mug shots, the photos that are taken at an arrest. Most of the photos are of teenagers, largely 13 to 16 years old, but children as young as 11 have been included.
Elected officials and civil rights groups said the disclosure that the city was deploying a powerful surveillance tool on adolescents — whose privacy seems sacrosanct and whose status is protected in the criminal justice system — was a striking example of the Police Department’s ability to adopt advancing technology with little public scrutiny.
Several members of the City Council as well as a range of civil liberties groups said they were unaware of the policy until they were contacted by The New York Times.
Police Department officials defended the decision, saying it was just the latest evolution of a longstanding policing technique: using arrest photos to identify suspects.
“I don’t think this is any secret decision that’s made behind closed doors,” the city’s chief of detectives, Dermot F. Shea, said in an interview. “This is just process, and making sure we’re doing everything to fight crime.”
Other cities have begun to debate whether law enforcement should use facial recognition, which relies on an algorithm to quickly pore through images and suggest matches. In May, San Francisco blocked city agencies, including the police , from using the tool amid unease about potential government abuse. Detroit is facing public resistance to a technology that has been shown to have lower accuracy with people with darker skin.
In New York, the state Education Department recently told the Lockport, N.Y., school district to delay a plan to use facial recognition on students, citing privacy concerns.
“At the end of the day, it should be banned — no young people,” said Councilman Donovan Richards, a Queens Democrat who heads the Public Safety Committee, which oversees the Police Department.
Image Dermot Shea, the city’s chief of detectives, said investigators would not arrest anyone based solely on a facial recognition match. Credit Chang W. Lee/The New York Times Still, facial recognition has not been widely tested on children. Most algorithms are taught to “think” based on adult faces, and there is growing evidence that they do not work as well on children.
The National Institute of Standards and Technology, which is part of the Commerce Department and evaluates facial recognition algorithms for accuracy, recently found the vast majority of more than 100 facial recognition algorithms had a higher rate of mistaken matches among children. The error rate was most pronounced in young children but was also seen in those aged 10 to 16.
Aging poses another problem: The appearance of children and adolescents can change drastically as bones stretch and shift, altering the underlying facial structure.
“I would use extreme caution in using those algorithms,” said Karl Ricanek Jr., a computer science professor and co-founder of the Face Aging Group at the University of North Carolina-Wilmington.
Technology that can match an image of a younger teenager to a recent arrest photo may be less effective at finding the same person even one or two years later, he said.
“The systems do not have the capacity to understand the dynamic changes that occur to a child’s face,” Dr. Ricanek said.
Idemia and DataWorks Plus, the two companies that provide facial recognition software to the Police Department, did not respond to requests for comment.
The New York Police Department can take arrest photos of minors as young as 11 who are charged with a felony, depending on the severity of the charge.
And in many cases, the department keeps the photos for years, making facial recognition comparisons to what may have effectively become outdated images. There are photos of 5,500 individuals in the juvenile database, 4,100 of whom are no longer 16 or under, the department said. Teenagers 17 and older are considered adults in the criminal justice system.
Police officials declined to provide statistics on how often their facial recognition systems provide false matches, or to explain how they evaluate the system’s effectiveness.
“We are comfortable with this technology because it has proved to be a valuable investigative method,” Chief Shea said. Facial recognition has helped lead to thousands of arrests of both adults and juveniles, the department has said.
Mayor Bill de Blasio had been aware the department was using the technology on minors, said Freddi Goldstein, a spokeswoman for the mayor.
She said the Police Department followed “strict guidelines” in applying the technology and City Hall monitored the agency’s compliance with the policies.
The Times learned details of the department’s use of facial recognition by reviewing documents posted online earlier this year by Clare Garvie, a senior associate at the Center on Privacy and Technology at Georgetown Law . Ms. Garvie received the documents as part of an open records lawsuit.
It could not be determined whether other large police departments used facial recognition with juveniles because very few have written policies governing the use of the technology, Ms. Garvie said.
New York detectives rely on a vast network of surveillance cameras throughout the city to provide images of people believed to have committed a crime. Since 2011, the department has had a dedicated unit of officers who use facial recognition to compare those images against millions of photos, usually mug shots. The software proposes matches, which have led to thousands of arrests, the department said.
By 2013, top police officials were meeting to discuss including juveniles in the program, the documents reviewed by The Times showed.
The documents showed that the juvenile database had been integrated into the system by 2015.
“We have these photos. It makes sense,” Chief Shea said in the interview.
State law requires that arrest photos be destroyed if the case is resolved in the juvenile’s favor, or if the child is found to have committed only a misdemeanor, rather than a felony. The photos also must be destroyed if a person reaches age 21 without a criminal record.
When children are charged with crimes, the court system usually takes some steps to prevent their acts from defining them in later years. Children who are 16 and under, for instance, are generally sent to Family Court, where records are not public.
Yet including their photos in a facial recognition database runs the risk that an imperfect algorithm identifies them as possible suspects in later crimes, civil rights advocates said. A mistaken match could lead investigators to focus on the wrong person from the outset, they said.
“It’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, a 17-year-old Brooklyn girl who had admitted guilt in Family Court to a group attack that happened when she was 14. She said she was present at the attack but did not participate.
Bailey, who asked that she be identified only by her last name because she did not want her juvenile arrest to be public, has not been arrested again and is now a student at John Jay College of Criminal Justice.
Recent studies indicate that people of color, as well as children and women, have a greater risk of misidentification than their counterparts, said Joy Buolamwini, the founder of the Algorithmic Justice League and graduate researcher at the M.I.T. Media Lab, who has examined how human biases are built into artificial intelligence.
The racial disparities in the juvenile justice system are stark: In New York, black and Latino juveniles were charged with crimes at far higher rates than whites in 2017, the most recent year for which numbers were available. Black juveniles outnumbered white juveniles more than 15 to 1 .
“If the facial recognition algorithm has a negative bias toward a black population, that will get magnified more toward children,” Dr. Ricanek said, adding that in terms of diminished accuracy, “you’re now putting yourself in unknown territory.”
Joseph Goldstein writes about policing and the criminal justice system. He has been a reporter at The Times since 2011, and is based in New York. He also worked for a year in the Kabul bureau, reporting on Afghanistan. @ JoeKGoldstein
Ali Watkins is a reporter on the Metro Desk, covering courts and social services. Previously, she covered national security in Washington for The Times, BuzzFeed and McClatchy Newspapers. @ AliWatkins
A version of this article appears in print on , Section A, Page 1 of the New York edition with the headline: In New York, Police Computers Scan Faces, Some as Young as 11
. Order Reprints | Today’s Paper | Subscribe
Site Information Navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/rich_media/amz.html b/test/fixtures/rich_media/amz.html
new file mode 100644
index 000000000..d4f8bd1a3
--- /dev/null
+++ b/test/fixtures/rich_media/amz.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/fixtures/httpoison_mock/7369654.atom b/test/fixtures/tesla_mock/7369654.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/7369654.atom
rename to test/fixtures/tesla_mock/7369654.atom
diff --git a/test/fixtures/httpoison_mock/7369654.html b/test/fixtures/tesla_mock/7369654.html
similarity index 100%
rename from test/fixtures/httpoison_mock/7369654.html
rename to test/fixtures/tesla_mock/7369654.html
diff --git a/test/fixtures/httpoison_mock/7even.json b/test/fixtures/tesla_mock/7even.json
similarity index 100%
rename from test/fixtures/httpoison_mock/7even.json
rename to test/fixtures/tesla_mock/7even.json
diff --git a/test/fixtures/httpoison_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json
similarity index 100%
rename from test/fixtures/httpoison_mock/admin@mastdon.example.org.json
rename to test/fixtures/tesla_mock/admin@mastdon.example.org.json
diff --git a/test/fixtures/httpoison_mock/atarifrosch_feed.xml b/test/fixtures/tesla_mock/atarifrosch_feed.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/atarifrosch_feed.xml
rename to test/fixtures/tesla_mock/atarifrosch_feed.xml
diff --git a/test/fixtures/httpoison_mock/atarifrosch_webfinger.xml b/test/fixtures/tesla_mock/atarifrosch_webfinger.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/atarifrosch_webfinger.xml
rename to test/fixtures/tesla_mock/atarifrosch_webfinger.xml
diff --git a/test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json b/test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json
similarity index 100%
rename from test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json
rename to test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json
diff --git a/test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json b/test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json
similarity index 100%
rename from test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json
rename to test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json
diff --git a/test/fixtures/httpoison_mock/eal_sakamoto.xml b/test/fixtures/tesla_mock/eal_sakamoto.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/eal_sakamoto.xml
rename to test/fixtures/tesla_mock/eal_sakamoto.xml
diff --git a/test/fixtures/httpoison_mock/emelie.atom b/test/fixtures/tesla_mock/emelie.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/emelie.atom
rename to test/fixtures/tesla_mock/emelie.atom
diff --git a/test/fixtures/httpoison_mock/emelie.json b/test/fixtures/tesla_mock/emelie.json
similarity index 100%
rename from test/fixtures/httpoison_mock/emelie.json
rename to test/fixtures/tesla_mock/emelie.json
diff --git a/test/fixtures/httpoison_mock/framasoft@framatube.org.json b/test/fixtures/tesla_mock/framasoft@framatube.org.json
similarity index 100%
rename from test/fixtures/httpoison_mock/framasoft@framatube.org.json
rename to test/fixtures/tesla_mock/framasoft@framatube.org.json
diff --git a/test/fixtures/httpoison_mock/framatube.org_host_meta b/test/fixtures/tesla_mock/framatube.org_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/framatube.org_host_meta
rename to test/fixtures/tesla_mock/framatube.org_host_meta
diff --git a/test/fixtures/httpoison_mock/gerzilla.de_host_meta b/test/fixtures/tesla_mock/gerzilla.de_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/gerzilla.de_host_meta
rename to test/fixtures/tesla_mock/gerzilla.de_host_meta
diff --git a/test/fixtures/httpoison_mock/gnusocial.de_host_meta b/test/fixtures/tesla_mock/gnusocial.de_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/gnusocial.de_host_meta
rename to test/fixtures/tesla_mock/gnusocial.de_host_meta
diff --git a/test/fixtures/httpoison_mock/gs.example.org_host_meta b/test/fixtures/tesla_mock/gs.example.org_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/gs.example.org_host_meta
rename to test/fixtures/tesla_mock/gs.example.org_host_meta
diff --git a/test/fixtures/httpoison_mock/hellpie.json b/test/fixtures/tesla_mock/hellpie.json
similarity index 100%
rename from test/fixtures/httpoison_mock/hellpie.json
rename to test/fixtures/tesla_mock/hellpie.json
diff --git a/test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml b/test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml
rename to test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml
diff --git a/test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json b/test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json
similarity index 100%
rename from test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json
rename to test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json
diff --git a/test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml
rename to test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json b/test/fixtures/tesla_mock/https___info.pleroma.site_actor.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json
rename to test/fixtures/tesla_mock/https___info.pleroma.site_actor.json
diff --git a/test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom b/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom
rename to test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom
diff --git a/test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.atom b/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.atom
rename to test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom
diff --git a/test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml b/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml
rename to test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml
diff --git a/test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json
rename to test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json
diff --git a/test/fixtures/httpoison_mock/https___pawoo.net_users_aqidaqidaqid.xml b/test/fixtures/tesla_mock/https___pawoo.net_users_aqidaqidaqid.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___pawoo.net_users_aqidaqidaqid.xml
rename to test/fixtures/tesla_mock/https___pawoo.net_users_aqidaqidaqid.xml
diff --git a/test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom b/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom
rename to test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom
diff --git a/test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml b/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml
rename to test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml
diff --git a/test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml b/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml
rename to test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml
diff --git a/test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml b/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml
rename to test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___prismo.news__mxb.json b/test/fixtures/tesla_mock/https___prismo.news__mxb.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https___prismo.news__mxb.json
rename to test/fixtures/tesla_mock/https___prismo.news__mxb.json
diff --git a/test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml
rename to test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml
rename to test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html b/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.html
similarity index 100%
rename from test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html
rename to test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.html
diff --git a/test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml b/test/fixtures/tesla_mock/https___shitposter.club_user_1.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml
rename to test/fixtures/tesla_mock/https___shitposter.club_user_1.xml
diff --git a/test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml
rename to test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml
rename to test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml
diff --git a/test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml
rename to test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml
diff --git a/test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml
rename to test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml
diff --git a/test/fixtures/httpoison_mock/https__info.pleroma.site_activity.json b/test/fixtures/tesla_mock/https__info.pleroma.site_activity.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https__info.pleroma.site_activity.json
rename to test/fixtures/tesla_mock/https__info.pleroma.site_activity.json
diff --git a/test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json b/test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json
rename to test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json
diff --git a/test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json b/test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json
rename to test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json
diff --git a/test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json b/test/fixtures/tesla_mock/https__info.pleroma.site_activity4.json
similarity index 100%
rename from test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json
rename to test/fixtures/tesla_mock/https__info.pleroma.site_activity4.json
diff --git a/test/fixtures/httpoison_mock/kaniini@gerzilla.de.json b/test/fixtures/tesla_mock/kaniini@gerzilla.de.json
similarity index 100%
rename from test/fixtures/httpoison_mock/kaniini@gerzilla.de.json
rename to test/fixtures/tesla_mock/kaniini@gerzilla.de.json
diff --git a/test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json b/test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json
similarity index 100%
rename from test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json
rename to test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json
diff --git a/test/fixtures/tesla_mock/kpherox@mstdn.jp.xml b/test/fixtures/tesla_mock/kpherox@mstdn.jp.xml
new file mode 100644
index 000000000..2ec134eaa
--- /dev/null
+++ b/test/fixtures/tesla_mock/kpherox@mstdn.jp.xml
@@ -0,0 +1,10 @@
+
+
+ acct:kPherox@mstdn.jp
+ https://mstdn.jp/@kPherox
+ https://mstdn.jp/users/kPherox
+
+
+
+
+
diff --git a/test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml b/test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml
rename to test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml
diff --git a/test/fixtures/httpoison_mock/lucifermysticus.json b/test/fixtures/tesla_mock/lucifermysticus.json
similarity index 100%
rename from test/fixtures/httpoison_mock/lucifermysticus.json
rename to test/fixtures/tesla_mock/lucifermysticus.json
diff --git a/test/fixtures/httpoison_mock/macgirvin.com_host_meta b/test/fixtures/tesla_mock/macgirvin.com_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/macgirvin.com_host_meta
rename to test/fixtures/tesla_mock/macgirvin.com_host_meta
diff --git a/test/fixtures/httpoison_mock/mamot.fr_host_meta b/test/fixtures/tesla_mock/mamot.fr_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/mamot.fr_host_meta
rename to test/fixtures/tesla_mock/mamot.fr_host_meta
diff --git a/test/fixtures/httpoison_mock/mastodon.social_host_meta b/test/fixtures/tesla_mock/mastodon.social_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/mastodon.social_host_meta
rename to test/fixtures/tesla_mock/mastodon.social_host_meta
diff --git a/test/fixtures/httpoison_mock/mastodon.xyz_host_meta b/test/fixtures/tesla_mock/mastodon.xyz_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/mastodon.xyz_host_meta
rename to test/fixtures/tesla_mock/mastodon.xyz_host_meta
diff --git a/test/fixtures/httpoison_mock/mayumayu.json b/test/fixtures/tesla_mock/mayumayu.json
similarity index 100%
rename from test/fixtures/httpoison_mock/mayumayu.json
rename to test/fixtures/tesla_mock/mayumayu.json
diff --git a/test/fixtures/httpoison_mock/mayumayupost.json b/test/fixtures/tesla_mock/mayumayupost.json
similarity index 100%
rename from test/fixtures/httpoison_mock/mayumayupost.json
rename to test/fixtures/tesla_mock/mayumayupost.json
diff --git a/test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json b/test/fixtures/tesla_mock/mike@osada.macgirvin.com.json
similarity index 100%
rename from test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json
rename to test/fixtures/tesla_mock/mike@osada.macgirvin.com.json
diff --git a/test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml b/test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml
rename to test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml
diff --git a/test/fixtures/httpoison_mock/pawoo.net_host_meta b/test/fixtures/tesla_mock/pawoo.net_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/pawoo.net_host_meta
rename to test/fixtures/tesla_mock/pawoo.net_host_meta
diff --git a/test/fixtures/httpoison_mock/peertube.moe-vid.json b/test/fixtures/tesla_mock/peertube.moe-vid.json
similarity index 100%
rename from test/fixtures/httpoison_mock/peertube.moe-vid.json
rename to test/fixtures/tesla_mock/peertube.moe-vid.json
diff --git a/test/fixtures/httpoison_mock/pleroma.soykaf.com_host_meta b/test/fixtures/tesla_mock/pleroma.soykaf.com_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/pleroma.soykaf.com_host_meta
rename to test/fixtures/tesla_mock/pleroma.soykaf.com_host_meta
diff --git a/test/fixtures/httpoison_mock/puckipedia.com.json b/test/fixtures/tesla_mock/puckipedia.com.json
similarity index 100%
rename from test/fixtures/httpoison_mock/puckipedia.com.json
rename to test/fixtures/tesla_mock/puckipedia.com.json
diff --git a/test/fixtures/httpoison_mock/rinpatch.json b/test/fixtures/tesla_mock/rinpatch.json
similarity index 100%
rename from test/fixtures/httpoison_mock/rinpatch.json
rename to test/fixtures/tesla_mock/rinpatch.json
diff --git a/test/fixtures/httpoison_mock/rye.json b/test/fixtures/tesla_mock/rye.json
similarity index 100%
rename from test/fixtures/httpoison_mock/rye.json
rename to test/fixtures/tesla_mock/rye.json
diff --git a/test/fixtures/httpoison_mock/sakamoto.atom b/test/fixtures/tesla_mock/sakamoto.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/sakamoto.atom
rename to test/fixtures/tesla_mock/sakamoto.atom
diff --git a/test/fixtures/httpoison_mock/sakamoto_eal_feed.atom b/test/fixtures/tesla_mock/sakamoto_eal_feed.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/sakamoto_eal_feed.atom
rename to test/fixtures/tesla_mock/sakamoto_eal_feed.atom
diff --git a/test/fixtures/httpoison_mock/shitposter.club_host_meta b/test/fixtures/tesla_mock/shitposter.club_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/shitposter.club_host_meta
rename to test/fixtures/tesla_mock/shitposter.club_host_meta
diff --git a/test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.feed b/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed
similarity index 100%
rename from test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.feed
rename to test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed
diff --git a/test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.webfigner b/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.webfigner
similarity index 100%
rename from test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.webfigner
rename to test/fixtures/tesla_mock/shp@pleroma.soykaf.com.webfigner
diff --git a/test/fixtures/httpoison_mock/shp@social.heldscal.la.xml b/test/fixtures/tesla_mock/shp@social.heldscal.la.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/shp@social.heldscal.la.xml
rename to test/fixtures/tesla_mock/shp@social.heldscal.la.xml
diff --git a/test/fixtures/httpoison_mock/skruyb@mamot.fr.atom b/test/fixtures/tesla_mock/skruyb@mamot.fr.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/skruyb@mamot.fr.atom
rename to test/fixtures/tesla_mock/skruyb@mamot.fr.atom
diff --git a/test/fixtures/httpoison_mock/social.heldscal.la_host_meta b/test/fixtures/tesla_mock/social.heldscal.la_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/social.heldscal.la_host_meta
rename to test/fixtures/tesla_mock/social.heldscal.la_host_meta
diff --git a/test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta b/test/fixtures/tesla_mock/social.sakamoto.gq_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta
rename to test/fixtures/tesla_mock/social.sakamoto.gq_host_meta
diff --git a/test/fixtures/httpoison_mock/social.stopwatchingus-heidelberg.de_host_meta b/test/fixtures/tesla_mock/social.stopwatchingus-heidelberg.de_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/social.stopwatchingus-heidelberg.de_host_meta
rename to test/fixtures/tesla_mock/social.stopwatchingus-heidelberg.de_host_meta
diff --git a/test/fixtures/httpoison_mock/social.wxcafe.net_host_meta b/test/fixtures/tesla_mock/social.wxcafe.net_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/social.wxcafe.net_host_meta
rename to test/fixtures/tesla_mock/social.wxcafe.net_host_meta
diff --git a/test/fixtures/httpoison_mock/spc_5381.atom b/test/fixtures/tesla_mock/spc_5381.atom
similarity index 100%
rename from test/fixtures/httpoison_mock/spc_5381.atom
rename to test/fixtures/tesla_mock/spc_5381.atom
diff --git a/test/fixtures/httpoison_mock/spc_5381_xrd.xml b/test/fixtures/tesla_mock/spc_5381_xrd.xml
similarity index 100%
rename from test/fixtures/httpoison_mock/spc_5381_xrd.xml
rename to test/fixtures/tesla_mock/spc_5381_xrd.xml
diff --git a/test/fixtures/httpoison_mock/squeet.me_host_meta b/test/fixtures/tesla_mock/squeet.me_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/squeet.me_host_meta
rename to test/fixtures/tesla_mock/squeet.me_host_meta
diff --git a/test/fixtures/httpoison_mock/status.alpicola.com_host_meta b/test/fixtures/tesla_mock/status.alpicola.com_host_meta
similarity index 100%
rename from test/fixtures/httpoison_mock/status.alpicola.com_host_meta
rename to test/fixtures/tesla_mock/status.alpicola.com_host_meta
diff --git a/test/fixtures/httpoison_mock/status.emelie.json b/test/fixtures/tesla_mock/status.emelie.json
similarity index 100%
rename from test/fixtures/httpoison_mock/status.emelie.json
rename to test/fixtures/tesla_mock/status.emelie.json
diff --git a/test/fixtures/httpoison_mock/webfinger_emelie.json b/test/fixtures/tesla_mock/webfinger_emelie.json
similarity index 100%
rename from test/fixtures/httpoison_mock/webfinger_emelie.json
rename to test/fixtures/tesla_mock/webfinger_emelie.json
diff --git a/test/fixtures/tesla_mock/wedistribute-article.json b/test/fixtures/tesla_mock/wedistribute-article.json
new file mode 100644
index 000000000..39dc1b982
--- /dev/null
+++ b/test/fixtures/tesla_mock/wedistribute-article.json
@@ -0,0 +1,18 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams"
+ ],
+ "type": "Article",
+ "name": "The end is near: Mastodon plans to drop OStatus support",
+ "content": "\nThe days of OStatus are numbered. The venerable protocol has served as a glue between many different types of servers since the early days of the Fediverse, connecting StatusNet (now GNU Social) to Friendica, Hubzilla, Mastodon, and Pleroma.
\n\n\n\nNow that many fediverse platforms support ActivityPub as a successor protocol, Mastodon appears to be drawing a line in the sand. In a Patreon update , Eugen Rochko writes:
\n\n\n\n...OStatus...has overstayed its welcome in the code...and now that most of the network uses ActivityPub, it's time for it to go.
Eugen Rochko, Mastodon creator \n\n\n\nThe pull request to remove Pubsubhubbub and Salmon, two of the main components of OStatus, has already been merged into Mastodon's master branch.
\n\n\n\nSome projects will be left in the dark as a side effect of this. GNU Social and PostActiv, for example, both only communicate using OStatus. While some discussion exists regarding adopting ActivityPub for GNU Social, and a plugin is in development , it hasn't been formally adopted yet. We just hope that the Free Software Foundation's instance gets updated in time!
\n",
+ "summary": "One of the largest platforms in the federated social web is dropping the protocol that it started with.",
+ "attributedTo": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog",
+ "url": "https://wedistribute.org/2019/07/mastodon-drops-ostatus/",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/followers"
+ ],
+ "id": "https://wedistribute.org/wp-json/pterotype/v1/object/85810",
+ "likes": "https://wedistribute.org/wp-json/pterotype/v1/object/85810/likes",
+ "shares": "https://wedistribute.org/wp-json/pterotype/v1/object/85810/shares"
+}
diff --git a/test/fixtures/tesla_mock/wedistribute-user.json b/test/fixtures/tesla_mock/wedistribute-user.json
new file mode 100644
index 000000000..fe2a15703
--- /dev/null
+++ b/test/fixtures/tesla_mock/wedistribute-user.json
@@ -0,0 +1,31 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers"
+ }
+ ],
+ "type": "Organization",
+ "id": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog",
+ "following": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/following",
+ "followers": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/followers",
+ "liked": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/liked",
+ "inbox": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/inbox",
+ "outbox": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog/outbox",
+ "name": "We Distribute",
+ "preferredUsername": "blog",
+ "summary": "Connecting many threads in the federated web. We Distribute is an independent publication dedicated to the fediverse, decentralization, P2P technologies, and Free Software!
",
+ "url": "https://wedistribute.org/",
+ "publicKey": {
+ "id": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog#publicKey",
+ "owner": "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1bmUJ+y8PS8JFVi0KugN\r\nFl4pLvLog3V2lsV9ftmCXpveB/WJx66Tr1fQLsU3qYvQFc8UPGWD52zV4RENR1SN\r\nx0O6T2f97KUbRM+Ckow7Jyjtssgl+Mqq8UBZQ/+H8I/1Vpvt5E5hUykhFgwzx9qg\r\nzoIA3OK7alOpQbSoKXo0QcOh6yTRUnMSRMJAgUoZJzzXI/FmH/DtKr7ziQ1T2KWs\r\nVs8mWnTb/OlCxiheLuMlmJNMF+lPyVthvMIxF6Z5gV9d5QAmASSCI628e6uH2EUF\r\nDEEF5jo+Z5ffeNv28953lrnM+VB/wTjl3tYA+zCQeAmUPksX3E+YkXGxj+4rxBAY\r\n8wIDAQAB\r\n-----END PUBLIC KEY-----"
+ },
+ "manuallyApprovesFollowers": false,
+ "icon": {
+ "url": "https://wedistribute.org/wp-content/uploads/2019/02/b067de423757a538.png",
+ "type": "Image",
+ "mediaType": "image/png"
+ }
+}
diff --git a/test/fixtures/httpoison_mock/winterdienst_webfinger.json b/test/fixtures/tesla_mock/winterdienst_webfinger.json
similarity index 100%
rename from test/fixtures/httpoison_mock/winterdienst_webfinger.json
rename to test/fixtures/tesla_mock/winterdienst_webfinger.json
diff --git a/test/fixtures/users_mock/masto_closed_followers.json b/test/fixtures/users_mock/masto_closed_followers.json
new file mode 100644
index 000000000..da296892d
--- /dev/null
+++ b/test/fixtures/users_mock/masto_closed_followers.json
@@ -0,0 +1,7 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "http://localhost:4001/users/masto_closed/followers",
+ "type": "OrderedCollection",
+ "totalItems": 437,
+ "first": "http://localhost:4001/users/masto_closed/followers?page=1"
+}
diff --git a/test/fixtures/users_mock/masto_closed_followers_page.json b/test/fixtures/users_mock/masto_closed_followers_page.json
new file mode 100644
index 000000000..04ab0c4d3
--- /dev/null
+++ b/test/fixtures/users_mock/masto_closed_followers_page.json
@@ -0,0 +1 @@
+{"@context":"https://www.w3.org/ns/activitystreams","id":"http://localhost:4001/users/masto_closed/followers?page=1","type":"OrderedCollectionPage","totalItems":437,"next":"http://localhost:4001/users/masto_closed/followers?page=2","partOf":"http://localhost:4001/users/masto_closed/followers","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]}
diff --git a/test/fixtures/users_mock/masto_closed_following.json b/test/fixtures/users_mock/masto_closed_following.json
new file mode 100644
index 000000000..146d49f9c
--- /dev/null
+++ b/test/fixtures/users_mock/masto_closed_following.json
@@ -0,0 +1,7 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "http://localhost:4001/users/masto_closed/following",
+ "type": "OrderedCollection",
+ "totalItems": 152,
+ "first": "http://localhost:4001/users/masto_closed/following?page=1"
+}
diff --git a/test/fixtures/users_mock/masto_closed_following_page.json b/test/fixtures/users_mock/masto_closed_following_page.json
new file mode 100644
index 000000000..8d8324699
--- /dev/null
+++ b/test/fixtures/users_mock/masto_closed_following_page.json
@@ -0,0 +1 @@
+{"@context":"https://www.w3.org/ns/activitystreams","id":"http://localhost:4001/users/masto_closed/following?page=1","type":"OrderedCollectionPage","totalItems":152,"next":"http://localhost:4001/users/masto_closed/following?page=2","partOf":"http://localhost:4001/users/masto_closed/following","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]}
diff --git a/test/fixtures/users_mock/pleroma_followers.json b/test/fixtures/users_mock/pleroma_followers.json
new file mode 100644
index 000000000..db71d084b
--- /dev/null
+++ b/test/fixtures/users_mock/pleroma_followers.json
@@ -0,0 +1,20 @@
+{
+ "type": "OrderedCollection",
+ "totalItems": 527,
+ "id": "http://localhost:4001/users/fuser2/followers",
+ "first": {
+ "type": "OrderedCollectionPage",
+ "totalItems": 527,
+ "partOf": "http://localhost:4001/users/fuser2/followers",
+ "orderedItems": [],
+ "next": "http://localhost:4001/users/fuser2/followers?page=2",
+ "id": "http://localhost:4001/users/fuser2/followers?page=1"
+ },
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ]
+}
diff --git a/test/fixtures/users_mock/pleroma_following.json b/test/fixtures/users_mock/pleroma_following.json
new file mode 100644
index 000000000..33d087703
--- /dev/null
+++ b/test/fixtures/users_mock/pleroma_following.json
@@ -0,0 +1,20 @@
+{
+ "type": "OrderedCollection",
+ "totalItems": 267,
+ "id": "http://localhost:4001/users/fuser2/following",
+ "first": {
+ "type": "OrderedCollectionPage",
+ "totalItems": 267,
+ "partOf": "http://localhost:4001/users/fuser2/following",
+ "orderedItems": [],
+ "next": "http://localhost:4001/users/fuser2/following?page=2",
+ "id": "http://localhost:4001/users/fuser2/following?page=1"
+ },
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ]
+}
diff --git a/test/flake_id_test.exs b/test/flake_id_test.exs
index ca2338041..85ed5bbdf 100644
--- a/test/flake_id_test.exs
+++ b/test/flake_id_test.exs
@@ -39,4 +39,9 @@ defmodule Pleroma.FlakeIdTest do
assert dump(flake_s) == {:ok, flake}
assert dump(flake) == {:ok, flake}
end
+
+ test "is_flake_id?/1" do
+ assert is_flake_id?("9eoozpwTul5mjSEDRI")
+ refute is_flake_id?("http://example.com/activities/3ebbadd1-eb14-4e20-8118-b6f79c0c7b0b")
+ end
end
diff --git a/test/healthcheck_test.exs b/test/healthcheck_test.exs
index e05061220..6bb8d5b7f 100644
--- a/test/healthcheck_test.exs
+++ b/test/healthcheck_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.HealthcheckTest do
use Pleroma.DataCase
alias Pleroma.Healthcheck
diff --git a/test/http/request_builder_test.exs b/test/http/request_builder_test.exs
new file mode 100644
index 000000000..7febe84c5
--- /dev/null
+++ b/test/http/request_builder_test.exs
@@ -0,0 +1,95 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.RequestBuilderTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.HTTP.RequestBuilder
+
+ describe "headers/2" do
+ test "don't send pleroma user agent" do
+ assert RequestBuilder.headers(%{}, []) == %{headers: []}
+ end
+
+ test "send pleroma user agent" do
+ send = Pleroma.Config.get([:http, :send_user_agent])
+ Pleroma.Config.put([:http, :send_user_agent], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:http, :send_user_agent], send)
+ end)
+
+ assert RequestBuilder.headers(%{}, []) == %{
+ headers: [{"User-Agent", Pleroma.Application.user_agent()}]
+ }
+ end
+ end
+
+ describe "add_optional_params/3" do
+ test "don't add if keyword is empty" do
+ assert RequestBuilder.add_optional_params(%{}, %{}, []) == %{}
+ end
+
+ test "add query parameter" do
+ assert RequestBuilder.add_optional_params(
+ %{},
+ %{query: :query, body: :body, another: :val},
+ [
+ {:query, "param1=val1¶m2=val2"},
+ {:body, "some body"}
+ ]
+ ) == %{query: "param1=val1¶m2=val2", body: "some body"}
+ end
+ end
+
+ describe "add_param/4" do
+ test "add file parameter" do
+ %{
+ body: %Tesla.Multipart{
+ boundary: _,
+ content_type_params: [],
+ parts: [
+ %Tesla.Multipart.Part{
+ body: %File.Stream{
+ line_or_bytes: 2048,
+ modes: [:raw, :read_ahead, :read, :binary],
+ path: "some-path/filename.png",
+ raw: true
+ },
+ dispositions: [name: "filename.png", filename: "filename.png"],
+ headers: []
+ }
+ ]
+ }
+ } = RequestBuilder.add_param(%{}, :file, "filename.png", "some-path/filename.png")
+ end
+
+ test "add key to body" do
+ %{
+ body: %Tesla.Multipart{
+ boundary: _,
+ content_type_params: [],
+ parts: [
+ %Tesla.Multipart.Part{
+ body: "\"someval\"",
+ dispositions: [name: "somekey"],
+ headers: ["Content-Type": "application/json"]
+ }
+ ]
+ }
+ } = RequestBuilder.add_param(%{}, :body, "somekey", "someval")
+ end
+
+ test "add form parameter" do
+ assert RequestBuilder.add_param(%{}, :form, "somename", "someval") == %{
+ body: %{"somename" => "someval"}
+ }
+ end
+
+ test "add for location" do
+ assert RequestBuilder.add_param(%{}, :some_location, "somekey", "someval") == %{
+ some_location: [{"somekey", "someval"}]
+ }
+ end
+ end
+end
diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs
index a604713d8..3975cdcd6 100644
--- a/test/integration/mastodon_websocket_test.exs
+++ b/test/integration/mastodon_websocket_test.exs
@@ -107,5 +107,12 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification")
end
+
+ test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
+ assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
+
+ assert {:error, {403, "Forbidden"}} =
+ start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
+ end
end
end
diff --git a/test/keys_test.exs b/test/keys_test.exs
index 776fdea6f..059f70b74 100644
--- a/test/keys_test.exs
+++ b/test/keys_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.KeysTest do
use Pleroma.DataCase
diff --git a/test/list_test.exs b/test/list_test.exs
index 1909c0cd9..f39033d02 100644
--- a/test/list_test.exs
+++ b/test/list_test.exs
@@ -113,4 +113,30 @@ defmodule Pleroma.ListTest do
assert owned_list in lists_2
refute not_owned_list in lists_2
end
+
+ test "get by ap_id" do
+ user = insert(:user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+ assert Pleroma.List.get_by_ap_id(list.ap_id) == list
+ end
+
+ test "memberships" do
+ user = insert(:user)
+ member = insert(:user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+ {:ok, list} = Pleroma.List.follow(list, member)
+
+ assert Pleroma.List.memberships(member) == [list.ap_id]
+ end
+
+ test "member?" do
+ user = insert(:user)
+ member = insert(:user)
+
+ {:ok, list} = Pleroma.List.create("foo", user)
+ {:ok, list} = Pleroma.List.follow(list, member)
+
+ assert Pleroma.List.member?(list, member)
+ refute Pleroma.List.member?(list, user)
+ end
end
diff --git a/test/mix/tasks/pleroma.digest_test.exs b/test/mix/tasks/pleroma.digest_test.exs
new file mode 100644
index 000000000..595f64ed7
--- /dev/null
+++ b/test/mix/tasks/pleroma.digest_test.exs
@@ -0,0 +1,51 @@
+defmodule Mix.Tasks.Pleroma.DigestTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+ import Swoosh.TestAssertions
+
+ alias Pleroma.Web.CommonAPI
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "pleroma.digest test" do
+ test "Sends digest to the given user" do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ Enum.each(0..10, fn i ->
+ {:ok, _activity} =
+ CommonAPI.post(user1, %{
+ "status" => "hey ##{i} @#{user2.nickname}!"
+ })
+ end)
+
+ yesterday =
+ NaiveDateTime.add(
+ NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ -60 * 60 * 24,
+ :second
+ )
+
+ {:ok, yesterday_date} = Timex.format(yesterday, "%F", :strftime)
+
+ :ok = Mix.Tasks.Pleroma.Digest.run(["test", user2.nickname, yesterday_date])
+
+ assert_receive {:mix_shell, :info, [message]}
+ assert message =~ "Digest email have been sent"
+
+ assert_email_sent(
+ to: {user2.name, user2.email},
+ html_body: ~r/new mentions:/i
+ )
+ end
+ end
+end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 1d36f14bf..80ea2a085 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -4,13 +4,15 @@
defmodule Pleroma.NotificationTest do
use Pleroma.DataCase
+
+ import Pleroma.Factory
+
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Streamer
alias Pleroma.Web.TwitterAPI.TwitterAPI
- import Pleroma.Factory
describe "create_notifications" do
test "notifies someone when they are directly addressed" do
@@ -42,6 +44,28 @@ defmodule Pleroma.NotificationTest do
assert notification.user_id == subscriber.id
end
+
+ test "does not create a notification for subscribed users if status is a reply" do
+ user = insert(:user)
+ other_user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, other_user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ {:ok, _reply_activity} =
+ CommonAPI.post(other_user, %{
+ "status" => "test reply",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ user_notifications = Notification.for_user(user)
+ assert length(user_notifications) == 1
+
+ subscriber_notifications = Notification.for_user(subscriber)
+ assert Enum.empty?(subscriber_notifications)
+ end
end
describe "create_notification" do
@@ -74,26 +98,37 @@ defmodule Pleroma.NotificationTest do
Task.await(task_user_notification)
end
- test "it doesn't create a notification for user if the user blocks the activity author" do
+ test "it creates a notification for user if the user blocks the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user)
{:ok, user} = User.block(user, author)
- refute Notification.create_notification(activity, user)
+ assert Notification.create_notification(activity, user)
end
- test "it doesn't create a notificatin for the user if the user mutes the activity author" do
+ test "it creates a notificatin for the user if the user mutes the activity author" do
muter = insert(:user)
muted = insert(:user)
{:ok, _} = User.mute(muter, muted)
muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
- refute Notification.create_notification(activity, muter)
+ assert Notification.create_notification(activity, muter)
end
- test "it doesn't create a notification for an activity from a muted thread" do
+ test "notification created if user is muted without notifications" do
+ muter = insert(:user)
+ muted = insert(:user)
+
+ {:ok, muter} = User.mute(muter, muted, false)
+
+ {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
+
+ assert Notification.create_notification(activity, muter)
+ end
+
+ test "it creates a notification for an activity from a muted thread" do
muter = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
@@ -105,7 +140,7 @@ defmodule Pleroma.NotificationTest do
"in_reply_to_status_id" => activity.id
})
- refute Notification.create_notification(activity, muter)
+ assert Notification.create_notification(activity, muter)
end
test "it disables notifications from followers" do
@@ -319,6 +354,51 @@ defmodule Pleroma.NotificationTest do
end
end
+ describe "for_user_since/2" do
+ defp days_ago(days) do
+ NaiveDateTime.add(
+ NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ -days * 60 * 60 * 24,
+ :second
+ )
+ end
+
+ test "Returns recent notifications" do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ Enum.each(0..10, fn i ->
+ {:ok, _activity} =
+ CommonAPI.post(user1, %{
+ "status" => "hey ##{i} @#{user2.nickname}!"
+ })
+ end)
+
+ {old, new} = Enum.split(Notification.for_user(user2), 5)
+
+ Enum.each(old, fn notification ->
+ notification
+ |> cast(%{updated_at: days_ago(10)}, [:updated_at])
+ |> Pleroma.Repo.update!()
+ end)
+
+ recent_notifications_ids =
+ user2
+ |> Notification.for_user_since(
+ NaiveDateTime.add(NaiveDateTime.utc_now(), -5 * 86_400, :second)
+ )
+ |> Enum.map(& &1.id)
+
+ Enum.each(old, fn %{id: id} ->
+ refute id in recent_notifications_ids
+ end)
+
+ Enum.each(new, fn %{id: id} ->
+ assert id in recent_notifications_ids
+ end)
+ end
+ end
+
describe "notification target determination" do
test "it sends notifications to addressed users in new messages" do
user = insert(:user)
@@ -531,5 +611,157 @@ defmodule Pleroma.NotificationTest do
assert Enum.empty?(Notification.for_user(user))
end
+
+ test "notifications are deleted if a local user is deleted" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"})
+
+ refute Enum.empty?(Notification.for_user(other_user))
+
+ User.delete(user)
+
+ assert Enum.empty?(Notification.for_user(other_user))
+ end
+
+ test "notifications are deleted if a remote user is deleted" do
+ remote_user = insert(:user)
+ local_user = insert(:user)
+
+ dm_message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "actor" => remote_user.ap_id,
+ "id" => remote_user.ap_id <> "/activities/test",
+ "to" => [local_user.ap_id],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "content" => "Hello!",
+ "tag" => [
+ %{
+ "type" => "Mention",
+ "href" => local_user.ap_id,
+ "name" => "@#{local_user.nickname}"
+ }
+ ],
+ "to" => [local_user.ap_id],
+ "cc" => [],
+ "attributedTo" => remote_user.ap_id
+ }
+ }
+
+ {:ok, _dm_activity} = Transmogrifier.handle_incoming(dm_message)
+
+ refute Enum.empty?(Notification.for_user(local_user))
+
+ delete_user_message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => remote_user.ap_id <> "/activities/delete",
+ "actor" => remote_user.ap_id,
+ "type" => "Delete",
+ "object" => remote_user.ap_id
+ }
+
+ {:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message)
+
+ assert Enum.empty?(Notification.for_user(local_user))
+ end
+ end
+
+ describe "for_user" do
+ test "it returns notifications for muted user without notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted, false)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user)) == 1
+ end
+
+ test "it doesn't return notifications for muted user with notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for blocked user" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notificatitons for blocked domain" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for muted thread" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} =
+ TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert Notification.for_user(user) == []
+ end
+
+ test "it returns notifications for muted user with notifications and with_muted parameter" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for blocked user and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notificatitons for blocked domain and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for muted thread with_muted parameter" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} =
+ TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
end
end
diff --git a/test/object/containment_test.exs b/test/object/containment_test.exs
index a860355b8..61cd1b412 100644
--- a/test/object/containment_test.exs
+++ b/test/object/containment_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Object.ContainmentTest do
use Pleroma.DataCase
@@ -64,4 +68,34 @@ defmodule Pleroma.Object.ContainmentTest do
"[error] Could not decode user at fetch https://n1u.moe/users/rye, {:error, :error}"
end
end
+
+ describe "containment of children" do
+ test "contain_child() catches spoofing attempts" do
+ data = %{
+ "id" => "http://example.com/whatever",
+ "type" => "Create",
+ "object" => %{
+ "id" => "http://example.net/~alyssa/activities/1234",
+ "attributedTo" => "http://example.org/~alyssa"
+ },
+ "actor" => "http://example.com/~bob"
+ }
+
+ :error = Containment.contain_child(data)
+ end
+
+ test "contain_child() allows correct origins" do
+ data = %{
+ "id" => "http://example.org/~alyssa/activities/5678",
+ "type" => "Create",
+ "object" => %{
+ "id" => "http://example.org/~alyssa/activities/1234",
+ "attributedTo" => "http://example.org/~alyssa"
+ },
+ "actor" => "http://example.org/~alyssa"
+ }
+
+ :ok = Containment.contain_child(data)
+ end
+ end
end
diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs
index 26dc9496d..0ca87f035 100644
--- a/test/object/fetcher_test.exs
+++ b/test/object/fetcher_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Object.FetcherTest do
use Pleroma.DataCase
@@ -5,6 +9,7 @@ defmodule Pleroma.Object.FetcherTest do
alias Pleroma.Object
alias Pleroma.Object.Fetcher
import Tesla.Mock
+ import Mock
setup do
mock(fn
@@ -22,16 +27,31 @@ defmodule Pleroma.Object.FetcherTest do
end
describe "actor origin containment" do
- test "it rejects objects with a bogus origin" do
+ test_with_mock "it rejects objects with a bogus origin",
+ Pleroma.Web.OStatus,
+ [:passthrough],
+ [] do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
+
+ refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
end
- test "it rejects objects when attributedTo is wrong (variant 1)" do
+ test_with_mock "it rejects objects when attributedTo is wrong (variant 1)",
+ Pleroma.Web.OStatus,
+ [:passthrough],
+ [] do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
+
+ refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
end
- test "it rejects objects when attributedTo is wrong (variant 2)" do
+ test_with_mock "it rejects objects when attributedTo is wrong (variant 2)",
+ Pleroma.Web.OStatus,
+ [:passthrough],
+ [] do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
+
+ refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
end
end
@@ -90,6 +110,13 @@ defmodule Pleroma.Object.FetcherTest do
assert object
end
+ test "it can fetch wedistribute articles" do
+ {:ok, object} =
+ Fetcher.fetch_object_from_id("https://wedistribute.org/wp-json/pterotype/v1/object/85810")
+
+ assert object
+ end
+
test "all objects with fake directions are rejected by the object fetcher" do
assert {:error, _} =
Fetcher.fetch_and_contain_remote_object_from_id(
@@ -130,4 +157,34 @@ defmodule Pleroma.Object.FetcherTest do
assert object.id != object_two.id
end
end
+
+ describe "signed fetches" do
+ test_with_mock "it signs fetches when configured to do so",
+ Pleroma.Signature,
+ [:passthrough],
+ [] do
+ option = Pleroma.Config.get([:activitypub, :sign_object_fetches])
+ Pleroma.Config.put([:activitypub, :sign_object_fetches], true)
+
+ Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+ assert called(Pleroma.Signature.sign(:_, :_))
+
+ Pleroma.Config.put([:activitypub, :sign_object_fetches], option)
+ end
+
+ test_with_mock "it doesn't sign fetches when not configured to do so",
+ Pleroma.Signature,
+ [:passthrough],
+ [] do
+ option = Pleroma.Config.get([:activitypub, :sign_object_fetches])
+ Pleroma.Config.put([:activitypub, :sign_object_fetches], false)
+
+ Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+ refute called(Pleroma.Signature.sign(:_, :_))
+
+ Pleroma.Config.put([:activitypub, :sign_object_fetches], option)
+ end
+ end
end
diff --git a/test/plugs/authentication_plug_test.exs b/test/plugs/authentication_plug_test.exs
index 6158086ea..f7f8fd9f3 100644
--- a/test/plugs/authentication_plug_test.exs
+++ b/test/plugs/authentication_plug_test.exs
@@ -8,6 +8,8 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
+ import ExUnit.CaptureLog
+
setup %{conn: conn} do
user = %User{
id: 1,
@@ -54,4 +56,31 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
assert conn == ret_conn
end
+
+ describe "checkpw/2" do
+ test "check pbkdf2 hash" do
+ hash =
+ "$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
+
+ assert AuthenticationPlug.checkpw("test-password", hash)
+ refute AuthenticationPlug.checkpw("test-password1", hash)
+ end
+
+ @tag :skip_on_mac
+ test "check sha512-crypt hash" do
+ hash =
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+
+ assert AuthenticationPlug.checkpw("password", hash)
+ end
+
+ test "it returns false when hash invalid" do
+ hash =
+ "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+
+ assert capture_log(fn ->
+ refute Pleroma.Plugs.AuthenticationPlug.checkpw("password", hash)
+ end) =~ "[error] Password hash not recognized"
+ end
+ end
end
diff --git a/test/plugs/http_signature_plug_test.exs b/test/plugs/http_signature_plug_test.exs
index efd811df7..d6fd9ea81 100644
--- a/test/plugs/http_signature_plug_test.exs
+++ b/test/plugs/http_signature_plug_test.exs
@@ -26,22 +26,4 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
assert called(HTTPSignatures.validate_conn(:_))
end
end
-
- test "bails out early if the signature isn't by the activity actor" do
- params = %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"}
- conn = build_conn(:get, "/doesntmattter", params)
-
- with_mock HTTPSignatures, validate_conn: fn _ -> false end do
- conn =
- conn
- |> put_req_header(
- "signature",
- "keyId=\"http://mastodon.example.org/users/admin#main-key"
- )
- |> HTTPSignaturePlug.call(%{})
-
- assert conn.assigns.valid_signature == false
- refute called(HTTPSignatures.validate_conn(:_))
- end
- end
end
diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs
index 02f530058..9804e073b 100644
--- a/test/plugs/legacy_authentication_plug_test.exs
+++ b/test/plugs/legacy_authentication_plug_test.exs
@@ -5,19 +5,18 @@
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
use Pleroma.Web.ConnCase
+ import Pleroma.Factory
+
alias Pleroma.Plugs.LegacyAuthenticationPlug
alias Pleroma.User
- import Mock
-
setup do
- # password is "password"
- user = %User{
- id: 1,
- name: "dude",
- password_hash:
- "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
- }
+ user =
+ insert(:user,
+ password: "password",
+ password_hash:
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+ )
%{user: user}
end
@@ -36,6 +35,7 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
assert ret_conn == conn
end
+ @tag :skip_on_mac
test "it authenticates the auth_user if present and password is correct and resets the password",
%{
conn: conn,
@@ -46,22 +46,12 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
|> assign(:auth_credentials, %{username: "dude", password: "password"})
|> assign(:auth_user, user)
- conn =
- with_mocks([
- {:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
- {User, [],
- [
- reset_password: fn user, %{password: password, password_confirmation: password} ->
- {:ok, user}
- end
- ]}
- ]) do
- LegacyAuthenticationPlug.call(conn, %{})
- end
+ conn = LegacyAuthenticationPlug.call(conn, %{})
- assert conn.assigns.user == user
+ assert conn.assigns.user.id == user.id
end
+ @tag :skip_on_mac
test "it does nothing if the password is wrong", %{
conn: conn,
user: user
diff --git a/test/plugs/mapped_identity_to_signature_plug_test.exs b/test/plugs/mapped_identity_to_signature_plug_test.exs
new file mode 100644
index 000000000..bb45d9edf
--- /dev/null
+++ b/test/plugs/mapped_identity_to_signature_plug_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
+ use Pleroma.Web.ConnCase
+ alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug
+
+ import Tesla.Mock
+ import Plug.Conn
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ defp set_signature(conn, key_id) do
+ conn
+ |> put_req_header("signature", "keyId=\"#{key_id}\"")
+ |> assign(:valid_signature, true)
+ end
+
+ test "it successfully maps a valid identity with a valid signature" do
+ conn =
+ build_conn(:get, "/doesntmattter")
+ |> set_signature("http://mastodon.example.org/users/admin")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ refute is_nil(conn.assigns.user)
+ end
+
+ test "it successfully maps a valid identity with a valid signature with payload" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ |> set_signature("http://mastodon.example.org/users/admin")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ refute is_nil(conn.assigns.user)
+ end
+
+ test "it considers a mapped identity to be invalid when it mismatches a payload" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ |> set_signature("https://niu.moe/users/rye")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ assert %{valid_signature: false} == conn.assigns
+ end
+
+ @tag skip: "known breakage; the testsuite presently depends on it"
+ test "it considers a mapped identity to be invalid when the identity cannot be found" do
+ conn =
+ build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ |> set_signature("http://niu.moe/users/rye")
+ |> MappedSignatureToIdentityPlug.call(%{})
+
+ assert %{valid_signature: false} == conn.assigns
+ end
+end
diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs
index b8d6aff89..395095079 100644
--- a/test/plugs/rate_limiter_test.exs
+++ b/test/plugs/rate_limiter_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Plugs.RateLimiterTest do
use ExUnit.Case, async: true
use Plug.Test
@@ -6,12 +10,13 @@ defmodule Pleroma.Plugs.RateLimiterTest do
import Pleroma.Factory
- @limiter_name :testing
+ # Note: each example must work with separate buckets in order to prevent concurrency issues
test "init/1" do
- Pleroma.Config.put([:rate_limit, @limiter_name], {1, 1})
+ limiter_name = :test_init
+ Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
- assert {@limiter_name, {1, 1}} == RateLimiter.init(@limiter_name)
+ assert {limiter_name, {1, 1}, []} == RateLimiter.init(limiter_name)
assert nil == RateLimiter.init(:foo)
end
@@ -20,14 +25,15 @@ defmodule Pleroma.Plugs.RateLimiterTest do
end
test "it restricts by opts" do
+ limiter_name = :test_opts
scale = 1000
limit = 5
- Pleroma.Config.put([:rate_limit, @limiter_name], {scale, limit})
+ Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
- opts = RateLimiter.init(@limiter_name)
+ opts = RateLimiter.init(limiter_name)
conn = conn(:get, "/")
- bucket_name = "#{@limiter_name}:#{RateLimiter.ip(conn)}"
+ bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
conn = RateLimiter.call(conn, opts)
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
@@ -61,18 +67,78 @@ defmodule Pleroma.Plugs.RateLimiterTest do
refute conn.halted
end
+ test "`bucket_name` option overrides default bucket name" do
+ limiter_name = :test_bucket_name
+ scale = 1000
+ limit = 5
+
+ Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
+ base_bucket_name = "#{limiter_name}:group1"
+ opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name})
+
+ conn = conn(:get, "/")
+ default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
+ customized_bucket_name = "#{base_bucket_name}:#{RateLimiter.ip(conn)}"
+
+ RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(customized_bucket_name, scale, limit)
+ assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
+ end
+
+ test "`params` option appends specified params' values to bucket name" do
+ limiter_name = :test_params
+ scale = 1000
+ limit = 5
+
+ Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
+ opts = RateLimiter.init({limiter_name, params: ["id"]})
+ id = "1"
+
+ conn = conn(:get, "/?id=#{id}")
+ conn = Plug.Conn.fetch_query_params(conn)
+
+ default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
+ parametrized_bucket_name = "#{limiter_name}:#{id}:#{RateLimiter.ip(conn)}"
+
+ RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit)
+ assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
+ end
+
+ test "it supports combination of options modifying bucket name" do
+ limiter_name = :test_options_combo
+ scale = 1000
+ limit = 5
+
+ Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
+ base_bucket_name = "#{limiter_name}:group1"
+ opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name, params: ["id"]})
+ id = "100"
+
+ conn = conn(:get, "/?id=#{id}")
+ conn = Plug.Conn.fetch_query_params(conn)
+
+ default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
+ parametrized_bucket_name = "#{base_bucket_name}:#{id}:#{RateLimiter.ip(conn)}"
+
+ RateLimiter.call(conn, opts)
+ assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit)
+ assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
+ end
+
test "optional limits for authenticated users" do
+ limiter_name = :test_authenticated
Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
scale = 1000
limit = 5
- Pleroma.Config.put([:rate_limit, @limiter_name], [{1, 10}, {scale, limit}])
+ Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {scale, limit}])
- opts = RateLimiter.init(@limiter_name)
+ opts = RateLimiter.init(limiter_name)
user = insert(:user)
conn = conn(:get, "/") |> assign(:user, user)
- bucket_name = "#{@limiter_name}:#{user.id}"
+ bucket_name = "#{limiter_name}:#{user.id}"
conn = RateLimiter.call(conn, opts)
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
diff --git a/test/plugs/set_format_plug_test.exs b/test/plugs/set_format_plug_test.exs
new file mode 100644
index 000000000..bb21956bb
--- /dev/null
+++ b/test/plugs/set_format_plug_test.exs
@@ -0,0 +1,38 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.SetFormatPlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Plugs.SetFormatPlug
+
+ test "set format from params" do
+ conn =
+ :get
+ |> conn("/cofe?_format=json")
+ |> SetFormatPlug.call([])
+
+ assert %{format: "json"} == conn.assigns
+ end
+
+ test "set format from header" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_private(:phoenix_format, "xml")
+ |> SetFormatPlug.call([])
+
+ assert %{format: "xml"} == conn.assigns
+ end
+
+ test "doesn't set format" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> SetFormatPlug.call([])
+
+ refute conn.assigns[:format]
+ end
+end
diff --git a/test/plugs/set_locale_plug_test.exs b/test/plugs/set_locale_plug_test.exs
new file mode 100644
index 000000000..b6c4c1cea
--- /dev/null
+++ b/test/plugs/set_locale_plug_test.exs
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.SetLocalePlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Plugs.SetLocalePlug
+ alias Plug.Conn
+
+ test "default locale is `en`" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> SetLocalePlug.call([])
+
+ assert "en" == Gettext.get_locale()
+ assert %{locale: "en"} == conn.assigns
+ end
+
+ test "use supported locale from `accept-language`" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "ru" == Gettext.get_locale()
+ assert %{locale: "ru"} == conn.assigns
+ end
+
+ test "use default locale if locale from `accept-language` is not supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header("accept-language", "tlh")
+ |> SetLocalePlug.call([])
+
+ assert "en" == Gettext.get_locale()
+ assert %{locale: "en"} == conn.assigns
+ end
+end
diff --git a/test/repo_test.exs b/test/repo_test.exs
index 85085a1fa..85b64d4d1 100644
--- a/test/repo_test.exs
+++ b/test/repo_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.RepoTest do
use Pleroma.DataCase
import Pleroma.Factory
diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy_test.exs
new file mode 100644
index 000000000..f4b7d6add
--- /dev/null
+++ b/test/reverse_proxy_test.exs
@@ -0,0 +1,300 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxyTest do
+ use Pleroma.Web.ConnCase, async: true
+ import ExUnit.CaptureLog
+ import Mox
+ alias Pleroma.ReverseProxy
+ alias Pleroma.ReverseProxy.ClientMock
+
+ setup_all do
+ {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.ReverseProxy.ClientMock)
+ :ok
+ end
+
+ setup :verify_on_exit!
+
+ defp user_agent_mock(user_agent, invokes) do
+ json = Jason.encode!(%{"user-agent": user_agent})
+
+ ClientMock
+ |> expect(:request, fn :get, url, _, _, _ ->
+ Registry.register(Pleroma.ReverseProxy.ClientMock, url, 0)
+
+ {:ok, 200,
+ [
+ {"content-type", "application/json"},
+ {"content-length", byte_size(json) |> to_string()}
+ ], %{url: url}}
+ end)
+ |> expect(:stream_body, invokes, fn %{url: url} ->
+ case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+ [{_, 0}] ->
+ Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+ {:ok, json}
+
+ [{_, 1}] ->
+ Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+ :done
+ end
+ end)
+ end
+
+ describe "user-agent" do
+ test "don't keep", %{conn: conn} do
+ user_agent_mock("hackney/1.15.1", 2)
+ conn = ReverseProxy.call(conn, "/user-agent")
+ assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
+ end
+
+ test "keep", %{conn: conn} do
+ user_agent_mock(Pleroma.Application.user_agent(), 2)
+ conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
+ assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
+ end
+ end
+
+ test "closed connection", %{conn: conn} do
+ ClientMock
+ |> expect(:request, fn :get, "/closed", _, _, _ -> {:ok, 200, [], %{}} end)
+ |> expect(:stream_body, fn _ -> {:error, :closed} end)
+ |> expect(:close, fn _ -> :ok end)
+
+ conn = ReverseProxy.call(conn, "/closed")
+ assert conn.halted
+ end
+
+ describe "max_body " do
+ test "length returns error if content-length more than option", %{conn: conn} do
+ user_agent_mock("hackney/1.15.1", 0)
+
+ assert capture_log(fn ->
+ ReverseProxy.call(conn, "/user-agent", max_body_length: 4)
+ end) =~
+ "[error] Elixir.Pleroma.ReverseProxy: request to \"/user-agent\" failed: :body_too_large"
+ end
+
+ defp stream_mock(invokes, with_close? \\ false) do
+ ClientMock
+ |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
+ Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
+
+ {:ok, 200, [{"content-type", "application/octet-stream"}],
+ %{url: "/stream-bytes/" <> length}}
+ end)
+ |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} ->
+ max = String.to_integer(length)
+
+ case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
+ [{_, current}] when current < max ->
+ Registry.update_value(
+ Pleroma.ReverseProxy.ClientMock,
+ "/stream-bytes/" <> length,
+ &(&1 + 10)
+ )
+
+ {:ok, "0123456789"}
+
+ [{_, ^max}] ->
+ Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
+ :done
+ end
+ end)
+
+ if with_close? do
+ expect(ClientMock, :close, fn _ -> :ok end)
+ end
+ end
+
+ test "max_body_size returns error if streaming body more than that option", %{conn: conn} do
+ stream_mock(3, true)
+
+ assert capture_log(fn ->
+ ReverseProxy.call(conn, "/stream-bytes/50", max_body_size: 30)
+ end) =~
+ "[warn] Elixir.Pleroma.ReverseProxy request to /stream-bytes/50 failed while reading/chunking: :body_too_large"
+ end
+ end
+
+ describe "HEAD requests" do
+ test "common", %{conn: conn} do
+ ClientMock
+ |> expect(:request, fn :head, "/head", _, _, _ ->
+ {:ok, 200, [{"content-type", "text/html; charset=utf-8"}]}
+ end)
+
+ conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
+ assert html_response(conn, 200) == ""
+ end
+ end
+
+ defp error_mock(status) when is_integer(status) do
+ ClientMock
+ |> expect(:request, fn :get, "/status/" <> _, _, _, _ ->
+ {:error, status}
+ end)
+ end
+
+ describe "returns error on" do
+ test "500", %{conn: conn} do
+ error_mock(500)
+
+ capture_log(fn -> ReverseProxy.call(conn, "/status/500") end) =~
+ "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
+ end
+
+ test "400", %{conn: conn} do
+ error_mock(400)
+
+ capture_log(fn -> ReverseProxy.call(conn, "/status/400") end) =~
+ "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
+ end
+
+ test "204", %{conn: conn} do
+ ClientMock
+ |> expect(:request, fn :get, "/status/204", _, _, _ -> {:ok, 204, [], %{}} end)
+
+ capture_log(fn ->
+ conn = ReverseProxy.call(conn, "/status/204")
+ assert conn.resp_body == "Request failed: No Content"
+ assert conn.halted
+ end) =~
+ "[error] Elixir.Pleroma.ReverseProxy: request to \"/status/204\" failed with HTTP status 204"
+ end
+ end
+
+ test "streaming", %{conn: conn} do
+ stream_mock(21)
+ conn = ReverseProxy.call(conn, "/stream-bytes/200")
+ assert conn.state == :chunked
+ assert byte_size(conn.resp_body) == 200
+ assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
+ end
+
+ defp headers_mock(_) do
+ ClientMock
+ |> expect(:request, fn :get, "/headers", headers, _, _ ->
+ Registry.register(Pleroma.ReverseProxy.ClientMock, "/headers", 0)
+ {:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}}
+ end)
+ |> expect(:stream_body, 2, fn %{url: url, headers: headers} ->
+ case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+ [{_, 0}] ->
+ Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+ headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v}
+ {:ok, Jason.encode!(%{headers: headers})}
+
+ [{_, 1}] ->
+ Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+ :done
+ end
+ end)
+
+ :ok
+ end
+
+ describe "keep request headers" do
+ setup [:headers_mock]
+
+ test "header passes", %{conn: conn} do
+ conn =
+ Plug.Conn.put_req_header(
+ conn,
+ "accept",
+ "text/html"
+ )
+ |> ReverseProxy.call("/headers")
+
+ %{"headers" => headers} = json_response(conn, 200)
+ assert headers["Accept"] == "text/html"
+ end
+
+ test "header is filtered", %{conn: conn} do
+ conn =
+ Plug.Conn.put_req_header(
+ conn,
+ "accept-language",
+ "en-US"
+ )
+ |> ReverseProxy.call("/headers")
+
+ %{"headers" => headers} = json_response(conn, 200)
+ refute headers["Accept-Language"]
+ end
+ end
+
+ test "returns 400 on non GET, HEAD requests", %{conn: conn} do
+ conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
+ assert conn.status == 400
+ end
+
+ describe "cache resp headers" do
+ test "returns headers", %{conn: conn} do
+ ClientMock
+ |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
+ {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
+ end)
+ |> expect(:stream_body, fn _ -> :done end)
+
+ conn = ReverseProxy.call(conn, "/cache/10")
+ assert {"cache-control", "public, max-age=10"} in conn.resp_headers
+ end
+
+ test "add cache-control", %{conn: conn} do
+ ClientMock
+ |> expect(:request, fn :get, "/cache", _, _, _ ->
+ {:ok, 200, [{"ETag", "some ETag"}], %{}}
+ end)
+ |> expect(:stream_body, fn _ -> :done end)
+
+ conn = ReverseProxy.call(conn, "/cache")
+ assert {"cache-control", "public"} in conn.resp_headers
+ end
+ end
+
+ defp disposition_headers_mock(headers) do
+ ClientMock
+ |> expect(:request, fn :get, "/disposition", _, _, _ ->
+ Registry.register(Pleroma.ReverseProxy.ClientMock, "/disposition", 0)
+
+ {:ok, 200, headers, %{url: "/disposition"}}
+ end)
+ |> expect(:stream_body, 2, fn %{url: "/disposition"} ->
+ case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/disposition") do
+ [{_, 0}] ->
+ Registry.update_value(Pleroma.ReverseProxy.ClientMock, "/disposition", &(&1 + 1))
+ {:ok, ""}
+
+ [{_, 1}] ->
+ Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/disposition")
+ :done
+ end
+ end)
+ end
+
+ describe "response content disposition header" do
+ test "not atachment", %{conn: conn} do
+ disposition_headers_mock([
+ {"content-type", "image/gif"},
+ {"content-length", 0}
+ ])
+
+ conn = ReverseProxy.call(conn, "/disposition")
+
+ assert {"content-type", "image/gif"} in conn.resp_headers
+ end
+
+ test "with content-disposition header", %{conn: conn} do
+ disposition_headers_mock([
+ {"content-disposition", "attachment; filename=\"filename.jpg\""},
+ {"content-length", 0}
+ ])
+
+ conn = ReverseProxy.call(conn, "/disposition")
+
+ assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
+ end
+ end
+end
diff --git a/test/signature_test.exs b/test/signature_test.exs
new file mode 100644
index 000000000..26337eaf9
--- /dev/null
+++ b/test/signature_test.exs
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.SignatureTest do
+ use Pleroma.DataCase
+
+ import ExUnit.CaptureLog
+ import Pleroma.Factory
+ import Tesla.Mock
+
+ alias Pleroma.Signature
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ @private_key "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA48qb4v6kqigZutO9Ot0wkp27GIF2LiVaADgxQORZozZR63jH\nTaoOrS3Xhngbgc8SSOhfXET3omzeCLqaLNfXnZ8OXmuhJfJSU6mPUvmZ9QdT332j\nfN/g3iWGhYMf/M9ftCKh96nvFVO/tMruzS9xx7tkrfJjehdxh/3LlJMMImPtwcD7\nkFXwyt1qZTAU6Si4oQAJxRDQXHp1ttLl3Ob829VM7IKkrVmY8TD+JSlV0jtVJPj6\n1J19ytKTx/7UaucYvb9HIiBpkuiy5n/irDqKLVf5QEdZoNCdojOZlKJmTLqHhzKP\n3E9TxsUjhrf4/EqegNc/j982RvOxeu4i40zMQwIDAQABAoIBAQDH5DXjfh21i7b4\ncXJuw0cqget617CDUhemdakTDs9yH+rHPZd3mbGDWuT0hVVuFe4vuGpmJ8c+61X0\nRvugOlBlavxK8xvYlsqTzAmPgKUPljyNtEzQ+gz0I+3mH2jkin2rL3D+SksZZgKm\nfiYMPIQWB2WUF04gB46DDb2mRVuymGHyBOQjIx3WC0KW2mzfoFUFRlZEF+Nt8Ilw\nT+g/u0aZ1IWoszbsVFOEdghgZET0HEarum0B2Je/ozcPYtwmU10iBANGMKdLqaP/\nj954BPunrUf6gmlnLZKIKklJj0advx0NA+cL79+zeVB3zexRYSA5o9q0WPhiuTwR\n/aedWHnBAoGBAP0sDWBAM1Y4TRAf8ZI9PcztwLyHPzfEIqzbObJJnx1icUMt7BWi\n+/RMOnhrlPGE1kMhOqSxvXYN3u+eSmWTqai2sSH5Hdw2EqnrISSTnwNUPINX7fHH\njEkgmXQ6ixE48SuBZnb4w1EjdB/BA6/sjL+FNhggOc87tizLTkMXmMtTAoGBAOZV\n+wPuAMBDBXmbmxCuDIjoVmgSlgeRunB1SA8RCPAFAiUo3+/zEgzW2Oz8kgI+xVwM\n33XkLKrWG1Orhpp6Hm57MjIc5MG+zF4/YRDpE/KNG9qU1tiz0UD5hOpIU9pP4bR/\ngxgPxZzvbk4h5BfHWLpjlk8UUpgk6uxqfti48c1RAoGBALBOKDZ6HwYRCSGMjUcg\n3NPEUi84JD8qmFc2B7Tv7h2he2ykIz9iFAGpwCIyETQsJKX1Ewi0OlNnD3RhEEAy\nl7jFGQ+mkzPSeCbadmcpYlgIJmf1KN/x7fDTAepeBpCEzfZVE80QKbxsaybd3Dp8\nCfwpwWUFtBxr4c7J+gNhAGe/AoGAPn8ZyqkrPv9wXtyfqFjxQbx4pWhVmNwrkBPi\nZ2Qh3q4dNOPwTvTO8vjghvzIyR8rAZzkjOJKVFgftgYWUZfM5gE7T2mTkBYq8W+U\n8LetF+S9qAM2gDnaDx0kuUTCq7t87DKk6URuQ/SbI0wCzYjjRD99KxvChVGPBHKo\n1DjqMuECgYEAgJGNm7/lJCS2wk81whfy/ttKGsEIkyhPFYQmdGzSYC5aDc2gp1R3\nxtOkYEvdjfaLfDGEa4UX8CHHF+w3t9u8hBtcdhMH6GYb9iv6z0VBTt4A/11HUR49\n3Z7TQ18Iyh3jAUCzFV9IJlLIExq5Y7P4B3ojWFBN607sDCt8BMPbDYs=\n-----END RSA PRIVATE KEY-----"
+
+ @public_key %{
+ "id" => "https://mastodon.social/users/lambadalambda#main-key",
+ "owner" => "https://mastodon.social/users/lambadalambda",
+ "publicKeyPem" =>
+ "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0P/Tq4gb4G/QVuMGbJo\nC/AfMNcv+m7NfrlOwkVzcU47jgESuYI4UtJayissCdBycHUnfVUd9qol+eznSODz\nCJhfJloqEIC+aSnuEPGA0POtWad6DU0E6/Ho5zQn5WAWUwbRQqowbrsm/GHo2+3v\neR5jGenwA6sYhINg/c3QQbksyV0uJ20Umyx88w8+TJuv53twOfmyDWuYNoQ3y5cc\nHKOZcLHxYOhvwg3PFaGfFHMFiNmF40dTXt9K96r7sbzc44iLD+VphbMPJEjkMuf8\nPGEFOBzy8pm3wJZw2v32RNW2VESwMYyqDzwHXGSq1a73cS7hEnc79gXlELsK04L9\nQQIDAQAB\n-----END PUBLIC KEY-----\n"
+ }
+
+ @rsa_public_key {
+ :RSAPublicKey,
+ 24_650_000_183_914_698_290_885_268_529_673_621_967_457_234_469_123_179_408_466_269_598_577_505_928_170_923_974_132_111_403_341_217_239_999_189_084_572_368_839_502_170_501_850_920_051_662_384_964_248_315_257_926_552_945_648_828_895_432_624_227_029_881_278_113_244_073_644_360_744_504_606_177_648_469_825_063_267_913_017_309_199_785_535_546_734_904_379_798_564_556_494_962_268_682_532_371_146_333_972_821_570_577_277_375_020_977_087_539_994_500_097_107_935_618_711_808_260_846_821_077_839_605_098_669_707_417_692_791_905_543_116_911_754_774_323_678_879_466_618_738_207_538_013_885_607_095_203_516_030_057_611_111_308_904_599_045_146_148_350_745_339_208_006_497_478_057_622_336_882_506_112_530_056_970_653_403_292_123_624_453_213_574_011_183_684_739_084_105_206_483_178_943_532_208_537_215_396_831_110_268_758_639_826_369_857,
+ # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+ 65_537
+ }
+
+ defp make_fake_signature(key_id), do: "keyId=\"#{key_id}\""
+
+ defp make_fake_conn(key_id),
+ do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
+
+ describe "fetch_public_key/1" do
+ test "it returns key" do
+ expected_result = {:ok, @rsa_public_key}
+
+ user = insert(:user, %{info: %{source_data: %{"publicKey" => @public_key}}})
+
+ assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
+ end
+
+ test "it returns error when not found user" do
+ assert capture_log(fn ->
+ assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error}
+ end) =~ "[error] Could not decode user"
+ end
+
+ test "it returns error if public key is empty" do
+ user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}})
+
+ assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
+ end
+ end
+
+ describe "refetch_public_key/1" do
+ test "it returns key" do
+ ap_id = "https://mastodon.social/users/lambadalambda"
+
+ assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
+ end
+
+ test "it returns error when not found user" do
+ assert capture_log(fn ->
+ assert Signature.refetch_public_key(make_fake_conn("test-ap_id")) ==
+ {:error, {:error, :ok}}
+ end) =~ "[error] Could not decode user"
+ end
+ end
+
+ describe "sign/2" do
+ test "it returns signature headers" do
+ user =
+ insert(:user, %{
+ ap_id: "https://mastodon.social/users/lambadalambda",
+ info: %{keys: @private_key}
+ })
+
+ assert Signature.sign(
+ user,
+ %{
+ host: "test.test",
+ "content-length": 100
+ }
+ ) ==
+ "keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
+ end
+
+ test "it returns error" do
+ user =
+ insert(:user, %{ap_id: "https://mastodon.social/users/lambadalambda", info: %{keys: ""}})
+
+ assert Signature.sign(
+ user,
+ %{host: "test.test", "content-length": 100}
+ ) == {:error, []}
+ end
+ end
+
+ describe "key_id_to_actor_id/1" do
+ test "it properly deduces the actor id for misskey" do
+ assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
+ "https://example.com/users/1234"
+ end
+
+ test "it properly deduces the actor id for mastodon and pleroma" do
+ assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
+ "https://example.com/users/1234"
+ end
+ end
+end
diff --git a/test/support/builders/user_builder.ex b/test/support/builders/user_builder.ex
index f58e1b0ad..6da16f71a 100644
--- a/test/support/builders/user_builder.ex
+++ b/test/support/builders/user_builder.ex
@@ -9,7 +9,8 @@ defmodule Pleroma.Builders.UserBuilder do
nickname: "testname",
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
bio: "A tester.",
- ap_id: "some id"
+ ap_id: "some id",
+ last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
}
Map.merge(user, data)
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index df260bd3f..f3d98e7e3 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -42,19 +42,18 @@ defmodule Pleroma.DataCase do
:ok
end
- def ensure_local_uploader(_context) do
+ def ensure_local_uploader(context) do
+ test_uploader = Map.get(context, :uploader, Pleroma.Uploaders.Local)
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
filters = Pleroma.Config.get([Pleroma.Upload, :filters])
- unless uploader == Pleroma.Uploaders.Local || filters != [] do
- Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
- Pleroma.Config.put([Pleroma.Upload, :filters], [])
+ Pleroma.Config.put([Pleroma.Upload, :uploader], test_uploader)
+ Pleroma.Config.put([Pleroma.Upload, :filters], [])
- on_exit(fn ->
- Pleroma.Config.put([Pleroma.Upload, :uploader], uploader)
- Pleroma.Config.put([Pleroma.Upload, :filters], filters)
- end)
- end
+ on_exit(fn ->
+ Pleroma.Config.put([Pleroma.Upload, :uploader], uploader)
+ Pleroma.Config.put([Pleroma.Upload, :filters], filters)
+ end)
:ok
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index c2812e8f7..1787c1088 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Factory do
use ExMachina.Ecto, repo: Pleroma.Repo
+ alias Pleroma.Object
alias Pleroma.User
def participation_factory do
@@ -30,13 +31,15 @@ defmodule Pleroma.Factory do
nickname: sequence(:nickname, &"nick#{&1}"),
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
bio: sequence(:bio, &"Tester Number #{&1}"),
- info: %{}
+ info: %{},
+ last_digest_emailed_at: NaiveDateTime.utc_now()
}
%{
user
| ap_id: User.ap_id(user),
follower_address: User.ap_followers(user),
+ following_address: User.ap_following(user),
following: [User.ap_id(user)]
}
end
@@ -117,21 +120,27 @@ defmodule Pleroma.Factory do
user = attrs[:user] || insert(:user)
note = attrs[:note] || insert(:note, user: user)
- data = %{
- "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
- "type" => "Create",
- "actor" => note.data["actor"],
- "to" => note.data["to"],
- "object" => note.data,
- "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
- "context" => note.data["context"]
- }
+ data_attrs = attrs[:data_attrs] || %{}
+ attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => note.data["actor"],
+ "to" => note.data["to"],
+ "object" => note.data["id"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => note.data["context"]
+ }
+ |> Map.merge(data_attrs)
%Pleroma.Activity{
data: data,
actor: data["actor"],
recipients: data["to"]
}
+ |> Map.merge(attrs)
end
def article_activity_factory do
@@ -174,15 +183,16 @@ defmodule Pleroma.Factory do
}
end
- def like_activity_factory do
- note_activity = insert(:note_activity)
+ def like_activity_factory(attrs \\ %{}) do
+ note_activity = attrs[:note_activity] || insert(:note_activity)
+ object = Object.normalize(note_activity)
user = insert(:user)
data = %{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"actor" => user.ap_id,
"type" => "Like",
- "object" => note_activity.data["object"]["id"],
+ "object" => object.data["id"],
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
}
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 30169edb0..3adb5ba3b 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -31,8 +31,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body:
- File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
+ body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json")
}}
end
@@ -40,7 +39,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/status.emelie.json")
+ body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
}}
end
@@ -48,15 +47,19 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/emelie.json")
+ body: File.read!("test/fixtures/tesla_mock/emelie.json")
}}
end
+ def get("https://mastodon.social/users/not_found", _, _, _) do
+ {:ok, %Tesla.Env{status: 404}}
+ end
+
def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/rinpatch.json")
+ body: File.read!("test/fixtures/tesla_mock/rinpatch.json")
}}
end
@@ -69,7 +72,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json")
+ body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json")
}}
end
@@ -77,7 +80,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/emelie.atom")
+ body: File.read!("test/fixtures/tesla_mock/emelie.atom")
}}
end
@@ -90,7 +93,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
+ body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json")
}}
end
@@ -103,7 +106,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
+ body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml")
}}
end
@@ -111,7 +114,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
+ body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom")
}}
end
@@ -124,7 +127,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
+ body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml")
}}
end
@@ -137,7 +140,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
+ body: File.read!("test/fixtures/tesla_mock/atarifrosch_feed.xml")
}}
end
@@ -150,7 +153,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
+ body: File.read!("test/fixtures/tesla_mock/atarifrosch_webfinger.xml")
}}
end
@@ -158,7 +161,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
+ body: File.read!("test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom")
}}
end
@@ -171,7 +174,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
+ body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom")
}}
end
@@ -184,7 +187,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
+ body: File.read!("test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml")
}}
end
@@ -197,7 +200,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+ body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
}}
end
@@ -210,7 +213,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
+ body: File.read!("test/fixtures/tesla_mock/lucifermysticus.json")
}}
end
@@ -218,7 +221,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
+ body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json")
}}
end
@@ -231,7 +234,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
+ body: File.read!("test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json")
}}
end
@@ -239,7 +242,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/rye.json")
+ body: File.read!("test/fixtures/tesla_mock/rye.json")
}}
end
@@ -247,7 +250,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/rye.json")
+ body: File.read!("test/fixtures/tesla_mock/rye.json")
}}
end
@@ -257,7 +260,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
+ "test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json"
)
}}
end
@@ -266,7 +269,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
+ body: File.read!("test/fixtures/tesla_mock/puckipedia.com.json")
}}
end
@@ -274,7 +277,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/7even.json")
+ body: File.read!("test/fixtures/tesla_mock/7even.json")
}}
end
@@ -282,7 +285,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
+ body: File.read!("test/fixtures/tesla_mock/peertube.moe-vid.json")
}}
end
@@ -290,7 +293,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json")
}}
end
@@ -298,7 +301,23 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
+ body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json")
+ }}
+ end
+
+ def get("https://wedistribute.org/wp-json/pterotype/v1/object/85810", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/wedistribute-article.json")
+ }}
+ end
+
+ def get("https://wedistribute.org/wp-json/pterotype/v1/actor/-blog", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json")
}}
end
@@ -306,7 +325,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
+ body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json")
}}
end
@@ -331,7 +350,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+ body: File.read!("test/fixtures/tesla_mock/7369654.html")
}}
end
@@ -339,7 +358,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
+ body: File.read!("test/fixtures/tesla_mock/mayumayu.json")
}}
end
@@ -352,7 +371,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
+ body: File.read!("test/fixtures/tesla_mock/mayumayupost.json")
}}
end
@@ -362,7 +381,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
+ "test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
)
}}
end
@@ -375,7 +394,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
+ body: File.read!("test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml")
}}
end
@@ -385,7 +404,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
+ "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
)
}}
end
@@ -399,7 +418,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
+ body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_user_1.xml")
}}
end
@@ -407,8 +426,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body:
- File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
+ body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.html")
}}
end
@@ -418,7 +436,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
+ "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
)
}}
end
@@ -431,7 +449,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
+ body: File.read!("test/fixtures/tesla_mock/spc_5381.atom")
}}
end
@@ -444,7 +462,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
+ body: File.read!("test/fixtures/tesla_mock/spc_5381_xrd.xml")
}}
end
@@ -452,7 +470,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/shitposter.club_host_meta")
}}
end
@@ -460,7 +478,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
+ body: File.read!("test/fixtures/tesla_mock/7369654.atom")
}}
end
@@ -468,7 +486,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+ body: File.read!("test/fixtures/tesla_mock/7369654.html")
}}
end
@@ -476,7 +494,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
+ body: File.read!("test/fixtures/tesla_mock/sakamoto_eal_feed.atom")
}}
end
@@ -484,7 +502,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/social.sakamoto.gq_host_meta")
}}
end
@@ -497,7 +515,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
+ body: File.read!("test/fixtures/tesla_mock/eal_sakamoto.xml")
}}
end
@@ -507,14 +525,14 @@ defmodule HttpRequestMock do
_,
Accept: "application/atom+xml"
) do
- {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}}
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sakamoto.atom")}}
end
def get("http://mastodon.social/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/mastodon.social_host_meta")
}}
end
@@ -528,9 +546,7 @@ defmodule HttpRequestMock do
%Tesla.Env{
status: 200,
body:
- File.read!(
- "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
- )
+ File.read!("test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml")
}}
end
@@ -538,7 +554,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/gs.example.org_host_meta")
}}
end
@@ -552,9 +568,7 @@ defmodule HttpRequestMock do
%Tesla.Env{
status: 200,
body:
- File.read!(
- "test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
- )
+ File.read!("test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml")
}}
end
@@ -573,7 +587,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
+ "test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
)
}}
end
@@ -584,14 +598,14 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
+ "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
)
}}
end
def get("http://squeet.me/.well-known/host-meta", _, _, _) do
{:ok,
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}}
end
def get(
@@ -603,7 +617,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+ body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
}}
end
@@ -616,15 +630,24 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
+ body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml")
}}
end
+ def get(
+ "https://social.heldscal.la/.well-known/webfinger?resource=invalid_content@social.heldscal.la",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end
+
def get("http://framatube.org/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/framatube.org_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/framatube.org_host_meta")
}}
end
@@ -638,7 +661,7 @@ defmodule HttpRequestMock do
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/json"}],
- body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
+ body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
}}
end
@@ -646,7 +669,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/gnusocial.de_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/gnusocial.de_host_meta")
}}
end
@@ -659,7 +682,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
+ body: File.read!("test/fixtures/tesla_mock/winterdienst_webfinger.json")
}}
end
@@ -667,7 +690,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/status.alpicola.com_host_meta")
}}
end
@@ -675,7 +698,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/macgirvin.com_host_meta")
}}
end
@@ -683,7 +706,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/gerzilla.de_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/gerzilla.de_host_meta")
}}
end
@@ -697,7 +720,7 @@ defmodule HttpRequestMock do
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/json"}],
- body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
+ body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
}}
end
@@ -707,7 +730,7 @@ defmodule HttpRequestMock do
status: 200,
body:
File.read!(
- "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
+ "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
)
}}
end
@@ -721,7 +744,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
+ body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml")
}}
end
@@ -729,7 +752,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
}}
end
@@ -737,7 +760,7 @@ defmodule HttpRequestMock do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+ body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
}}
end
@@ -765,6 +788,70 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
end
+ def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_followers.json")
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/followers?page=1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_followers_page.json")
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_following.json")
+ }}
+ end
+
+ def get("http://localhost:4001/users/masto_closed/following?page=1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/masto_closed_following_page.json")
+ }}
+ end
+
+ def get("http://localhost:4001/users/fuser2/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/pleroma_followers.json")
+ }}
+ end
+
+ def get("http://localhost:4001/users/fuser2/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/pleroma_following.json")
+ }}
+ end
+
+ def get("http://domain-with-errors:4001/users/fuser1/followers", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 504,
+ body: ""
+ }}
+ end
+
+ def get("http://domain-with-errors:4001/users/fuser1/following", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 504,
+ body: ""
+ }}
+ end
+
def get("http://example.com/ogp-missing-data", _, _, _) do
{:ok,
%Tesla.Env{
@@ -798,6 +885,89 @@ defmodule HttpRequestMock do
}}
end
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=lain@zetsubou.xn--q9jyb4c",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lain.xml")
+ }}
+ end
+
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=https://zetsubou.xn--q9jyb4c/users/lain",
+ _,
+ _,
+ Accept: "application/xrd+xml,application/jrd+json"
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lain.xml")
+ }}
+ end
+
+ def get(
+ "https://zetsubou.xn--q9jyb4c/.well-known/host-meta",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml")
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity.json", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity.json")
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://info.pleroma.site/activity2.json", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json")
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity2.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://info.pleroma.site/activity3.json", _, _, Accept: "application/activity+json") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json")
+ }}
+ end
+
+ def get("https://info.pleroma.site/activity3.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 404, body: ""}}
+ end
+
+ def get("https://mstdn.jp/.well-known/webfinger?resource=acct:kpherox@mstdn.jp", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml")
+ }}
+ end
+
def get(url, query, body, headers) do
{:error,
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs
index 83a363356..a9b79eb5b 100644
--- a/test/tasks/config_test.exs
+++ b/test/tasks/config_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Mix.Tasks.Pleroma.ConfigTest do
use Pleroma.DataCase
alias Pleroma.Repo
@@ -30,8 +34,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
- first_db = Config.get_by_params(%{group: "pleroma", key: "first_setting"})
- second_db = Config.get_by_params(%{group: "pleroma", key: "second_setting"})
+ first_db = Config.get_by_params(%{group: "pleroma", key: ":first_setting"})
+ second_db = Config.get_by_params(%{group: "pleroma", key: ":second_setting"})
refute Config.get_by_params(%{group: "pleroma", key: "Pleroma.Repo"})
assert Config.from_binary(first_db.value) == [key: "value", key2: [Pleroma.Repo]]
@@ -41,13 +45,13 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do
Config.create(%{
group: "pleroma",
- key: "setting_first",
+ key: ":setting_first",
value: [key: "value", key2: [Pleroma.Activity]]
})
Config.create(%{
group: "pleroma",
- key: "setting_second",
+ key: ":setting_second",
value: [key: "valu2", key2: [Pleroma.Repo]]
})
@@ -57,7 +61,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
assert File.exists?(temp_file)
{:ok, file} = File.read(temp_file)
- assert file =~ "config :pleroma, setting_first:"
- assert file =~ "config :pleroma, setting_second:"
+ assert file =~ "config :pleroma, :setting_first,"
+ assert file =~ "config :pleroma, :setting_second,"
end
end
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
index 579130b05..a8f25f500 100644
--- a/test/tasks/database_test.exs
+++ b/test/tasks/database_test.exs
@@ -3,8 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.DatabaseTest do
+ alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
use Pleroma.DataCase
import Pleroma.Factory
@@ -46,4 +49,37 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
assert user.info.follower_count == 0
end
end
+
+ describe "running fix_likes_collections" do
+ test "it turns OrderedCollection likes into empty arrays" do
+ [user, user2] = insert_pair(:user)
+
+ {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
+ {:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
+
+ CommonAPI.favorite(id, user2)
+
+ likes = %{
+ "first" =>
+ "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
+ "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
+ "totalItems" => 3,
+ "type" => "OrderedCollection"
+ }
+
+ new_data = Map.put(object2.data, "likes", likes)
+
+ object2
+ |> Ecto.Changeset.change(%{data: new_data})
+ |> Repo.update()
+
+ assert length(Object.get_by_id(object.id).data["likes"]) == 1
+ assert is_map(Object.get_by_id(object2.id).data["likes"])
+
+ assert :ok == Mix.Tasks.Pleroma.Database.run(["fix_likes_collections"])
+
+ assert length(Object.get_by_id(object.id).data["likes"]) == 1
+ assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
+ end
+ end
end
diff --git a/test/tasks/ecto/ecto_test.exs b/test/tasks/ecto/ecto_test.exs
new file mode 100644
index 000000000..a1b9ca174
--- /dev/null
+++ b/test/tasks/ecto/ecto_test.exs
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.EctoTest do
+ use ExUnit.Case, async: true
+
+ test "raise on bad path" do
+ assert_raise RuntimeError, ~r/Could not find migrations directory/, fn ->
+ Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo,
+ migrations_path: "some-path"
+ )
+ end
+ end
+end
diff --git a/test/tasks/ecto/rollback_test.exs b/test/tasks/ecto/rollback_test.exs
index 33d093fca..c33c4e940 100644
--- a/test/tasks/ecto/rollback_test.exs
+++ b/test/tasks/ecto/rollback_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Mix.Tasks.Pleroma.Ecto.RollbackTest do
use Pleroma.DataCase
import ExUnit.CaptureLog
diff --git a/test/tasks/instance.exs b/test/tasks/instance_test.exs
similarity index 78%
rename from test/tasks/instance.exs
rename to test/tasks/instance_test.exs
index 1875f52a3..70986374e 100644
--- a/test/tasks/instance.exs
+++ b/test/tasks/instance_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.InstanceTest do
use ExUnit.Case, async: true
@@ -38,7 +42,17 @@ defmodule Pleroma.InstanceTest do
"--indexable",
"y",
"--db-configurable",
- "y"
+ "y",
+ "--rum",
+ "y",
+ "--listen-port",
+ "4000",
+ "--listen-ip",
+ "127.0.0.1",
+ "--uploads-dir",
+ "test/uploads",
+ "--static-dir",
+ "instance/static/"
])
end
@@ -56,10 +70,11 @@ defmodule Pleroma.InstanceTest do
assert generated_config =~ "username: \"dbuser\""
assert generated_config =~ "password: \"dbpass\""
assert generated_config =~ "dynamic_configuration: true"
+ assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
end
defp generated_setup_psql do
- ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n)
+ ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\nCREATE EXTENSION IF NOT EXISTS rum;\n)
end
end
diff --git a/test/tasks/pleroma_test.exs b/test/tasks/pleroma_test.exs
new file mode 100644
index 000000000..a20bd9cf2
--- /dev/null
+++ b/test/tasks/pleroma_test.exs
@@ -0,0 +1,50 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.PleromaTest do
+ use ExUnit.Case, async: true
+ import Mix.Pleroma
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "shell_prompt/1" do
+ test "input" do
+ send(self(), {:mix_shell_input, :prompt, "Yes"})
+
+ answer = shell_prompt("Do you want this?")
+ assert_received {:mix_shell, :prompt, [message]}
+ assert message =~ "Do you want this?"
+ assert answer == "Yes"
+ end
+
+ test "with defval" do
+ send(self(), {:mix_shell_input, :prompt, "\n"})
+
+ answer = shell_prompt("Do you want this?", "defval")
+
+ assert_received {:mix_shell, :prompt, [message]}
+ assert message =~ "Do you want this? [defval]"
+ assert answer == "defval"
+ end
+ end
+
+ describe "get_option/3" do
+ test "get from options" do
+ assert get_option([domain: "some-domain.com"], :domain, "Promt") == "some-domain.com"
+ end
+
+ test "get from prompt" do
+ send(self(), {:mix_shell_input, :prompt, "another-domain.com"})
+ assert get_option([], :domain, "Prompt") == "another-domain.com"
+ end
+ end
+end
diff --git a/test/tasks/robots_txt_test.exs b/test/tasks/robots_txt_test.exs
new file mode 100644
index 000000000..78a3f17b4
--- /dev/null
+++ b/test/tasks/robots_txt_test.exs
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.RobotsTxtTest do
+ use ExUnit.Case
+ alias Mix.Tasks.Pleroma.RobotsTxt
+
+ test "creates new dir" do
+ path = "test/fixtures/new_dir/"
+ file_path = path <> "robots.txt"
+
+ static_dir = Pleroma.Config.get([:instance, :static_dir])
+ Pleroma.Config.put([:instance, :static_dir], path)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :static_dir], static_dir)
+ {:ok, ["test/fixtures/new_dir/", "test/fixtures/new_dir/robots.txt"]} = File.rm_rf(path)
+ end)
+
+ RobotsTxt.run(["disallow_all"])
+
+ assert File.exists?(file_path)
+ {:ok, file} = File.read(file_path)
+
+ assert file == "User-Agent: *\nDisallow: /\n"
+ end
+
+ test "to existance folder" do
+ path = "test/fixtures/"
+ file_path = path <> "robots.txt"
+ static_dir = Pleroma.Config.get([:instance, :static_dir])
+ Pleroma.Config.put([:instance, :static_dir], path)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :static_dir], static_dir)
+ :ok = File.rm(file_path)
+ end)
+
+ RobotsTxt.run(["disallow_all"])
+
+ assert File.exists?(file_path)
+ {:ok, file} = File.read(file_path)
+
+ assert file == "User-Agent: *\nDisallow: /\n"
+ end
+end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index 3d4b08fba..2b9453042 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -5,6 +5,9 @@
defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.Token
+
use Pleroma.DataCase
import Pleroma.Factory
@@ -327,6 +330,13 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert_received {:mix_shell, :info, [message]}
assert message =~ "Invite for token #{invite.token} was revoked."
end
+
+ test "it prints an error message when invite is not exist" do
+ Mix.Tasks.Pleroma.User.run(["revoke_invite", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No invite found"
+ end
end
describe "running delete_activities" do
@@ -337,6 +347,13 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert_received {:mix_shell, :info, [message]}
assert message == "User #{nickname} statuses deleted."
end
+
+ test "it prints an error message when user is not exist" do
+ Mix.Tasks.Pleroma.User.run(["delete_activities", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
end
describe "running toggle_confirmed" do
@@ -364,6 +381,13 @@ defmodule Mix.Tasks.Pleroma.UserTest do
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
+
+ test "it prints an error message when user is not exist" do
+ Mix.Tasks.Pleroma.User.run(["toggle_confirmed", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
end
describe "search" do
@@ -386,4 +410,64 @@ defmodule Mix.Tasks.Pleroma.UserTest do
User.Search.search("moon fediverse", for_user: user) |> Enum.map(& &1.id)
end
end
+
+ describe "signing out" do
+ test "it deletes all user's tokens and authorizations" do
+ user = insert(:user)
+ insert(:oauth_token, user: user)
+ insert(:oauth_authorization, user: user)
+
+ assert Repo.get_by(Token, user_id: user.id)
+ assert Repo.get_by(Authorization, user_id: user.id)
+
+ :ok = Mix.Tasks.Pleroma.User.run(["sign_out", user.nickname])
+
+ refute Repo.get_by(Token, user_id: user.id)
+ refute Repo.get_by(Authorization, user_id: user.id)
+ end
+
+ test "it prints an error message when user is not exist" do
+ Mix.Tasks.Pleroma.User.run(["sign_out", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
+ end
+
+ describe "tagging" do
+ test "it add tags to a user" do
+ user = insert(:user)
+
+ :ok = Mix.Tasks.Pleroma.User.run(["tag", user.nickname, "pleroma"])
+
+ user = User.get_cached_by_nickname(user.nickname)
+ assert "pleroma" in user.tags
+ end
+
+ test "it prints an error message when user is not exist" do
+ Mix.Tasks.Pleroma.User.run(["tag", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Could not change user tags"
+ end
+ end
+
+ describe "untagging" do
+ test "it deletes tags from a user" do
+ user = insert(:user, tags: ["pleroma"])
+ assert "pleroma" in user.tags
+
+ :ok = Mix.Tasks.Pleroma.User.run(["untag", user.nickname, "pleroma"])
+
+ user = User.get_cached_by_nickname(user.nickname)
+ assert Enum.empty?(user.tags)
+ end
+
+ test "it prints an error message when user is not exist" do
+ Mix.Tasks.Pleroma.User.run(["untag", "foo"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Could not change user tags"
+ end
+ end
end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index f604ba63d..a927b2c3d 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -2,7 +2,8 @@
# Copyright © 2017-2018 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-ExUnit.start()
-
+os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
+ExUnit.start(exclude: os_exclude)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
+Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina)
diff --git a/test/upload/filter/anonymize_filename_test.exs b/test/upload/filter/anonymize_filename_test.exs
index 02241cfa4..a31b38ab1 100644
--- a/test/upload/filter/anonymize_filename_test.exs
+++ b/test/upload/filter/anonymize_filename_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Upload.Filter.AnonymizeFilenameTest do
use Pleroma.DataCase
diff --git a/test/upload/filter/dedupe_test.exs b/test/upload/filter/dedupe_test.exs
new file mode 100644
index 000000000..3de94dc20
--- /dev/null
+++ b/test/upload/filter/dedupe_test.exs
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.DedupeTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Upload
+ alias Pleroma.Upload.Filter.Dedupe
+
+ @shasum "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781"
+
+ test "adds shasum" do
+ File.cp!(
+ "test/fixtures/image.jpg",
+ "test/fixtures/image_tmp.jpg"
+ )
+
+ upload = %Upload{
+ name: "an… image.jpg",
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ assert {
+ :ok,
+ %Pleroma.Upload{id: @shasum, path: @shasum <> ".jpg"}
+ } = Dedupe.filter(upload)
+ end
+end
diff --git a/test/upload/filter/mogrifun_test.exs b/test/upload/filter/mogrifun_test.exs
new file mode 100644
index 000000000..d5a8751cc
--- /dev/null
+++ b/test/upload/filter/mogrifun_test.exs
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.MogrifunTest do
+ use Pleroma.DataCase
+ import Mock
+
+ alias Pleroma.Upload
+ alias Pleroma.Upload.Filter
+
+ test "apply mogrify filter" do
+ File.cp!(
+ "test/fixtures/image.jpg",
+ "test/fixtures/image_tmp.jpg"
+ )
+
+ upload = %Upload{
+ name: "an… image.jpg",
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ task =
+ Task.async(fn ->
+ assert_receive {:apply_filter, {}}, 4_000
+ end)
+
+ with_mocks([
+ {Mogrify, [],
+ [
+ open: fn _f -> %Mogrify.Image{} end,
+ custom: fn _m, _a -> send(task.pid, {:apply_filter, {}}) end,
+ custom: fn _m, _a, _o -> send(task.pid, {:apply_filter, {}}) end,
+ save: fn _f, _o -> :ok end
+ ]}
+ ]) do
+ assert Filter.Mogrifun.filter(upload) == :ok
+ end
+
+ Task.await(task)
+ end
+end
diff --git a/test/upload/filter/mogrify_test.exs b/test/upload/filter/mogrify_test.exs
new file mode 100644
index 000000000..c301440fd
--- /dev/null
+++ b/test/upload/filter/mogrify_test.exs
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.MogrifyTest do
+ use Pleroma.DataCase
+ import Mock
+
+ alias Pleroma.Config
+ alias Pleroma.Upload
+ alias Pleroma.Upload.Filter
+
+ setup do
+ filter = Config.get([Filter.Mogrify, :args])
+
+ on_exit(fn ->
+ Config.put([Filter.Mogrify, :args], filter)
+ end)
+ end
+
+ test "apply mogrify filter" do
+ Config.put([Filter.Mogrify, :args], [{"tint", "40"}])
+
+ File.cp!(
+ "test/fixtures/image.jpg",
+ "test/fixtures/image_tmp.jpg"
+ )
+
+ upload = %Upload{
+ name: "an… image.jpg",
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ task =
+ Task.async(fn ->
+ assert_receive {:apply_filter, {_, "tint", "40"}}, 4_000
+ end)
+
+ with_mock Mogrify,
+ open: fn _f -> %Mogrify.Image{} end,
+ custom: fn _m, _a -> :ok end,
+ custom: fn m, a, o -> send(task.pid, {:apply_filter, {m, a, o}}) end,
+ save: fn _f, _o -> :ok end do
+ assert Filter.Mogrify.filter(upload) == :ok
+ end
+
+ Task.await(task)
+ end
+end
diff --git a/test/upload/filter_test.exs b/test/upload/filter_test.exs
new file mode 100644
index 000000000..640cd7107
--- /dev/null
+++ b/test/upload/filter_test.exs
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.FilterTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.Upload.Filter
+
+ setup do
+ custom_filename = Config.get([Pleroma.Upload.Filter.AnonymizeFilename, :text])
+
+ on_exit(fn ->
+ Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], custom_filename)
+ end)
+ end
+
+ test "applies filters" do
+ Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], "custom-file.png")
+
+ File.cp!(
+ "test/fixtures/image.jpg",
+ "test/fixtures/image_tmp.jpg"
+ )
+
+ upload = %Pleroma.Upload{
+ name: "an… image.jpg",
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ assert Filter.filter([], upload) == {:ok, upload}
+
+ assert {:ok, upload} = Filter.filter([Pleroma.Upload.Filter.AnonymizeFilename], upload)
+ assert upload.name == "custom-file.png"
+ end
+end
diff --git a/test/upload_test.exs b/test/upload_test.exs
index 946ebcb5a..95b16078b 100644
--- a/test/upload_test.exs
+++ b/test/upload_test.exs
@@ -3,9 +3,106 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UploadTest do
- alias Pleroma.Upload
use Pleroma.DataCase
+ import ExUnit.CaptureLog
+
+ alias Pleroma.Upload
+ alias Pleroma.Uploaders.Uploader
+
+ @upload_file %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "image.jpg"
+ }
+
+ defmodule TestUploaderBase do
+ def put_file(%{path: path} = _upload, module_name) do
+ task_pid =
+ Task.async(fn ->
+ :timer.sleep(10)
+
+ {Uploader, path}
+ |> :global.whereis_name()
+ |> send({Uploader, self(), {:test}, %{}})
+
+ assert_receive {Uploader, {:test}}, 4_000
+ end)
+
+ Agent.start(fn -> task_pid end, name: module_name)
+
+ :wait_callback
+ end
+ end
+
+ describe "Tried storing a file when http callback response success result" do
+ defmodule TestUploaderSuccess do
+ def http_callback(conn, _params),
+ do: {:ok, conn, {:file, "post-process-file.jpg"}}
+
+ def put_file(upload), do: TestUploaderBase.put_file(upload, __MODULE__)
+ end
+
+ setup do: [uploader: TestUploaderSuccess]
+ setup [:ensure_local_uploader]
+
+ test "it returns file" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ assert Upload.store(@upload_file) ==
+ {:ok,
+ %{
+ "name" => "image.jpg",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "http://localhost:4001/media/post-process-file.jpg",
+ "mediaType" => "image/jpeg",
+ "type" => "Link"
+ }
+ ]
+ }}
+
+ Task.await(Agent.get(TestUploaderSuccess, fn task_pid -> task_pid end))
+ end
+ end
+
+ describe "Tried storing a file when http callback response error" do
+ defmodule TestUploaderError do
+ def http_callback(conn, _params), do: {:error, conn, "Errors"}
+
+ def put_file(upload), do: TestUploaderBase.put_file(upload, __MODULE__)
+ end
+
+ setup do: [uploader: TestUploaderError]
+ setup [:ensure_local_uploader]
+
+ test "it returns error" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ assert capture_log(fn ->
+ assert Upload.store(@upload_file) == {:error, "Errors"}
+ Task.await(Agent.get(TestUploaderError, fn task_pid -> task_pid end))
+ end) =~
+ "[error] Elixir.Pleroma.Upload store (using Pleroma.UploadTest.TestUploaderError) failed: \"Errors\""
+ end
+ end
+
+ describe "Tried storing a file when http callback doesn't response by timeout" do
+ defmodule(TestUploader, do: def(put_file(_upload), do: :wait_callback))
+ setup do: [uploader: TestUploader]
+ setup [:ensure_local_uploader]
+
+ test "it returns error" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ assert capture_log(fn ->
+ assert Upload.store(@upload_file) == {:error, "Uploader callback timeout"}
+ end) =~
+ "[error] Elixir.Pleroma.Upload store (using Pleroma.UploadTest.TestUploader) failed: \"Uploader callback timeout\""
+ end
+ end
+
describe "Storing a file with the Local uploader" do
setup [:ensure_local_uploader]
@@ -25,24 +122,6 @@ defmodule Pleroma.UploadTest do
assert String.starts_with?(url, Pleroma.Web.base_url() <> "/media/")
end
- test "returns a media url with configured base_url" do
- base_url = "https://cache.pleroma.social"
-
- File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
-
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image_tmp.jpg"),
- filename: "image.jpg"
- }
-
- {:ok, data} = Upload.store(file, base_url: base_url)
-
- assert %{"url" => [%{"href" => url}]} = data
-
- assert String.starts_with?(url, base_url <> "/media/")
- end
-
test "copies the file to the configured folder with deduping" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
@@ -169,4 +248,32 @@ defmodule Pleroma.UploadTest do
"%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg"
end
end
+
+ describe "Setting a custom base_url for uploaded media" do
+ setup do
+ Pleroma.Config.put([Pleroma.Upload, :base_url], "https://cache.pleroma.social")
+
+ on_exit(fn ->
+ Pleroma.Config.put([Pleroma.Upload, :base_url], nil)
+ end)
+ end
+
+ test "returns a media url with configured base_url" do
+ base_url = Pleroma.Config.get([Pleroma.Upload, :base_url])
+
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "image.jpg"
+ }
+
+ {:ok, data} = Upload.store(file, base_url: base_url)
+
+ assert %{"url" => [%{"href" => url}]} = data
+
+ refute String.starts_with?(url, base_url <> "/media/")
+ end
+ end
end
diff --git a/test/uploaders/local_test.exs b/test/uploaders/local_test.exs
new file mode 100644
index 000000000..fc442d0f1
--- /dev/null
+++ b/test/uploaders/local_test.exs
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Uploaders.LocalTest do
+ use Pleroma.DataCase
+ alias Pleroma.Uploaders.Local
+
+ describe "get_file/1" do
+ test "it returns path to local folder for files" do
+ assert Local.get_file("") == {:ok, {:static_dir, "test/uploads"}}
+ end
+ end
+
+ describe "put_file/1" do
+ test "put file to local folder" do
+ file_path = "local_upload/files/image.jpg"
+
+ file = %Pleroma.Upload{
+ name: "image.jpg",
+ content_type: "image/jpg",
+ path: file_path,
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ assert Local.put_file(file) == :ok
+
+ assert Path.join([Local.upload_path(), file_path])
+ |> File.exists?()
+ end
+ end
+end
diff --git a/test/uploaders/mdii_test.exs b/test/uploaders/mdii_test.exs
new file mode 100644
index 000000000..d432d40f0
--- /dev/null
+++ b/test/uploaders/mdii_test.exs
@@ -0,0 +1,50 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Uploaders.MDIITest do
+ use Pleroma.DataCase
+ alias Pleroma.Uploaders.MDII
+ import Tesla.Mock
+
+ describe "get_file/1" do
+ test "it returns path to local folder for files" do
+ assert MDII.get_file("") == {:ok, {:static_dir, "test/uploads"}}
+ end
+ end
+
+ describe "put_file/1" do
+ setup do
+ file_upload = %Pleroma.Upload{
+ name: "mdii-image.jpg",
+ content_type: "image/jpg",
+ path: "test_folder/mdii-image.jpg",
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ [file_upload: file_upload]
+ end
+
+ test "save file", %{file_upload: file_upload} do
+ mock(fn
+ %{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
+ %Tesla.Env{status: 200, body: "mdii-image"}
+ end)
+
+ assert MDII.put_file(file_upload) ==
+ {:ok, {:url, "https://mdii.sakura.ne.jp/mdii-image.jpg"}}
+ end
+
+ test "save file to local if MDII isn`t available", %{file_upload: file_upload} do
+ mock(fn
+ %{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
+ %Tesla.Env{status: 500}
+ end)
+
+ assert MDII.put_file(file_upload) == :ok
+
+ assert Path.join([Pleroma.Uploaders.Local.upload_path(), file_upload.path])
+ |> File.exists?()
+ end
+ end
+end
diff --git a/test/uploaders/s3_test.exs b/test/uploaders/s3_test.exs
new file mode 100644
index 000000000..a0a1cfdf0
--- /dev/null
+++ b/test/uploaders/s3_test.exs
@@ -0,0 +1,90 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Uploaders.S3Test do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.Uploaders.S3
+
+ import Mock
+ import ExUnit.CaptureLog
+
+ setup do
+ config = Config.get([Pleroma.Uploaders.S3])
+
+ Config.put([Pleroma.Uploaders.S3],
+ bucket: "test_bucket",
+ public_endpoint: "https://s3.amazonaws.com"
+ )
+
+ on_exit(fn ->
+ Config.put([Pleroma.Uploaders.S3], config)
+ end)
+
+ :ok
+ end
+
+ describe "get_file/1" do
+ test "it returns path to local folder for files" do
+ assert S3.get_file("test_image.jpg") == {
+ :ok,
+ {:url, "https://s3.amazonaws.com/test_bucket/test_image.jpg"}
+ }
+ end
+
+ test "it returns path without bucket when truncated_namespace set to ''" do
+ Config.put([Pleroma.Uploaders.S3],
+ bucket: "test_bucket",
+ public_endpoint: "https://s3.amazonaws.com",
+ truncated_namespace: ""
+ )
+
+ assert S3.get_file("test_image.jpg") == {
+ :ok,
+ {:url, "https://s3.amazonaws.com/test_image.jpg"}
+ }
+ end
+
+ test "it returns path with bucket namespace when namespace is set" do
+ Config.put([Pleroma.Uploaders.S3],
+ bucket: "test_bucket",
+ public_endpoint: "https://s3.amazonaws.com",
+ bucket_namespace: "family"
+ )
+
+ assert S3.get_file("test_image.jpg") == {
+ :ok,
+ {:url, "https://s3.amazonaws.com/family:test_bucket/test_image.jpg"}
+ }
+ end
+ end
+
+ describe "put_file/1" do
+ setup do
+ file_upload = %Pleroma.Upload{
+ name: "image-tet.jpg",
+ content_type: "image/jpg",
+ path: "test_folder/image-tet.jpg",
+ tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+ }
+
+ [file_upload: file_upload]
+ end
+
+ test "save file", %{file_upload: file_upload} do
+ with_mock ExAws, request: fn _ -> {:ok, :ok} end do
+ assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
+ end
+ end
+
+ test "returns error", %{file_upload: file_upload} do
+ with_mock ExAws, request: fn _ -> {:error, "S3 Upload failed"} end do
+ assert capture_log(fn ->
+ assert S3.put_file(file_upload) == {:error, "S3 Upload failed"}
+ end) =~ "Elixir.Pleroma.Uploaders.S3: {:error, \"S3 Upload failed\"}"
+ end
+ end
+ end
+end
diff --git a/test/user_info_test.exs b/test/user_info_test.exs
new file mode 100644
index 000000000..2d795594e
--- /dev/null
+++ b/test/user_info_test.exs
@@ -0,0 +1,24 @@
+defmodule Pleroma.UserInfoTest do
+ alias Pleroma.Repo
+ alias Pleroma.User.Info
+
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ describe "update_email_notifications/2" do
+ setup do
+ user = insert(:user, %{info: %{email_notifications: %{"digest" => true}}})
+
+ {:ok, user: user}
+ end
+
+ test "Notifications are updated", %{user: user} do
+ true = user.info.email_notifications["digest"]
+ changeset = Info.update_email_notifications(user.info, %{"digest" => false})
+ assert changeset.valid?
+ {:ok, result} = Ecto.Changeset.apply_action(changeset, :insert)
+ assert result.email_notifications["digest"] == false
+ end
+ end
+end
diff --git a/test/user_invite_token_test.exs b/test/user_invite_token_test.exs
index 276788254..111e40361 100644
--- a/test/user_invite_token_test.exs
+++ b/test/user_invite_token_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.UserInviteTokenTest do
use ExUnit.Case, async: true
use Pleroma.DataCase
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
index 8f8472aae..48ce973ad 100644
--- a/test/user_search_test.exs
+++ b/test/user_search_test.exs
@@ -193,7 +193,14 @@ defmodule Pleroma.UserSearchTest do
user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
assert length(results) == 1
- assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+
+ expected =
+ result
+ |> Map.put(:search_rank, nil)
+ |> Map.put(:search_type, nil)
+ |> Map.put(:last_digest_emailed_at, nil)
+
+ assert user == expected
end
test "excludes a blocked users from search result" do
@@ -217,5 +224,88 @@ defmodule Pleroma.UserSearchTest do
refute Enum.member?(account_ids, blocked_user2.id)
assert length(account_ids) == 3
end
+
+ test "local user has the same search_rank as for users with the same nickname, but another domain" do
+ user = insert(:user)
+ insert(:user, nickname: "lain@mastodon.social")
+ insert(:user, nickname: "lain")
+ insert(:user, nickname: "lain@pleroma.social")
+
+ assert User.search("lain@localhost", resolve: true, for_user: user)
+ |> Enum.each(fn u -> u.search_rank == 0.5 end)
+ end
+
+ test "localhost is the part of the domain" do
+ user = insert(:user)
+ insert(:user, nickname: "another@somedomain")
+ insert(:user, nickname: "lain")
+ insert(:user, nickname: "lain@examplelocalhost")
+
+ result = User.search("lain@examplelocalhost", resolve: true, for_user: user)
+ assert Enum.each(result, fn u -> u.search_rank == 0.5 end)
+ assert length(result) == 2
+ end
+
+ test "local user search with users" do
+ user = insert(:user)
+ local_user = insert(:user, nickname: "lain")
+ insert(:user, nickname: "another@localhost.com")
+ insert(:user, nickname: "localhost@localhost.com")
+
+ [result] = User.search("lain@localhost", resolve: true, for_user: user)
+ assert Map.put(result, :search_rank, nil) |> Map.put(:search_type, nil) == local_user
+ end
+
+ test "works with idna domains" do
+ user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
+
+ results = User.search("lain@zetsubou.みんな", resolve: false, for_user: user)
+
+ result = List.first(results)
+
+ assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+ end
+
+ test "works with idna domains converted input" do
+ user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
+
+ results =
+ User.search("lain@zetsubou." <> to_string(:idna.encode("zetsubou.みんな")),
+ resolve: false,
+ for_user: user
+ )
+
+ result = List.first(results)
+
+ assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+ end
+
+ test "works with idna domains and bad chars in domain" do
+ user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
+
+ results =
+ User.search("lain@zetsubou!@#$%^&*()+,-/:;<=>?[]'_{}|~`.みんな",
+ resolve: false,
+ for_user: user
+ )
+
+ result = List.first(results)
+
+ assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+ end
+
+ test "works with idna domains and query as link" do
+ user = insert(:user, nickname: "lain@" <> to_string(:idna.encode("zetsubou.みんな")))
+
+ results =
+ User.search("https://zetsubou.みんな/users/lain",
+ resolve: false,
+ for_user: user
+ )
+
+ result = List.first(results)
+
+ assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
+ end
end
end
diff --git a/test/user_test.exs b/test/user_test.exs
index fb497843c..8440d456d 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -14,6 +14,7 @@ defmodule Pleroma.UserTest do
use Pleroma.DataCase
import Pleroma.Factory
+ import Mock
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -53,6 +54,14 @@ defmodule Pleroma.UserTest do
assert expected_followers_collection == User.ap_followers(user)
end
+ test "ap_following returns the following collection for the user" do
+ user = UserBuilder.build()
+
+ expected_followers_collection = "#{User.ap_id(user)}/following"
+
+ assert expected_followers_collection == User.ap_following(user)
+ end
+
test "returns all pending follow requests" do
unlocked = insert(:user)
locked = insert(:user, %{info: %{locked: true}})
@@ -678,10 +687,12 @@ defmodule Pleroma.UserTest do
muted_user = insert(:user)
refute User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
{:ok, user} = User.mute(user, muted_user)
assert User.mutes?(user, muted_user)
+ assert User.muted_notifications?(user, muted_user)
end
test "it unmutes users" do
@@ -692,6 +703,20 @@ defmodule Pleroma.UserTest do
{:ok, user} = User.unmute(user, muted_user)
refute User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
+ end
+
+ test "it mutes user without notifications" do
+ user = insert(:user)
+ muted_user = insert(:user)
+
+ refute User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
+
+ {:ok, user} = User.mute(user, muted_user, false)
+
+ assert User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
end
end
@@ -799,6 +824,48 @@ defmodule Pleroma.UserTest do
assert User.blocks?(user, collateral_user)
end
+ test "does not block domain with same end" do
+ user = insert(:user)
+
+ collateral_user =
+ insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
+
+ refute User.blocks?(user, collateral_user)
+ end
+
+ test "does not block domain with same end if wildcard added" do
+ user = insert(:user)
+
+ collateral_user =
+ insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
+
+ refute User.blocks?(user, collateral_user)
+ end
+
+ test "blocks domain with wildcard for subdomain" do
+ user = insert(:user)
+
+ user_from_subdomain =
+ insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"})
+
+ user_with_two_subdomains =
+ insert(:user, %{
+ ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully"
+ })
+
+ user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
+
+ assert User.blocks?(user, user_from_subdomain)
+ assert User.blocks?(user, user_with_two_subdomains)
+ assert User.blocks?(user, user_domain)
+ end
+
test "unblocks domains" do
user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
@@ -915,49 +982,80 @@ defmodule Pleroma.UserTest do
end
end
- test ".delete_user_activities deletes all create activities" do
- user = insert(:user)
+ describe "delete" do
+ setup do
+ {:ok, user} = insert(:user) |> User.set_cache()
- {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
+ [user: user]
+ end
- {:ok, _} = User.delete_user_activities(user)
+ test ".delete_user_activities deletes all create activities", %{user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- # TODO: Remove favorites, repeats, delete activities.
- refute Activity.get_by_id(activity.id)
- end
+ {:ok, _} = User.delete_user_activities(user)
- test ".delete deactivates a user, all follow relationships and all activities" do
- user = insert(:user)
- follower = insert(:user)
+ # TODO: Remove favorites, repeats, delete activities.
+ refute Activity.get_by_id(activity.id)
+ end
- {:ok, follower} = User.follow(follower, user)
+ test "it deletes a user, all follow relationships and all activities", %{user: user} do
+ follower = insert(:user)
+ {:ok, follower} = User.follow(follower, user)
- {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- {:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"})
+ object = insert(:note, user: user)
+ activity = insert(:note_activity, user: user, note: object)
- {:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
- {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
- {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
+ object_two = insert(:note, user: follower)
+ activity_two = insert(:note_activity, user: follower, note: object_two)
- {:ok, _} = User.delete(user)
+ {:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
+ {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
+ {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
- follower = User.get_cached_by_id(follower.id)
+ {:ok, _} = User.delete(user)
- refute User.following?(follower, user)
- refute User.get_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
- user_activities =
- user.ap_id
- |> Activity.query_by_actor()
- |> Repo.all()
- |> Enum.map(fn act -> act.data["type"] end)
+ refute User.following?(follower, user)
+ refute User.get_by_id(user.id)
+ assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
- assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)
+ user_activities =
+ user.ap_id
+ |> Activity.query_by_actor()
+ |> Repo.all()
+ |> Enum.map(fn act -> act.data["type"] end)
- refute Activity.get_by_id(activity.id)
- refute Activity.get_by_id(like.id)
- refute Activity.get_by_id(like_two.id)
- refute Activity.get_by_id(repeat.id)
+ assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)
+
+ refute Activity.get_by_id(activity.id)
+ refute Activity.get_by_id(like.id)
+ refute Activity.get_by_id(like_two.id)
+ refute Activity.get_by_id(repeat.id)
+ end
+
+ test_with_mock "it sends out User Delete activity",
+ %{user: user},
+ Pleroma.Web.ActivityPub.Publisher,
+ [:passthrough],
+ [] do
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+ Pleroma.Config.put(config_path, true)
+
+ {:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
+ {:ok, _} = User.follow(follower, user)
+
+ {:ok, _user} = User.delete(user)
+
+ assert called(
+ Pleroma.Web.ActivityPub.Publisher.publish_one(%{
+ inbox: "http://mastodon.example.org/inbox"
+ })
+ )
+
+ Pleroma.Config.put(config_path, initial_setting)
+ end
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do
@@ -1139,6 +1237,109 @@ defmodule Pleroma.UserTest do
assert Map.get(user_show, "followers_count") == 2
end
+ describe "list_inactive_users_query/1" do
+ defp days_ago(days) do
+ NaiveDateTime.add(
+ NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ -days * 60 * 60 * 24,
+ :second
+ )
+ end
+
+ test "Users are inactive by default" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(users, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+
+ test "Only includes users who has no recent activity" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ {inactive, active} = Enum.split(users, trunc(total / 2))
+
+ Enum.map(active, fn user ->
+ to = Enum.random(users -- [user])
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
+ "status" => "hey @#{to.nickname}"
+ })
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(active, fn user ->
+ refute user.id in inactive_users_ids
+ end)
+
+ Enum.each(inactive, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+
+ test "Only includes users with no read notifications" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ [sender | recipients] = users
+ {inactive, active} = Enum.split(recipients, trunc(total / 2))
+
+ Enum.each(recipients, fn to ->
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
+ "status" => "hey @#{to.nickname}"
+ })
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
+ "status" => "hey again @#{to.nickname}"
+ })
+ end)
+
+ Enum.each(active, fn user ->
+ [n1, _n2] = Pleroma.Notification.for_user(user)
+ {:ok, _} = Pleroma.Notification.read_one(user, n1.id)
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(active, fn user ->
+ refute user.id in inactive_users_ids
+ end)
+
+ Enum.each(inactive, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+ end
+
describe "toggle_confirmation/1" do
test "if user is confirmed" do
user = insert(:user, info: %{confirmation_pending: false})
@@ -1183,4 +1384,190 @@ defmodule Pleroma.UserTest do
assert user_two.ap_id in ap_ids
end
end
+
+ describe "sync followers count" do
+ setup do
+ user1 = insert(:user, local: false, ap_id: "http://localhost:4001/users/masto_closed")
+ user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+ insert(:user, local: true)
+ insert(:user, local: false, info: %{deactivated: true})
+ {:ok, user1: user1, user2: user2}
+ end
+
+ test "external_users/1 external active users with limit", %{user1: user1, user2: user2} do
+ [fdb_user1] = User.external_users(limit: 1)
+
+ assert fdb_user1.ap_id
+ assert fdb_user1.ap_id == user1.ap_id
+ assert fdb_user1.id == user1.id
+
+ [fdb_user2] = User.external_users(max_id: fdb_user1.id, limit: 1)
+
+ assert fdb_user2.ap_id
+ assert fdb_user2.ap_id == user2.ap_id
+ assert fdb_user2.id == user2.id
+
+ assert User.external_users(max_id: fdb_user2.id, limit: 1) == []
+ end
+ end
+
+ describe "set_info_cache/2" do
+ setup do
+ user = insert(:user)
+ {:ok, user: user}
+ end
+
+ test "update from args", %{user: user} do
+ User.set_info_cache(user, %{following_count: 15, follower_count: 18})
+
+ %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+ assert followers == 18
+ assert following == 15
+ end
+
+ test "without args", %{user: user} do
+ User.set_info_cache(user, %{})
+
+ %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+ assert followers == 0
+ assert following == 0
+ end
+ end
+
+ describe "user_info/2" do
+ setup do
+ user = insert(:user)
+ {:ok, user: user}
+ end
+
+ test "update from args", %{user: user} do
+ %{follower_count: followers, following_count: following} =
+ User.user_info(user, %{following_count: 15, follower_count: 18})
+
+ assert followers == 18
+ assert following == 15
+ end
+
+ test "without args", %{user: user} do
+ %{follower_count: followers, following_count: following} = User.user_info(user)
+
+ assert followers == 0
+ assert following == 0
+ end
+ end
+
+ describe "is_internal_user?/1" do
+ test "non-internal user returns false" do
+ user = insert(:user)
+ refute User.is_internal_user?(user)
+ end
+
+ test "user with no nickname returns true" do
+ user = insert(:user, %{nickname: nil})
+ assert User.is_internal_user?(user)
+ end
+
+ test "user with internal-prefixed nickname returns true" do
+ user = insert(:user, %{nickname: "internal.test"})
+ assert User.is_internal_user?(user)
+ end
+ end
+
+ describe "update_and_set_cache/1" do
+ test "returns error when user is stale instead Ecto.StaleEntryError" do
+ user = insert(:user)
+
+ changeset = Ecto.Changeset.change(user, bio: "test")
+
+ Repo.delete(user)
+
+ assert {:error, %Ecto.Changeset{errors: [id: {"is stale", [stale: true]}], valid?: false}} =
+ User.update_and_set_cache(changeset)
+ end
+
+ test "performs update cache if user updated" do
+ user = insert(:user)
+ assert {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
+
+ changeset = Ecto.Changeset.change(user, bio: "test-bio")
+
+ assert {:ok, %User{bio: "test-bio"} = user} = User.update_and_set_cache(changeset)
+ assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
+ assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
+ end
+ end
+
+ describe "following/followers synchronization" do
+ setup do
+ sync = Pleroma.Config.get([:instance, :external_user_synchronization])
+ on_exit(fn -> Pleroma.Config.put([:instance, :external_user_synchronization], sync) end)
+ end
+
+ test "updates the counters normally on following/getting a follow when disabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ {:ok, user} = Pleroma.User.follow(user, other_user)
+ other_user = Pleroma.User.get_by_id(other_user.id)
+
+ assert User.user_info(user).following_count == 1
+ assert User.user_info(other_user).follower_count == 1
+ end
+
+ test "syncronizes the counters with the remote instance for the followed when enabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ Pleroma.Config.put([:instance, :external_user_synchronization], true)
+ {:ok, _user} = User.follow(user, other_user)
+ other_user = User.get_by_id(other_user.id)
+
+ assert User.user_info(other_user).follower_count == 437
+ end
+
+ test "syncronizes the counters with the remote instance for the follower when enabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ Pleroma.Config.put([:instance, :external_user_synchronization], true)
+ {:ok, other_user} = User.follow(other_user, user)
+
+ assert User.user_info(other_user).following_count == 152
+ end
+ 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 8b3233729..251055ee1 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -12,9 +12,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
:ok
end
@@ -40,6 +48,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end
end
+ describe "/internal/fetch" do
+ test "it returns the internal fetch user", %{conn: conn} do
+ res =
+ conn
+ |> get(activity_pub_path(conn, :internal_fetch))
+ |> json_response(200)
+
+ assert res["id"] =~ "/fetch"
+ end
+ end
+
describe "/users/:nickname" do
test "it returns a json representation of the user with accept application/json", %{
conn: conn
@@ -161,17 +180,65 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end
describe "/object/:uuid/likes" do
- test "it returns the like activities in a collection", %{conn: conn} do
+ setup do
like = insert(:like_activity)
- uuid = String.split(like.data["object"], "/") |> List.last()
+ like_object_ap_id = Object.normalize(like).data["id"]
+ uuid =
+ like_object_ap_id
+ |> String.split("/")
+ |> List.last()
+
+ [id: like.data["id"], uuid: uuid]
+ end
+
+ test "it returns the like activities in a collection", %{conn: conn, id: id, uuid: uuid} do
result =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/objects/#{uuid}/likes")
|> json_response(200)
- assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
+ assert List.first(result["first"]["orderedItems"])["id"] == id
+ assert result["type"] == "OrderedCollection"
+ assert result["totalItems"] == 1
+ refute result["first"]["next"]
+ end
+
+ test "it does not crash when page number is exceeded total pages", %{conn: conn, uuid: uuid} do
+ result =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}/likes?page=2")
+ |> json_response(200)
+
+ assert result["type"] == "OrderedCollectionPage"
+ assert result["totalItems"] == 1
+ refute result["next"]
+ assert Enum.empty?(result["orderedItems"])
+ end
+
+ test "it contains the next key when likes count is more than 10", %{conn: conn} do
+ note = insert(:note_activity)
+ insert_list(11, :like_activity, note_activity: note)
+
+ uuid =
+ note
+ |> Object.normalize()
+ |> Map.get(:data)
+ |> Map.get("id")
+ |> String.split("/")
+ |> List.last()
+
+ result =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}/likes?page=1")
+ |> json_response(200)
+
+ assert result["totalItems"] == 11
+ assert length(result["orderedItems"]) == 10
+ assert result["next"]
end
end
@@ -302,6 +369,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "it returns a note activity in a collection", %{conn: conn} do
note_activity = insert(:direct_note_activity)
+ note_object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
conn =
@@ -310,7 +378,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox")
- assert response(conn, 200) =~ note_activity.data["object"]["content"]
+ assert response(conn, 200) =~ note_object.data["content"]
end
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
@@ -388,6 +456,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "it returns a note activity in a collection", %{conn: conn} do
note_activity = insert(:note_activity)
+ note_object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
conn =
@@ -395,7 +464,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox")
- assert response(conn, 200) =~ note_activity.data["object"]["content"]
+ assert response(conn, 200) =~ note_object.data["content"]
end
test "it returns an announce activity in a collection", %{conn: conn} do
@@ -457,12 +526,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
note_activity = insert(:note_activity)
+ note_object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
data = %{
type: "Delete",
object: %{
- id: note_activity.data["object"]["id"]
+ id: note_object.data["id"]
}
}
@@ -475,19 +545,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
result = json_response(conn, 201)
assert Activity.get_by_ap_id(result["id"])
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
- assert object
+ assert object = Object.get_by_ap_id(note_object.data["id"])
assert object.data["type"] == "Tombstone"
end
test "it rejects delete activity of object from other actor", %{conn: conn} do
note_activity = insert(:note_activity)
+ note_object = Object.normalize(note_activity)
user = insert(:user)
data = %{
type: "Delete",
object: %{
- id: note_activity.data["object"]["id"]
+ id: note_object.data["id"]
}
}
@@ -502,12 +572,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "it increases like count when receiving a like action", %{conn: conn} do
note_activity = insert(:note_activity)
+ note_object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
data = %{
type: "Like",
object: %{
- id: note_activity.data["object"]["id"]
+ id: note_object.data["id"]
}
}
@@ -520,8 +591,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
result = json_response(conn, 201)
assert Activity.get_by_ap_id(result["id"])
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
- assert object
+ assert object = Object.get_by_ap_id(note_object.data["id"])
assert object.data["like_count"] == 1
end
end
@@ -540,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert result["first"]["orderedItems"] == [user.ap_id]
end
- test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
+ test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{info: %{hide_followers: true}})
User.follow(user, user_two)
@@ -550,8 +620,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> get("/users/#{user_two.nickname}/followers")
|> json_response(200)
- assert result["first"]["orderedItems"] == []
- assert result["totalItems"] == 0
+ assert is_binary(result["first"])
+ end
+
+ test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
+ %{conn: conn} do
+ user = insert(:user, %{info: %{hide_followers: true}})
+
+ result =
+ conn
+ |> get("/users/#{user.nickname}/followers?page=1")
+
+ assert result.status == 403
+ assert result.resp_body == ""
+ end
+
+ test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
+ %{conn: conn} do
+ user = insert(:user, %{info: %{hide_followers: true}})
+ other_user = insert(:user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/users/#{user.nickname}/followers?page=1")
+ |> json_response(200)
+
+ assert result["totalItems"] == 1
+ assert result["orderedItems"] == [other_user.ap_id]
end
test "it works for more than 10 users", %{conn: conn} do
@@ -595,7 +692,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert result["first"]["orderedItems"] == [user_two.ap_id]
end
- test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
+ test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
user_two = insert(:user)
User.follow(user, user_two)
@@ -605,8 +702,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> get("/users/#{user.nickname}/following")
|> json_response(200)
- assert result["first"]["orderedItems"] == []
- assert result["totalItems"] == 0
+ assert is_binary(result["first"])
+ end
+
+ test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
+ %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+
+ result =
+ conn
+ |> get("/users/#{user.nickname}/following?page=1")
+
+ assert result.status == 403
+ assert result.resp_body == ""
+ end
+
+ test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
+ %{conn: conn} do
+ user = insert(:user, %{info: %{hide_follows: true}})
+ other_user = insert(:user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/users/#{user.nickname}/following?page=1")
+ |> json_response(200)
+
+ assert result["totalItems"] == 1
+ assert result["orderedItems"] == [other_user.ap_id]
end
test "it works for more than 10 users", %{conn: conn} do
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 76586ee4a..d723f331f 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder
- alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
@@ -254,10 +252,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
}
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
- object = Object.normalize(activity.data["object"])
-
+ assert object = Object.normalize(activity)
assert is_binary(object.data["id"])
- assert %Object{} = Object.get_by_ap_id(activity.data["object"])
end
end
@@ -659,7 +655,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "like an object" do
test "adds a like activity to the db" do
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ assert object = Object.normalize(note_activity)
+
user = insert(:user)
user_two = insert(:user)
@@ -678,9 +675,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert like_activity == same_like_activity
assert object.data["likes"] == [user.ap_id]
-
- [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
- assert note_activity.data["object"]["like_count"] == 1
+ assert object.data["like_count"] == 1
{:ok, _like_activity, object} = ActivityPub.like(user_two, object)
assert object.data["like_count"] == 2
@@ -690,7 +685,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "unliking" do
test "unliking a previously liked object" do
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ object = Object.normalize(note_activity)
user = insert(:user)
# Unliking something that hasn't been liked does nothing
@@ -710,7 +705,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "announcing an object" do
test "adds an announce activity to the db" do
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ object = Object.normalize(note_activity)
user = insert(:user)
{:ok, announce_activity, object} = ActivityPub.announce(user, object)
@@ -731,7 +726,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "unannouncing an object" do
test "unannouncing a previously announced object" do
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ object = Object.normalize(note_activity)
user = insert(:user)
# Unannouncing an object that is not announced does nothing
@@ -810,10 +805,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.data["type"] == "Undo"
assert activity.data["actor"] == follower.ap_id
- assert is_map(activity.data["object"])
- assert activity.data["object"]["type"] == "Follow"
- assert activity.data["object"]["object"] == followed.ap_id
- assert activity.data["object"]["id"] == follow_activity.data["id"]
+ embedded_object = activity.data["object"]
+ assert is_map(embedded_object)
+ assert embedded_object["type"] == "Follow"
+ assert embedded_object["object"] == followed.ap_id
+ assert embedded_object["id"] == follow_activity.data["id"]
end
end
@@ -839,22 +835,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.data["type"] == "Undo"
assert activity.data["actor"] == blocker.ap_id
- assert is_map(activity.data["object"])
- assert activity.data["object"]["type"] == "Block"
- assert activity.data["object"]["object"] == blocked.ap_id
- assert activity.data["object"]["id"] == block_activity.data["id"]
+ embedded_object = activity.data["object"]
+ assert is_map(embedded_object)
+ assert embedded_object["type"] == "Block"
+ assert embedded_object["object"] == blocked.ap_id
+ assert embedded_object["id"] == block_activity.data["id"]
end
end
describe "deletion" do
test "it creates a delete activity and deletes the original object" do
note = insert(:note_activity)
- object = Object.get_by_ap_id(note.data["object"]["id"])
+ object = Object.normalize(note)
{:ok, delete} = ActivityPub.delete(object)
assert delete.data["type"] == "Delete"
assert delete.data["actor"] == note.data["actor"]
- assert delete.data["object"] == note.data["object"]["id"]
+ assert delete.data["object"] == object.data["id"]
assert Activity.get_by_id(delete.id) != nil
@@ -900,13 +897,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
user = insert(:user)
note = insert(:note_activity)
+ object = Object.normalize(note)
{:ok, object} =
- Object.get_by_ap_id(note.data["object"]["id"])
+ object
|> Object.change(%{
data: %{
- "actor" => note.data["object"]["actor"],
- "id" => note.data["object"]["id"],
+ "actor" => object.data["actor"],
+ "id" => object.data["id"],
"to" => [user.ap_id],
"type" => "Note"
}
@@ -1018,8 +1016,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert update.data["actor"] == user.ap_id
assert update.data["to"] == [user.follower_address]
- assert update.data["object"]["id"] == user_data["id"]
- assert update.data["object"]["type"] == user_data["type"]
+ assert embedded_object = update.data["object"]
+ assert embedded_object["id"] == user_data["id"]
+ assert embedded_object["type"] == user_data["type"]
end
end
@@ -1076,111 +1075,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
} = activity
end
- describe "publish_one/1" do
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
+ test "fetch_activities/2 returns activities addressed to a list " do
+ user = insert(:user)
+ member = insert(:user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+ {:ok, list} = Pleroma.List.follow(list, member)
- assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
- assert called(Instances.set_reachable(inbox))
- end
+ activity = Repo.preload(activity, :bookmark)
+ activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: NaiveDateTime.utc_now()
- })
-
- assert called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: nil
- })
-
- refute called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://404.site/users/nick1/inbox"
-
- assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
-
- assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- refute called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
-
- assert {:error, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: NaiveDateTime.utc_now()
- })
-
- refute called(Instances.set_unreachable(inbox))
- end
+ assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
end
def data_uri do
@@ -1215,4 +1122,65 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert result.id == activity.id
end
end
+
+ describe "fetch_follow_information_for_user" do
+ test "syncronizes following/followers counters" do
+ user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/fuser2/followers",
+ following_address: "http://localhost:4001/users/fuser2/following"
+ )
+
+ {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
+ assert info.follower_count == 527
+ assert info.following_count == 267
+ end
+
+ test "detects hidden followers" do
+ mock(fn env ->
+ case env.url do
+ "http://localhost:4001/users/masto_closed/followers?page=1" ->
+ %Tesla.Env{status: 403, body: ""}
+
+ _ ->
+ apply(HttpRequestMock, :request, [env])
+ end
+ end)
+
+ user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following"
+ )
+
+ {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
+ assert info.hide_followers == true
+ assert info.hide_follows == false
+ end
+
+ test "detects hidden follows" do
+ mock(fn env ->
+ case env.url do
+ "http://localhost:4001/users/masto_closed/following?page=1" ->
+ %Tesla.Env{status: 403, body: ""}
+
+ _ ->
+ apply(HttpRequestMock, :request, [env])
+ end
+ end)
+
+ user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following"
+ )
+
+ {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
+ assert info.hide_followers == false
+ assert info.hide_follows == true
+ end
+ end
end
diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs
new file mode 100644
index 000000000..dbc8b9e80
--- /dev/null
+++ b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF.EnsureRePrepended
+
+ describe "rewrites summary" do
+ test "it adds `re:` to summary object when child summary and parent summary equal" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res["object"]["summary"] == "re: object-summary"
+ end
+
+ test "it adds `re:` to summary object when child summary containts re-subject of parent summary " do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "re: object-summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res["object"]["summary"] == "re: object-summary"
+ end
+ end
+
+ describe "skip filter" do
+ test "it skip if type isn't 'Create'" do
+ message = %{
+ "type" => "Annotation",
+ "object" => %{"summary" => "object-summary"}
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if summary is empty" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if inReplyTo is empty" do
+ message = %{"type" => "Create", "object" => %{"summary" => "summary"}}
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if parent and child summary isn't equal" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+ end
+end
diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/web/activity_pub/mrf/mention_policy_test.exs
new file mode 100644
index 000000000..9fd9c31df
--- /dev/null
+++ b/test/web/activity_pub/mrf/mention_policy_test.exs
@@ -0,0 +1,92 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.MentionPolicy
+
+ test "pass filter if allow list is empty" do
+ Pleroma.Config.delete([:mrf_mention])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ describe "allow" do
+ test "empty" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create"
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "to" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "cc" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "cc" => ["https://example.com/ok"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "both" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/ok2"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+ end
+
+ describe "deny" do
+ test "to" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) == {:reject, nil}
+ end
+
+ test "cc" do
+ Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) == {:reject, nil}
+ end
+ end
+end
diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs
new file mode 100644
index 000000000..1a888e18f
--- /dev/null
+++ b/test/web/activity_pub/mrf/mrf_test.exs
@@ -0,0 +1,60 @@
+defmodule Pleroma.Web.ActivityPub.MRFTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Web.ActivityPub.MRF
+
+ test "subdomains_regex/1" do
+ assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
+ ~r/^unsafe.tld$/i,
+ ~r/^(.*\.)*unsafe.tld$/i
+ ]
+ end
+
+ describe "subdomain_match/2" do
+ test "common domains" do
+ regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
+
+ assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "unsafe2.tld")
+
+ refute MRF.subdomain_match?(regexes, "example.com")
+ end
+
+ test "wildcard domains with one subdomain" do
+ regexes = MRF.subdomains_regex(["*.unsafe.tld"])
+
+ assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
+ refute MRF.subdomain_match?(regexes, "anotherunsafe.tld")
+ refute MRF.subdomain_match?(regexes, "unsafe.tldanother")
+ end
+
+ test "wildcard domains with two subdomains" do
+ regexes = MRF.subdomains_regex(["*.unsafe.tld"])
+
+ assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
+ refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
+ refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
+ end
+
+ test "matches are case-insensitive" do
+ regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
+
+ assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]
+
+ assert MRF.subdomain_match?(regexes, "UNSAFE.TLD")
+ assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD")
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "unsafe2.tld")
+
+ refute MRF.subdomain_match?(regexes, "EXAMPLE.COM")
+ refute MRF.subdomain_match?(regexes, "example.com")
+ end
+ end
+end
diff --git a/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs
new file mode 100644
index 000000000..63ed71129
--- /dev/null
+++ b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicyTest do
+ use Pleroma.DataCase
+ alias Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy
+
+ test "it clears content object" do
+ message = %{
+ "type" => "Create",
+ "object" => %{"content" => ".", "attachment" => "image"}
+ }
+
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res["object"]["content"] == ""
+
+ message = put_in(message, ["object", "content"], ".
")
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res["object"]["content"] == ""
+ end
+
+ @messages [
+ %{
+ "type" => "Create",
+ "object" => %{"content" => "test", "attachment" => "image"}
+ },
+ %{"type" => "Create", "object" => %{"content" => "."}},
+ %{"type" => "Create", "object" => %{"content" => ".
"}}
+ ]
+ test "it skips filter" do
+ Enum.each(@messages, fn message ->
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res == message
+ end)
+ end
+end
diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/web/activity_pub/mrf/normalize_markup_test.exs
new file mode 100644
index 000000000..3916a1f35
--- /dev/null
+++ b/test/web/activity_pub/mrf/normalize_markup_test.exs
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do
+ use Pleroma.DataCase
+ alias Pleroma.Web.ActivityPub.MRF.NormalizeMarkup
+
+ @html_sample """
+ this is in bold
+ this is a paragraph
+ this is a linebreak
+ this is a link with allowed "rel" attribute: example.com
+ this is a link with not allowed "rel" attribute: example.com
+ this is an image:
+
+ """
+
+ test "it filter html tags" do
+ expected = """
+ this is in bold
+ this is a paragraph
+ this is a linebreak
+ this is a link with allowed "rel" attribute: example.com
+ this is a link with not allowed "rel" attribute: example.com
+ this is an image:
+ alert('hacked')
+ """
+
+ message = %{"type" => "Create", "object" => %{"content" => @html_sample}}
+
+ assert {:ok, res} = NormalizeMarkup.filter(message)
+ assert res["object"]["content"] == expected
+ end
+
+ test "it skips filter if type isn't `Create`" do
+ message = %{"type" => "Note", "object" => %{}}
+
+ assert {:ok, res} = NormalizeMarkup.filter(message)
+ assert res == message
+ end
+end
diff --git a/test/web/activity_pub/mrf/reject_non_public_test.exs b/test/web/activity_pub/mrf/reject_non_public_test.exs
new file mode 100644
index 000000000..fdf6b245e
--- /dev/null
+++ b/test/web/activity_pub/mrf/reject_non_public_test.exs
@@ -0,0 +1,105 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic
+
+ setup do
+ policy = Pleroma.Config.get([:mrf_rejectnonpublic])
+ on_exit(fn -> Pleroma.Config.put([:mrf_rejectnonpublic], policy) end)
+
+ :ok
+ end
+
+ describe "public message" do
+ test "it's allowed when address is public" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ assert {:ok, message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's allowed when cc address contain public address" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ assert {:ok, message} = RejectNonPublic.filter(message)
+ end
+ end
+
+ describe "followers message" do
+ test "it's allowed when addrer of message in the follower addresses of user and it enabled in config" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["test-address"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], true)
+ assert {:ok, message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's rejected when addrer of message in the follower addresses of user and it disabled in config" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["test-address"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], false)
+ assert {:reject, nil} = RejectNonPublic.filter(message)
+ end
+ end
+
+ describe "direct message" do
+ test "it's allows when direct messages are allow" do
+ actor = insert(:user)
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], true)
+ assert {:ok, message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's reject when direct messages aren't allow" do
+ actor = insert(:user)
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Publid~~~"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], false)
+ assert {:reject, nil} = RejectNonPublic.filter(message)
+ 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 0fd68e103..8e86d2219 100644
--- a/test/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/web/activity_pub/mrf/simple_policy_test.exs
@@ -49,6 +49,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :media_removal], ["*.remote.instance"])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
describe "when :media_nsfw" do
@@ -74,6 +87,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> put_in(["object", "tag"], ["foo", "nsfw"])
+ |> put_in(["object", "sensitive"], true)}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
defp build_media_message do
@@ -106,6 +133,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :report_removal], ["*.remote.instance"])
+ report_message = build_report_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(report_message) == {:reject, nil}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
defp build_report_message do
@@ -146,6 +182,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+ test "match with wildcard domain" do
+ {actor, ftl_message} = build_ftl_actor_and_message()
+
+ ftl_message_actor_host =
+ ftl_message
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host])
+ local_message = build_local_message()
+
+ assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+ assert actor.follower_address in ftl_message["to"]
+ refute actor.follower_address in ftl_message["cc"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message()
@@ -192,6 +249,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(remote_message) == {:reject, nil}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :reject], ["*.remote.instance"])
+
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(remote_message) == {:reject, nil}
+ end
end
describe "when :accept" do
@@ -224,6 +289,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :accept], ["*.remote.instance"])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
end
describe "when :avatar_removal" do
@@ -251,6 +326,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
refute filtered["icon"]
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["icon"]
+ end
end
describe "when :banner_removal" do
@@ -278,6 +362,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
refute filtered["image"]
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["image"]
+ end
end
defp build_local_message do
diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/web/activity_pub/mrf/tag_policy_test.exs
new file mode 100644
index 000000000..4aa35311e
--- /dev/null
+++ b/test/web/activity_pub/mrf/tag_policy_test.exs
@@ -0,0 +1,123 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.TagPolicy
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
+ describe "mrf_tag:disable-any-subscription" do
+ test "rejects message" do
+ actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"])
+ message = %{"object" => actor.ap_id, "type" => "Follow"}
+ assert {:reject, nil} = TagPolicy.filter(message)
+ end
+ end
+
+ describe "mrf_tag:disable-remote-subscription" do
+ test "rejects non-local follow requests" do
+ actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"])
+ follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false)
+ message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id}
+ assert {:reject, nil} = TagPolicy.filter(message)
+ end
+
+ test "allows non-local follow requests" do
+ actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"])
+ follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: true)
+ message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id}
+ assert {:ok, message} = TagPolicy.filter(message)
+ end
+ end
+
+ describe "mrf_tag:sandbox" do
+ test "removes from public timelines" do
+ actor = insert(:user, tags: ["mrf_tag:sandbox"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{},
+ "to" => [@public, "f"],
+ "cc" => [@public, "d"]
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d"]},
+ "to" => ["f", actor.follower_address],
+ "cc" => ["d"]
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:force-unlisted" do
+ test "removes from the federated timeline" do
+ actor = insert(:user, tags: ["mrf_tag:force-unlisted"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{},
+ "to" => [@public, "f"],
+ "cc" => [actor.follower_address, "d"]
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]},
+ "to" => ["f", actor.follower_address],
+ "cc" => ["d", @public]
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:media-strip" do
+ test "removes attachments" do
+ actor = insert(:user, tags: ["mrf_tag:media-strip"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:media-force-nsfw" do
+ test "Mark as sensitive on presence of attachments" do
+ actor = insert(:user, tags: ["mrf_tag:media-force-nsfw"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"tag" => ["test"], "attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"tag" => ["test", "nsfw"], "attachment" => ["file1"], "sensitive" => true}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+end
diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs
new file mode 100644
index 000000000..6519e2398
--- /dev/null
+++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy
+
+ setup do
+ policy = Pleroma.Config.get([:mrf_user_allowlist]) || []
+ on_exit(fn -> Pleroma.Config.put([:mrf_user_allowlist], policy) end)
+
+ :ok
+ end
+
+ test "pass filter if allow list is empty" do
+ actor = insert(:user)
+ message = %{"actor" => actor.ap_id}
+ assert UserAllowListPolicy.filter(message) == {:ok, message}
+ end
+
+ test "pass filter if allow list isn't empty and user in allow list" do
+ actor = insert(:user)
+ Pleroma.Config.put([:mrf_user_allowlist, :localhost], [actor.ap_id, "test-ap-id"])
+ message = %{"actor" => actor.ap_id}
+ assert UserAllowListPolicy.filter(message) == {:ok, message}
+ end
+
+ test "rejected if allow list isn't empty and user not in allow list" do
+ actor = insert(:user)
+ Pleroma.Config.put([:mrf_user_allowlist, :localhost], ["test-ap-id"])
+ message = %{"actor" => actor.ap_id}
+ assert UserAllowListPolicy.filter(message) == {:reject, nil}
+ end
+end
diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs
new file mode 100644
index 000000000..36a39c84c
--- /dev/null
+++ b/test/web/activity_pub/publisher_test.exs
@@ -0,0 +1,266 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.PublisherTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+ import Tesla.Mock
+ import Mock
+
+ alias Pleroma.Activity
+ alias Pleroma.Instances
+ alias Pleroma.Web.ActivityPub.Publisher
+
+ @as_public "https://www.w3.org/ns/activitystreams#Public"
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "determine_inbox/2" do
+ test "it returns sharedInbox for messages involving as:Public in to" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ activity = %Activity{
+ data: %{"to" => [@as_public], "cc" => [user.follower_address]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving as:Public in cc" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ activity = %Activity{
+ data: %{"cc" => [@as_public], "to" => [user.follower_address]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in to" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ user_two = insert(:user)
+ user_three = insert(:user)
+
+ activity = %Activity{
+ data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in cc" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ user_two = insert(:user)
+ user_three = insert(:user)
+
+ activity = %Activity{
+ data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in total" do
+ user =
+ insert(:user, %{
+ info: %{
+ source_data: %{
+ "inbox" => "http://example.com/personal-inbox",
+ "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
+ }
+ }
+ })
+
+ user_two = insert(:user)
+
+ activity = %Activity{
+ data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns inbox for messages involving single recipients in total" do
+ user =
+ insert(:user, %{
+ info: %{
+ source_data: %{
+ "inbox" => "http://example.com/personal-inbox",
+ "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
+ }
+ }
+ })
+
+ activity = %Activity{
+ data: %{"to" => [user.ap_id], "cc" => []}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
+ end
+ end
+
+ describe "publish_one/1" do
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: nil
+ })
+
+ refute called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://404.site/users/nick1/inbox"
+
+ assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+ end
+
+ describe "publish/2" do
+ test_with_mock "publishes an activity with BCC to all relevant peers.",
+ Pleroma.Web.Federator.Publisher,
+ [:passthrough],
+ [] do
+ follower =
+ insert(:user,
+ local: false,
+ info: %{
+ ap_enabled: true,
+ source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}
+ }
+ )
+
+ actor = insert(:user, follower_address: follower.ap_id)
+ user = insert(:user)
+
+ {:ok, _follower_one} = Pleroma.User.follow(follower, actor)
+ actor = refresh_record(actor)
+
+ note_activity =
+ insert(:note_activity,
+ recipients: [follower.ap_id],
+ data_attrs: %{"bcc" => [user.ap_id]}
+ )
+
+ res = Publisher.publish(actor, note_activity)
+ assert res == :ok
+
+ assert called(
+ Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ actor: actor,
+ id: note_activity.data["id"]
+ })
+ )
+ end
+ end
+end
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 68ec03c33..060b91e29 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -11,12 +11,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus
alias Pleroma.Web.Websub.WebsubClientSubscription
+ import Mock
import Pleroma.Factory
import ExUnit.CaptureLog
- alias Pleroma.Web.CommonAPI
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -30,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
- |> Map.put("object", activity.data["object"])
+ |> Map.put("object", Object.normalize(activity).data)
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
@@ -46,12 +47,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
data["object"]
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
- data =
- data
- |> Map.put("object", object)
-
+ data = Map.put(data, "object", object)
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
- returned_object = Object.normalize(returned_activity.data["object"])
+ returned_object = Object.normalize(returned_activity, false)
assert activity =
Activity.get_create_by_object_ap_id(
@@ -61,6 +59,32 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
end
+ test "it does not fetch replied-to activities beyond max_replies_depth" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
+
+ data = Map.put(data, "object", object)
+
+ with_mock Pleroma.Web.Federator,
+ allowed_incoming_reply_depth?: fn _ -> false end do
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+ returned_object = Object.normalize(returned_activity, false)
+
+ refute Activity.get_create_by_object_ap_id(
+ "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+ )
+
+ assert returned_object.data["inReplyToAtomUri"] ==
+ "https://shitposter.club/notice/2827873"
+ end
+ end
+
test "it does not crash if the object in inReplyTo can't be fetched" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@@ -99,25 +123,27 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert data["actor"] == "http://mastodon.example.org/users/admin"
- object = Object.normalize(data["object"]).data
- assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
+ object_data = Object.normalize(data["object"]).data
- assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+ assert object_data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99512778738411822"
- assert object["cc"] == [
+ assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert object_data["cc"] == [
"http://mastodon.example.org/users/admin/followers",
"http://localtesting.pleroma.lol/users/lain"
]
- assert object["actor"] == "http://mastodon.example.org/users/admin"
- assert object["attributedTo"] == "http://mastodon.example.org/users/admin"
+ assert object_data["actor"] == "http://mastodon.example.org/users/admin"
+ assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
- assert object["context"] ==
+ assert object_data["context"] ==
"tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
- assert object["sensitive"] == true
+ assert object_data["sensitive"] == true
- user = User.get_cached_by_ap_id(object["actor"])
+ user = User.get_cached_by_ap_id(object_data["actor"])
assert user.info.note_count == 1
end
@@ -390,6 +416,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|> Map.put("attributedTo", user.ap_id)
|> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
|> Map.put("cc", [])
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
data = Map.put(data, "object", object)
@@ -413,6 +440,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|> Map.put("attributedTo", user.ap_id)
|> Map.put("to", nil)
|> Map.put("cc", nil)
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
data = Map.put(data, "object", object)
@@ -422,6 +450,27 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert !is_nil(data["cc"])
end
+ test "it strips internal likes" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+
+ likes = %{
+ "first" =>
+ "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
+ "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
+ "totalItems" => 3,
+ "type" => "OrderedCollection"
+ }
+
+ object = Map.put(data["object"], "likes", likes)
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
+
+ refute Map.has_key?(object.data, "likes")
+ end
+
test "it works for incoming update activities" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
@@ -527,6 +576,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert Activity.get_by_id(activity.id)
end
+ test "it works for incoming user deletes" do
+ %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Poison.decode!()
+
+ {:ok, _} = Transmogrifier.handle_incoming(data)
+
+ refute User.get_cached_by_ap_id(ap_id)
+ end
+
+ test "it fails for incoming user deletes with spoofed origin" do
+ %{ap_id: ap_id} = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-delete-user.json")
+ |> Poison.decode!()
+ |> Map.put("actor", ap_id)
+
+ assert :error == Transmogrifier.handle_incoming(data)
+ assert User.get_cached_by_ap_id(ap_id)
+ end
+
test "it works for incoming unannounces with an existing notice" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
@@ -548,10 +621,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
- assert data["object"]["type"] == "Announce"
- assert data["object"]["object"] == activity.data["object"]
+ assert object_data = data["object"]
+ assert object_data["type"] == "Announce"
+ assert object_data["object"] == activity.data["object"]
- assert data["object"]["id"] ==
+ assert object_data["id"] ==
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
end
@@ -861,7 +935,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
@@ -1008,14 +1082,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"])
- end
-
- test "it adds like collection to object" do
- activity = insert(:note_activity)
- {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
-
- assert modified["object"]["likes"]["type"] == "OrderedCollection"
- assert modified["object"]["likes"]["totalItems"] == 0
+ assert is_nil(modified["object"]["likes"])
end
test "the directMessage flag is present" do
@@ -1045,6 +1112,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert modified["directMessage"] == true
end
+
+ test "it strips BCC field" do
+ user = insert(:user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
+
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert is_nil(modified["bcc"])
+ end
end
describe "user upgrade" do
@@ -1070,6 +1149,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert user.info.ap_enabled
assert user.info.note_count == 1
assert user.follower_address == "https://niu.moe/users/rye/followers"
+ assert user.following_address == "https://niu.moe/users/rye/following"
user = User.get_cached_by_id(user.id)
assert user.info.note_count == 1
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index 932d5f5e7..ca5f057a7 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.UtilsTest do
use Pleroma.DataCase
alias Pleroma.Activity
diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs
index d939fc5a7..13447dc29 100644
--- a/test/web/activity_pub/views/object_view_test.exs
+++ b/test/web/activity_pub/views/object_view_test.exs
@@ -1,7 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
use Pleroma.DataCase
import Pleroma.Factory
+ alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.CommonAPI
@@ -19,19 +24,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
test "renders a note activity" do
note = insert(:note_activity)
+ object = Object.normalize(note)
result = ObjectView.render("object.json", %{object: note})
assert result["id"] == note.data["id"]
assert result["to"] == note.data["to"]
assert result["object"]["type"] == "Note"
- assert result["object"]["content"] == note.data["object"]["content"]
+ assert result["object"]["content"] == object.data["content"]
assert result["type"] == "Create"
assert result["@context"]
end
test "renders a like activity" do
note = insert(:note_activity)
+ object = Object.normalize(note)
user = insert(:user)
{:ok, like_activity, _} = CommonAPI.favorite(note.id, user)
@@ -39,12 +46,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
result = ObjectView.render("object.json", %{object: like_activity})
assert result["id"] == like_activity.data["id"]
- assert result["object"] == note.data["object"]["id"]
+ assert result["object"] == object.data["id"]
assert result["type"] == "Like"
end
test "renders an announce activity" do
note = insert(:note_activity)
+ object = Object.normalize(note)
user = insert(:user)
{:ok, announce_activity, _} = CommonAPI.repeat(note.id, user)
@@ -52,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
result = ObjectView.render("object.json", %{object: announce_activity})
assert result["id"] == announce_activity.data["id"]
- assert result["object"] == note.data["object"]["id"]
+ assert result["object"] == object.data["id"]
assert result["type"] == "Announce"
end
end
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index e6483db8b..86254117f 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -1,9 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.UserViewTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.User
alias Pleroma.Web.ActivityPub.UserView
+ alias Pleroma.Web.CommonAPI
test "Renders a user, including the public key" do
user = insert(:user)
@@ -78,4 +83,28 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
refute result["endpoints"]["oauthTokenEndpoint"]
end
end
+
+ describe "followers" do
+ test "sets totalItems to zero when followers are hidden" do
+ user = insert(:user)
+ other_user = insert(:user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
+ assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
+ info = Map.put(user.info, :hide_followers, true)
+ user = Map.put(user, :info, info)
+ assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user})
+ end
+ end
+
+ describe "following" do
+ test "sets totalItems to zero when follows are hidden" do
+ user = insert(:user)
+ other_user = insert(:user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
+ assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
+ info = Map.put(user.info, :hide_follows, true)
+ user = Map.put(user, :info, info)
+ assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
+ end
+ end
end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index e24df3cab..b62a89e68 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.VisibilityTest do
use Pleroma.DataCase
@@ -12,6 +16,9 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
following = insert(:user)
unrelated = insert(:user)
{:ok, following} = Pleroma.User.follow(following, user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+
+ Pleroma.List.follow(list, unrelated)
{:ok, public} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"})
@@ -25,6 +32,12 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
{:ok, unlisted} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"})
+ {:ok, list} =
+ CommonAPI.post(user, %{
+ "status" => "@#{mentioned.nickname}",
+ "visibility" => "list:#{list.id}"
+ })
+
%{
public: public,
private: private,
@@ -33,29 +46,65 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
user: user,
mentioned: mentioned,
following: following,
- unrelated: unrelated
+ unrelated: unrelated,
+ list: list
}
end
- test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ test "is_direct?", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ list: list
+ } do
assert Visibility.is_direct?(direct)
refute Visibility.is_direct?(public)
refute Visibility.is_direct?(private)
refute Visibility.is_direct?(unlisted)
+ assert Visibility.is_direct?(list)
end
- test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ test "is_public?", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ list: list
+ } do
refute Visibility.is_public?(direct)
assert Visibility.is_public?(public)
refute Visibility.is_public?(private)
assert Visibility.is_public?(unlisted)
+ refute Visibility.is_public?(list)
end
- test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
+ test "is_private?", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ list: list
+ } do
refute Visibility.is_private?(direct)
refute Visibility.is_private?(public)
assert Visibility.is_private?(private)
refute Visibility.is_private?(unlisted)
+ refute Visibility.is_private?(list)
+ end
+
+ test "is_list?", %{
+ public: public,
+ private: private,
+ direct: direct,
+ unlisted: unlisted,
+ list: list
+ } do
+ refute Visibility.is_list?(direct)
+ refute Visibility.is_list?(public)
+ refute Visibility.is_list?(private)
+ refute Visibility.is_list?(unlisted)
+ assert Visibility.is_list?(list)
end
test "visible_for_user?", %{
@@ -66,7 +115,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
user: user,
mentioned: mentioned,
following: following,
- unrelated: unrelated
+ unrelated: unrelated,
+ list: list
} do
# All visible to author
@@ -74,6 +124,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
assert Visibility.visible_for_user?(private, user)
assert Visibility.visible_for_user?(unlisted, user)
assert Visibility.visible_for_user?(direct, user)
+ assert Visibility.visible_for_user?(list, user)
# All visible to a mentioned user
@@ -81,6 +132,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
assert Visibility.visible_for_user?(private, mentioned)
assert Visibility.visible_for_user?(unlisted, mentioned)
assert Visibility.visible_for_user?(direct, mentioned)
+ assert Visibility.visible_for_user?(list, mentioned)
# DM not visible for just follower
@@ -88,6 +140,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
assert Visibility.visible_for_user?(private, following)
assert Visibility.visible_for_user?(unlisted, following)
refute Visibility.visible_for_user?(direct, following)
+ refute Visibility.visible_for_user?(list, following)
# Public and unlisted visible for unrelated user
@@ -95,6 +148,9 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
assert Visibility.visible_for_user?(unlisted, unrelated)
refute Visibility.visible_for_user?(private, unrelated)
refute Visibility.visible_for_user?(direct, unrelated)
+
+ # Visible for a list member
+ assert Visibility.visible_for_user?(list, unrelated)
end
test "doesn't die when the user doesn't exist",
@@ -111,18 +167,24 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
public: public,
private: private,
direct: direct,
- unlisted: unlisted
+ unlisted: unlisted,
+ list: list
} do
assert Visibility.get_visibility(public) == "public"
assert Visibility.get_visibility(private) == "private"
assert Visibility.get_visibility(direct) == "direct"
assert Visibility.get_visibility(unlisted) == "unlisted"
+ assert Visibility.get_visibility(list) == "list"
end
test "get_visibility with directMessage flag" do
assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct"
end
+ test "get_visibility with listMessage flag" do
+ assert Visibility.get_visibility(%{data: %{"listMessage" => ""}}) == "list"
+ end
+
describe "entire_thread_visible_for_user?/2" do
test "returns false if not found activity", %{user: user} do
refute Visibility.entire_thread_visible_for_user?(%Activity{}, user)
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 4ea33a6cc..bcbc18639 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -1010,6 +1010,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"uses" => 0
}
end
+
+ test "with invalid token" do
+ admin = insert(:user, info: %{is_admin: true})
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
+
+ assert json_response(conn, :not_found) == "Not found"
+ end
end
describe "GET /api/pleroma/admin/reports/:id" do
@@ -1234,7 +1245,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
recipients = Enum.map(response["mentions"], & &1["username"])
- assert conn.assigns[:user].nickname in recipients
assert reporter.nickname in recipients
assert response["content"] == "I will check it out"
assert response["visibility"] == "direct"
@@ -1408,14 +1418,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
post(conn, "/api/pleroma/admin/config", %{
configs: [
%{group: "pleroma", key: "key1", value: "value1"},
+ %{
+ group: "ueberauth",
+ key: "Ueberauth.Strategy.Twitter.OAuth",
+ value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
+ },
%{
group: "pleroma",
key: "key2",
value: %{
- "nested_1" => "nested_value1",
- "nested_2" => [
- %{"nested_22" => "nested_value222"},
- %{"nested_33" => %{"nested_44" => "nested_444"}}
+ ":nested_1" => "nested_value1",
+ ":nested_2" => [
+ %{":nested_22" => "nested_value222"},
+ %{":nested_33" => %{":nested_44" => "nested_444"}}
]
}
},
@@ -1424,13 +1439,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
key: "key3",
value: [
%{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
- %{"nested_4" => ":true"}
+ %{"nested_4" => true}
]
},
%{
group: "pleroma",
key: "key4",
- value: %{"nested_5" => ":upload", "endpoint" => "https://example.com"}
+ value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
},
%{
group: "idna",
@@ -1447,31 +1462,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"key" => "key1",
"value" => "value1"
},
+ %{
+ "group" => "ueberauth",
+ "key" => "Ueberauth.Strategy.Twitter.OAuth",
+ "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}]
+ },
%{
"group" => "pleroma",
"key" => "key2",
- "value" => [
- %{"nested_1" => "nested_value1"},
- %{
- "nested_2" => [
- %{"nested_22" => "nested_value222"},
- %{"nested_33" => %{"nested_44" => "nested_444"}}
- ]
- }
- ]
+ "value" => %{
+ ":nested_1" => "nested_value1",
+ ":nested_2" => [
+ %{":nested_22" => "nested_value222"},
+ %{":nested_33" => %{":nested_44" => "nested_444"}}
+ ]
+ }
},
%{
"group" => "pleroma",
"key" => "key3",
"value" => [
- [%{"nested_3" => "nested_3"}, %{"nested_33" => "nested_33"}],
+ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
%{"nested_4" => true}
]
},
%{
"group" => "pleroma",
"key" => "key4",
- "value" => [%{"endpoint" => "https://example.com"}, %{"nested_5" => "upload"}]
+ "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"}
},
%{
"group" => "idna",
@@ -1483,23 +1501,23 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert Application.get_env(:pleroma, :key1) == "value1"
- assert Application.get_env(:pleroma, :key2) == [
+ assert Application.get_env(:pleroma, :key2) == %{
nested_1: "nested_value1",
nested_2: [
- [nested_22: "nested_value222"],
- [nested_33: [nested_44: "nested_444"]]
+ %{nested_22: "nested_value222"},
+ %{nested_33: %{nested_44: "nested_444"}}
]
- ]
+ }
assert Application.get_env(:pleroma, :key3) == [
- [nested_3: :nested_3, nested_33: "nested_33"],
- [nested_4: true]
+ %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
+ %{"nested_4" => true}
]
- assert Application.get_env(:pleroma, :key4) == [
- endpoint: "https://example.com",
+ assert Application.get_env(:pleroma, :key4) == %{
+ "endpoint" => "https://example.com",
nested_5: :upload
- ]
+ }
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
end
@@ -1508,11 +1526,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
config1 = insert(:config, key: "keyaa1")
config2 = insert(:config, key: "keyaa2")
+ insert(:config,
+ group: "ueberauth",
+ key: "Ueberauth.Strategy.Microsoft.OAuth",
+ value: :erlang.term_to_binary([])
+ )
+
conn =
post(conn, "/api/pleroma/admin/config", %{
configs: [
%{group: config1.group, key: config1.key, value: "another_value"},
- %{group: config2.group, key: config2.key, delete: "true"}
+ %{group: config2.group, key: config2.key, delete: "true"},
+ %{
+ group: "ueberauth",
+ key: "Ueberauth.Strategy.Microsoft.OAuth",
+ delete: "true"
+ }
]
})
@@ -1537,11 +1566,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
%{
"group" => "pleroma",
"key" => "Pleroma.Captcha.NotReal",
- "value" => %{
- "enabled" => ":false",
- "method" => "Pleroma.Captcha.Kocaptcha",
- "seconds_valid" => "i:60"
- }
+ "value" => [
+ %{"tuple" => [":enabled", false]},
+ %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+ %{"tuple" => [":seconds_valid", 60]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
+ ]
}
]
})
@@ -1552,9 +1584,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"group" => "pleroma",
"key" => "Pleroma.Captcha.NotReal",
"value" => [
- %{"enabled" => false},
- %{"method" => "Pleroma.Captcha.Kocaptcha"},
- %{"seconds_valid" => 60}
+ %{"tuple" => [":enabled", false]},
+ %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+ %{"tuple" => [":seconds_valid", 60]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
]
}
]
@@ -1570,51 +1605,57 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"key" => "Pleroma.Web.Endpoint.NotReal",
"value" => [
%{
- "http" => %{
- "dispatch" => [
+ "tuple" => [
+ ":http",
+ [
%{
"tuple" => [
- ":_",
+ ":key2",
[
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
- %{
- "tuple" => [
- "Phoenix.Transports.WebSocket",
- %{
- "tuple" => [
- "Pleroma.Web.Endpoint",
- "Pleroma.Web.UserSocket",
- []
- ]
- }
- ]
- }
- ]
- },
%{
"tuple" => [
":_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{
- "tuple" => ["Pleroma.Web.Endpoint", []]
- }
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
+ ]
]
}
]
]
}
]
- }
+ ]
}
]
}
@@ -1628,47 +1669,380 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"key" => "Pleroma.Web.Endpoint.NotReal",
"value" => [
%{
- "http" => %{
- "dispatch" => %{
- "_" => [
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
+ "tuple" => [
+ ":http",
+ [
+ %{
+ "tuple" => [
+ ":key2",
+ [
%{
- "Elixir.Phoenix.Transports.WebSocket" => %{
- "tuple" => [
- "Pleroma.Web.Endpoint",
- "Pleroma.Web.UserSocket",
- []
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
]
- }
+ ]
}
]
- },
- %{
- "tuple" => [
- "_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{"Elixir.Pleroma.Web.Endpoint" => []}
- ]
- }
- ]
- }
- }
+ ]
+ }
+ ]
+ ]
}
]
}
]
}
end
+
+ test "settings with nesting map", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key2", "some_val"]},
+ %{
+ "tuple" => [
+ ":key3",
+ %{
+ ":max_options" => 20,
+ ":max_option_chars" => 200,
+ ":min_expiration" => 0,
+ ":max_expiration" => 31_536_000,
+ "nested" => %{
+ ":max_options" => 20,
+ ":max_option_chars" => 200,
+ ":min_expiration" => 0,
+ ":max_expiration" => 31_536_000
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) ==
+ %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key2", "some_val"]},
+ %{
+ "tuple" => [
+ ":key3",
+ %{
+ ":max_expiration" => 31_536_000,
+ ":max_option_chars" => 200,
+ ":max_options" => 20,
+ ":min_expiration" => 0,
+ "nested" => %{
+ ":max_expiration" => 31_536_000,
+ ":max_option_chars" => 200,
+ ":max_options" => 20,
+ ":min_expiration" => 0
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ test "value as map", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma",
+ "key" => ":key1",
+ "value" => %{"key" => "some_val"}
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) ==
+ %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => ":key1",
+ "value" => %{"key" => "some_val"}
+ }
+ ]
+ }
+ end
+
+ test "dispatch setting", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]},
+ %{"tuple" => [":dispatch", ["{:_,
+ [
+ {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]}"]]}
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ dispatch_string =
+ "{:_, [{\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, " <>
+ "{\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, " <>
+ "{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, " <>
+ "{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}]}"
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]},
+ %{
+ "tuple" => [
+ ":dispatch",
+ [
+ dispatch_string
+ ]
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ test "queues key as atom", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => "pleroma_job_queue",
+ "key" => ":queues",
+ "value" => [
+ %{"tuple" => [":federator_incoming", 50]},
+ %{"tuple" => [":federator_outgoing", 50]},
+ %{"tuple" => [":web_push", 50]},
+ %{"tuple" => [":mailer", 10]},
+ %{"tuple" => [":transmogrifier", 20]},
+ %{"tuple" => [":scheduled_activities", 10]},
+ %{"tuple" => [":background", 5]}
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma_job_queue",
+ "key" => ":queues",
+ "value" => [
+ %{"tuple" => [":federator_incoming", 50]},
+ %{"tuple" => [":federator_outgoing", 50]},
+ %{"tuple" => [":web_push", 50]},
+ %{"tuple" => [":mailer", 10]},
+ %{"tuple" => [":transmogrifier", 20]},
+ %{"tuple" => [":scheduled_activities", 10]},
+ %{"tuple" => [":background", 5]}
+ ]
+ }
+ ]
+ }
+ end
+
+ test "delete part of settings by atom subkeys", %{conn: conn} do
+ config =
+ insert(:config,
+ key: "keyaa1",
+ value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
+ )
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: config.group,
+ key: config.key,
+ subkeys: [":subkey1", ":subkey3"],
+ delete: "true"
+ }
+ ]
+ })
+
+ assert(
+ json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => "pleroma",
+ "key" => "keyaa1",
+ "value" => [%{"tuple" => [":subkey2", "val2"]}]
+ }
+ ]
+ }
+ )
+ end
+ end
+
+ describe "config mix tasks run" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ temp_file = "config/test.exported_from_db.secret.exs"
+
+ Mix.shell(Mix.Shell.Quiet)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ :ok = File.rm(temp_file)
+ end)
+
+ dynamic = Pleroma.Config.get([:instance, :dynamic_configuration])
+
+ Pleroma.Config.put([:instance, :dynamic_configuration], true)
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :dynamic_configuration], dynamic)
+ end)
+
+ %{conn: assign(conn, :user, admin), admin: admin}
+ end
+
+ test "transfer settings to DB and to file", %{conn: conn, admin: admin} do
+ assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
+ conn = get(conn, "/api/pleroma/admin/config/migrate_to_db")
+ assert json_response(conn, 200) == %{}
+ assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/config/migrate_from_db")
+
+ assert json_response(conn, 200) == %{}
+ assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
+ end
+ end
+
+ describe "GET /api/pleroma/admin/users/:nickname/statuses" do
+ setup do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+
+ date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
+ date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
+ date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
+
+ insert(:note_activity, user: user, published: date1)
+ insert(:note_activity, user: user, published: date2)
+ insert(:note_activity, user: user, published: date3)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+
+ {:ok, conn: conn, user: user}
+ end
+
+ test "renders user's statuses", %{conn: conn, user: user} do
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+
+ assert json_response(conn, 200) |> length() == 3
+ end
+
+ test "renders user's statuses with a limit", %{conn: conn, user: user} do
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
+
+ assert json_response(conn, 200) |> length() == 2
+ end
+
+ test "doesn't return private statuses by default", %{conn: conn, user: user} do
+ {:ok, _private_status} =
+ CommonAPI.post(user, %{"status" => "private", "visibility" => "private"})
+
+ {:ok, _public_status} =
+ CommonAPI.post(user, %{"status" => "public", "visibility" => "public"})
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+
+ assert json_response(conn, 200) |> length() == 4
+ end
+
+ test "returns private statuses with godmode on", %{conn: conn, user: user} do
+ {:ok, _private_status} =
+ CommonAPI.post(user, %{"status" => "private", "visibility" => "private"})
+
+ {:ok, _public_status} =
+ CommonAPI.post(user, %{"status" => "public", "visibility" => "public"})
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
+
+ assert json_response(conn, 200) |> length() == 5
+ end
end
end
diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs
index 10cb3b68a..3190dc1c8 100644
--- a/test/web/admin_api/config_test.exs
+++ b/test/web/admin_api/config_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.AdminAPI.ConfigTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
@@ -57,117 +61,314 @@ defmodule Pleroma.Web.AdminAPI.ConfigTest do
assert Config.from_binary(binary) == "value as string"
end
+ test "boolean" do
+ binary = Config.transform(false)
+ assert binary == :erlang.term_to_binary(false)
+ assert Config.from_binary(binary) == false
+ end
+
+ test "nil" do
+ binary = Config.transform(nil)
+ assert binary == :erlang.term_to_binary(nil)
+ assert Config.from_binary(binary) == nil
+ end
+
+ test "integer" do
+ binary = Config.transform(150)
+ assert binary == :erlang.term_to_binary(150)
+ assert Config.from_binary(binary) == 150
+ end
+
+ test "atom" do
+ binary = Config.transform(":atom")
+ assert binary == :erlang.term_to_binary(:atom)
+ assert Config.from_binary(binary) == :atom
+ end
+
+ test "pleroma module" do
+ binary = Config.transform("Pleroma.Bookmark")
+ assert binary == :erlang.term_to_binary(Pleroma.Bookmark)
+ assert Config.from_binary(binary) == Pleroma.Bookmark
+ end
+
+ test "phoenix module" do
+ binary = Config.transform("Phoenix.Socket.V1.JSONSerializer")
+ assert binary == :erlang.term_to_binary(Phoenix.Socket.V1.JSONSerializer)
+ assert Config.from_binary(binary) == Phoenix.Socket.V1.JSONSerializer
+ end
+
+ test "sigil" do
+ binary = Config.transform("~r/comp[lL][aA][iI][nN]er/")
+ assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/)
+ assert Config.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/
+ end
+
+ test "2 child tuple" do
+ binary = Config.transform(%{"tuple" => ["v1", ":v2"]})
+ assert binary == :erlang.term_to_binary({"v1", :v2})
+ assert Config.from_binary(binary) == {"v1", :v2}
+ end
+
+ test "tuple with n childs" do
+ binary =
+ Config.transform(%{
+ "tuple" => [
+ "v1",
+ ":v2",
+ "Pleroma.Bookmark",
+ 150,
+ false,
+ "Phoenix.Socket.V1.JSONSerializer"
+ ]
+ })
+
+ assert binary ==
+ :erlang.term_to_binary(
+ {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
+ )
+
+ assert Config.from_binary(binary) ==
+ {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
+ end
+
+ test "tuple with dispatch key" do
+ binary = Config.transform(%{"tuple" => [":dispatch", ["{:_,
+ [
+ {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]}"]]})
+
+ assert binary ==
+ :erlang.term_to_binary(
+ {:dispatch,
+ [
+ {:_,
+ [
+ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]}
+ ]}
+ )
+
+ assert Config.from_binary(binary) ==
+ {:dispatch,
+ [
+ {:_,
+ [
+ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+ {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+ {Phoenix.Transports.WebSocket,
+ {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}},
+ {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+ ]}
+ ]}
+ end
+
+ test "map with string key" do
+ binary = Config.transform(%{"key" => "value"})
+ assert binary == :erlang.term_to_binary(%{"key" => "value"})
+ assert Config.from_binary(binary) == %{"key" => "value"}
+ end
+
+ test "map with atom key" do
+ binary = Config.transform(%{":key" => "value"})
+ assert binary == :erlang.term_to_binary(%{key: "value"})
+ assert Config.from_binary(binary) == %{key: "value"}
+ end
+
+ test "list of strings" do
+ binary = Config.transform(["v1", "v2", "v3"])
+ assert binary == :erlang.term_to_binary(["v1", "v2", "v3"])
+ assert Config.from_binary(binary) == ["v1", "v2", "v3"]
+ end
+
test "list of modules" do
binary = Config.transform(["Pleroma.Repo", "Pleroma.Activity"])
assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity])
assert Config.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity]
end
- test "list of strings" do
- binary = Config.transform(["string1", "string2"])
- assert binary == :erlang.term_to_binary(["string1", "string2"])
- assert Config.from_binary(binary) == ["string1", "string2"]
+ test "list of atoms" do
+ binary = Config.transform([":v1", ":v2", ":v3"])
+ assert binary == :erlang.term_to_binary([:v1, :v2, :v3])
+ assert Config.from_binary(binary) == [:v1, :v2, :v3]
end
- test "map" do
+ test "list of mixed values" do
binary =
- Config.transform(%{
- "types" => "Pleroma.PostgresTypes",
- "telemetry_event" => ["Pleroma.Repo.Instrumenter"],
- "migration_lock" => ""
- })
+ Config.transform([
+ "v1",
+ ":v2",
+ "Pleroma.Repo",
+ "Phoenix.Socket.V1.JSONSerializer",
+ 15,
+ false
+ ])
assert binary ==
- :erlang.term_to_binary(
- telemetry_event: [Pleroma.Repo.Instrumenter],
- types: Pleroma.PostgresTypes
- )
+ :erlang.term_to_binary([
+ "v1",
+ :v2,
+ Pleroma.Repo,
+ Phoenix.Socket.V1.JSONSerializer,
+ 15,
+ false
+ ])
assert Config.from_binary(binary) == [
- telemetry_event: [Pleroma.Repo.Instrumenter],
- types: Pleroma.PostgresTypes
+ "v1",
+ :v2,
+ Pleroma.Repo,
+ Phoenix.Socket.V1.JSONSerializer,
+ 15,
+ false
]
end
- test "complex map with nested integers, lists and atoms" do
+ test "simple keyword" do
+ binary = Config.transform([%{"tuple" => [":key", "value"]}])
+ assert binary == :erlang.term_to_binary([{:key, "value"}])
+ assert Config.from_binary(binary) == [{:key, "value"}]
+ assert Config.from_binary(binary) == [key: "value"]
+ end
+
+ test "keyword with partial_chain key" do
binary =
- Config.transform(%{
- "uploader" => "Pleroma.Uploaders.Local",
- "filters" => ["Pleroma.Upload.Filter.Dedupe"],
- "link_name" => ":true",
- "proxy_remote" => ":false",
- "proxy_opts" => %{
- "redirect_on_failure" => ":false",
- "max_body_length" => "i:1048576",
- "http" => %{
- "follow_redirect" => ":true",
- "pool" => ":upload"
- }
- }
- })
+ Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
- assert binary ==
- :erlang.term_to_binary(
- filters: [Pleroma.Upload.Filter.Dedupe],
- link_name: true,
- proxy_opts: [
- http: [
- follow_redirect: true,
- pool: :upload
- ],
- max_body_length: 1_048_576,
- redirect_on_failure: false
- ],
- proxy_remote: false,
- uploader: Pleroma.Uploaders.Local
- )
-
- assert Config.from_binary(binary) ==
- [
- filters: [Pleroma.Upload.Filter.Dedupe],
- link_name: true,
- proxy_opts: [
- http: [
- follow_redirect: true,
- pool: :upload
- ],
- max_body_length: 1_048_576,
- redirect_on_failure: false
- ],
- proxy_remote: false,
- uploader: Pleroma.Uploaders.Local
- ]
+ assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
+ assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
end
test "keyword" do
binary =
- Config.transform(%{
- "level" => ":warn",
- "meta" => [":all"],
- "webhook_url" => "https://hooks.slack.com/services/YOUR-KEY-HERE"
- })
+ Config.transform([
+ %{"tuple" => [":types", "Pleroma.PostgresTypes"]},
+ %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
+ %{"tuple" => [":migration_lock", nil]},
+ %{"tuple" => [":key1", 150]},
+ %{"tuple" => [":key2", "string"]}
+ ])
+
+ assert binary ==
+ :erlang.term_to_binary(
+ types: Pleroma.PostgresTypes,
+ telemetry_event: [Pleroma.Repo.Instrumenter],
+ migration_lock: nil,
+ key1: 150,
+ key2: "string"
+ )
+
+ assert Config.from_binary(binary) == [
+ types: Pleroma.PostgresTypes,
+ telemetry_event: [Pleroma.Repo.Instrumenter],
+ migration_lock: nil,
+ key1: 150,
+ key2: "string"
+ ]
+ end
+
+ test "complex keyword with nested mixed childs" do
+ binary =
+ Config.transform([
+ %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
+ %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
+ %{"tuple" => [":link_name", true]},
+ %{"tuple" => [":proxy_remote", false]},
+ %{"tuple" => [":common_map", %{":key" => "value"}]},
+ %{
+ "tuple" => [
+ ":proxy_opts",
+ [
+ %{"tuple" => [":redirect_on_failure", false]},
+ %{"tuple" => [":max_body_length", 1_048_576]},
+ %{
+ "tuple" => [
+ ":http",
+ [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}]
+ ]
+ }
+ ]
+ ]
+ }
+ ])
+
+ assert binary ==
+ :erlang.term_to_binary(
+ uploader: Pleroma.Uploaders.Local,
+ filters: [Pleroma.Upload.Filter.Dedupe],
+ link_name: true,
+ proxy_remote: false,
+ common_map: %{key: "value"},
+ proxy_opts: [
+ redirect_on_failure: false,
+ max_body_length: 1_048_576,
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ]
+ ]
+ )
+
+ assert Config.from_binary(binary) ==
+ [
+ uploader: Pleroma.Uploaders.Local,
+ filters: [Pleroma.Upload.Filter.Dedupe],
+ link_name: true,
+ proxy_remote: false,
+ common_map: %{key: "value"},
+ proxy_opts: [
+ redirect_on_failure: false,
+ max_body_length: 1_048_576,
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ]
+ ]
+ ]
+ end
+
+ test "common keyword" do
+ binary =
+ Config.transform([
+ %{"tuple" => [":level", ":warn"]},
+ %{"tuple" => [":meta", [":all"]]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":val", nil]},
+ %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
+ ])
assert binary ==
:erlang.term_to_binary(
level: :warn,
meta: [:all],
+ path: "",
+ val: nil,
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
)
assert Config.from_binary(binary) == [
level: :warn,
meta: [:all],
+ path: "",
+ val: nil,
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
]
end
- test "complex map with sigil" do
+ test "complex keyword with sigil" do
binary =
- Config.transform(%{
- federated_timeline_removal: [],
- reject: [~r/comp[lL][aA][iI][nN]er/],
- replace: []
- })
+ Config.transform([
+ %{"tuple" => [":federated_timeline_removal", []]},
+ %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
+ %{"tuple" => [":replace", []]}
+ ])
assert binary ==
:erlang.term_to_binary(
@@ -180,54 +381,68 @@ defmodule Pleroma.Web.AdminAPI.ConfigTest do
[federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []]
end
- test "complex map with tuples with more than 2 values" do
+ test "complex keyword with tuples with more than 2 values" do
binary =
- Config.transform(%{
- "http" => %{
- "dispatch" => [
- %{
- "tuple" => [
- ":_",
- [
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
- %{
- "tuple" => [
- "Phoenix.Transports.WebSocket",
- %{"tuple" => ["Pleroma.Web.Endpoint", "Pleroma.Web.UserSocket", []]}
+ Config.transform([
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{
+ "tuple" => [
+ ":key1",
+ [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
]
- }
- ]
- },
- %{
- "tuple" => [
- ":_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{
- "tuple" => ["Pleroma.Web.Endpoint", []]
- }
- ]
- }
+ ]
+ }
+ ]
]
- ]
- }
+ }
+ ]
]
}
- })
+ ])
assert binary ==
:erlang.term_to_binary(
http: [
- dispatch: [
+ key1: [
_: [
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
@@ -241,7 +456,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigTest do
assert Config.from_binary(binary) == [
http: [
- dispatch: [
+ key1: [
{:_,
[
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 6f57bbe1f..16b3f121d 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.CommonAPITest do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert object.data["tag"] == ["2hu"]
end
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.CommonAPITest do
"content_type" => "text/html"
})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert object.data["content"] == "2hu
alert('xss')"
end
@@ -103,7 +103,7 @@ defmodule Pleroma.Web.CommonAPITest do
"content_type" => "text/markdown"
})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert object.data["content"] == "2hu
alert('xss')"
end
@@ -129,6 +129,37 @@ defmodule Pleroma.Web.CommonAPITest do
})
end)
end
+
+ test "it allows to address a list" do
+ user = insert(:user)
+ {:ok, list} = Pleroma.List.create("foo", user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
+
+ assert activity.data["bcc"] == [list.ap_id]
+ assert activity.recipients == [list.ap_id, user.ap_id]
+ assert activity.data["listMessage"] == list.ap_id
+ end
+
+ test "it returns error when status is empty and no attachments" do
+ user = insert(:user)
+
+ assert {:error, "Cannot post an empty status without attachments"} =
+ CommonAPI.post(user, %{"status" => ""})
+ end
+
+ test "it returns error when character limit is exceeded" do
+ limit = Pleroma.Config.get([:instance, :limit])
+ Pleroma.Config.put([:instance, :limit], 5)
+
+ user = insert(:user)
+
+ assert {:error, "The status is over the character limit"} =
+ CommonAPI.post(user, %{"status" => "foobar"})
+
+ Pleroma.Config.put([:instance, :limit], limit)
+ end
end
describe "reactions" do
@@ -346,6 +377,20 @@ defmodule Pleroma.Web.CommonAPITest do
end
end
+ describe "unfollow/2" do
+ test "also unsubscribes a user" do
+ [follower, followed] = insert_pair(:user)
+ {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
+ {:ok, followed} = User.subscribe(follower, followed)
+
+ assert User.subscribed_to?(follower, followed)
+
+ {:ok, follower} = CommonAPI.unfollow(follower, followed)
+
+ refute User.subscribed_to?(follower, followed)
+ end
+ end
+
describe "accept_follow_request/2" do
test "after acceptance, it sets all existing pending follow request states to 'accept'" do
user = insert(:user, info: %{locked: true})
@@ -387,4 +432,23 @@ defmodule Pleroma.Web.CommonAPITest do
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
end
end
+
+ describe "vote/3" do
+ test "does not allow to vote twice" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Am I cute?",
+ "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+ })
+
+ object = Object.normalize(activity)
+
+ {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
+
+ assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
+ end
+ end
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index b3a334d50..5989d7d29 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
alias Pleroma.Web.Endpoint
use Pleroma.DataCase
+ import ExUnit.CaptureLog
import Pleroma.Factory
@public_address "https://www.w3.org/ns/activitystreams#Public"
@@ -202,7 +203,9 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
expected = ""
- assert Utils.date_to_asctime(date) == expected
+ assert capture_log(fn ->
+ assert Utils.date_to_asctime(date) == expected
+ end) =~ "[warn] Date #{date} in wrong format, must be ISO 8601"
end
test "when date is a Unix timestamp" do
@@ -210,13 +213,23 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
expected = ""
- assert Utils.date_to_asctime(date) == expected
+ assert capture_log(fn ->
+ assert Utils.date_to_asctime(date) == expected
+ end) =~ "[warn] Date #{date} in wrong format, must be ISO 8601"
end
test "when date is nil" do
expected = ""
- assert Utils.date_to_asctime(nil) == expected
+ assert capture_log(fn ->
+ assert Utils.date_to_asctime(nil) == expected
+ end) =~ "[warn] Date in wrong format, must be ISO 8601"
+ end
+
+ test "when date is a random string" do
+ assert capture_log(fn ->
+ assert Utils.date_to_asctime("foo") == ""
+ end) =~ "[warn] Date foo in wrong format, must be ISO 8601"
end
end
@@ -293,7 +306,6 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
mentions = [mentioned_user.ap_id]
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private")
-
assert length(to) == 2
assert length(cc) == 0
@@ -347,4 +359,242 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
assert third_user.ap_id in to
end
end
+
+ describe "get_by_id_or_ap_id/1" do
+ test "get activity by id" do
+ activity = insert(:note_activity)
+ %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.id)
+ assert note.id == activity.id
+ end
+
+ test "get activity by ap_id" do
+ activity = insert(:note_activity)
+ %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.data["object"])
+ assert note.id == activity.id
+ end
+
+ test "get activity by object when type isn't `Create` " do
+ activity = insert(:like_activity)
+ %Pleroma.Activity{} = like = Utils.get_by_id_or_ap_id(activity.id)
+ assert like.data["object"] == activity.data["object"]
+ end
+ end
+
+ describe "to_master_date/1" do
+ test "removes microseconds from date (NaiveDateTime)" do
+ assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z"
+ end
+
+ test "removes microseconds from date (String)" do
+ assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z"
+ end
+
+ test "returns empty string when date invalid" do
+ assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == ""
+ end
+ end
+
+ describe "conversation_id_to_context/1" do
+ test "returns id" do
+ object = insert(:note)
+ assert Utils.conversation_id_to_context(object.id) == object.data["id"]
+ end
+
+ test "returns error if object not found" do
+ assert Utils.conversation_id_to_context("123") == {:error, "No such conversation"}
+ end
+ end
+
+ describe "maybe_notify_mentioned_recipients/2" do
+ test "returns recipients when activity is not `Create`" do
+ activity = insert(:like_activity)
+ assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == ["test"]
+ end
+
+ test "returns recipients from tag" do
+ user = insert(:user)
+
+ object =
+ insert(:note,
+ user: user,
+ data: %{
+ "tag" => [
+ %{"type" => "Hashtag"},
+ "",
+ %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
+ %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
+ %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
+ ]
+ }
+ )
+
+ activity = insert(:note_activity, user: user, note: object)
+
+ assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
+ "test",
+ "https://testing.pleroma.lol/users/lain",
+ "https://shitposter.club/user/5381"
+ ]
+ end
+
+ test "returns recipients when object is map" do
+ user = insert(:user)
+ object = insert(:note, user: user)
+
+ activity =
+ insert(:note_activity,
+ user: user,
+ note: object,
+ data_attrs: %{
+ "object" => %{
+ "tag" => [
+ %{"type" => "Hashtag"},
+ "",
+ %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
+ %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
+ %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
+ ]
+ }
+ }
+ )
+
+ Pleroma.Repo.delete(object)
+
+ assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
+ "test",
+ "https://testing.pleroma.lol/users/lain",
+ "https://shitposter.club/user/5381"
+ ]
+ end
+
+ test "returns recipients when object not found" do
+ user = insert(:user)
+ object = insert(:note, user: user)
+
+ activity = insert(:note_activity, user: user, note: object)
+ Pleroma.Repo.delete(object)
+
+ assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [
+ "test-test"
+ ]
+ end
+ end
+
+ describe "attachments_from_ids_descs/2" do
+ test "returns [] when attachment ids is empty" do
+ assert Utils.attachments_from_ids_descs([], "{}") == []
+ end
+
+ test "returns list attachments with desc" do
+ object = insert(:note)
+ desc = Jason.encode!(%{object.id => "test-desc"})
+
+ assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [
+ Map.merge(object.data, %{"name" => "test-desc"})
+ ]
+ end
+ end
+
+ describe "attachments_from_ids/1" do
+ test "returns attachments with descs" do
+ object = insert(:note)
+ desc = Jason.encode!(%{object.id => "test-desc"})
+
+ assert Utils.attachments_from_ids(%{
+ "media_ids" => ["#{object.id}"],
+ "descriptions" => desc
+ }) == [
+ Map.merge(object.data, %{"name" => "test-desc"})
+ ]
+ end
+
+ test "returns attachments without descs" do
+ object = insert(:note)
+ assert Utils.attachments_from_ids(%{"media_ids" => ["#{object.id}"]}) == [object.data]
+ end
+
+ test "returns [] when not pass media_ids" do
+ assert Utils.attachments_from_ids(%{}) == []
+ end
+ end
+
+ describe "maybe_add_list_data/3" do
+ test "adds list params when found user list" do
+ user = insert(:user)
+ {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user)
+
+ assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
+ %{
+ additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id},
+ object: %{"listMessage" => list.ap_id}
+ }
+ end
+
+ test "returns original params when list not found" do
+ user = insert(:user)
+ {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user))
+
+ assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
+ %{additional: %{}, object: %{}}
+ end
+ end
+
+ describe "make_note_data/11" do
+ test "returns note data" do
+ user = insert(:user)
+ note = insert(:note)
+ user2 = insert(:user)
+ user3 = insert(:user)
+
+ assert Utils.make_note_data(
+ user.ap_id,
+ [user2.ap_id],
+ "2hu",
+ "This is :moominmamma: note ",
+ [],
+ note.id,
+ [name: "jimm"],
+ "test summary",
+ [user3.ap_id],
+ false,
+ %{"custom_tag" => "test"}
+ ) == %{
+ "actor" => user.ap_id,
+ "attachment" => [],
+ "cc" => [user3.ap_id],
+ "content" => "This is :moominmamma: note ",
+ "context" => "2hu",
+ "sensitive" => false,
+ "summary" => "test summary",
+ "tag" => ["jimm"],
+ "to" => [user2.ap_id],
+ "type" => "Note",
+ "custom_tag" => "test"
+ }
+ end
+ end
+
+ describe "maybe_add_attachments/3" do
+ test "returns parsed results when no_links is true" do
+ assert Utils.maybe_add_attachments(
+ {"test", [], ["tags"]},
+ [],
+ true
+ ) == {"test", [], ["tags"]}
+ end
+
+ test "adds attachments to parsed results" do
+ attachment = %{"url" => [%{"href" => "SakuraPM.png"}]}
+
+ assert Utils.maybe_add_attachments(
+ {"test", [], ["tags"]},
+ [attachment],
+ false
+ ) == {
+ "testSakuraPM.png ",
+ [],
+ ["tags"]
+ }
+ end
+ end
end
diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs
index cc78b3ae1..c13db9526 100644
--- a/test/web/fallback_test.exs
+++ b/test/web/fallback_test.exs
@@ -30,6 +30,10 @@ defmodule Pleroma.Web.FallbackTest do
|> json_response(404) == %{"error" => "Not implemented"}
end
+ test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do
+ assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/"
+ end
+
test "GET /*path", %{conn: conn} do
assert conn
|> get("/foo")
diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs
index 0f43bc8f2..73cfaa8f1 100644
--- a/test/web/federator_test.exs
+++ b/test/web/federator_test.exs
@@ -12,9 +12,25 @@ defmodule Pleroma.Web.FederatorTest do
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
:ok
end
+ describe "Publisher.perform" do
+ test "call `perform` with unknown task" do
+ assert {
+ :error,
+ "Don't know what to do with this"
+ } = Pleroma.Web.Federator.Publisher.perform("test", :ok, :ok)
+ end
+ end
+
describe "Publish an activity" do
setup do
user = insert(:user)
@@ -213,5 +229,21 @@ defmodule Pleroma.Web.FederatorTest do
:error = Federator.incoming_ap_doc(params)
end
+
+ test "it does not crash if MRF rejects the post" do
+ policies = Pleroma.Config.get([:instance, :rewrite_policy])
+ mrf_keyword_policy = Pleroma.Config.get(:mrf_keyword)
+ Pleroma.Config.put([:mrf_keyword, :reject], ["lain"])
+ Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.KeywordPolicy)
+
+ params =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+
+ assert Federator.incoming_ap_doc(params) == :error
+
+ Pleroma.Config.put([:instance, :rewrite_policy], policies)
+ Pleroma.Config.put(:mrf_keyword, mrf_keyword_policy)
+ end
end
end
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index de6aeec72..a26f514a5 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
test "Represent a user account" do
@@ -152,6 +153,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert expected == AccountView.render("account.json", %{user: user})
end
+ test "Represent a deactivated user for an admin" do
+ admin = insert(:user, %{info: %{is_admin: true}})
+ deactivated_user = insert(:user, %{info: %{deactivated: true}})
+ represented = AccountView.render("account.json", %{user: deactivated_user, for: admin})
+ assert represented[:pleroma][:deactivated] == true
+ end
+
test "Represent a smaller mention" do
user = insert(:user)
@@ -165,28 +173,100 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert expected == AccountView.render("mention.json", %{user: user})
end
- test "represent a relationship" do
- user = insert(:user)
- other_user = insert(:user)
+ describe "relationship" do
+ test "represent a relationship for the following and followed user" do
+ user = insert(:user)
+ other_user = insert(:user)
- {:ok, user} = User.follow(user, other_user)
- {:ok, user} = User.block(user, other_user)
+ {:ok, user} = User.follow(user, other_user)
+ {:ok, other_user} = User.follow(other_user, user)
+ {:ok, other_user} = User.subscribe(user, other_user)
+ {:ok, user} = User.mute(user, other_user, true)
+ {:ok, user} = CommonAPI.hide_reblogs(user, other_user)
- expected = %{
- id: to_string(other_user.id),
- following: false,
- followed_by: false,
- blocking: true,
- muting: false,
- muting_notifications: false,
- subscribing: false,
- requested: false,
- domain_blocking: false,
- showing_reblogs: true,
- endorsed: false
- }
+ expected = %{
+ id: to_string(other_user.id),
+ following: true,
+ followed_by: true,
+ blocking: false,
+ blocked_by: false,
+ muting: true,
+ muting_notifications: true,
+ subscribing: true,
+ requested: false,
+ domain_blocking: false,
+ showing_reblogs: false,
+ endorsed: false
+ }
- assert expected == AccountView.render("relationship.json", %{user: user, target: other_user})
+ assert expected ==
+ AccountView.render("relationship.json", %{user: user, target: other_user})
+ end
+
+ test "represent a relationship for the blocking and blocked user" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, user} = User.follow(user, other_user)
+ {:ok, other_user} = User.subscribe(user, other_user)
+ {:ok, user} = User.block(user, other_user)
+ {:ok, other_user} = User.block(other_user, user)
+
+ expected = %{
+ id: to_string(other_user.id),
+ following: false,
+ followed_by: false,
+ blocking: true,
+ blocked_by: true,
+ muting: false,
+ muting_notifications: false,
+ subscribing: false,
+ requested: false,
+ domain_blocking: false,
+ showing_reblogs: true,
+ endorsed: false
+ }
+
+ assert expected ==
+ AccountView.render("relationship.json", %{user: user, target: other_user})
+ end
+
+ test "represent a relationship for the user blocking a domain" do
+ user = insert(:user)
+ other_user = insert(:user, ap_id: "https://bad.site/users/other_user")
+
+ {:ok, user} = User.block_domain(user, "bad.site")
+
+ assert %{domain_blocking: true, blocking: false} =
+ AccountView.render("relationship.json", %{user: user, target: other_user})
+ end
+
+ test "represent a relationship for the user with a pending follow request" do
+ user = insert(:user)
+ other_user = insert(:user, %{info: %User.Info{locked: true}})
+
+ {:ok, user, other_user, _} = CommonAPI.follow(user, other_user)
+ user = User.get_cached_by_id(user.id)
+ other_user = User.get_cached_by_id(other_user.id)
+
+ expected = %{
+ id: to_string(other_user.id),
+ following: false,
+ followed_by: false,
+ blocking: false,
+ blocked_by: false,
+ muting: false,
+ muting_notifications: false,
+ subscribing: false,
+ requested: true,
+ domain_blocking: false,
+ showing_reblogs: true,
+ endorsed: false
+ }
+
+ assert expected ==
+ AccountView.render("relationship.json", %{user: user, target: other_user})
+ end
end
test "represent an embedded relationship" do
@@ -240,6 +320,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
following: false,
followed_by: false,
blocking: true,
+ blocked_by: false,
subscribing: false,
muting: false,
muting_notifications: false,
@@ -275,4 +356,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
result = AccountView.render("account.json", %{user: user})
refute result.display_name == " username "
end
+
+ describe "hiding follows/following" do
+ test "shows when follows/following are hidden and sets follower/following count to 0" do
+ user = insert(:user, info: %{hide_followers: true, hide_follows: true})
+ other_user = insert(:user)
+ {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
+
+ assert %{
+ followers_count: 0,
+ following_count: 0,
+ pleroma: %{hide_follows: true, hide_followers: true}
+ } = AccountView.render("account.json", %{user: user})
+ end
+
+ test "shows actual follower/following count to the account owner" do
+ user = insert(:user, info: %{hide_followers: true, hide_follows: true})
+ other_user = insert(:user)
+ {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
+
+ assert %{
+ followers_count: 1,
+ following_count: 1
+ } = AccountView.render("account.json", %{user: user, for: user})
+ end
+ 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 b7487c68c..b023d1e4f 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -23,6 +23,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
+ import Swoosh.TestAssertions
+
+ @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -33,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user)
following = insert(:user)
- {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
+ {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
conn =
conn
@@ -56,7 +59,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
following = insert(:user)
capture_log(fn ->
- {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
+ {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
@@ -584,6 +587,101 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert expected == json_response(conn, 200)
end
+ test "user avatar can be set", %{conn: conn} do
+ user = insert(:user)
+ avatar_image = File.read!("test/fixtures/avatar_data_uri")
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
+
+ user = refresh_record(user)
+
+ assert %{
+ "name" => _,
+ "type" => _,
+ "url" => [
+ %{
+ "href" => _,
+ "mediaType" => _,
+ "type" => _
+ }
+ ]
+ } = user.avatar
+
+ assert %{"url" => _} = json_response(conn, 200)
+ end
+
+ test "user avatar can be reset", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
+
+ user = User.get_cached_by_id(user.id)
+
+ assert user.avatar == nil
+
+ assert %{"url" => nil} = json_response(conn, 200)
+ end
+
+ test "can set profile banner", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
+
+ user = refresh_record(user)
+ assert user.info.banner["type"] == "Image"
+
+ assert %{"url" => _} = json_response(conn, 200)
+ end
+
+ test "can reset profile banner", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
+
+ user = refresh_record(user)
+ assert user.info.banner == %{}
+
+ assert %{"url" => nil} = json_response(conn, 200)
+ end
+
+ test "background image can be set", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
+
+ user = refresh_record(user)
+ assert user.info.background["type"] == "Image"
+ assert %{"url" => _} = json_response(conn, 200)
+ end
+
+ test "background image can be reset", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
+
+ user = refresh_record(user)
+ assert user.info.background == %{}
+ assert %{"url" => nil} = json_response(conn, 200)
+ end
+
test "creates an oauth app", %{conn: conn} do
user = insert(:user)
app_attrs = build(:oauth_app)
@@ -907,8 +1005,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
test "list timeline", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
- {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
- {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
+ {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
+ {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
@@ -925,10 +1023,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
+ {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
{:ok, _activity_two} =
- TwitterAPI.create_status(other_user, %{
+ CommonAPI.post(other_user, %{
"status" => "Marisa is cute.",
"visibility" => "private"
})
@@ -952,8 +1050,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} =
- TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
@@ -975,8 +1072,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} =
- TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
@@ -998,8 +1094,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} =
- TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
@@ -1015,8 +1110,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} =
- TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
@@ -1177,6 +1271,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
end
+
+ test "doesn't see notifications after muting user with notifications", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications")
+
+ assert json_response(conn, 200) == []
+ end
+
+ test "see notifications after muting user without notifications", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2, false)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+ end
+
+ test "see notifications after muting user with notifications and with_muted parameter", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
+
+ assert length(json_response(conn, 200)) == 1
+ end
end
describe "reblogging" do
@@ -1233,6 +1392,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert to_string(activity.id) == id
end
+
+ test "returns 400 error when activity is not exist", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/foo/reblog")
+
+ assert json_response(conn, 400) == %{"error" => "Could not repeat"}
+ end
end
describe "unreblogging" do
@@ -1251,6 +1421,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert to_string(activity.id) == id
end
+
+ test "returns 400 error when activity is not exist", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/foo/unreblog")
+
+ assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
+ end
end
describe "favoriting" do
@@ -1269,16 +1450,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert to_string(activity.id) == id
end
- test "returns 500 for a wrong id", %{conn: conn} do
+ test "returns 400 error for a wrong id", %{conn: conn} do
user = insert(:user)
- resp =
+ conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/1/favourite")
- |> json_response(500)
- assert resp == "Something went wrong"
+ assert json_response(conn, 400) == %{"error" => "Could not favorite"}
end
end
@@ -1299,6 +1479,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert to_string(activity.id) == id
end
+
+ test "returns 400 error for a wrong id", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/1/unfavourite")
+
+ assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
+ end
end
describe "user timelines" do
@@ -1369,10 +1560,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
media =
TwitterAPI.upload(file, user, "json")
- |> Poison.decode!()
+ |> Jason.decode!()
{:ok, image_post} =
- TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
+ CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
conn =
conn
@@ -1480,40 +1671,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
object = Repo.get(Object, media["id"])
assert object.data["actor"] == User.ap_id(conn.assigns[:user])
end
-
- test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
- Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
-
- proxy_url = "https://cache.pleroma.social"
- Pleroma.Config.put([:media_proxy, :enabled], true)
- Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
-
- media =
- conn
- |> post("/api/v1/media", %{"file" => image})
- |> json_response(:ok)
-
- assert String.starts_with?(media["url"], proxy_url)
- end
-
- test "returns media url when proxy is enabled but media url is whitelisted", %{
- conn: conn,
- image: image
- } do
- media_url = "https://media.pleroma.social"
- Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
-
- Pleroma.Config.put([:media_proxy, :enabled], true)
- Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
- Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
-
- media =
- conn
- |> post("/api/v1/media", %{"file" => image})
- |> json_response(:ok)
-
- assert String.starts_with?(media["url"], media_url)
- end
end
describe "locked accounts" do
@@ -1695,7 +1852,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
following = insert(:user)
capture_log(fn ->
- {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
+ {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
@@ -2008,25 +2165,52 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
end
- test "muting / unmuting a user", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
+ describe "mute/unmute" do
+ test "with notifications", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/accounts/#{other_user.id}/mute")
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/mute")
- assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
+ response = json_response(conn, 200)
- user = User.get_cached_by_id(user.id)
+ assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
+ user = User.get_cached_by_id(user.id)
- conn =
- build_conn()
- |> assign(:user, user)
- |> post("/api/v1/accounts/#{other_user.id}/unmute")
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/unmute")
- assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
+ response = json_response(conn, 200)
+ assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+ end
+
+ test "without notifications", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
+
+ response = json_response(conn, 200)
+
+ assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
+ user = User.get_cached_by_id(user.id)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/accounts/#{other_user.id}/unmute")
+
+ response = json_response(conn, 200)
+ assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+ end
end
test "subscribing / unsubscribing to a user", %{conn: conn} do
@@ -2427,7 +2611,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
- {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
+ {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value
user = User.get_cached_by_id(user.id)
@@ -2520,6 +2704,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> json_response(200)
end
+ test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
+ {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{dm.id}/pin")
+
+ assert json_response(conn, 400) == %{"error" => "Could not pin"}
+ end
+
test "unpin status", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.pin(activity.id, user)
@@ -2539,6 +2734,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> json_response(200)
end
+ test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/1/unpin")
+
+ assert json_response(conn, 400) == %{"error" => "Could not unpin"}
+ end
+
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
@@ -2577,11 +2781,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
- "provider_name" => "www.imdb.com",
- "provider_url" => "http://www.imdb.com",
+ "provider_name" => "example.com",
+ "provider_url" => "https://example.com",
"title" => "The Rock",
"type" => "link",
- "url" => "http://www.imdb.com/title/tt0117500/",
+ "url" => "https://example.com/ogp",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"pleroma" => %{
@@ -2589,7 +2793,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock",
"type" => "video.movie",
- "url" => "http://www.imdb.com/title/tt0117500/",
+ "url" => "https://example.com/ogp",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
}
@@ -2630,14 +2834,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
"title" => "Pleroma",
"description" => "",
"image" => nil,
- "provider_name" => "pleroma.social",
- "provider_url" => "https://pleroma.social",
- "url" => "https://pleroma.social/",
+ "provider_name" => "example.com",
+ "provider_url" => "https://example.com",
+ "url" => "https://example.com/ogp-missing-data",
"pleroma" => %{
"opengraph" => %{
"title" => "Pleroma",
"type" => "website",
- "url" => "https://pleroma.social/"
+ "url" => "https://example.com/ogp-missing-data"
}
}
}
@@ -2697,8 +2901,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
describe "conversation muting" do
setup do
+ post_user = insert(:user)
user = insert(:user)
- {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
+
+ {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
[user: user, activity: activity]
end
@@ -2713,6 +2919,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> json_response(200)
end
+ test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
+ {:ok, _} = CommonAPI.add_mute(user, activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/mute")
+
+ assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
+ end
+
test "unmute conversation", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.add_mute(user, activity)
@@ -2757,7 +2974,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"status_ids" => [activity.id],
- "comment" => "bad status!"
+ "comment" => "bad status!",
+ "forward" => "false"
})
|> json_response(200)
end
@@ -2790,6 +3008,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
|> json_response(400)
end
+
+ test "returns error when account is not exist", %{
+ conn: conn,
+ reporter: reporter,
+ activity: activity
+ } do
+ conn =
+ conn
+ |> assign(:user, reporter)
+ |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"})
+
+ assert json_response(conn, 400) == %{"error" => "Account not found"}
+ end
end
describe "link headers" do
@@ -2861,6 +3092,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert Map.has_key?(emoji, "static_url")
assert Map.has_key?(emoji, "tags")
assert is_list(emoji["tags"])
+ assert Map.has_key?(emoji, "category")
assert Map.has_key?(emoji, "url")
assert Map.has_key?(emoji, "visible_in_picker")
end
@@ -2890,6 +3122,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert redirected_to(conn) == "/web/login"
end
+ test "redirects not logged-in users to the login page on private instances", %{
+ conn: conn,
+ path: path
+ } do
+ is_public = Pleroma.Config.get([:instance, :public])
+ Pleroma.Config.put([:instance, :public], false)
+
+ conn = get(conn, path)
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/login"
+
+ Pleroma.Config.put([:instance, :public], is_public)
+ end
+
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
token = insert(:oauth_token)
@@ -3148,7 +3395,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
user2 = insert(:user)
user3 = insert(:user)
- {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
+ {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
# Reply to status from another user
conn1 =
@@ -3313,7 +3560,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> get("/api/v1/polls/#{object.id}")
response = json_response(conn, 200)
- id = object.id
+ id = to_string(object.id)
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
end
@@ -3413,5 +3660,291 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
total_items == 1
end)
end
+
+ test "does not allow choice index to be greater than options count", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Am I cute?",
+ "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+ })
+
+ object = Object.normalize(activity)
+
+ conn =
+ conn
+ |> assign(:user, other_user)
+ |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
+
+ assert json_response(conn, 422) == %{"error" => "Invalid indices"}
+ end
+
+ test "returns 404 error when object is not exist", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/polls/1/votes", %{"choices" => [0]})
+
+ assert json_response(conn, 404) == %{"error" => "Record not found"}
+ end
+
+ test "returns 404 when poll is private and not available for user", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Am I cute?",
+ "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20},
+ "visibility" => "private"
+ })
+
+ object = Object.normalize(activity)
+
+ conn =
+ conn
+ |> assign(:user, other_user)
+ |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
+
+ assert json_response(conn, 404) == %{"error" => "Record not found"}
+ end
+ end
+
+ describe "GET /api/v1/statuses/:id/favourited_by" do
+ setup do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+
+ [conn: conn, activity: activity]
+ end
+
+ test "returns users who have favorited the status", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+
+ assert id == other_user.id
+ end
+
+ test "returns empty array when status has not been favorited yet", %{
+ conn: conn,
+ activity: activity
+ } do
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response)
+ end
+
+ test "does not return users who have favorited the status but are blocked", %{
+ conn: %{assigns: %{user: user}} = conn,
+ activity: activity
+ } do
+ other_user = insert(:user)
+ {:ok, user} = User.block(user, other_user)
+
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response)
+ end
+
+ test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, nil)
+ |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+ assert id == other_user.id
+ end
+ end
+
+ describe "GET /api/v1/statuses/:id/reblogged_by" do
+ setup do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+
+ [conn: conn, activity: activity]
+ end
+
+ test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+
+ assert id == other_user.id
+ end
+
+ test "returns empty array when status has not been reblogged yet", %{
+ conn: conn,
+ activity: activity
+ } do
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response)
+ end
+
+ test "does not return users who have reblogged the status but are blocked", %{
+ conn: %{assigns: %{user: user}} = conn,
+ activity: activity
+ } do
+ other_user = insert(:user)
+ {:ok, user} = User.block(user, other_user)
+
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response)
+ end
+
+ test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, nil)
+ |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+ assert id == other_user.id
+ end
+ end
+
+ describe "POST /auth/password, with valid parameters" do
+ setup %{conn: conn} do
+ user = insert(:user)
+ conn = post(conn, "/auth/password?email=#{user.email}")
+ %{conn: conn, user: user}
+ end
+
+ test "it returns 204", %{conn: conn} do
+ assert json_response(conn, :no_content)
+ end
+
+ test "it creates a PasswordResetToken record for user", %{user: user} do
+ token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+ assert token_record
+ end
+
+ test "it sends an email to user", %{user: user} do
+ token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+
+ email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
+ end
+
+ describe "POST /auth/password, with invalid parameters" do
+ setup do
+ user = insert(:user)
+ {:ok, user: user}
+ end
+
+ test "it returns 404 when user is not found", %{conn: conn, user: user} do
+ conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
+ assert conn.status == 404
+ assert conn.resp_body == ""
+ end
+
+ test "it returns 400 when user is not local", %{conn: conn, user: user} do
+ {:ok, user} = Repo.update(Changeset.change(user, local: false))
+ conn = post(conn, "/auth/password?email=#{user.email}")
+ assert conn.status == 400
+ assert conn.resp_body == ""
+ end
+ end
+
+ describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
+ setup do
+ setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+ end
+
+ user = insert(:user)
+ info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
+
+ {:ok, user} =
+ user
+ |> Changeset.change()
+ |> Changeset.put_embed(:info, info_change)
+ |> Repo.update()
+
+ assert user.info.confirmation_pending
+
+ [user: user]
+ end
+
+ test "resend account confirmation email", %{conn: conn, user: user} do
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}")
+ |> json_response(:no_content)
+
+ email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
end
end
diff --git a/test/web/mastodon_api/search_controller_test.exs b/test/web/mastodon_api/search_controller_test.exs
index c3f531590..49c79ff0a 100644
--- a/test/web/mastodon_api/search_controller_test.exs
+++ b/test/web/mastodon_api/search_controller_test.exs
@@ -6,123 +6,280 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Object
+ alias Pleroma.Web
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
+ import Mock
setup do
- mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
- test "account search", %{conn: conn} do
- user = insert(:user)
- user_two = insert(:user, %{nickname: "shp@shitposter.club"})
- user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+ describe ".search2" do
+ test "it returns empty result if user or status search return undefined error", %{conn: conn} do
+ with_mocks [
+ {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
+ {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
+ ] do
+ capture_log(fn ->
+ results =
+ conn
+ |> get("/api/v2/search", %{"q" => "2hu"})
+ |> json_response(200)
- results =
- conn
- |> assign(:user, user)
- |> get("/api/v1/accounts/search", %{"q" => "shp"})
- |> json_response(200)
+ assert results["accounts"] == []
+ assert results["statuses"] == []
+ end) =~
+ "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
+ end
+ end
- result_ids = for result <- results, do: result["acct"]
+ test "search", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
- assert user_two.nickname in result_ids
- assert user_three.nickname in result_ids
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private"})
- results =
- conn
- |> assign(:user, user)
- |> get("/api/v1/accounts/search", %{"q" => "2hu"})
- |> json_response(200)
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "This is about 2hu, but private",
+ "visibility" => "private"
+ })
- result_ids = for result <- results, do: result["acct"]
+ {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
- assert user_three.nickname in result_ids
- end
-
- test "search", %{conn: conn} do
- user = insert(:user)
- user_two = insert(:user, %{nickname: "shp@shitposter.club"})
- user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
-
- {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
-
- {:ok, _activity} =
- CommonAPI.post(user, %{
- "status" => "This is about 2hu, but private",
- "visibility" => "private"
- })
-
- {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
-
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "2hu"})
-
- assert results = json_response(conn, 200)
-
- [account | _] = results["accounts"]
- assert account["id"] == to_string(user_three.id)
-
- assert results["hashtags"] == []
-
- [status] = results["statuses"]
- assert status["id"] == to_string(activity.id)
- end
-
- test "search fetches remote statuses", %{conn: conn} do
- capture_log(fn ->
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
+ conn = get(conn, "/api/v2/search", %{"q" => "2hu #private"})
assert results = json_response(conn, 200)
+ [account | _] = results["accounts"]
+ assert account["id"] == to_string(user_three.id)
+
+ assert results["hashtags"] == [
+ %{"name" => "private", "url" => "#{Web.base_url()}/tag/private"}
+ ]
+
[status] = results["statuses"]
- assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
- end)
+ assert status["id"] == to_string(activity.id)
+ end
end
- test "search doesn't show statuses that it shouldn't", %{conn: conn} do
- {:ok, activity} =
- CommonAPI.post(insert(:user), %{
- "status" => "This is about 2hu, but private",
- "visibility" => "private"
- })
+ describe ".account_search" do
+ test "account search", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ results =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/search", %{"q" => "shp"})
+ |> json_response(200)
+
+ result_ids = for result <- results, do: result["acct"]
+
+ assert user_two.nickname in result_ids
+ assert user_three.nickname in result_ids
+
+ results =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/search", %{"q" => "2hu"})
+ |> json_response(200)
+
+ result_ids = for result <- results, do: result["acct"]
+
+ assert user_three.nickname in result_ids
+ end
+
+ test "returns account if query contains a space", %{conn: conn} do
+ user = insert(:user, %{nickname: "shp@shitposter.club"})
+
+ results =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "})
+ |> json_response(200)
+
+ assert length(results) == 1
+ end
+ end
+
+ describe ".search" do
+ test "it returns empty result if user or status search return undefined error", %{conn: conn} do
+ with_mocks [
+ {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
+ {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
+ ] do
+ capture_log(fn ->
+ results =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu"})
+ |> json_response(200)
+
+ assert results["accounts"] == []
+ assert results["statuses"] == []
+ end) =~
+ "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
+ end
+ end
+
+ test "search", %{conn: conn} do
+ user = insert(:user)
+ user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "This is about 2hu, but private",
+ "visibility" => "private"
+ })
+
+ {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
- capture_log(fn ->
conn =
conn
- |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
+ |> get("/api/v1/search", %{"q" => "2hu"})
assert results = json_response(conn, 200)
- [] = results["statuses"]
- end)
- end
+ [account | _] = results["accounts"]
+ assert account["id"] == to_string(user_three.id)
- test "search fetches remote accounts", %{conn: conn} do
- user = insert(:user)
+ assert results["hashtags"] == []
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
+ [status] = results["statuses"]
+ assert status["id"] == to_string(activity.id)
+ end
- assert results = json_response(conn, 200)
- [account] = results["accounts"]
- assert account["acct"] == "shp@social.heldscal.la"
- end
+ test "search fetches remote statuses", %{conn: conn} do
+ capture_log(fn ->
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
- test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
- conn =
- conn
- |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"})
+ assert results = json_response(conn, 200)
- assert results = json_response(conn, 200)
- assert [] == results["accounts"]
+ [status] = results["statuses"]
+
+ assert status["uri"] ==
+ "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+ end)
+ end
+
+ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
+ {:ok, activity} =
+ CommonAPI.post(insert(:user), %{
+ "status" => "This is about 2hu, but private",
+ "visibility" => "private"
+ })
+
+ capture_log(fn ->
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
+
+ assert results = json_response(conn, 200)
+
+ [] = results["statuses"]
+ end)
+ end
+
+ test "search fetches remote accounts", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
+
+ assert results = json_response(conn, 200)
+ [account] = results["accounts"]
+ assert account["acct"] == "shp@social.heldscal.la"
+ end
+
+ test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
+ conn =
+ conn
+ |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"})
+
+ assert results = json_response(conn, 200)
+ assert [] == results["accounts"]
+ end
+
+ test "search with limit and offset", %{conn: conn} do
+ user = insert(:user)
+ _user_two = insert(:user, %{nickname: "shp@shitposter.club"})
+ _user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ {:ok, _activity1} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
+ {:ok, _activity2} = CommonAPI.post(user, %{"status" => "This is also about 2hu"})
+
+ result =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1})
+
+ assert results = json_response(result, 200)
+ assert [%{"id" => activity_id1}] = results["statuses"]
+ assert [_] = results["accounts"]
+
+ results =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1, "offset" => 1})
+ |> json_response(200)
+
+ assert [%{"id" => activity_id2}] = results["statuses"]
+ assert [] = results["accounts"]
+
+ assert activity_id1 != activity_id2
+ end
+
+ test "search returns results only for the given type", %{conn: conn} do
+ user = insert(:user)
+ _user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
+
+ assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"})
+ |> json_response(200)
+
+ assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "type" => "accounts"})
+ |> json_response(200)
+ end
+
+ test "search uses account_id to filter statuses by the author", %{conn: conn} do
+ user = insert(:user, %{nickname: "shp@shitposter.club"})
+ user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
+
+ {:ok, activity1} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
+ {:ok, activity2} = CommonAPI.post(user_two, %{"status" => "This is also about 2hu"})
+
+ results =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id})
+ |> json_response(200)
+
+ assert [%{"id" => activity_id1}] = results["statuses"]
+ assert activity_id1 == activity1.id
+ assert [_] = results["accounts"]
+
+ results =
+ conn
+ |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id})
+ |> json_response(200)
+
+ assert [%{"id" => activity_id2}] = results["statuses"]
+ assert activity_id2 == activity2.id
+ end
end
end
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs
index ec75150ab..0b167f839 100644
--- a/test/web/mastodon_api/status_view_test.exs
+++ b/test/web/mastodon_api/status_view_test.exs
@@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
test "a note with null content" do
note = insert(:note_activity)
- note_object = Object.normalize(note.data["object"])
+ note_object = Object.normalize(note)
data =
note_object.data
@@ -73,26 +73,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
test "a note activity" do
note = insert(:note_activity)
+ object_data = Object.normalize(note).data
user = User.get_cached_by_ap_id(note.data["actor"])
- convo_id = Utils.context_to_conversation_id(note.data["object"]["context"])
+ convo_id = Utils.context_to_conversation_id(object_data["context"])
status = StatusView.render("status.json", %{activity: note})
created_at =
- (note.data["object"]["published"] || "")
+ (object_data["published"] || "")
|> String.replace(~r/\.\d+Z/, ".000Z")
expected = %{
id: to_string(note.id),
- uri: note.data["object"]["id"],
+ uri: object_data["id"],
url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
card: nil,
reblog: nil,
- content: HtmlSanitizeEx.basic_html(note.data["object"]["content"]),
+ content: HtmlSanitizeEx.basic_html(object_data["content"]),
created_at: created_at,
reblogs_count: 0,
replies_count: 0,
@@ -104,14 +105,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
pinned: false,
sensitive: false,
poll: nil,
- spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),
+ spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]),
visibility: "public",
media_attachments: [],
mentions: [],
tags: [
%{
- name: "#{note.data["object"]["tag"]}",
- url: "/tag/#{note.data["object"]["tag"]}"
+ name: "#{object_data["tag"]}",
+ url: "/tag/#{object_data["tag"]}"
}
],
application: %{
@@ -131,8 +132,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
local: true,
conversation_id: convo_id,
in_reply_to_account_acct: nil,
- content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])},
- spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])}
+ content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])},
+ spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}
}
}
@@ -202,10 +203,71 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
status = StatusView.render("status.json", %{activity: activity})
- actor = User.get_cached_by_ap_id(activity.actor)
-
assert status.mentions ==
- Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)
+ Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end)
+ end
+
+ test "create mentions from the 'to' field" do
+ %User{ap_id: recipient_ap_id} = insert(:user)
+ cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
+
+ object =
+ insert(:note, %{
+ data: %{
+ "to" => [recipient_ap_id],
+ "cc" => cc
+ }
+ })
+
+ activity =
+ insert(:note_activity, %{
+ note: object,
+ recipients: [recipient_ap_id | cc]
+ })
+
+ assert length(activity.recipients) == 3
+
+ %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
+
+ assert length(mentions) == 1
+ assert mention.url == recipient_ap_id
+ end
+
+ test "create mentions from the 'tag' field" do
+ recipient = insert(:user)
+ cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
+
+ object =
+ insert(:note, %{
+ data: %{
+ "cc" => cc,
+ "tag" => [
+ %{
+ "href" => recipient.ap_id,
+ "name" => recipient.nickname,
+ "type" => "Mention"
+ },
+ %{
+ "href" => "https://example.com/search?tag=test",
+ "name" => "#test",
+ "type" => "Hashtag"
+ }
+ ]
+ }
+ })
+
+ activity =
+ insert(:note_activity, %{
+ note: object,
+ recipients: [recipient.ap_id | cc]
+ })
+
+ assert length(activity.recipients) == 3
+
+ %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
+
+ assert length(mentions) == 1
+ assert mention.url == recipient.ap_id
end
test "attachments" do
@@ -238,6 +300,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})
end
+ test "put the url advertised in the Activity in to the url attribute" do
+ id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
+ [activity] = Activity.search(nil, id)
+
+ status = StatusView.render("status.json", %{activity: activity})
+
+ assert status.uri == id
+ assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
+ end
+
test "a reblog" do
user = insert(:user)
activity = insert(:note_activity)
@@ -361,7 +433,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
expected = %{
emojis: [],
expired: false,
- id: object.id,
+ id: to_string(object.id),
multiple: false,
options: [
%{title: "absolutely!", votes_count: 0},
@@ -444,4 +516,52 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert Enum.at(result[:options], 2)[:votes_count] == 1
end
end
+
+ test "embeds a relationship in the account" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "drink more water"
+ })
+
+ result = StatusView.render("status.json", %{activity: activity, for: other_user})
+
+ assert result[:account][:pleroma][:relationship] ==
+ AccountView.render("relationship.json", %{user: other_user, target: user})
+ end
+
+ test "embeds a relationship in the account in reposts" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "˙˙ɐʎns"
+ })
+
+ {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
+
+ result = StatusView.render("status.json", %{activity: activity, for: user})
+
+ assert result[:account][:pleroma][:relationship] ==
+ AccountView.render("relationship.json", %{user: user, target: other_user})
+
+ assert result[:reblog][:account][:pleroma][:relationship] ==
+ AccountView.render("relationship.json", %{user: user, target: user})
+ end
+
+ test "visibility/list" do
+ user = insert(:user)
+
+ {:ok, list} = Pleroma.List.create("foo", user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
+
+ status = StatusView.render("status.json", activity: activity)
+
+ assert status.visibility == "list"
+ end
end
diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs
new file mode 100644
index 000000000..53b8f556b
--- /dev/null
+++ b/test/web/media_proxy/media_proxy_controller_test.exs
@@ -0,0 +1,73 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
+ use Pleroma.Web.ConnCase
+ import Mock
+ alias Pleroma.Config
+
+ setup do
+ media_proxy_config = Config.get([:media_proxy]) || []
+ on_exit(fn -> Config.put([:media_proxy], media_proxy_config) end)
+ :ok
+ end
+
+ test "it returns 404 when MediaProxy disabled", %{conn: conn} do
+ Config.put([:media_proxy, :enabled], false)
+
+ assert %Plug.Conn{
+ status: 404,
+ resp_body: "Not Found"
+ } = get(conn, "/proxy/hhgfh/eeeee")
+
+ assert %Plug.Conn{
+ status: 404,
+ resp_body: "Not Found"
+ } = get(conn, "/proxy/hhgfh/eeee/fff")
+ end
+
+ test "it returns 403 when signature invalidated", %{conn: conn} do
+ Config.put([:media_proxy, :enabled], true)
+ Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+ path = URI.parse(Pleroma.Web.MediaProxy.encode_url("https://google.fn")).path
+ Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000")
+
+ assert %Plug.Conn{
+ status: 403,
+ resp_body: "Forbidden"
+ } = get(conn, path)
+
+ assert %Plug.Conn{
+ status: 403,
+ resp_body: "Forbidden"
+ } = get(conn, "/proxy/hhgfh/eeee")
+
+ assert %Plug.Conn{
+ status: 403,
+ resp_body: "Forbidden"
+ } = get(conn, "/proxy/hhgfh/eeee/fff")
+ end
+
+ test "redirects on valid url when filename invalidated", %{conn: conn} do
+ Config.put([:media_proxy, :enabled], true)
+ Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+ url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
+ invalid_url = String.replace(url, "test.png", "test-file.png")
+ response = get(conn, invalid_url)
+ html = "You are being redirected ."
+ assert response.status == 302
+ assert response.resp_body == html
+ end
+
+ test "it performs ReverseProxy.call when signature valid", %{conn: conn} do
+ Config.put([:media_proxy, :enabled], true)
+ Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+ url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
+
+ with_mock Pleroma.ReverseProxy,
+ call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
+ assert %Plug.Conn{status: :success} = get(conn, url)
+ end
+ end
+end
diff --git a/test/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs
similarity index 57%
rename from test/media_proxy_test.exs
rename to test/web/media_proxy/media_proxy_test.exs
index b23aeb88b..0c94755df 100644
--- a/test/media_proxy_test.exs
+++ b/test/web/media_proxy/media_proxy_test.exs
@@ -2,7 +2,7 @@
# Copyright © 2017-2018 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.MediaProxyTest do
+defmodule Pleroma.Web.MediaProxyTest do
use ExUnit.Case
import Pleroma.Web.MediaProxy
alias Pleroma.Web.MediaProxy.MediaProxyController
@@ -70,14 +70,6 @@ defmodule Pleroma.MediaProxyTest do
assert decode_result(encoded) == url
end
- test "ensures urls are url-encoded" do
- assert decode_result(url("https://pleroma.social/Hello world.jpg")) ==
- "https://pleroma.social/Hello%20world.jpg"
-
- assert decode_result(url("https://pleroma.social/Hello%20world.jpg")) ==
- "https://pleroma.social/Hello%20world.jpg"
- end
-
test "validates signature" do
secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
@@ -96,31 +88,40 @@ defmodule Pleroma.MediaProxyTest do
assert decode_url(sig, base64) == {:error, :invalid_signature}
end
- test "filename_matches matches url encoded paths" do
+ test "filename_matches preserves the encoded or decoded path" do
assert MediaProxyController.filename_matches(
- true,
- "/Hello%20world.jpg",
+ %{"filename" => "/Hello world.jpg"},
+ "/Hello world.jpg",
"http://pleroma.social/Hello world.jpg"
) == :ok
assert MediaProxyController.filename_matches(
- true,
+ %{"filename" => "/Hello%20world.jpg"},
"/Hello%20world.jpg",
"http://pleroma.social/Hello%20world.jpg"
) == :ok
+
+ assert MediaProxyController.filename_matches(
+ %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"},
+ "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
+ "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
+ ) == :ok
+
+ assert MediaProxyController.filename_matches(
+ %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"},
+ "/my%2Flong%2Furl%2F2019%2F07%2FS.jp",
+ "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
+ ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
end
- test "filename_matches matches non-url encoded paths" do
- assert MediaProxyController.filename_matches(
- true,
- "/Hello world.jpg",
- "http://pleroma.social/Hello%20world.jpg"
- ) == :ok
+ test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
+ # conn.request_path will return encoded url
+ request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
assert MediaProxyController.filename_matches(
true,
- "/Hello world.jpg",
- "http://pleroma.social/Hello world.jpg"
+ request_path,
+ "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
) == :ok
end
@@ -141,28 +142,34 @@ defmodule Pleroma.MediaProxyTest do
assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url]))
end
- # https://git.pleroma.social/pleroma/pleroma/issues/580
- test "encoding S3 links (must preserve `%2F`)" do
+ # Some sites expect ASCII encoded characters in the URL to be preserved even if
+ # unnecessary.
+ # Issues: https://git.pleroma.social/pleroma/pleroma/issues/580
+ # https://git.pleroma.social/pleroma/pleroma/issues/1055
+ test "preserve ASCII encoding" do
url =
- "https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request"
+ "https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF"
encoded = url(url)
assert decode_result(encoded) == url
end
- test "does not change whitelisted urls" do
- upload_config = Pleroma.Config.get([Pleroma.Upload])
- media_url = "https://media.pleroma.social"
- Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
- Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
- Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+ # This includes unsafe/reserved characters which are not interpreted as part of the URL
+ # and would otherwise have to be ASCII encoded. It is our role to ensure the proxied URL
+ # is unmodified, so we are testing these characters anyway.
+ test "preserve non-unicode characters per RFC3986" do
+ url =
+ "https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}"
- url = "#{media_url}/static/logo.png"
encoded = url(url)
+ assert decode_result(encoded) == url
+ end
- assert String.starts_with?(encoded, media_url)
+ test "preserve unicode characters" do
+ url = "https://ko.wikipedia.org/wiki/위키백과:대문"
- Pleroma.Config.put([Pleroma.Upload], upload_config)
+ encoded = url(url)
+ assert decode_result(encoded) == url
end
end
@@ -193,12 +200,43 @@ defmodule Pleroma.MediaProxyTest do
decoded
end
- test "mediaproxy whitelist" do
- Pleroma.Config.put([:media_proxy, :enabled], true)
- Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"])
- url = "https://feld.me/foo.png"
+ describe "whitelist" do
+ setup do
+ Pleroma.Config.put([:media_proxy, :enabled], true)
+ :ok
+ end
- unencoded = url(url)
- assert unencoded == url
+ test "mediaproxy whitelist" do
+ Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"])
+ url = "https://feld.me/foo.png"
+
+ unencoded = url(url)
+ assert unencoded == url
+ end
+
+ test "does not change whitelisted urls" do
+ Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"])
+ Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+
+ media_url = "https://mycdn.akamai.com"
+
+ url = "#{media_url}/static/logo.png"
+ encoded = url(url)
+
+ assert String.starts_with?(encoded, media_url)
+ end
+
+ test "ensure Pleroma.Upload base_url is always whitelisted" do
+ upload_config = Pleroma.Config.get([Pleroma.Upload])
+ media_url = "https://media.pleroma.social"
+ Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
+
+ url = "#{media_url}/static/logo.png"
+ encoded = url(url)
+
+ assert String.starts_with?(encoded, media_url)
+
+ Pleroma.Config.put([Pleroma.Upload], upload_config)
+ end
end
end
diff --git a/test/web/metadata/player_view_test.exs b/test/web/metadata/player_view_test.exs
new file mode 100644
index 000000000..742b0ed8b
--- /dev/null
+++ b/test/web/metadata/player_view_test.exs
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.PlayerViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.Metadata.PlayerView
+
+ test "it renders audio tag" do
+ res =
+ PlayerView.render(
+ "player.html",
+ %{"mediaType" => "audio", "href" => "test-href"}
+ )
+ |> Phoenix.HTML.safe_to_string()
+
+ assert res ==
+ "Your browser does not support audio playback. "
+ end
+
+ test "it renders videos tag" do
+ res =
+ PlayerView.render(
+ "player.html",
+ %{"mediaType" => "video", "href" => "test-href"}
+ )
+ |> Phoenix.HTML.safe_to_string()
+
+ assert res ==
+ "Your browser does not support video playback. "
+ end
+end
diff --git a/test/web/metadata/rel_me_test.exs b/test/web/metadata/rel_me_test.exs
index f66bf7834..3874e077b 100644
--- a/test/web/metadata/rel_me_test.exs
+++ b/test/web/metadata/rel_me_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Metadata.Providers.RelMeTest do
use Pleroma.DataCase
import Pleroma.Factory
diff --git a/test/web/metadata/twitter_card_test.exs b/test/web/metadata/twitter_card_test.exs
new file mode 100644
index 000000000..0814006d2
--- /dev/null
+++ b/test/web/metadata/twitter_card_test.exs
@@ -0,0 +1,123 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Metadata.Providers.TwitterCard
+ alias Pleroma.Web.Metadata.Utils
+ alias Pleroma.Web.Router
+
+ test "it renders twitter card for user info" do
+ user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
+ avatar_url = Utils.attachment_url(User.avatar_url(user))
+ res = TwitterCard.build_tags(%{user: user})
+
+ assert res == [
+ {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
+ {:meta, [property: "twitter:description", content: "born 19 March 1994"], []},
+ {:meta, [property: "twitter:image", content: avatar_url], []},
+ {:meta, [property: "twitter:card", content: "summary"], []}
+ ]
+ end
+
+ test "it does not render attachments if post is nsfw" do
+ Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false)
+ user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"})
+
+ note =
+ insert(:note, %{
+ data: %{
+ "actor" => user.ap_id,
+ "tag" => [],
+ "id" => "https://pleroma.gov/objects/whatever",
+ "content" => "pleroma in a nutshell",
+ "sensitive" => true,
+ "attachment" => [
+ %{
+ "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}]
+ },
+ %{
+ "url" => [
+ %{
+ "mediaType" => "application/octet-stream",
+ "href" => "https://pleroma.gov/fqa/badapple.sfc"
+ }
+ ]
+ },
+ %{
+ "url" => [
+ %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
+ ]
+ }
+ ]
+ }
+ })
+
+ result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
+
+ assert [
+ {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
+ {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
+ {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
+ []},
+ {:meta, [property: "twitter:card", content: "summary_large_image"], []}
+ ] == result
+ end
+
+ test "it renders supported types of attachments and skips unknown types" do
+ user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"})
+
+ note =
+ insert(:note, %{
+ data: %{
+ "actor" => user.ap_id,
+ "tag" => [],
+ "id" => "https://pleroma.gov/objects/whatever",
+ "content" => "pleroma in a nutshell",
+ "attachment" => [
+ %{
+ "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}]
+ },
+ %{
+ "url" => [
+ %{
+ "mediaType" => "application/octet-stream",
+ "href" => "https://pleroma.gov/fqa/badapple.sfc"
+ }
+ ]
+ },
+ %{
+ "url" => [
+ %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
+ ]
+ }
+ ]
+ }
+ })
+
+ result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
+
+ assert [
+ {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
+ {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
+ {:meta, [property: "twitter:card", content: "summary_large_image"], []},
+ {:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
+ {:meta, [property: "twitter:card", content: "player"], []},
+ {:meta,
+ [
+ property: "twitter:player",
+ content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
+ ], []},
+ {:meta, [property: "twitter:player:width", content: "480"], []},
+ {:meta, [property: "twitter:player:height", content: "480"], []}
+ ] == result
+ end
+end
diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs
index be1173513..d7f848bfa 100644
--- a/test/web/node_info_test.exs
+++ b/test/web/node_info_test.exs
@@ -83,4 +83,47 @@ defmodule Pleroma.Web.NodeInfoTest do
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
end
+
+ test "it shows MRF transparency data if enabled", %{conn: conn} do
+ option = Pleroma.Config.get([:instance, :mrf_transparency])
+ Pleroma.Config.put([:instance, :mrf_transparency], true)
+
+ simple_config = %{"reject" => ["example.com"]}
+ Pleroma.Config.put(:mrf_simple, simple_config)
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert response["metadata"]["federation"]["mrf_simple"] == simple_config
+
+ Pleroma.Config.put([:instance, :mrf_transparency], option)
+ Pleroma.Config.put(:mrf_simple, %{})
+ end
+
+ test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
+ option = Pleroma.Config.get([:instance, :mrf_transparency])
+ Pleroma.Config.put([:instance, :mrf_transparency], true)
+
+ exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
+
+ simple_config = %{"reject" => ["example.com", "other.site"]}
+ expected_config = %{"reject" => ["example.com"]}
+
+ Pleroma.Config.put(:mrf_simple, simple_config)
+
+ response =
+ conn
+ |> get("/nodeinfo/2.1.json")
+ |> json_response(:ok)
+
+ assert response["metadata"]["federation"]["mrf_simple"] == expected_config
+ assert response["metadata"]["federation"]["exclusions"] == true
+
+ Pleroma.Config.put([:instance, :mrf_transparency], option)
+ Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
+ Pleroma.Config.put(:mrf_simple, %{})
+ end
end
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index aae34804d..92e156347 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -5,9 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
- import Mock
- alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController
@@ -108,28 +106,26 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"state" => ""
}
- with_mock Pleroma.Web.Auth.Authenticator,
- get_registration: fn _ -> {:ok, registration} end do
- conn =
- get(
- conn,
- "/oauth/twitter/callback",
- %{
- "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
- "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
- "provider" => "twitter",
- "state" => Poison.encode!(state_params)
- }
- )
+ conn =
+ conn
+ |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
+ |> get(
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
- assert response = html_response(conn, 302)
- assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
- end
+ assert response = html_response(conn, 302)
+ assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
end
test "with user-unbound registration, GET /oauth//callback renders registration_details page",
%{app: app, conn: conn} do
- registration = insert(:registration, user: nil)
+ user = insert(:user)
state_params = %{
"scope" => "read write",
@@ -138,26 +134,28 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"state" => "a_state"
}
- with_mock Pleroma.Web.Auth.Authenticator,
- get_registration: fn _ -> {:ok, registration} end do
- conn =
- get(
- conn,
- "/oauth/twitter/callback",
- %{
- "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
- "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
- "provider" => "twitter",
- "state" => Poison.encode!(state_params)
- }
- )
+ conn =
+ conn
+ |> assign(:ueberauth_auth, %{
+ provider: "twitter",
+ uid: "171799000",
+ info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
+ })
+ |> get(
+ "/oauth/twitter/callback",
+ %{
+ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+ "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+ "provider" => "twitter",
+ "state" => Poison.encode!(state_params)
+ }
+ )
- assert response = html_response(conn, 200)
- assert response =~ ~r/name="op" type="submit" value="register"/
- assert response =~ ~r/name="op" type="submit" value="connect"/
- assert response =~ Registration.email(registration)
- assert response =~ Registration.nickname(registration)
- end
+ assert response = html_response(conn, 200)
+ assert response =~ ~r/name="op" type="submit" value="register"/
+ assert response =~ ~r/name="op" type="submit" value="connect"/
+ assert response =~ user.email
+ assert response =~ user.nickname
end
test "on authentication error, GET /oauth//callback redirects to `redirect_uri`", %{
diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs
index 16ee02abb..a3a92ce5b 100644
--- a/test/web/ostatus/activity_representer_test.exs
+++ b/test/web/ostatus/activity_representer_test.exs
@@ -38,22 +38,23 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
test "a note activity" do
note_activity = insert(:note_activity)
+ object_data = Object.normalize(note_activity).data
user = User.get_cached_by_ap_id(note_activity.data["actor"])
expected = """
http://activitystrea.ms/schema/1.0/note
http://activitystrea.ms/schema/1.0/post
- #{note_activity.data["object"]["id"]}
+ #{object_data["id"]}
New note by #{user.nickname}
- #{note_activity.data["object"]["content"]}
- #{note_activity.data["object"]["published"]}
- #{note_activity.data["object"]["published"]}
+ #{object_data["content"]}
+ #{object_data["published"]}
+ #{object_data["published"]}
#{note_activity.data["context"]}
- #{note_activity.data["object"]["summary"]}
-
-
+ #{object_data["summary"]}
+
+
@@ -106,7 +107,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
test "an announce activity" do
note = insert(:note_activity)
user = insert(:user)
- object = Object.get_cached_by_ap_id(note.data["object"]["id"])
+ object = Object.normalize(note)
{:ok, announce, _object} = ActivityPub.announce(user, object)
@@ -125,7 +126,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do
http://activitystrea.ms/schema/1.0/share
#{announce.data["id"]}
#{user.nickname} repeated a notice
- RT #{note.data["object"]["content"]}
+ RT #{object.data["content"]}
#{announce.data["published"]}
#{announce.data["published"]}
#{announce.data["context"]}
diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs
index ca6e61339..cd0447af7 100644
--- a/test/web/ostatus/incoming_documents/delete_handling_test.exs
+++ b/test/web/ostatus/incoming_documents/delete_handling_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
use Pleroma.DataCase
@@ -17,8 +21,9 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
test "it removes the mentioned activity" do
note = insert(:note_activity)
second_note = insert(:note_activity)
+ object = Object.normalize(note)
+ second_object = Object.normalize(second_note)
user = insert(:user)
- object = Object.get_by_ap_id(note.data["object"]["id"])
{:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object)
@@ -26,16 +31,16 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
File.read!("test/fixtures/delete.xml")
|> String.replace(
"tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status",
- note.data["object"]["id"]
+ object.data["id"]
)
{:ok, [delete]} = OStatus.handle_incoming(incoming)
refute Activity.get_by_id(note.id)
refute Activity.get_by_id(like.id)
- assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone"
+ assert Object.get_by_ap_id(object.data["id"]).data["type"] == "Tombstone"
assert Activity.get_by_id(second_note.id)
- assert Object.get_by_ap_id(second_note.data["object"]["id"])
+ assert Object.get_by_ap_id(second_object.data["id"])
assert delete.data["type"] == "Delete"
end
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs
index 7441e5fce..9f756effb 100644
--- a/test/web/ostatus/ostatus_controller_test.exs
+++ b/test/web/ostatus/ostatus_controller_test.exs
@@ -4,7 +4,10 @@
defmodule Pleroma.Web.OStatus.OStatusControllerTest do
use Pleroma.Web.ConnCase
+
+ import ExUnit.CaptureLog
import Pleroma.Factory
+
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@@ -12,6 +15,13 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
:ok
end
@@ -20,24 +30,28 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
user = insert(:user)
salmon = File.read!("test/fixtures/salmon.xml")
- conn =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ assert capture_log(fn ->
+ conn =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- assert response(conn, 200)
+ assert response(conn, 200)
+ end) =~ "[error]"
end
test "decodes a salmon with a changed magic key", %{conn: conn} do
user = insert(:user)
salmon = File.read!("test/fixtures/salmon.xml")
- conn =
- conn
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ assert capture_log(fn ->
+ conn =
+ conn
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- assert response(conn, 200)
+ assert response(conn, 200)
+ end) =~ "[error]"
# Set a wrong magic-key for a user so it has to refetch
salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1")
@@ -54,17 +68,20 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> Ecto.Changeset.put_embed(:info, info_cng)
|> User.update_and_set_cache()
- conn =
- build_conn()
- |> put_req_header("content-type", "application/atom+xml")
- |> post("/users/#{user.nickname}/salmon", salmon)
+ assert capture_log(fn ->
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "application/atom+xml")
+ |> post("/users/#{user.nickname}/salmon", salmon)
- assert response(conn, 200)
+ assert response(conn, 200)
+ end) =~ "[error]"
end
end
test "gets a feed", %{conn: conn} do
note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
conn =
@@ -72,7 +89,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> put_req_header("content-type", "application/atom+xml")
|> get("/users/#{user.nickname}/feed.atom")
- assert response(conn, 200) =~ note_activity.data["object"]["content"]
+ assert response(conn, 200) =~ object.data["content"]
end
test "returns 404 for a missing feed", %{conn: conn} do
@@ -84,158 +101,538 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 404)
end
- test "gets an object", %{conn: conn} do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
- url = "/objects/#{uuid}"
+ describe "GET object/2" do
+ test "gets an object", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
+ url = "/objects/#{uuid}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get(url)
+
+ expected =
+ ActivityRepresenter.to_simple_form(note_activity, user, true)
+ |> ActivityRepresenter.wrap_with_entry()
+ |> :xmerl.export_simple(:xmerl_xml)
+ |> to_string
+
+ assert response(conn, 200) == expected
+ end
+
+ test "redirects to /notice/id for html format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
+ url = "/objects/#{uuid}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get(url)
+
+ assert redirected_to(conn) == "/notice/#{note_activity.id}"
+ end
+
+ test "500s when user not found", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+ User.invalidate_cache(user)
+ Pleroma.Repo.delete(user)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
+ url = "/objects/#{uuid}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get(url)
+
+ assert response(conn, 500) == ~S({"error":"Something went wrong"})
+ end
+
+ test "404s on private objects", %{conn: conn} do
+ note_activity = insert(:direct_note_activity)
+ object = Object.normalize(note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
+
+ conn
+ |> get("/objects/#{uuid}")
+ |> response(404)
+ end
+
+ test "404s on nonexisting objects", %{conn: conn} do
+ conn
+ |> get("/objects/123")
+ |> response(404)
+ end
+ end
+
+ describe "GET activity/2" do
+ test "gets an activity in xml format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- conn =
conn
|> put_req_header("accept", "application/xml")
- |> get(url)
+ |> get("/activities/#{uuid}")
+ |> response(200)
+ end
- expected =
- ActivityRepresenter.to_simple_form(note_activity, user, true)
- |> ActivityRepresenter.wrap_with_entry()
- |> :xmerl.export_simple(:xmerl_xml)
- |> to_string
+ test "redirects to /notice/id for html format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- assert response(conn, 200) == expected
+ conn =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/activities/#{uuid}")
+
+ assert redirected_to(conn) == "/notice/#{note_activity.id}"
+ end
+
+ test "505s when user not found", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+ User.invalidate_cache(user)
+ Pleroma.Repo.delete(user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/activities/#{uuid}")
+
+ assert response(conn, 500) == ~S({"error":"Something went wrong"})
+ end
+
+ test "404s on deleted objects", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
+
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/objects/#{uuid}")
+ |> response(200)
+
+ Object.delete(object)
+
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/objects/#{uuid}")
+ |> response(404)
+ end
+
+ test "404s on private activities", %{conn: conn} do
+ note_activity = insert(:direct_note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
+
+ conn
+ |> get("/activities/#{uuid}")
+ |> response(404)
+ end
+
+ test "404s on nonexistent activities", %{conn: conn} do
+ conn
+ |> get("/activities/123")
+ |> response(404)
+ end
+
+ test "gets an activity in AS2 format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
+ url = "/activities/#{uuid}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
end
- test "404s on private objects", %{conn: conn} do
- note_activity = insert(:direct_note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
+ describe "GET notice/2" do
+ test "gets a notice in xml format", %{conn: conn} do
+ note_activity = insert(:note_activity)
- conn
- |> get("/objects/#{uuid}")
- |> response(404)
- end
+ conn
+ |> get("/notice/#{note_activity.id}")
+ |> response(200)
+ end
- test "404s on nonexisting objects", %{conn: conn} do
- conn
- |> get("/objects/123")
- |> response(404)
- end
+ test "gets a notice in AS2 format", %{conn: conn} do
+ note_activity = insert(:note_activity)
- test "gets an activity in xml format", %{conn: conn} do
- note_activity = insert(:note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
-
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/activities/#{uuid}")
- |> response(200)
- end
-
- test "404s on deleted objects", %{conn: conn} do
- note_activity = insert(:note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
-
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/objects/#{uuid}")
- |> response(200)
-
- Object.delete(object)
-
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/objects/#{uuid}")
- |> response(404)
- end
-
- test "404s on private activities", %{conn: conn} do
- note_activity = insert(:direct_note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
-
- conn
- |> get("/activities/#{uuid}")
- |> response(404)
- end
-
- test "404s on nonexistent activities", %{conn: conn} do
- conn
- |> get("/activities/123")
- |> response(404)
- end
-
- test "gets a notice in xml format", %{conn: conn} do
- note_activity = insert(:note_activity)
-
- conn
- |> get("/notice/#{note_activity.id}")
- |> response(200)
- end
-
- test "gets a notice in AS2 format", %{conn: conn} do
- note_activity = insert(:note_activity)
-
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get("/notice/#{note_activity.id}")
- |> json_response(200)
- end
-
- test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
- note_activity = insert(:note_activity)
- url = "/notice/#{note_activity.id}"
-
- conn =
conn
|> put_req_header("accept", "application/activity+json")
- |> get(url)
+ |> get("/notice/#{note_activity.id}")
+ |> json_response(200)
+ end
- assert json_response(conn, 200)
+ test "500s when actor not found", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+ User.invalidate_cache(user)
+ Pleroma.Repo.delete(user)
- user = insert(:user)
+ conn =
+ conn
+ |> get("/notice/#{note_activity.id}")
- {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
- url = "/notice/#{like_activity.id}"
+ assert response(conn, 500) == ~S({"error":"Something went wrong"})
+ end
- assert like_activity.data["type"] == "Like"
+ test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ url = "/notice/#{note_activity.id}"
- conn =
- build_conn()
- |> put_req_header("accept", "application/activity+json")
- |> get(url)
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(url)
- assert response(conn, 404)
+ assert json_response(conn, 200)
+
+ user = insert(:user)
+
+ {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
+ url = "/notice/#{like_activity.id}"
+
+ assert like_activity.data["type"] == "Like"
+
+ conn =
+ build_conn()
+ |> put_req_header("accept", "application/activity+json")
+ |> get(url)
+
+ assert response(conn, 404)
+ end
+
+ test "render html for redirect for html format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+
+ resp =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/notice/#{note_activity.id}")
+ |> response(200)
+
+ assert resp =~
+ " "
+
+ user = insert(:user)
+
+ {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
+
+ assert like_activity.data["type"] == "Like"
+
+ resp =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/notice/#{like_activity.id}")
+ |> response(200)
+
+ assert resp =~ ""
+ end
+
+ test "404s a private notice", %{conn: conn} do
+ note_activity = insert(:direct_note_activity)
+ url = "/notice/#{note_activity.id}"
+
+ conn =
+ conn
+ |> get(url)
+
+ assert response(conn, 404)
+ end
+
+ test "404s a nonexisting notice", %{conn: conn} do
+ url = "/notice/123"
+
+ conn =
+ conn
+ |> get(url)
+
+ assert response(conn, 404)
+ end
end
- test "gets an activity in AS2 format", %{conn: conn} do
- note_activity = insert(:note_activity)
- [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
- url = "/activities/#{uuid}"
+ describe "feed_redirect" do
+ test "undefined format. it redirects to feed", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
- conn =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get(url)
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/users/#{user.nickname}")
+ |> response(302)
- assert json_response(conn, 200)
+ assert response ==
+ "You are being redirected ."
+ end
+
+ test "undefined format. it returns error when user not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/users/jimm")
+ |> response(404)
+
+ assert response == ~S({"error":"Not found"})
+ end
+
+ test "activity+json format. it redirects on actual feed of user", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/#{user.nickname}")
+ |> json_response(200)
+
+ assert response["endpoints"] == %{
+ "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
+ "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
+ "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
+ "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
+ }
+
+ assert response["@context"] == [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ %{"@language" => "und"}
+ ]
+
+ assert Map.take(response, [
+ "followers",
+ "following",
+ "id",
+ "inbox",
+ "manuallyApprovesFollowers",
+ "name",
+ "outbox",
+ "preferredUsername",
+ "summary",
+ "tag",
+ "type",
+ "url"
+ ]) == %{
+ "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
+ "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
+ "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
+ "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
+ "manuallyApprovesFollowers" => false,
+ "name" => user.name,
+ "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
+ "preferredUsername" => user.nickname,
+ "summary" => user.bio,
+ "tag" => [],
+ "type" => "Person",
+ "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
+ }
+ end
+
+ test "activity+json format. it returns error whe use not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/jimm")
+ |> json_response(404)
+
+ assert response == "Not found"
+ end
+
+ test "json format. it redirects on actual feed of user", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/#{user.nickname}")
+ |> json_response(200)
+
+ assert response["endpoints"] == %{
+ "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
+ "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
+ "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
+ "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
+ }
+
+ assert response["@context"] == [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ %{"@language" => "und"}
+ ]
+
+ assert Map.take(response, [
+ "followers",
+ "following",
+ "id",
+ "inbox",
+ "manuallyApprovesFollowers",
+ "name",
+ "outbox",
+ "preferredUsername",
+ "summary",
+ "tag",
+ "type",
+ "url"
+ ]) == %{
+ "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
+ "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
+ "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
+ "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
+ "manuallyApprovesFollowers" => false,
+ "name" => user.name,
+ "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
+ "preferredUsername" => user.nickname,
+ "summary" => user.bio,
+ "tag" => [],
+ "type" => "Person",
+ "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
+ }
+ end
+
+ test "json format. it returns error whe use not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/jimm")
+ |> json_response(404)
+
+ assert response == "Not found"
+ end
+
+ test "html format. it redirects on actual feed of user", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ response =
+ conn
+ |> get("/users/#{user.nickname}")
+ |> response(200)
+
+ assert response ==
+ Fallback.RedirectController.redirector_with_meta(
+ conn,
+ %{user: user}
+ ).resp_body
+ end
+
+ test "html format. it returns error when user not found", %{conn: conn} do
+ response =
+ conn
+ |> get("/users/jimm")
+ |> json_response(404)
+
+ assert response == %{"error" => "Not found"}
+ end
end
- test "404s a private notice", %{conn: conn} do
- note_activity = insert(:direct_note_activity)
- url = "/notice/#{note_activity.id}"
+ describe "GET /notice/:id/embed_player" do
+ test "render embed player", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Pleroma.Object.normalize(note_activity)
- conn =
- conn
- |> get(url)
+ object_data =
+ Map.put(object.data, "attachment", [
+ %{
+ "url" => [
+ %{
+ "href" =>
+ "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ])
- assert response(conn, 404)
- end
+ object
+ |> Ecto.Changeset.change(data: object_data)
+ |> Pleroma.Repo.update()
- test "404s a nonexisting notice", %{conn: conn} do
- url = "/notice/123"
+ conn =
+ conn
+ |> get("/notice/#{note_activity.id}/embed_player")
- conn =
- conn
- |> get(url)
+ assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"]
- assert response(conn, 404)
+ assert Plug.Conn.get_resp_header(
+ conn,
+ "content-security-policy"
+ ) == [
+ "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
+ ]
+
+ assert response(conn, 200) =~
+ "Your browser does not support video/mp4 playback. "
+ end
+
+ test "404s when activity isn't create", %{conn: conn} do
+ note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"})
+
+ assert conn
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(404)
+ end
+
+ test "404s when activity is direct message", %{conn: conn} do
+ note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true})
+
+ assert conn
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(404)
+ end
+
+ test "404s when attachment is empty", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Pleroma.Object.normalize(note_activity)
+ object_data = Map.put(object.data, "attachment", [])
+
+ object
+ |> Ecto.Changeset.change(data: object_data)
+ |> Pleroma.Repo.update()
+
+ assert conn
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(404)
+ end
+
+ test "404s when attachment isn't audio or video", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ object = Pleroma.Object.normalize(note_activity)
+
+ object_data =
+ Map.put(object.data, "attachment", [
+ %{
+ "url" => [
+ %{
+ "href" => "https://peertube.moe/static/webseed/480.jpg",
+ "mediaType" => "image/jpg",
+ "type" => "Link"
+ }
+ ]
+ }
+ ])
+
+ object
+ |> Ecto.Changeset.change(data: object_data)
+ |> Pleroma.Repo.update()
+
+ assert conn
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(404)
+ end
end
end
diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs
index f6be16862..803a97695 100644
--- a/test/web/ostatus/ostatus_test.exs
+++ b/test/web/ostatus/ostatus_test.exs
@@ -11,8 +11,10 @@ defmodule Pleroma.Web.OStatusTest do
alias Pleroma.User
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
- import Pleroma.Factory
+
import ExUnit.CaptureLog
+ import Mock
+ import Pleroma.Factory
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -28,7 +30,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming note - GS, Salmon" do
incoming = File.read!("test/fixtures/incoming_note_activity.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
user = User.get_cached_by_ap_id(activity.data["actor"])
assert user.info.note_count == 1
@@ -51,7 +53,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming notes - GS, subscription" do
incoming = File.read!("test/fixtures/ostatus_incoming_post.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -65,7 +67,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming notes with attachments - GS, subscription" do
incoming = File.read!("test/fixtures/incoming_websub_gnusocial_attachments.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -78,7 +80,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming notes with tags" do
incoming = File.read!("test/fixtures/ostatus_incoming_post_tag.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert object.data["tag"] == ["nsfw"]
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
@@ -95,7 +97,7 @@ defmodule Pleroma.Web.OStatusTest do
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -107,7 +109,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming notes - Mastodon, with CW" do
incoming = File.read!("test/fixtures/mastodon-note-cw.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -119,7 +121,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming unlisted messages, put public into cc" do
incoming = File.read!("test/fixtures/mastodon-note-unlisted.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["cc"]
@@ -130,7 +132,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming retweets - Mastodon, with CW" do
incoming = File.read!("test/fixtures/cw_retweet.xml")
{:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
- retweeted_object = Object.normalize(retweeted_activity.data["object"])
+ retweeted_object = Object.normalize(retweeted_activity)
assert retweeted_object.data["summary"] == "Hey."
end
@@ -138,7 +140,7 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming notes - GS, subscription, reply" do
incoming = File.read!("test/fixtures/ostatus_incoming_reply.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -164,7 +166,7 @@ defmodule Pleroma.Web.OStatusTest do
refute activity.local
retweeted_activity = Activity.get_by_id(retweeted_activity.id)
- retweeted_object = Object.normalize(retweeted_activity.data["object"])
+ retweeted_object = Object.normalize(retweeted_activity)
assert retweeted_activity.data["type"] == "Create"
assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
refute retweeted_activity.local
@@ -176,18 +178,19 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming retweets - GS, subscription - local message" do
incoming = File.read!("test/fixtures/share-gs-local.xml")
note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
incoming =
incoming
- |> String.replace("LOCAL_ID", note_activity.data["object"]["id"])
+ |> String.replace("LOCAL_ID", object.data["id"])
|> String.replace("LOCAL_USER", user.ap_id)
{:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
assert activity.data["type"] == "Announce"
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
- assert activity.data["object"] == retweeted_activity.data["object"]["id"]
+ assert activity.data["object"] == object.data["id"]
assert user.ap_id in activity.data["to"]
refute activity.local
@@ -196,13 +199,13 @@ defmodule Pleroma.Web.OStatusTest do
assert retweeted_activity.data["type"] == "Create"
assert retweeted_activity.data["actor"] == user.ap_id
assert retweeted_activity.local
- assert retweeted_activity.data["object"]["announcement_count"] == 1
+ assert Object.normalize(retweeted_activity).data["announcement_count"] == 1
end
test "handle incoming retweets - Mastodon, salmon" do
incoming = File.read!("test/fixtures/share.xml")
{:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
- retweeted_object = Object.normalize(retweeted_activity.data["object"])
+ retweeted_object = Object.normalize(retweeted_activity)
assert activity.data["type"] == "Announce"
assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda"
@@ -251,25 +254,29 @@ defmodule Pleroma.Web.OStatusTest do
test "handle incoming favorites with locally available object - GS, websub" do
note_activity = insert(:note_activity)
+ object = Object.normalize(note_activity)
incoming =
File.read!("test/fixtures/favorite_with_local_note.xml")
- |> String.replace("localid", note_activity.data["object"]["id"])
+ |> String.replace("localid", object.data["id"])
{:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming)
assert activity.data["type"] == "Like"
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
- assert activity.data["object"] == favorited_activity.data["object"]["id"]
+ assert activity.data["object"] == object.data["id"]
refute activity.local
assert note_activity.id == favorited_activity.id
assert favorited_activity.local
end
- test "handle incoming replies" do
+ test_with_mock "handle incoming replies, fetching replied-to activities if we don't have them",
+ OStatus,
+ [:passthrough],
+ [] do
incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity, false)
assert activity.data["type"] == "Create"
assert object.data["type"] == "Note"
@@ -282,6 +289,23 @@ defmodule Pleroma.Web.OStatusTest do
assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
+
+ assert called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
+ end
+
+ test_with_mock "handle incoming replies, not fetching replied-to activities beyond max_replies_depth",
+ OStatus,
+ [:passthrough],
+ [] do
+ incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
+
+ with_mock Pleroma.Web.Federator,
+ allowed_incoming_reply_depth?: fn _ -> false end do
+ {:ok, [activity]} = OStatus.handle_incoming(incoming)
+ object = Object.normalize(activity, false)
+
+ refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
+ end
end
test "handle incoming follows" do
@@ -302,6 +326,14 @@ defmodule Pleroma.Web.OStatusTest do
assert User.following?(follower, followed)
end
+ test "refuse following over OStatus if the followed's account is locked" do
+ incoming = File.read!("test/fixtures/follow.xml")
+ _user = insert(:user, info: %{locked: true}, ap_id: "https://pawoo.net/users/pekorino")
+
+ {:ok, [{:error, "It's not possible to follow locked accounts over OStatus"}]} =
+ OStatus.handle_incoming(incoming)
+ end
+
test "handle incoming unfollows with existing follow" do
incoming_follow = File.read!("test/fixtures/follow.xml")
{:ok, [_activity]} = OStatus.handle_incoming(incoming_follow)
@@ -315,13 +347,14 @@ defmodule Pleroma.Web.OStatusTest do
"undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00"
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
- assert is_map(activity.data["object"])
- assert activity.data["object"]["type"] == "Follow"
- assert activity.data["object"]["object"] == "https://pawoo.net/users/pekorino"
+ embedded_object = activity.data["object"]
+ assert is_map(embedded_object)
+ assert embedded_object["type"] == "Follow"
+ assert embedded_object["object"] == "https://pawoo.net/users/pekorino"
refute activity.local
follower = User.get_cached_by_ap_id(activity.data["actor"])
- followed = User.get_cached_by_ap_id(activity.data["object"]["object"])
+ followed = User.get_cached_by_ap_id(embedded_object["object"])
refute User.following?(follower, followed)
end
@@ -401,7 +434,7 @@ defmodule Pleroma.Web.OStatusTest do
}
end
- test "find_make_or_update_user takes an author element and returns an updated user" do
+ test "find_make_or_update_actor takes an author element and returns an updated user" do
uri = "https://social.heldscal.la/user/23211"
{:ok, user} = OStatus.find_or_make_user(uri)
@@ -414,14 +447,56 @@ defmodule Pleroma.Web.OStatusTest do
doc = XML.parse_document(File.read!("test/fixtures/23211.atom"))
[author] = :xmerl_xpath.string('//author[1]', doc)
- {:ok, user} = OStatus.find_make_or_update_user(author)
+ {:ok, user} = OStatus.find_make_or_update_actor(author)
assert user.avatar["type"] == "Image"
assert user.name == old_name
assert user.bio == old_bio
- {:ok, user_again} = OStatus.find_make_or_update_user(author)
+ {:ok, user_again} = OStatus.find_make_or_update_actor(author)
assert user_again == user
end
+
+ test "find_or_make_user disallows protocol downgrade" do
+ user = insert(:user, %{local: true})
+ {:ok, user} = OStatus.find_or_make_user(user.ap_id)
+
+ assert User.ap_enabled?(user)
+
+ user =
+ insert(:user, %{
+ ap_id: "https://social.heldscal.la/user/23211",
+ info: %{ap_enabled: true},
+ local: false
+ })
+
+ assert User.ap_enabled?(user)
+
+ {:ok, user} = OStatus.find_or_make_user(user.ap_id)
+ assert User.ap_enabled?(user)
+ end
+
+ test "find_make_or_update_actor disallows protocol downgrade" do
+ user = insert(:user, %{local: true})
+ {:ok, user} = OStatus.find_or_make_user(user.ap_id)
+
+ assert User.ap_enabled?(user)
+
+ user =
+ insert(:user, %{
+ ap_id: "https://social.heldscal.la/user/23211",
+ info: %{ap_enabled: true},
+ local: false
+ })
+
+ assert User.ap_enabled?(user)
+
+ {:ok, user} = OStatus.find_or_make_user(user.ap_id)
+ assert User.ap_enabled?(user)
+
+ doc = XML.parse_document(File.read!("test/fixtures/23211.atom"))
+ [author] = :xmerl_xpath.string('//author[1]', doc)
+ {:error, :invalid_protocol} = OStatus.find_make_or_update_actor(author)
+ end
end
describe "gathering user info from a user id" do
@@ -538,8 +613,7 @@ defmodule Pleroma.Web.OStatusTest do
test "Article objects are not representable" do
note_activity = insert(:note_activity)
-
- note_object = Object.normalize(note_activity.data["object"])
+ note_object = Object.normalize(note_activity)
note_data =
note_object.data
diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs
index 530562325..c01e01124 100644
--- a/test/web/plugs/federating_plug_test.exs
+++ b/test/web/plugs/federating_plug_test.exs
@@ -5,6 +5,15 @@
defmodule Pleroma.Web.FederatingPlugTest do
use Pleroma.Web.ConnCase
+ setup_all do
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+ :ok
+ end
+
test "returns and halt the conn when federating is disabled" do
Pleroma.Config.put([:instance, :federating], false)
@@ -14,11 +23,11 @@ defmodule Pleroma.Web.FederatingPlugTest do
assert conn.status == 404
assert conn.halted
-
- Pleroma.Config.put([:instance, :federating], true)
end
test "does nothing when federating is enabled" do
+ Pleroma.Config.put([:instance, :federating], true)
+
conn =
build_conn()
|> Pleroma.Web.FederatingPlug.call(%{})
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index 1e948086a..e2f89f40a 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -124,8 +124,7 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
object = Object.normalize(activity)
- assert Impl.format_body(%{activity: activity}, user, object) ==
- "@Bob has followed you"
+ assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"
end
test "renders body for announce activity" do
@@ -156,7 +155,6 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, activity, _} = CommonAPI.favorite(activity.id, user)
object = Object.normalize(activity)
- assert Impl.format_body(%{activity: activity}, user, object) ==
- "@Bob has favorited your post"
+ assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
end
end
diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs
index 5188f4de1..85515c432 100644
--- a/test/web/rel_me_test.exs
+++ b/test/web/rel_me_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RelMeTest do
use ExUnit.Case, async: true
diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/web/rich_media/aws_signed_url_test.exs
new file mode 100644
index 000000000..122787bc2
--- /dev/null
+++ b/test/web/rich_media/aws_signed_url_test.exs
@@ -0,0 +1,81 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do
+ use ExUnit.Case, async: true
+
+ test "s3 signed url is parsed correct for expiration time" do
+ url = "https://pleroma.social/amz"
+
+ {:ok, timestamp} =
+ Timex.now()
+ |> DateTime.truncate(:second)
+ |> Timex.format("{ISO:Basic:Z}")
+
+ # in seconds
+ valid_till = 30
+
+ metadata = construct_metadata(timestamp, valid_till, url)
+
+ expire_time =
+ Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till)
+
+ assert expire_time == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url)
+ end
+
+ test "s3 signed url is parsed and correct ttl is set for rich media" do
+ url = "https://pleroma.social/amz"
+
+ {:ok, timestamp} =
+ Timex.now()
+ |> DateTime.truncate(:second)
+ |> Timex.format("{ISO:Basic:Z}")
+
+ # in seconds
+ valid_till = 30
+
+ metadata = construct_metadata(timestamp, valid_till, url)
+
+ body = """
+
+
+
+
+
+ """
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: "https://pleroma.social/amz"
+ } ->
+ %Tesla.Env{status: 200, body: body}
+ end)
+
+ Cachex.put(:rich_media_cache, url, metadata)
+
+ Pleroma.Web.RichMedia.Parser.set_ttl_based_on_image({:ok, metadata}, url)
+
+ {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url)
+
+ # as there is delay in setting and pulling the data from cache we ignore 1 second
+ assert_in_delta(valid_till * 1000, cache_ttl, 1000)
+ end
+
+ defp construct_s3_url(timestamp, valid_till) do
+ "https://pleroma.s3.ap-southeast-1.amazonaws.com/sachin%20%281%29%20_a%20-%25%2Aasdasd%20BNN%20bnnn%20.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIBLWWK6RGDQXDLJQ%2F20190716%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=#{
+ timestamp
+ }&X-Amz-Expires=#{valid_till}&X-Amz-Signature=04ffd6b98634f4b1bbabc62e0fac4879093cd54a6eed24fe8eb38e8369526bbf&X-Amz-SignedHeaders=host"
+ end
+
+ defp construct_metadata(timestamp, valid_till, url) do
+ %{
+ image: construct_s3_url(timestamp, valid_till),
+ site: "Pleroma",
+ title: "Pleroma",
+ description: "Pleroma",
+ url: url
+ }
+ end
+end
diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs
index c8f442b05..92198f3d9 100644
--- a/test/web/rich_media/helpers_test.exs
+++ b/test/web/rich_media/helpers_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.HelpersTest do
use Pleroma.DataCase
diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs
index bc48341ca..b75bdf96f 100644
--- a/test/web/rich_media/parser_test.exs
+++ b/test/web/rich_media/parser_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.ParserTest do
use ExUnit.Case, async: true
@@ -55,7 +59,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
test "doesn't just add a title" do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
- {:error, "Found metadata was invalid or incomplete: %{}"}
+ {:error,
+ "Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"}
end
test "parses ogp" do
@@ -67,7 +72,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
description:
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
type: "video.movie",
- url: "http://www.imdb.com/title/tt0117500/"
+ url: "http://example.com/ogp"
}}
end
@@ -80,7 +85,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
description:
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
type: "video.movie",
- url: "http://www.imdb.com/title/tt0117500/"
+ url: "http://example.com/ogp-missing-title"
}}
end
@@ -92,7 +97,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
site: "@flickr",
image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
title: "Small Island Developing States Photo Submission",
- description: "View the album on Flickr."
+ description: "View the album on Flickr.",
+ url: "http://example.com/twitter-card"
}}
end
@@ -116,7 +122,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
thumbnail_width: 150,
title: "Bacon Lollys",
type: "photo",
- url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg",
+ url: "http://example.com/oembed",
version: "1.0",
web_page: "https://www.flickr.com/photos/bees/2362225867/",
web_page_short_url: "https://flic.kr/p/4AK2sc",
diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs
new file mode 100644
index 000000000..f8e1c9b40
--- /dev/null
+++ b/test/web/rich_media/parsers/twitter_card_test.exs
@@ -0,0 +1,69 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Web.RichMedia.Parsers.TwitterCard
+
+ test "returns error when html not contains twitter card" do
+ assert TwitterCard.parse("", %{}) == {:error, "No twitter card metadata found"}
+ end
+
+ test "parses twitter card with only name attributes" do
+ html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html")
+
+ assert TwitterCard.parse(html, %{}) ==
+ {:ok,
+ %{
+ "app:id:googleplay": "com.nytimes.android",
+ "app:name:googleplay": "NYTimes",
+ "app:url:googleplay": "nytimes://reader/id/100000006583622",
+ site: nil,
+ title:
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times"
+ }}
+ end
+
+ test "parses twitter card with only property attributes" do
+ html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html")
+
+ assert TwitterCard.parse(html, %{}) ==
+ {:ok,
+ %{
+ card: "summary_large_image",
+ description:
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ image:
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
+ "image:alt": "",
+ title:
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
+ url:
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
+ }}
+ end
+
+ test "parses twitter card with name & property attributes" do
+ html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html")
+
+ assert TwitterCard.parse(html, %{}) ==
+ {:ok,
+ %{
+ "app:id:googleplay": "com.nytimes.android",
+ "app:name:googleplay": "NYTimes",
+ "app:url:googleplay": "nytimes://reader/id/100000006583622",
+ card: "summary_large_image",
+ description:
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ image:
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
+ "image:alt": "",
+ site: nil,
+ title:
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
+ url:
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
+ }}
+ end
+end
diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs
index 4633d7765..d47b37efb 100644
--- a/test/web/streamer_test.exs
+++ b/test/web/streamer_test.exs
@@ -65,6 +65,63 @@ defmodule Pleroma.Web.StreamerTest do
Streamer.stream("user:notification", notify)
Task.await(task)
end
+
+ test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
+ user: user
+ } do
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
+
+ Streamer.add_socket(
+ "user:notification",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
+ {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
+
+ Streamer.stream("user:notification", notif)
+ Task.await(task)
+ end
+
+ test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{
+ user: user
+ } do
+ user2 = insert(:user)
+ task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
+
+ Streamer.add_socket(
+ "user:notification",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
+ {:ok, activity} = CommonAPI.add_mute(user, activity)
+ {:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
+ Streamer.stream("user:notification", notif)
+ Task.await(task)
+ end
+
+ test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{
+ user: user
+ } do
+ user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
+ task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
+
+ Streamer.add_socket(
+ "user:notification",
+ %{transport_pid: task.pid, assigns: %{user: user}}
+ )
+
+ {:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
+ {:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
+
+ Streamer.stream("user:notification", notif)
+ Task.await(task)
+ end
end
test "it sends to public" do
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs
index 6b9da8204..3a7246ea8 100644
--- a/test/web/twitter_api/password_controller_test.exs
+++ b/test/web/twitter_api/password_controller_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
use Pleroma.Web.ConnCase
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs
index 8187ffd0e..8bb8aa36d 100644
--- a/test/web/twitter_api/twitter_api_controller_test.exs
+++ b/test/web/twitter_api/twitter_api_controller_test.exs
@@ -40,6 +40,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
user = refresh_record(user)
assert user.info.banner["type"] == "Image"
end
+
+ test "profile banner can be reset", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => ""})
+ |> json_response(200)
+
+ user = refresh_record(user)
+ assert user.info.banner == %{}
+ end
end
describe "POST /api/qvitter/update_background_image" do
@@ -54,6 +66,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
user = refresh_record(user)
assert user.info.background["type"] == "Image"
end
+
+ test "background can be reset", %{conn: conn} do
+ user = insert(:user)
+
+ conn
+ |> assign(:user, user)
+ |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => ""})
+ |> json_response(200)
+
+ user = refresh_record(user)
+ assert user.info.background == %{}
+ end
end
describe "POST /api/account/verify_credentials" do
@@ -497,6 +521,38 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
for: current_user
})
end
+
+ test "muted user", %{conn: conn, user: current_user} do
+ other_user = insert(:user)
+
+ {:ok, current_user} = User.mute(current_user, other_user)
+
+ {:ok, _activity} =
+ ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/qvitter/statuses/notifications.json")
+
+ assert json_response(conn, 200) == []
+ end
+
+ test "muted user with with_muted parameter", %{conn: conn, user: current_user} do
+ other_user = insert(:user)
+
+ {:ok, current_user} = User.mute(current_user, other_user)
+
+ {:ok, _activity} =
+ ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/qvitter/statuses/notifications.json", %{"with_muted" => "true"})
+
+ assert length(json_response(conn, 200)) == 1
+ end
end
describe "POST /api/qvitter/statuses/notifications/read" do
@@ -821,6 +877,19 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: current_user, for: current_user})
end
+
+ test "user avatar can be reset", %{conn: conn, user: current_user} do
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> post("/api/qvitter/update_avatar.json", %{img: ""})
+
+ current_user = User.get_cached_by_id(current_user.id)
+ assert current_user.avatar == nil
+
+ assert json_response(conn, 200) ==
+ UserView.render("show.json", %{user: current_user, for: current_user})
+ end
end
describe "GET /api/qvitter/mutes.json" do
@@ -892,7 +961,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ object = Object.normalize(note_activity)
ActivityPub.like(current_user, object)
conn =
@@ -1047,15 +1116,17 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
describe "POST /api/account/password_reset, with invalid parameters" do
setup [:valid_user]
- test "it returns 500 when user is not found", %{conn: conn, user: user} do
+ test "it returns 404 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
- assert json_response(conn, :internal_server_error)
+ assert conn.status == 404
+ assert conn.resp_body == ""
end
- test "it returns 500 when user is not local", %{conn: conn, user: user} do
+ test "it returns 400 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
- assert json_response(conn, :internal_server_error)
+ assert conn.status == 400
+ assert conn.resp_body == ""
end
end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index 475531a09..cbe83852e 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
}
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
expected_text =
"Hello again, @shp .<script></script> This is on another :firefox: line. #2hu #epic #phantasmagoric image.jpg "
@@ -91,7 +91,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
}
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
input = %{
"status" => "Here's your (you).",
@@ -99,7 +99,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
}
{:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input)
- reply_object = Object.normalize(reply.data["object"])
+ reply_object = Object.normalize(reply)
assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"])
@@ -216,7 +216,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1
- object = Object.normalize(note_activity.data["object"])
+ object = Object.normalize(note_activity)
assert object.data["like_count"] == 1
@@ -224,7 +224,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:ok, _status} = TwitterAPI.fav(other_user, note_activity.id)
- object = Object.normalize(note_activity.data["object"])
+ object = Object.normalize(note_activity)
assert object.data["like_count"] == 2
@@ -235,7 +235,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
test "it unfavorites a status, returns the updated activity" do
user = insert(:user)
note_activity = insert(:note_activity)
- object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+ object = Object.normalize(note_activity)
{:ok, _like_activity, _object} = ActivityPub.like(user, object)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index cab9e5d90..640579c09 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase
@@ -6,9 +10,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
+ import Mock
setup do
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ instance_config = Pleroma.Config.get([:instance])
+ pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
+ deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance], instance_config)
+ Pleroma.Config.put([:frontend_configurations, :pleroma_fe], pleroma_fe)
+ Pleroma.Config.put([:user, :deny_follow_blocked], deny_follow_blocked)
+ end)
+
:ok
end
@@ -26,6 +42,35 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert response == "job started"
end
+ test "it imports follow lists from file", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ with_mocks([
+ {File, [],
+ read!: fn "follow_list.txt" ->
+ "Account address,Show boosts\n#{user2.ap_id},true"
+ end},
+ {PleromaJobQueue, [:passthrough], []}
+ ]) do
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}})
+ |> json_response(:ok)
+
+ assert called(
+ PleromaJobQueue.enqueue(
+ :background,
+ User,
+ [:follow_import, user1, [user2.ap_id]]
+ )
+ )
+
+ assert response == "job started"
+ end
+ end
+
test "it imports new-style mastodon follow lists", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
@@ -74,6 +119,33 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert response == "job started"
end
+
+ test "it imports blocks users from file", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ user3 = insert(:user)
+
+ with_mocks([
+ {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end},
+ {PleromaJobQueue, [:passthrough], []}
+ ]) do
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}})
+ |> json_response(:ok)
+
+ assert called(
+ PleromaJobQueue.enqueue(
+ :background,
+ User,
+ [:blocks_import, user1, [user2.ap_id, user3.ap_id]]
+ )
+ )
+
+ assert response == "job started"
+ end
+ end
end
describe "POST /api/pleroma/notifications/read" do
@@ -93,6 +165,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
end
+
+ test "it returns error when notification not found", %{conn: conn} do
+ user1 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/pleroma/notifications/read", %{"id" => "22222222222222"})
+ |> json_response(403)
+
+ assert response == %{"error" => "Cannot get notification"}
+ end
end
describe "PUT /api/pleroma/notification_settings" do
@@ -118,7 +202,63 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
end
end
- describe "GET /api/statusnet/config.json" do
+ describe "GET /api/statusnet/config" do
+ test "it returns config in xml format", %{conn: conn} do
+ instance = Pleroma.Config.get(:instance)
+
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/api/statusnet/config")
+ |> response(:ok)
+
+ assert response ==
+ "\n\n#{Keyword.get(instance, :name)} \n#{
+ Pleroma.Web.base_url()
+ } \n#{Keyword.get(instance, :limit)} \n#{
+ !Keyword.get(instance, :registrations_open)
+ } \n \n \n"
+ end
+
+ test "it returns config in json format", %{conn: conn} do
+ instance = Pleroma.Config.get(:instance)
+ Pleroma.Config.put([:instance, :managed_config], true)
+ Pleroma.Config.put([:instance, :registrations_open], false)
+ Pleroma.Config.put([:instance, :invites_enabled], true)
+ Pleroma.Config.put([:instance, :public], false)
+ Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/statusnet/config")
+ |> json_response(:ok)
+
+ expected_data = %{
+ "site" => %{
+ "accountActivationRequired" => "0",
+ "closed" => "1",
+ "description" => Keyword.get(instance, :description),
+ "invitesEnabled" => "1",
+ "name" => Keyword.get(instance, :name),
+ "pleromafe" => %{"theme" => "asuka-hospital"},
+ "private" => "1",
+ "safeDMMentionsEnabled" => "0",
+ "server" => Pleroma.Web.base_url(),
+ "textlimit" => to_string(Keyword.get(instance, :limit)),
+ "uploadlimit" => %{
+ "avatarlimit" => to_string(Keyword.get(instance, :avatar_upload_limit)),
+ "backgroundlimit" => to_string(Keyword.get(instance, :background_upload_limit)),
+ "bannerlimit" => to_string(Keyword.get(instance, :banner_upload_limit)),
+ "uploadlimit" => to_string(Keyword.get(instance, :upload_limit))
+ },
+ "vapidPublicKey" => Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
+ }
+ }
+
+ assert response == expected_data
+ end
+
test "returns the state of safe_dm_mentions flag", %{conn: conn} do
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
@@ -205,7 +345,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
end
end
- describe "GET /ostatus_subscribe?acct=...." do
+ describe "GET /ostatus_subscribe - remote_follow/2" do
test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
conn =
get(
@@ -225,12 +365,235 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert html_response(response, 200) =~ "Log in to follow"
end
+
+ test "show follow page if the `acct` is a account link", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie")
+
+ assert html_response(response, 200) =~ "Remote follow"
+ end
+
+ test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found")
+
+ assert html_response(response, 200) =~ "Error fetching user"
+ end
end
- test "GET /api/pleroma/healthcheck", %{conn: conn} do
- conn = get(conn, "/api/pleroma/healthcheck")
+ describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do
+ test "follows user", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
- assert conn.status in [200, 503]
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ |> response(200)
+
+ assert response =~ "Account followed!"
+ assert user2.follower_address in refresh_record(user).following
+ end
+
+ test "returns error when user is deactivated", %{conn: conn} do
+ user = insert(:user, info: %{deactivated: true})
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ |> response(200)
+
+ assert response =~ "Error following account"
+ end
+
+ test "returns error when user is blocked", %{conn: conn} do
+ Pleroma.Config.put([:user, :deny_follow_blocked], true)
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _user} = Pleroma.User.block(user2, user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ |> response(200)
+
+ assert response =~ "Error following account"
+ end
+
+ test "returns error when followee not found", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}})
+ |> response(200)
+
+ assert response =~ "Error following account"
+ end
+
+ test "returns success result when user already in followers", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+
+ response =
+ conn
+ |> assign(:user, refresh_record(user))
+ |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ |> response(200)
+
+ assert response =~ "Account followed!"
+ end
+ end
+
+ describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do
+ test "follows", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> post("/ostatus_subscribe", %{
+ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+ })
+ |> response(200)
+
+ assert response =~ "Account followed!"
+ assert user2.follower_address in refresh_record(user).following
+ end
+
+ test "returns error when followee not found", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> post("/ostatus_subscribe", %{
+ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"}
+ })
+ |> response(200)
+
+ assert response =~ "Error following account"
+ end
+
+ test "returns error when login invalid", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> post("/ostatus_subscribe", %{
+ "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id}
+ })
+ |> response(200)
+
+ assert response =~ "Wrong username or password"
+ end
+
+ test "returns error when password invalid", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> post("/ostatus_subscribe", %{
+ "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id}
+ })
+ |> response(200)
+
+ assert response =~ "Wrong username or password"
+ end
+
+ test "returns error when user is blocked", %{conn: conn} do
+ Pleroma.Config.put([:user, :deny_follow_blocked], true)
+ user = insert(:user)
+ user2 = insert(:user)
+ {:ok, _user} = Pleroma.User.block(user2, user)
+
+ response =
+ conn
+ |> post("/ostatus_subscribe", %{
+ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+ })
+ |> response(200)
+
+ assert response =~ "Error following account"
+ end
+ end
+
+ describe "GET /api/pleroma/healthcheck" do
+ setup do
+ config_healthcheck = Pleroma.Config.get([:instance, :healthcheck])
+
+ on_exit(fn ->
+ Pleroma.Config.put([:instance, :healthcheck], config_healthcheck)
+ end)
+
+ :ok
+ end
+
+ test "returns 503 when healthcheck disabled", %{conn: conn} do
+ Pleroma.Config.put([:instance, :healthcheck], false)
+
+ response =
+ conn
+ |> get("/api/pleroma/healthcheck")
+ |> json_response(503)
+
+ assert response == %{}
+ end
+
+ test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
+ Pleroma.Config.put([:instance, :healthcheck], true)
+
+ with_mock Pleroma.Healthcheck,
+ system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do
+ response =
+ conn
+ |> get("/api/pleroma/healthcheck")
+ |> json_response(200)
+
+ assert %{
+ "active" => _,
+ "healthy" => true,
+ "idle" => _,
+ "memory_used" => _,
+ "pool_size" => _
+ } = response
+ end
+ end
+
+ test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do
+ Pleroma.Config.put([:instance, :healthcheck], true)
+
+ with_mock Pleroma.Healthcheck,
+ system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do
+ response =
+ conn
+ |> get("/api/pleroma/healthcheck")
+ |> json_response(503)
+
+ assert %{
+ "active" => _,
+ "healthy" => false,
+ "idle" => _,
+ "memory_used" => _,
+ "pool_size" => _
+ } = response
+ end
+ end
end
describe "POST /api/pleroma/disable_account" do
@@ -249,5 +612,104 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert user.info.deactivated == true
end
+
+ test "it returns returns when password invalid", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/disable_account", %{"password" => "test1"})
+ |> json_response(:ok)
+
+ assert response == %{"error" => "Invalid password."}
+ user = User.get_cached_by_id(user.id)
+
+ refute user.info.deactivated
+ end
+ end
+
+ describe "GET /api/statusnet/version" do
+ test "it returns version in xml format", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/api/statusnet/version")
+ |> response(:ok)
+
+ assert response == "#{Pleroma.Application.named_version()} "
+ end
+
+ test "it returns version in json format", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/statusnet/version")
+ |> json_response(:ok)
+
+ assert response == "#{Pleroma.Application.named_version()}"
+ end
+ end
+
+ describe "POST /main/ostatus - remote_subscribe/2" do
+ test "renders subscribe form", %{conn: conn} do
+ user = insert(:user)
+
+ response =
+ conn
+ |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""})
+ |> response(:ok)
+
+ refute response =~ "Could not find user"
+ assert response =~ "Remotely follow #{user.nickname}"
+ end
+
+ test "renders subscribe form with error when user not found", %{conn: conn} do
+ response =
+ conn
+ |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""})
+ |> response(:ok)
+
+ assert response =~ "Could not find user"
+ refute response =~ "Remotely follow"
+ end
+
+ test "it redirect to webfinger url", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user, ap_id: "shp@social.heldscal.la")
+
+ conn =
+ conn
+ |> post("/main/ostatus", %{
+ "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id}
+ })
+
+ assert redirected_to(conn) ==
+ "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}"
+ end
+
+ test "it renders form with error when use not found", %{conn: conn} do
+ user2 = insert(:user, ap_id: "shp@social.heldscal.la")
+
+ response =
+ conn
+ |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}})
+ |> response(:ok)
+
+ assert response =~ "Something went wrong."
+ end
+ end
+
+ test "it returns new captcha", %{conn: conn} do
+ with_mock Pleroma.Captcha,
+ new: fn -> "test_captcha" end do
+ resp =
+ conn
+ |> get("/api/pleroma/captcha")
+ |> response(200)
+
+ assert resp == "\"test_captcha\""
+ assert called(Pleroma.Captcha.new())
+ end
end
end
diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs
index 43bd77f78..56d861efb 100644
--- a/test/web/twitter_api/views/activity_view_test.exs
+++ b/test/web/twitter_api/views/activity_view_test.exs
@@ -126,7 +126,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
other_user = insert(:user, %{nickname: "shp"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
result = ActivityView.render("activity.json", activity: activity)
@@ -177,7 +177,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
- object = Object.normalize(activity.data["object"])
+ object = Object.normalize(activity)
convo_id = Utils.context_to_conversation_id(object.data["context"])
@@ -351,7 +351,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
"is_post_verb" => false,
"statusnet_html" => "deleted notice {{tag",
"text" => "deleted notice {{tag",
- "uri" => delete.data["object"],
+ "uri" => Object.normalize(delete).data["id"],
"user" => UserView.render("show.json", user: user)
}
diff --git a/test/web/uploader_controller_test.exs b/test/web/uploader_controller_test.exs
new file mode 100644
index 000000000..70028df1c
--- /dev/null
+++ b/test/web/uploader_controller_test.exs
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.UploaderControllerTest do
+ use Pleroma.Web.ConnCase
+ alias Pleroma.Uploaders.Uploader
+
+ describe "callback/2" do
+ test "it returns 400 response when process callback isn't alive", %{conn: conn} do
+ res =
+ conn
+ |> post(uploader_path(conn, :callback, "test-path"))
+
+ assert res.status == 400
+ assert res.resp_body == "{\"error\":\"bad request\"}"
+ end
+
+ test "it returns success result", %{conn: conn} do
+ task =
+ Task.async(fn ->
+ receive do
+ {Uploader, pid, conn, _params} ->
+ conn =
+ conn
+ |> put_status(:ok)
+ |> Phoenix.Controller.json(%{upload_path: "test-path"})
+
+ send(pid, {Uploader, conn})
+ end
+ end)
+
+ :global.register_name({Uploader, "test-path"}, task.pid)
+
+ res =
+ conn
+ |> post(uploader_path(conn, :callback, "test-path"))
+ |> json_response(200)
+
+ assert res == %{"upload_path" => "test-path"}
+ end
+ end
+end
diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs
index 43fccfc7a..7d861cbf5 100644
--- a/test/web/web_finger/web_finger_controller_test.exs
+++ b/test/web/web_finger/web_finger_controller_test.exs
@@ -10,9 +10,28 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
:ok
end
+ test "GET host-meta" do
+ response =
+ build_conn()
+ |> get("/.well-known/host-meta")
+
+ assert response.status == 200
+
+ assert response.resp_body ==
+ ~s( )
+ end
+
test "Webfinger JRD" do
user = insert(:user)
@@ -24,6 +43,16 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost"
end
+ test "it returns 404 when user isn't found (JSON)" do
+ result =
+ build_conn()
+ |> put_req_header("accept", "application/jrd+json")
+ |> get("/.well-known/webfinger?resource=acct:jimm@localhost")
+ |> json_response(404)
+
+ assert result == "Couldn't find user"
+ end
+
test "Webfinger XML" do
user = insert(:user)
@@ -35,6 +64,26 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
assert response(response, 200)
end
+ test "it returns 404 when user isn't found (XML)" do
+ result =
+ build_conn()
+ |> put_req_header("accept", "application/xrd+xml")
+ |> get("/.well-known/webfinger?resource=acct:jimm@localhost")
+ |> response(404)
+
+ assert result == "Couldn't find user"
+ end
+
+ test "Sends a 404 when invalid format" do
+ user = insert(:user)
+
+ assert_raise Phoenix.NotAcceptableError, fn ->
+ build_conn()
+ |> put_req_header("accept", "text/html")
+ |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+ end
+ end
+
test "Sends a 400 when resource param is missing" do
response =
build_conn()
diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs
index 335c95b18..8fdb9adea 100644
--- a/test/web/web_finger/web_finger_test.exs
+++ b/test/web/web_finger/web_finger_test.exs
@@ -40,6 +40,11 @@ defmodule Pleroma.Web.WebFingerTest do
end
describe "fingering" do
+ test "returns error when fails parse xml or json" do
+ user = "invalid_content@social.heldscal.la"
+ assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user)
+ end
+
test "returns the info for an OStatus user" do
user = "shp@social.heldscal.la"
@@ -81,6 +86,20 @@ defmodule Pleroma.Web.WebFingerTest do
assert data["subscribe_address"] == "https://gnusocial.de/main/ostatussub?profile={uri}"
end
+ test "it work for AP-only user" do
+ user = "kpherox@mstdn.jp"
+
+ {:ok, data} = WebFinger.finger(user)
+
+ assert data["magic_key"] == nil
+ assert data["salmon"] == nil
+
+ assert data["topic"] == "https://mstdn.jp/users/kPherox.atom"
+ assert data["subject"] == "acct:kPherox@mstdn.jp"
+ assert data["ap_id"] == "https://mstdn.jp/users/kPherox"
+ assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"
+ end
+
test "it works for friendica" do
user = "lain@squeet.me"
@@ -104,5 +123,16 @@ defmodule Pleroma.Web.WebFingerTest do
assert template == "http://status.alpicola.com/main/xrd?uri={uri}"
end
+
+ test "it works with idna domains as nickname" do
+ nickname = "lain@" <> to_string(:idna.encode("zetsubou.みんな"))
+
+ {:ok, _data} = WebFinger.finger(nickname)
+ end
+
+ test "it works with idna domains as link" do
+ ap_id = "https://" <> to_string(:idna.encode("zetsubou.みんな")) <> "/users/lain"
+ {:ok, _data} = WebFinger.finger(ap_id)
+ end
end
end
diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs
index f79745d58..aa7262beb 100644
--- a/test/web/websub/websub_controller_test.exs
+++ b/test/web/websub/websub_controller_test.exs
@@ -9,6 +9,16 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
alias Pleroma.Web.Websub
alias Pleroma.Web.Websub.WebsubClientSubscription
+ setup_all do
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+
+ Pleroma.Config.put(config_path, true)
+ on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+ :ok
+ end
+
test "websub subscription request", %{conn: conn} do
user = insert(:user)