diff options
35 files changed, 727 insertions, 532 deletions
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 0cec3bf5c..86c90da0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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 +- Mastodon API: Unsubscribe followers when they unfollow a user ### Fixed - Not being able to pin unlisted posts @@ -36,12 +37,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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 ### 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. +## [1.0.1] - 2019-07-14 +### Security +- OStatus: fix an object spoofing vulnerability. + ## [1.0.0] - 2019-06-29 ### Security - Mastodon API: Fix display names not being sanitized diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 3ee7115cf..d2e9bcc4b 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` @@ -72,6 +74,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/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index a71bcd447..a7d0fac5d 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -28,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) @@ -53,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/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 3c13a0558..7799b2a78 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -35,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/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/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/user.ex b/lib/pleroma/user.ex index 29c87d4a9..ffba3f390 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1190,10 +1190,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 @@ -1379,23 +1381,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 diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a3174a787..31397b09f 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 @@ -26,19 +27,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # 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 +44,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 +126,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{ @@ -896,13 +897,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) @@ -931,11 +930,31 @@ 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: diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index a05e03263..18145e45f 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -92,18 +92,68 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - @doc """ - Publishes an activity to all relevant peers. - """ - def publish(%User{} = actor, %Activity{} = activity) do - remote_followers = + defp recipients(actor, activity) do + followers = if actor.follower_address in activity.recipients do {:ok, followers} = User.get_followers(actor) - followers |> Enum.filter(&(!&1.local)) + Enum.filter(followers, &(!&1.local)) else [] end + Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers + end + + defp get_cc_ap_ids(ap_id, recipients) do + host = Map.get(URI.parse(ap_id), :host) + + recipients + |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end) + |> Enum.map(& &1.ap_id) + end + + @doc """ + Publishes an activity with BCC to all relevant peers. + """ + + def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do + public = is_public?(activity) + {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + + recipients = recipients(actor, activity) + + recipients + |> Enum.filter(&User.ap_enabled?/1) + |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end) + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) + |> Instances.filter_reachable() + |> Enum.each(fn {inbox, unreachable_since} -> + %User{ap_id: ap_id} = + Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end) + + # Get all the recipients on the same host and add them to cc. Otherwise it 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 public = is_public?(activity) if public && Config.get([:instance, :allow_relay]) do @@ -114,7 +164,7 @@ 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"] diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index d14490bb5..602ae48e1 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -814,13 +814,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 diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 4288ea4c8..c146f59d4 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -25,12 +25,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"])) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 9908a2e75..2666edc7c 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -34,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 @@ -73,6 +87,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/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index f1450b113..44669b228 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -31,7 +31,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 @@ -175,6 +176,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} @@ -235,19 +241,18 @@ 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 - ) - - res + preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false + direct? = visibility == "direct" + + %{ + 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, dgettext("errors", "The message visibility must be direct")} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index e013188cf..fcc000969 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -100,12 +100,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} = diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index dd8888a02..a661e9bb7 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -3,68 +3,71 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxy do - @base64_opts [padding: false] - - def url(nil), do: nil + alias Pleroma.Config + alias Pleroma.Web - def url(""), do: nil + @base64_opts [padding: false] + 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 -> + Enum.any?(Config.get([:media_proxy, :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) - sig = :crypto.hmac(:sha, secret, base64) - sig64 = sig |> Base.url_encode64(@base64_opts) + + sig64 = + base64 + |> signed_url + |> 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 end diff --git a/lib/pleroma/web/media_proxy/controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index ea33d7685..1e9520d46 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,13 +27,15 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - def filename_matches(has_filename, path, url) do - filename = url |> MediaProxy.filename() + def filename_matches(%{"filename" => _} = _, path, url) do + filename = MediaProxy.filename(url) - if has_filename && filename && Path.basename(path) != filename do + if filename && Path.basename(path) != filename do {:wrong_filename, filename} else :ok end end + + def filename_matches(_, _, _), do: :ok 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", []) + + 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 - |> Enum.map(fn id -> User.get_cached_by_ap_id(id) 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) @@ -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"}, @@ -6,7 +6,7 @@ "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"}, + "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"}, @@ -82,9 +82,9 @@ "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"}, 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/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/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/object/containment_test.exs b/test/object/containment_test.exs index 1beed6236..61cd1b412 100644 --- a/test/object/containment_test.exs +++ b/test/object/containment_test.exs @@ -68,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/signature_test.exs b/test/signature_test.exs new file mode 100644 index 000000000..4920196c7 --- /dev/null +++ b/test/signature_test.exs @@ -0,0 +1,99 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.SignatureTest do + use Pleroma.DataCase + + 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 + } + + 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(%Plug.Conn{params: %{"actor" => user.ap_id}}) == + expected_result + end + + test "it returns error when not found user" do + assert Signature.fetch_public_key(%Plug.Conn{params: %{"actor" => "test-ap_id"}}) == + {:error, :error} + end + + test "it returns error if public key is empty" do + user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}}) + + assert Signature.fetch_public_key(%Plug.Conn{params: %{"actor" => 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(%Plug.Conn{params: %{"actor" => ap_id}}) == + {:ok, @rsa_public_key} + end + + test "it returns error when not found user" do + assert Signature.refetch_public_key(%Plug.Conn{params: %{"actor" => "test-ap_id"}}) == + {:error, {:error, :ok}} + 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 +end diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs index bbcc57217..a9b79eb5b 100644 --- a/test/tasks/config_test.exs +++ b/test/tasks/config_test.exs @@ -34,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]] @@ -45,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]] }) @@ -61,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/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 59d56f3a7..00adbc0f9 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1190,6 +1190,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end + 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) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + + activity = Repo.preload(activity, :bookmark) + activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} + + assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity] + end + def data_uri do File.read!("test/fixtures/avatar_data_uri") end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index b896a532b..a1f5f6e36 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -416,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) @@ -439,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) @@ -1096,6 +1098,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 diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index 4d5c07da4..b62a89e68 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -16,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"}) @@ -29,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, @@ -37,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?", %{ @@ -70,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 @@ -78,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 @@ -85,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 @@ -92,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 @@ -99,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", @@ -115,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 1b71cbff3..ee48b752c 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1720,7 +1720,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do configs: [ %{ "group" => "pleroma", - "key" => "key1", + "key" => ":key1", "value" => [ %{"tuple" => [":key2", "some_val"]}, %{ @@ -1750,7 +1750,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "configs" => [ %{ "group" => "pleroma", - "key" => "key1", + "key" => ":key1", "value" => [ %{"tuple" => [":key2", "some_val"]}, %{ @@ -1782,7 +1782,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do configs: [ %{ "group" => "pleroma", - "key" => "key1", + "key" => ":key1", "value" => %{"key" => "some_val"} } ] @@ -1793,7 +1793,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "configs" => [ %{ "group" => "pleroma", - "key" => "key1", + "key" => ":key1", "value" => %{"key" => "some_val"} } ] @@ -1862,6 +1862,45 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ] } 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 end end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 958c931c4..7d8eeb12e 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -129,6 +129,18 @@ 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 end describe "reactions" do @@ -346,6 +358,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}) diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index ac42819d8..995bd52c8 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -541,4 +541,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do 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 <https://pleroma.social/> +# 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 = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>" + 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 index fbf200931..cb4807e0b 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 <https://pleroma.social/> # 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 @@ -90,22 +90,28 @@ defmodule Pleroma.MediaProxyTest do test "filename_matches preserves the encoded or decoded path" do assert MediaProxyController.filename_matches( - true, + %{"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( - true, + %{"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 "uses the configured base_url" do |