Compare commits
132 Commits
develop
...
release/1.
Author | SHA1 | Date |
---|---|---|
|
f5305d2815 | |
|
7aa17c3e75 | |
|
a067cf0f23 | |
|
b60ec3b173 | |
|
7030721b31 | |
|
df8fda267e | |
|
c8694d6d7b | |
|
31421916c0 | |
|
13e7753382 | |
|
821fb2a584 | |
|
700d0a56a1 | |
|
68e7bea354 | |
|
231e5e7914 | |
|
e17113d821 | |
|
b9188361dc | |
|
65cc68ff2f | |
|
5999b4c5b9 | |
|
9aec8fe242 | |
|
974488a52e | |
|
acc3c0ed58 | |
|
094db46923 | |
|
d6da4a75ce | |
|
dcb4711e92 | |
|
9fa2011c17 | |
|
bc6d1f9ed9 | |
|
1a46a13d15 | |
|
e652f83e5a | |
|
606ccbab03 | |
|
4681747ec1 | |
|
c7a3a15c3d | |
|
b3afc0bb32 | |
|
d5df62edd8 | |
|
7a5e1d64a5 | |
|
eed36278a6 | |
|
fb77dc50aa | |
|
3af51afdd7 | |
|
c9468c9a59 | |
|
4ccd3410a8 | |
|
7e2bc39f3c | |
|
b8186c26fa | |
|
e13edc2d3b | |
|
29db8dc799 | |
|
54405919d8 | |
|
5af3c00072 | |
|
60c75d6740 | |
|
14efbcf1f9 | |
|
4d0dd04653 | |
|
25c818ed6f | |
|
f7028ae8ac | |
|
48bd4ee933 | |
|
7e412fd88a | |
|
f0c32364b7 | |
|
e5161961bd | |
|
1807d3a115 | |
|
b85840b536 | |
|
a7b947534c | |
|
b9def3758a | |
|
e08b97853f | |
|
8a8fe57670 | |
|
1310cdc24f | |
|
54d4ceec5c | |
|
f270e33208 | |
|
d2c3c64da5 | |
|
65bd927cf2 | |
|
951d8c28d1 | |
|
a3654d9479 | |
|
b29dcc8c1b | |
|
6525fbac95 | |
|
ba21d515d6 | |
|
dcd30e3ceb | |
|
9c499435f0 | |
|
455f2934b7 | |
|
8998620d71 | |
|
0eff6349a0 | |
|
2536628cac | |
|
d006742fd7 | |
|
e5cb15ce2b | |
|
d18a7dc0de | |
|
2c2c075fd6 | |
|
59e60c6db1 | |
|
cca9d64cb8 | |
|
daf0d0dd9a | |
|
1e77e71fb2 | |
|
7fdbef3e1b | |
|
ce6dfb6f06 | |
|
d9aacbec4d | |
|
3e3b00f658 | |
|
f685e887b3 | |
|
2b38961bf6 | |
|
99989758ff | |
|
1426358538 | |
|
2914f8a749 | |
|
8b1b3e84f4 | |
|
1c364f7407 | |
|
109e7813c9 | |
|
15aaffc6d8 | |
|
67c5e6541e | |
|
7486a917d4 | |
|
0c028f345a | |
|
48aed88dbd | |
|
fd4963006a | |
|
876b5b45a3 | |
|
3ec6f34ef0 | |
|
6a35c151c6 | |
|
1e5d889aec | |
|
ccafecf9be | |
|
9e88ab2cad | |
|
494a611afe | |
|
ed639376ac | |
|
8123578bf8 | |
|
5cb37412a2 | |
|
e01bab843b | |
|
ba26208cec | |
|
f1147a3d7f | |
|
c51b2abead | |
|
3e298cc85a | |
|
7523ab1495 | |
|
6b12cb60e9 | |
|
cdf2ff8176 | |
|
48927b1d3b | |
|
1c79ec2c08 | |
|
e7a472a11f | |
|
5e9befc7d4 | |
|
6d715b7702 | |
|
73a3dbe31e | |
|
9504544d5b | |
|
5bc9e6e0f3 | |
|
e9426d15d8 | |
|
54d2873770 | |
|
9b014eae68 | |
|
69585a3218 | |
|
d9f8230d2c |
|
@ -86,7 +86,7 @@ docs-deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: alpine:3.9
|
image: alpine:3.9
|
||||||
only:
|
only:
|
||||||
- master@pleroma/pleroma
|
- stable@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
before_script:
|
before_script:
|
||||||
- apk update && apk add openssh-client rsync
|
- apk update && apk add openssh-client rsync
|
||||||
|
@ -148,8 +148,10 @@ amd64:
|
||||||
# TODO: Replace with upstream image when 1.9.0 comes out
|
# TODO: Replace with upstream image when 1.9.0 comes out
|
||||||
image: rinpatch/elixir:1.9.0-rc.0
|
image: rinpatch/elixir:1.9.0-rc.0
|
||||||
only: &release-only
|
only: &release-only
|
||||||
- master@pleroma/pleroma
|
- stable@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
|
- /^maint/.*$/@pleroma/pleroma
|
||||||
|
- /^release/.*$/@pleroma/pleroma
|
||||||
artifacts: &release-artifacts
|
artifacts: &release-artifacts
|
||||||
name: "pleroma-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA-$CI_JOB_NAME"
|
name: "pleroma-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA-$CI_JOB_NAME"
|
||||||
paths:
|
paths:
|
||||||
|
|
403
CC-BY-NC-ND-4.0
403
CC-BY-NC-ND-4.0
|
@ -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.
|
|
||||||
|
|
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -3,6 +3,78 @@ 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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [1.0.7] - 2019-09-26
|
||||||
|
### Fixed
|
||||||
|
- Broken federation on Erlang 22 (previous versions of hackney http client were using an option that got deprecated)
|
||||||
|
### Changed
|
||||||
|
- ActivityPub: The first page in inboxes/outboxes is no longer embedded.
|
||||||
|
|
||||||
|
## [1.0.6] - 2019-08-14
|
||||||
|
### Fixed
|
||||||
|
- MRF: fix use of unserializable keyword lists in describe() implementations
|
||||||
|
- ActivityPub S2S: POST requests are now signed with `(request-target)` pseudo-header.
|
||||||
|
|
||||||
|
## [1.0.5] - 2019-08-13
|
||||||
|
### Fixed
|
||||||
|
- 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 thread was muted
|
||||||
|
- Mastodon API: return the actual profile URL in the Account entity's `url` property when appropriate
|
||||||
|
- Templates: properly style anchor tags
|
||||||
|
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
|
||||||
|
- Not being able to access the Mastodon FE login page on private instances
|
||||||
|
- MRF: ensure that subdomain_match calls are case-insensitive
|
||||||
|
- Fix internal server error when using the healthcheck API.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.
|
||||||
|
Custom modules will need to be updated by adding, at the very least, `def describe, do: {:ok, %{}}` to the MRF policy modules.
|
||||||
|
- Relays: Added a task to list relay subscriptions.
|
||||||
|
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
|
||||||
|
- MRF (Simple Policy): Support for wildcard domains.
|
||||||
|
- Support for wildcard domains in user domain blocks setting.
|
||||||
|
- Configuration: `quarantined_instances` support wildcard domains.
|
||||||
|
- Mix Tasks: `mix pleroma.database fix_likes_collections`
|
||||||
|
- Configuration: `federation_incoming_replies_max_depth` option
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Federation: Remove `likes` from objects.
|
||||||
|
- ActivityPub: The `accept_blocks` configuration setting.
|
||||||
|
|
||||||
|
## [1.0.4] - 2019-08-01
|
||||||
|
### Fixed
|
||||||
|
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
|
||||||
|
|
||||||
|
## [1.0.3] - 2019-07-31
|
||||||
|
### Security
|
||||||
|
- OStatus: eliminate the possibility of a protocol downgrade attack.
|
||||||
|
- OStatus: prevent following locked accounts, bypassing the approval process.
|
||||||
|
- TwitterAPI: use CommonAPI to handle remote follows instead of OStatus.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `pleroma_ctl` not detecting the master branch properly. If you get "Releases are built only for master and develop branches" error when updating, please add `-` to the end of the line in `releases/start_erl.data`
|
||||||
|
|
||||||
|
## [1.0.2] - 2019-07-28
|
||||||
|
### Fixed
|
||||||
|
- Not being able to pin unlisted posts
|
||||||
|
- Mastodon API: represent poll IDs as strings
|
||||||
|
- MediaProxy: fix matching filenames
|
||||||
|
- MediaProxy: fix filename encoding
|
||||||
|
- Migrations: fix a sporadic migration failure
|
||||||
|
- Metadata rendering errors resulting in the entire page being inaccessible
|
||||||
|
- Federation/MediaProxy not working with instances that have wrong certificate order
|
||||||
|
- ActivityPub S2S: remote user deletions now work the same as local user deletions.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Configuration: OpenGraph and TwitterCard providers enabled by default
|
||||||
|
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-07-14
|
||||||
|
### Security
|
||||||
|
- OStatus: fix an object spoofing vulnerability.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- MRF: Support for excluding specific domains from Transparency.
|
||||||
|
|
||||||
## [1.0.0] - 2019-06-29
|
## [1.0.0] - 2019-06-29
|
||||||
### Security
|
### Security
|
||||||
- Mastodon API: Fix display names not being sanitized
|
- Mastodon API: Fix display names not being sanitized
|
||||||
|
|
|
@ -194,6 +194,8 @@
|
||||||
send_user_agent: true,
|
send_user_agent: true,
|
||||||
adapter: [
|
adapter: [
|
||||||
ssl_options: [
|
ssl_options: [
|
||||||
|
# Workaround for remote server certificate chain issues
|
||||||
|
partial_chain: &:hackney_connect.partial_chain/1,
|
||||||
# We don't support TLS v1.3 yet
|
# We don't support TLS v1.3 yet
|
||||||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
|
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
|
||||||
]
|
]
|
||||||
|
@ -218,6 +220,7 @@
|
||||||
},
|
},
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
federating: true,
|
federating: true,
|
||||||
|
federation_incoming_replies_max_depth: 100,
|
||||||
federation_reachability_timeout_days: 7,
|
federation_reachability_timeout_days: 7,
|
||||||
federation_publisher_modules: [
|
federation_publisher_modules: [
|
||||||
Pleroma.Web.ActivityPub.Publisher,
|
Pleroma.Web.ActivityPub.Publisher,
|
||||||
|
@ -237,6 +240,7 @@
|
||||||
"text/bbcode"
|
"text/bbcode"
|
||||||
],
|
],
|
||||||
mrf_transparency: true,
|
mrf_transparency: true,
|
||||||
|
mrf_transparency_exclusions: [],
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
no_attachment_links: false,
|
no_attachment_links: false,
|
||||||
|
@ -297,7 +301,6 @@
|
||||||
default_mascot: :pleroma_fox_tan
|
default_mascot: :pleroma_fox_tan
|
||||||
|
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
accept_blocks: true,
|
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
outgoing_blocks: true,
|
outgoing_blocks: true,
|
||||||
follow_handshake_timeout: 500
|
follow_handshake_timeout: 500
|
||||||
|
@ -331,6 +334,10 @@
|
||||||
|
|
||||||
config :pleroma, :mrf_subchain, match_actor: %{}
|
config :pleroma, :mrf_subchain, match_actor: %{}
|
||||||
|
|
||||||
|
config :pleroma, :mrf_vocabulary,
|
||||||
|
accept: [],
|
||||||
|
reject: []
|
||||||
|
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
|
@ -358,7 +365,11 @@
|
||||||
port: 9999
|
port: 9999
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.Metadata,
|
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
|
unfurl_nsfw: false
|
||||||
|
|
||||||
config :pleroma, :suggestions,
|
config :pleroma, :suggestions,
|
||||||
|
|
|
@ -31,10 +31,11 @@ Feel free to contact us to be added to this list!
|
||||||
- Features: No Streaming
|
- Features: No Streaming
|
||||||
|
|
||||||
### Fedilab
|
### Fedilab
|
||||||
- Source Code: <https://gitlab.com/tom79/mastalab/>
|
- Homepage: <https://fedilab.app/>
|
||||||
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
|
- Source Code: <https://framagit.org/tom79/fedilab/>
|
||||||
|
- Contact: [@fedilab@framapiaf.org](https://framapiaf.org/users/fedilab)
|
||||||
- Platforms: Android
|
- Platforms: Android
|
||||||
- Features: Streaming Ready
|
- Features: Streaming Ready, Moderation, Text Formatting
|
||||||
|
|
||||||
### Nekonium
|
### 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/)
|
- 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/)
|
||||||
|
|
|
@ -87,6 +87,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
||||||
* `account_activation_required`: Require users to confirm their emails before signing in.
|
* `account_activation_required`: Require users to confirm their emails before signing in.
|
||||||
* `federating`: Enable federation with other instances
|
* `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.
|
* `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
|
* `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:
|
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
||||||
|
@ -98,11 +99,13 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
|
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
|
||||||
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
|
* `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.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` 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.
|
* `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.
|
* `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``
|
* `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)
|
* `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`: 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.
|
* `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:
|
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
|
||||||
* "email": Copy and preprend re:, as in email.
|
* "email": Copy and preprend re:, as in email.
|
||||||
|
@ -265,6 +268,10 @@ 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)
|
* `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)
|
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
|
||||||
|
|
||||||
|
## :mrf_vocabulary
|
||||||
|
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
|
||||||
|
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
|
||||||
|
|
||||||
## :media_proxy
|
## :media_proxy
|
||||||
* `enabled`: Enables proxying of remote media to the instance’s 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.
|
* `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.
|
||||||
|
@ -318,7 +325,6 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
||||||
|
|
||||||
## :activitypub
|
## :activitypub
|
||||||
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
|
||||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||||
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
* ``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
|
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
||||||
|
|
|
@ -87,7 +87,7 @@ sudo adduser -S -s /bin/false -h /opt/pleroma -H pleroma
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -66,7 +66,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -143,7 +143,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -68,7 +68,7 @@ sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -69,7 +69,9 @@ cd ~
|
||||||
|
|
||||||
* Gitリポジトリをクローンします。
|
* Gitリポジトリをクローンします。
|
||||||
```
|
```
|
||||||
git clone -b master https://git.pleroma.social/pleroma/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* 新しいディレクトリに移動します。
|
* 新しいディレクトリに移動します。
|
||||||
|
|
|
@ -106,7 +106,7 @@ It is highly recommended you use your own fork for the `https://path/to/repo` pa
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pleroma$ cd ~
|
pleroma$ cd ~
|
||||||
pleroma$ git clone -b master https://path/to/repo
|
pleroma$ git clone -b stable https://path/to/repo
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -49,7 +49,7 @@ mkdir -p /var/lib/pleroma/static
|
||||||
chown -R pleroma /var/lib/pleroma
|
chown -R pleroma /var/lib/pleroma
|
||||||
|
|
||||||
# If you use the local uploader with default settings your uploads should be located in `~pleroma/uploads`
|
# If you use the local uploader with default settings your uploads should be located in `~pleroma/uploads`
|
||||||
mv ~pleroma/uploads /var/lib/pleroma/uploads
|
mv ~pleroma/uploads/* /var/lib/pleroma/uploads
|
||||||
|
|
||||||
# If you have created the custom public files directory with default settings it should be located in `~pleroma/instance/static`
|
# If you have created the custom public files directory with default settings it should be located in `~pleroma/instance/static`
|
||||||
mv ~pleroma/instance/static /var/lib/pleroma/static
|
mv ~pleroma/instance/static /var/lib/pleroma/static
|
||||||
|
@ -96,9 +96,9 @@ rm -r ~pleroma/*
|
||||||
export FLAVOUR="arm64-musl"
|
export FLAVOUR="arm64-musl"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
# Replace `master` with `develop` if you want to run the develop branch
|
# Replace `stable` with `unstable` if you want to run the unstable branch
|
||||||
su pleroma -s $SHELL -lc "
|
su pleroma -s $SHELL -lc "
|
||||||
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/master/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
||||||
unzip /tmp/pleroma.zip -d /tmp/
|
unzip /tmp/pleroma.zip -d /tmp/
|
||||||
"
|
"
|
||||||
|
|
||||||
|
@ -122,13 +122,15 @@ su pleroma -s $SHELL -lc "./bin/pleroma stop"
|
||||||
## Setting up a system service
|
## Setting up a system service
|
||||||
OTP releases have different service files than from-source installs so they need to be copied over again.
|
OTP releases have different service files than from-source installs so they need to be copied over again.
|
||||||
|
|
||||||
|
**Warning:** The service files assume pleroma user's home directory is `/opt/pleroma`, please make sure all paths fit your installation.
|
||||||
|
|
||||||
Debian/Ubuntu:
|
Debian/Ubuntu:
|
||||||
```sh
|
```sh
|
||||||
# Copy the service into a proper directory
|
# Copy the service into a proper directory
|
||||||
cp ~pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
cp ~pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||||
|
|
||||||
# Reload service files
|
# Reload service files
|
||||||
systemctl reload-daemon
|
systemctl daemon-reload
|
||||||
|
|
||||||
# Reenable pleroma to start on boot
|
# Reenable pleroma to start on boot
|
||||||
systemctl reenable pleroma
|
systemctl reenable pleroma
|
||||||
|
|
|
@ -58,7 +58,7 @@ Clone the repository:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd /home/pleroma
|
$ cd /home/pleroma
|
||||||
$ git clone -b master https://git.pleroma.social/pleroma/pleroma.git
|
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure Pleroma. Note that you need a domain name at this point:
|
Configure Pleroma. Note that you need a domain name at this point:
|
||||||
|
|
|
@ -29,7 +29,7 @@ This creates a "pleroma" login class and sets higher values than default for dat
|
||||||
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/): `useradd -m -L pleroma _pleroma`
|
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/): `useradd -m -L pleroma _pleroma`
|
||||||
|
|
||||||
#### Clone pleroma's directory
|
#### Clone pleroma's directory
|
||||||
Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b master https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
|
Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
|
||||||
|
|
||||||
#### Postgresql
|
#### Postgresql
|
||||||
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
||||||
|
|
|
@ -44,7 +44,7 @@ Vaihda pleroma-käyttäjään ja mene kotihakemistoosi:
|
||||||
|
|
||||||
Lataa pleroman lähdekoodi:
|
Lataa pleroman lähdekoodi:
|
||||||
|
|
||||||
`$ git clone -b master https://git.pleroma.social/pleroma/pleroma.git`
|
`$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`
|
||||||
|
|
||||||
`$ cd pleroma`
|
`$ cd pleroma`
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ export FLAVOUR="arm64-musl"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
su pleroma -s $SHELL -lc "
|
su pleroma -s $SHELL -lc "
|
||||||
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/master/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
||||||
unzip /tmp/pleroma.zip -d /tmp/
|
unzip /tmp/pleroma.zip -d /tmp/
|
||||||
"
|
"
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --
|
||||||
|
|
||||||
# Add it to the daily cron
|
# Add it to the daily cron
|
||||||
echo '#!/bin/sh
|
echo '#!/bin/sh
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "systemctl reload nginx"
|
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
|
||||||
' > /etc/cron.daily/renew-pleroma-cert
|
' > /etc/cron.daily/renew-pleroma-cert
|
||||||
chmod +x /etc/cron.daily/renew-pleroma-cert
|
chmod +x /etc/cron.daily/renew-pleroma-cert
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --
|
||||||
|
|
||||||
# Add it to the daily cron
|
# Add it to the daily cron
|
||||||
echo '#!/bin/sh
|
echo '#!/bin/sh
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "rc-service nginx reload"
|
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
|
||||||
' > /etc/periodic/daily/renew-pleroma-cert
|
' > /etc/periodic/daily/renew-pleroma-cert
|
||||||
chmod +x /etc/periodic/daily/renew-pleroma-cert
|
chmod +x /etc/periodic/daily/renew-pleroma-cert
|
||||||
|
|
||||||
|
@ -242,6 +242,14 @@ So for example, if the task is `mix pleroma.user set admin --admin`, you should
|
||||||
```sh
|
```sh
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user set admin --admin"
|
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user set admin --admin"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Create your first user and set as admin
|
||||||
|
```sh
|
||||||
|
cd /opt/pleroma/bin
|
||||||
|
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
|
||||||
|
```
|
||||||
|
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
Generally, doing the following is enough:
|
Generally, doing the following is enough:
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -35,6 +35,10 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
## Remove duplicated items from following and update followers count for all users
|
## Remove duplicated items from following and update followers count for all users
|
||||||
|
|
||||||
mix pleroma.database update_users_following_followers_counts
|
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
|
def run(["remove_embedded_objects" | args]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -119,4 +123,36 @@ def run(["prune_objects" | args]) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||||
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
|
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
|
||||||
- `--uploads-dir` - the directory uploads go in when using a local uploader
|
- `--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.)
|
- `--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
|
def run(["gen" | rest]) do
|
||||||
|
@ -56,7 +58,9 @@ def run(["gen" | rest]) do
|
||||||
indexable: :string,
|
indexable: :string,
|
||||||
db_configurable: :string,
|
db_configurable: :string,
|
||||||
uploads_dir: :string,
|
uploads_dir: :string,
|
||||||
static_dir: :string
|
static_dir: :string,
|
||||||
|
listen_ip: :string,
|
||||||
|
listen_port: :string
|
||||||
],
|
],
|
||||||
aliases: [
|
aliases: [
|
||||||
o: :output,
|
o: :output,
|
||||||
|
@ -146,10 +150,26 @@ def run(["gen" | rest]) do
|
||||||
"n"
|
"n"
|
||||||
) === "y"
|
) === "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 =
|
uploads_dir =
|
||||||
get_option(
|
get_option(
|
||||||
options,
|
options,
|
||||||
:upload_dir,
|
:uploads_dir,
|
||||||
"What directory should media uploads go in (when using the local uploader)?",
|
"What directory should media uploads go in (when using the local uploader)?",
|
||||||
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
|
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||||
)
|
)
|
||||||
|
@ -186,7 +206,9 @@ def run(["gen" | rest]) do
|
||||||
db_configurable?: db_configurable?,
|
db_configurable?: db_configurable?,
|
||||||
static_dir: static_dir,
|
static_dir: static_dir,
|
||||||
uploads_dir: uploads_dir,
|
uploads_dir: uploads_dir,
|
||||||
rum_enabled: rum_enabled
|
rum_enabled: rum_enabled,
|
||||||
|
listen_ip: listen_ip,
|
||||||
|
listen_port: listen_port
|
||||||
)
|
)
|
||||||
|
|
||||||
result_psql =
|
result_psql =
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Mix.Tasks.Pleroma.Relay do
|
defmodule Mix.Tasks.Pleroma.Relay do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
|
||||||
@shortdoc "Manages remote relays"
|
@shortdoc "Manages remote relays"
|
||||||
|
@ -22,6 +23,10 @@ defmodule Mix.Tasks.Pleroma.Relay do
|
||||||
``mix pleroma.relay unfollow <relay_url>``
|
``mix pleroma.relay unfollow <relay_url>``
|
||||||
|
|
||||||
Example: ``mix pleroma.relay unfollow https://example.org/relay``
|
Example: ``mix pleroma.relay unfollow https://example.org/relay``
|
||||||
|
|
||||||
|
## List relay subscriptions
|
||||||
|
|
||||||
|
``mix pleroma.relay list``
|
||||||
"""
|
"""
|
||||||
def run(["follow", target]) do
|
def run(["follow", target]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
@ -44,4 +49,19 @@ def run(["unfollow", target]) do
|
||||||
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
|
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -31,8 +31,8 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
mix pleroma.user invite [OPTION...]
|
mix pleroma.user invite [OPTION...]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05")
|
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
|
||||||
- `--max_use NUMBER` - maximum numbers of token uses
|
- `--max-use NUMBER` - maximum numbers of token uses
|
||||||
|
|
||||||
## List generated invites
|
## List generated invites
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.HTTP.Connection do
|
||||||
connect_timeout: 10_000,
|
connect_timeout: 10_000,
|
||||||
recv_timeout: 20_000,
|
recv_timeout: 20_000,
|
||||||
follow_redirect: true,
|
follow_redirect: true,
|
||||||
|
force_redirect: true,
|
||||||
pool: :federation
|
pool: :federation
|
||||||
]
|
]
|
||||||
@adapter Application.get_env(:tesla, :adapter)
|
@adapter Application.get_env(:tesla, :adapter)
|
||||||
|
@ -29,7 +30,7 @@ def new(opts \\ []) do
|
||||||
|
|
||||||
# fetch Hackney options
|
# fetch Hackney options
|
||||||
#
|
#
|
||||||
defp hackney_options(opts) do
|
def hackney_options(opts) do
|
||||||
options = Keyword.get(opts, :adapter, [])
|
options = Keyword.get(opts, :adapter, [])
|
||||||
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
||||||
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
|
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
|
||||||
|
|
|
@ -65,10 +65,7 @@ defp process_sni_options(options, url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_request_options(options) do
|
def process_request_options(options) do
|
||||||
case Pleroma.Config.get([:http, :proxy_url]) do
|
Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
|
||||||
nil -> options
|
|
||||||
proxy -> options ++ [proxy: proxy]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -44,20 +44,20 @@ def get_by_ap_id(ap_id) do
|
||||||
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(_, fetch_remote \\ true)
|
def normalize(_, fetch_remote \\ true, options \\ [])
|
||||||
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
|
# 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!
|
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
||||||
def normalize(%Object{} = object, _), do: object
|
def normalize(%Object{} = object, _, _), do: object
|
||||||
def normalize(%Activity{object: %Object{} = object}, _), do: object
|
def normalize(%Activity{object: %Object{} = object}, _, _), do: object
|
||||||
|
|
||||||
# A hack for fake activities
|
# 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}
|
%Object{id: "pleroma:fake_object_id", data: data}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Catch and log Object.normalize() calls where the Activity's child object is not
|
# Catch and log Object.normalize() calls where the Activity's child object is not
|
||||||
# preloaded.
|
# preloaded.
|
||||||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do
|
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
||||||
)
|
)
|
||||||
|
@ -67,7 +67,7 @@ def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do
|
||||||
normalize(ap_id, fetch_remote)
|
normalize(ap_id, fetch_remote)
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do
|
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
||||||
)
|
)
|
||||||
|
@ -78,10 +78,14 @@ def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Old way, try fetching the object through cache.
|
# Old way, try fetching the object through cache.
|
||||||
def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote)
|
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, 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(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
|
# Owned objects can only be mutated by their owner
|
||||||
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
|
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
|
||||||
|
|
|
@ -48,6 +48,9 @@ def contain_origin(id, %{"actor" => _actor} = params) do
|
||||||
end
|
end
|
||||||
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" => nil}), do: :error
|
||||||
|
|
||||||
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
||||||
|
@ -60,4 +63,9 @@ def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
||||||
|
do: contain_origin(id, object)
|
||||||
|
|
||||||
|
def contain_child(_), do: :ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,39 +22,45 @@ defp reinject_object(data) do
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# This will create a Create activity, which we need internally at the moment.
|
# 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
|
if object = Object.get_cached_by_ap_id(id) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
Logger.info("Fetching #{id} via AP")
|
Logger.info("Fetching #{id} via AP")
|
||||||
|
|
||||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||||
nil <- Object.normalize(data, false),
|
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
|
||||||
params <- %{
|
params <- %{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => data["to"],
|
"to" => data["to"],
|
||||||
"cc" => data["cc"],
|
"cc" => data["cc"],
|
||||||
|
# Should we seriously keep this attributedTo thing?
|
||||||
"actor" => data["actor"] || data["attributedTo"],
|
"actor" => data["actor"] || data["attributedTo"],
|
||||||
"object" => data
|
"object" => data
|
||||||
},
|
},
|
||||||
:ok <- Containment.contain_origin(id, params),
|
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||||
{:ok, activity} <- Transmogrifier.handle_incoming(params),
|
{:ok, activity} <- Transmogrifier.handle_incoming(params, options),
|
||||||
{:object, _data, %Object{} = object} <-
|
{:object, _data, %Object{} = object} <-
|
||||||
{:object, data, Object.normalize(activity, false)} do
|
{:object, data, Object.normalize(activity, false)} do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
|
{:containment, _} ->
|
||||||
|
{:error, "Object containment failed."}
|
||||||
|
|
||||||
{:error, {:reject, nil}} ->
|
{:error, {:reject, nil}} ->
|
||||||
{:reject, nil}
|
{:reject, nil}
|
||||||
|
|
||||||
{:object, data, nil} ->
|
{:object, data, nil} ->
|
||||||
reinject_object(data)
|
reinject_object(data)
|
||||||
|
|
||||||
object = %Object{} ->
|
{:normalize, object = %Object{}} ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
_e ->
|
_e ->
|
||||||
|
# Only fallback when receiving a fetch/normalization error with ActivityPub
|
||||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||||
|
|
||||||
|
# FIXME: OStatus Object Containment?
|
||||||
case OStatus.fetch_activity_from_url(id) do
|
case OStatus.fetch_activity_from_url(id) do
|
||||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
||||||
e -> e
|
e -> e
|
||||||
|
@ -63,8 +69,8 @@ def fetch_object_from_id(id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_object_from_id!(id) do
|
def fetch_object_from_id!(id, options \\ []) do
|
||||||
with {:ok, object} <- fetch_object_from_id(id) do
|
with {:ok, object} <- fetch_object_from_id(id, options) do
|
||||||
object
|
object
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
|
|
@ -61,7 +61,7 @@ defmodule Pleroma.ReverseProxy do
|
||||||
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@default_hackney_options []
|
@default_hackney_options [pool: :media]
|
||||||
|
|
||||||
@inline_content_types [
|
@inline_content_types [
|
||||||
"image/gif",
|
"image/gif",
|
||||||
|
@ -94,7 +94,8 @@ def call(_conn, _url, _opts \\ [])
|
||||||
|
|
||||||
def call(conn = %{method: method}, url, opts) when method in @methods do
|
def call(conn = %{method: method}, url, opts) when method in @methods do
|
||||||
hackney_opts =
|
hackney_opts =
|
||||||
@default_hackney_options
|
Pleroma.HTTP.Connection.hackney_options([])
|
||||||
|
|> Keyword.merge(@default_hackney_options)
|
||||||
|> Keyword.merge(Keyword.get(opts, :http, []))
|
|> Keyword.merge(Keyword.get(opts, :http, []))
|
||||||
|> HTTP.process_request_options()
|
|> HTTP.process_request_options()
|
||||||
|
|
||||||
|
|
|
@ -836,15 +836,15 @@ def unblock(blocker, %{ap_id: ap_id}) do
|
||||||
def mutes?(nil, _), do: false
|
def mutes?(nil, _), do: false
|
||||||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
||||||
|
|
||||||
def blocks?(user, %{ap_id: ap_id}) do
|
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
|
||||||
blocks = user.info.blocks
|
blocks = info.blocks
|
||||||
domain_blocks = user.info.domain_blocks
|
|
||||||
|
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
|
||||||
|
|
||||||
%{host: host} = URI.parse(ap_id)
|
%{host: host} = URI.parse(ap_id)
|
||||||
|
|
||||||
Enum.member?(blocks, ap_id) ||
|
Enum.member?(blocks, ap_id) ||
|
||||||
Enum.any?(domain_blocks, fn domain ->
|
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
|
||||||
host == domain
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribed_to?(user, %{ap_id: ap_id}) do
|
def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -126,6 +127,7 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
{:ok, map} <- MRF.filter(map),
|
{:ok, map} <- MRF.filter(map),
|
||||||
{recipients, _, _} = get_recipients(map),
|
{recipients, _, _} = get_recipients(map),
|
||||||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||||
|
:ok <- Containment.contain_child(map),
|
||||||
{:ok, map, object} <- insert_full_object(map) do
|
{:ok, map, object} <- insert_full_object(map) do
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
Repo.insert(%Activity{
|
Repo.insert(%Activity{
|
||||||
|
@ -272,6 +274,9 @@ def create(%{to: to, actor: actor, context: context, object: object} = params, f
|
||||||
else
|
else
|
||||||
{:fake, true, activity} ->
|
{:fake, true, activity} ->
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
||||||
|
{:error, message} ->
|
||||||
|
{:error, message}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -728,8 +733,8 @@ defp restrict_state(query, _), do: query
|
||||||
|
|
||||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||||
from(
|
from(
|
||||||
activity in query,
|
[_activity, object] in query,
|
||||||
where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
|
where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -837,7 +842,7 @@ defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
|
||||||
|
|
||||||
defp restrict_muted_reblogs(query, _), do: query
|
defp restrict_muted_reblogs(query, _), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
|
defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, _) do
|
defp exclude_poll_votes(query, _) do
|
||||||
if has_named_binding?(query, :object) do
|
if has_named_binding?(query, :object) do
|
||||||
|
|
|
@ -144,12 +144,43 @@ def followers(conn, %{"nickname" => nickname}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def outbox(conn, %{"nickname" => nickname} = params) do
|
def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
|
||||||
|
when page? in [true, "true"] do
|
||||||
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
|
{:ok, user} <- User.ensure_keys_present(user) do
|
||||||
|
activities =
|
||||||
|
if params["max_id"] do
|
||||||
|
ActivityPub.fetch_user_activities(user, nil, %{
|
||||||
|
"max_id" => params["max_id"],
|
||||||
|
# This is a hack because postgres generates inefficient queries when filtering by
|
||||||
|
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway
|
||||||
|
"include_poll_votes" => true,
|
||||||
|
"limit" => 10
|
||||||
|
})
|
||||||
|
else
|
||||||
|
ActivityPub.fetch_user_activities(user, nil, %{
|
||||||
|
"limit" => 10,
|
||||||
|
"include_poll_votes" => true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(UserView)
|
||||||
|
|> render("activity_collection_page.json", %{
|
||||||
|
activities: activities,
|
||||||
|
iri: "#{user.ap_id}/outbox"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def outbox(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{:ok, user} <- User.ensure_keys_present(user) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
|
|> put_view(UserView)
|
||||||
|
|> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -212,16 +243,61 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|
||||||
|
|
||||||
def whoami(_conn, _params), do: {:error, :not_found}
|
def whoami(_conn, _params), do: {:error, :not_found}
|
||||||
|
|
||||||
def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
|
def read_inbox(
|
||||||
if nickname == user.nickname do
|
%{assigns: %{user: %{nickname: nickname} = user}} = conn,
|
||||||
conn
|
%{"nickname" => nickname, "page" => page?} = params
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
)
|
||||||
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
|
when page? in [true, "true"] do
|
||||||
|
activities =
|
||||||
|
if params["max_id"] do
|
||||||
|
ActivityPub.fetch_activities([user.ap_id | user.following], %{
|
||||||
|
"max_id" => params["max_id"],
|
||||||
|
"limit" => 10
|
||||||
|
})
|
||||||
else
|
else
|
||||||
|
ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(UserView)
|
||||||
|
|> render("activity_collection_page.json", %{
|
||||||
|
activities: activities,
|
||||||
|
iri: "#{user.ap_id}/inbox"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
|
||||||
|
"nickname" => nickname
|
||||||
|
}) do
|
||||||
|
with {:ok, user} <- User.ensure_keys_present(user) do
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(UserView)
|
||||||
|
|> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
|
||||||
|
err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json("can't read inbox of #{nickname} as #{user.nickname}")
|
|> json(err)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
|
||||||
|
"nickname" => nickname
|
||||||
|
}) do
|
||||||
|
err =
|
||||||
|
dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
|
||||||
|
nickname: nickname,
|
||||||
|
as_nickname: as_nickname
|
||||||
|
)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(err)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_user_activity(user, %{"type" => "Create"} = params) do
|
def handle_user_activity(user, %{"type" => "Create"} = params) do
|
||||||
|
|
|
@ -25,4 +25,46 @@ def get_policies do
|
||||||
defp get_policies(policy) when is_atom(policy), do: [policy]
|
defp get_policies(policy) when is_atom(policy), do: [policy]
|
||||||
defp get_policies(policies) when is_list(policies), do: policies
|
defp get_policies(policies) when is_list(policies), do: policies
|
||||||
defp get_policies(_), do: []
|
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
|
||||||
|
|
||||||
|
@callback describe() :: {:ok | :error, Map.t()}
|
||||||
|
|
||||||
|
def describe(policies) do
|
||||||
|
{:ok, policy_configs} =
|
||||||
|
policies
|
||||||
|
|> Enum.reduce({:ok, %{}}, fn
|
||||||
|
policy, {:ok, data} ->
|
||||||
|
{:ok, policy_data} = policy.describe()
|
||||||
|
{:ok, Map.merge(data, policy_data)}
|
||||||
|
|
||||||
|
_, error ->
|
||||||
|
error
|
||||||
|
end)
|
||||||
|
|
||||||
|
mrf_policies =
|
||||||
|
get_policies()
|
||||||
|
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||||
|
|
||||||
|
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
|
||||||
|
base =
|
||||||
|
%{
|
||||||
|
mrf_policies: mrf_policies,
|
||||||
|
exclusions: length(exclusions) > 0
|
||||||
|
}
|
||||||
|
|> Map.merge(policy_configs)
|
||||||
|
|
||||||
|
{:ok, base}
|
||||||
|
end
|
||||||
|
|
||||||
|
def describe, do: get_policies() |> describe()
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,4 +62,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
# has the user successfully posted before?
|
# has the user successfully posted before?
|
||||||
|
@ -22,6 +24,7 @@ defp contains_links?(%{"content" => content} = _object) do
|
||||||
|
|
||||||
defp contains_links?(_), do: false
|
defp contains_links?(_), do: false
|
||||||
|
|
||||||
|
@impl true
|
||||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||||
|
@ -45,4 +48,7 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
|
||||||
|
|
||||||
# in all other cases, pass through
|
# in all other cases, pass through
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,4 +12,7 @@ def filter(object) do
|
||||||
Logger.info("REJECTING #{inspect(object)}")
|
Logger.info("REJECTING #{inspect(object)}")
|
||||||
{:reject, object}
|
{:reject, object}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,4 +42,6 @@ def filter(%{"type" => activity_type} = object) when activity_type == "Create" d
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,4 +87,8 @@ def filter(%{"type" => "Create"} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe,
|
||||||
|
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -94,4 +94,36 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
mrf_keyword =
|
||||||
|
Pleroma.Config.get(:mrf_keyword, [])
|
||||||
|
|> Enum.map(fn {key, value} ->
|
||||||
|
{key,
|
||||||
|
Enum.map(value, fn
|
||||||
|
{pattern, replacement} ->
|
||||||
|
%{
|
||||||
|
"pattern" =>
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end,
|
||||||
|
"replacement" => replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern ->
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end
|
||||||
|
end)}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_keyword: mrf_keyword}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,4 +27,7 @@ def filter(
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
|
||||||
def filter(object) do
|
def filter(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,4 +25,6 @@ def filter(%{"type" => activity_type} = object) when activity_type == "Create" d
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,4 +48,8 @@ def filter(%{"type" => "Create"} = object) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe,
|
||||||
|
do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,22 +4,29 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
@moduledoc "Filter activities depending on their origin instance"
|
@moduledoc "Filter activities depending on their origin instance"
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour MRF
|
||||||
|
|
||||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
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
|
cond do
|
||||||
accepts == [] -> {:ok, object}
|
accepts == [] -> {:ok, object}
|
||||||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {: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}
|
true -> {:reject, nil}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
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}
|
{:reject, nil}
|
||||||
else
|
else
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -31,8 +38,12 @@ defp check_media_removal(
|
||||||
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
|
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
|
media_removal =
|
||||||
|
Pleroma.Config.get([:mrf_simple, :media_removal])
|
||||||
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
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")
|
child_object = Map.delete(object["object"], "attachment")
|
||||||
Map.put(object, "object", child_object)
|
Map.put(object, "object", child_object)
|
||||||
else
|
else
|
||||||
|
@ -51,8 +62,12 @@ defp check_media_nsfw(
|
||||||
"object" => child_object
|
"object" => child_object
|
||||||
} = object
|
} = object
|
||||||
) do
|
) do
|
||||||
|
media_nsfw =
|
||||||
|
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|
||||||
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
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"]
|
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||||
child_object = Map.put(child_object, "tag", tags)
|
child_object = Map.put(child_object, "tag", tags)
|
||||||
child_object = Map.put(child_object, "sensitive", true)
|
child_object = Map.put(child_object, "sensitive", true)
|
||||||
|
@ -67,12 +82,12 @@ defp check_media_nsfw(
|
||||||
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||||
|
timeline_removal =
|
||||||
|
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
|
||||||
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
with true <-
|
with true <- MRF.subdomain_match?(timeline_removal, actor_host),
|
||||||
Enum.member?(
|
|
||||||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
|
|
||||||
actor_host
|
|
||||||
),
|
|
||||||
user <- User.get_cached_by_ap_id(object["actor"]),
|
user <- User.get_cached_by_ap_id(object["actor"]),
|
||||||
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
|
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
|
||||||
to =
|
to =
|
||||||
|
@ -94,7 +109,11 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
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}
|
{:reject, nil}
|
||||||
else
|
else
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -104,7 +123,11 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
|
||||||
defp check_report_removal(_actor_info, object), do: {:ok, object}
|
defp check_report_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
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")}
|
{:ok, Map.delete(object, "icon")}
|
||||||
else
|
else
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -114,7 +137,11 @@ defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon}
|
||||||
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
|
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
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")}
|
{:ok, Map.delete(object, "image")}
|
||||||
else
|
else
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -152,4 +179,16 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
|
||||||
|
mrf_simple =
|
||||||
|
Pleroma.Config.get(:mrf_simple)
|
||||||
|
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_simple: mrf_simple}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,4 +37,7 @@ def filter(%{"actor" => actor} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -149,4 +149,7 @@ def filter(%{"actor" => actor, "type" => "Create"} = message),
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,4 +27,13 @@ def filter(%{"actor" => actor} = object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_user_allowlist =
|
||||||
|
Config.get([:mrf_user_allowlist], [])
|
||||||
|
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
||||||
|
|
||||||
|
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
||||||
|
@moduledoc "Filter messages which belong to certain activity vocabularies"
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
||||||
|
with {:ok, _} <- filter(child_message) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
{:reject, nil} ->
|
||||||
|
{:reject, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(%{"type" => message_type} = message) do
|
||||||
|
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||||
|
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||||
|
true <-
|
||||||
|
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
||||||
|
false <-
|
||||||
|
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||||
|
{:ok, _} <- filter(message["object"]) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
_ -> {:reject, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
def describe,
|
||||||
|
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
|
||||||
|
end
|
|
@ -44,7 +44,7 @@ def is_representable?(%Activity{} = activity) do
|
||||||
"""
|
"""
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
host = URI.parse(inbox).host
|
%{host: host, path: path} = URI.parse(inbox)
|
||||||
|
|
||||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
|
||||||
|
|
||||||
signature =
|
signature =
|
||||||
Pleroma.Signature.sign(actor, %{
|
Pleroma.Signature.sign(actor, %{
|
||||||
|
"(request-target)": "post #{path}",
|
||||||
host: host,
|
host: host,
|
||||||
"content-length": byte_size(json),
|
"content-length": byte_size(json),
|
||||||
digest: digest,
|
digest: digest,
|
||||||
|
@ -87,8 +88,13 @@ defp should_federate?(inbox, public) do
|
||||||
if public do
|
if public do
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
inbox_info = URI.parse(inbox)
|
%{host: host} = URI.parse(inbox)
|
||||||
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
|
||||||
|
quarantined_instances =
|
||||||
|
Config.get([:instance, :quarantined_instances], [])
|
||||||
|
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||||
|
|
||||||
|
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@ -22,20 +23,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
@doc """
|
@doc """
|
||||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||||
"""
|
"""
|
||||||
def fix_object(object) do
|
def fix_object(object, options \\ []) do
|
||||||
object
|
object
|
||||||
|
|> strip_internal_fields
|
||||||
|> fix_actor
|
|> fix_actor
|
||||||
|> fix_url
|
|> fix_url
|
||||||
|> fix_attachments
|
|> fix_attachments
|
||||||
|> fix_context
|
|> fix_context
|
||||||
|> fix_in_reply_to
|
|> fix_in_reply_to(options)
|
||||||
|> fix_emoji
|
|> fix_emoji
|
||||||
|> fix_tag
|
|> fix_tag
|
||||||
|> fix_content_map
|
|> fix_content_map
|
||||||
|> fix_likes
|
|
||||||
|> fix_addressing
|
|> fix_addressing
|
||||||
|> fix_summary
|
|> fix_summary
|
||||||
|> fix_type
|
|> fix_type(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_summary(%{"summary" => nil} = object) do
|
def fix_summary(%{"summary" => nil} = object) do
|
||||||
|
@ -150,21 +151,9 @@ def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
|
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check for standardisation
|
def fix_in_reply_to(object, options \\ [])
|
||||||
# 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_likes(object) do
|
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
|
|
||||||
when not is_nil(in_reply_to) do
|
when not is_nil(in_reply_to) do
|
||||||
in_reply_to_id =
|
in_reply_to_id =
|
||||||
cond do
|
cond do
|
||||||
|
@ -182,7 +171,10 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
|
||||||
case get_obj_helper(in_reply_to_id) do
|
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
|
||||||
|
|
||||||
|
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
|
||||||
|
case get_obj_helper(in_reply_to_id, options) do
|
||||||
{:ok, replied_object} ->
|
{:ok, replied_object} ->
|
||||||
with %Activity{} = _activity <-
|
with %Activity{} = _activity <-
|
||||||
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
||||||
|
@ -201,9 +193,12 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
|
||||||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_in_reply_to(object), do: object
|
def fix_in_reply_to(object, _options), do: object
|
||||||
|
|
||||||
def fix_context(object) do
|
def fix_context(object) do
|
||||||
context = object["context"] || object["conversation"] || Utils.generate_context_id()
|
context = object["context"] || object["conversation"] || Utils.generate_context_id()
|
||||||
|
@ -336,17 +331,24 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
|
||||||
|
|
||||||
def fix_content_map(object), do: object
|
def fix_content_map(object), do: object
|
||||||
|
|
||||||
def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
|
def fix_type(object, options \\ [])
|
||||||
reply = Object.normalize(reply_id)
|
|
||||||
|
|
||||||
if reply && (reply.data["type"] == "Question" and object["name"]) do
|
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
||||||
|
when is_binary(reply_id) do
|
||||||
|
reply =
|
||||||
|
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
|
||||||
|
{:ok, object} <- get_obj_helper(reply_id, options) do
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
if reply && reply.data["type"] == "Question" do
|
||||||
Map.put(object, "type", "Answer")
|
Map.put(object, "type", "Answer")
|
||||||
else
|
else
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
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
|
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
|
||||||
with true <- id =~ "follows",
|
with true <- id =~ "follows",
|
||||||
|
@ -374,9 +376,11 @@ defp get_follow_activity(follow_object, followed) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_incoming(data, options \\ [])
|
||||||
|
|
||||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||||
# with nil ID.
|
# 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(),
|
with context <- data["context"] || Utils.generate_context_id(),
|
||||||
content <- data["content"] || "",
|
content <- data["content"] || "",
|
||||||
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
||||||
|
@ -409,15 +413,19 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
|
||||||
end
|
end
|
||||||
|
|
||||||
# disallow objects with bogus IDs
|
# disallow objects with bogus IDs
|
||||||
def handle_incoming(%{"id" => nil}), do: :error
|
def handle_incoming(%{"id" => nil}, _options), do: :error
|
||||||
def handle_incoming(%{"id" => ""}), do: :error
|
def handle_incoming(%{"id" => ""}, _options), do: :error
|
||||||
# length of https:// = 8, should validate better, but good enough for now.
|
# 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
|
# TODO: validate those with a Ecto scheme
|
||||||
# - tags
|
# - tags
|
||||||
# - emoji
|
# - 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
|
when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
|
||||||
actor = Containment.get_actor(data)
|
actor = Containment.get_actor(data)
|
||||||
|
|
||||||
|
@ -427,7 +435,8 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = obj
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
{: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 = %{
|
params = %{
|
||||||
to: data["to"],
|
to: data["to"],
|
||||||
|
@ -452,7 +461,8 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = obj
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
|
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||||
|
@ -503,7 +513,8 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
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
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -524,7 +535,8 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
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
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -548,7 +560,8 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
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
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -561,7 +574,8 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
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
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -576,7 +590,8 @@ def handle_incoming(
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
|
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
|
||||||
data
|
data,
|
||||||
|
_options
|
||||||
)
|
)
|
||||||
when object_type in ["Person", "Application", "Service", "Organization"] do
|
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
|
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||||
|
@ -614,7 +629,8 @@ def handle_incoming(
|
||||||
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
||||||
# place.
|
# place.
|
||||||
def handle_incoming(
|
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
|
) do
|
||||||
object_id = Utils.get_ap_id(object_id)
|
object_id = Utils.get_ap_id(object_id)
|
||||||
|
|
||||||
|
@ -625,7 +641,17 @@ def handle_incoming(
|
||||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -635,7 +661,8 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Announce", "object" => object_id},
|
"object" => %{"type" => "Announce", "object" => object_id},
|
||||||
"actor" => _actor,
|
"actor" => _actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data
|
} = data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -653,7 +680,8 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Follow", "object" => followed},
|
"object" => %{"type" => "Follow", "object" => followed},
|
||||||
"actor" => follower,
|
"actor" => follower,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = _data
|
} = _data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||||
|
@ -671,10 +699,10 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Block", "object" => blocked},
|
"object" => %{"type" => "Block", "object" => blocked},
|
||||||
"actor" => blocker,
|
"actor" => blocker,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = _data
|
} = _data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
||||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||||
User.unblock(blocker, blocked)
|
User.unblock(blocker, blocked)
|
||||||
|
@ -685,10 +713,10 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data
|
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
||||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||||
User.unfollow(blocker, blocked)
|
User.unfollow(blocker, blocked)
|
||||||
|
@ -705,7 +733,8 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Like", "object" => object_id},
|
"object" => %{"type" => "Like", "object" => object_id},
|
||||||
"actor" => _actor,
|
"actor" => _actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data
|
} = data,
|
||||||
|
_options
|
||||||
) do
|
) do
|
||||||
with actor <- Containment.get_actor(data),
|
with actor <- Containment.get_actor(data),
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
@ -717,10 +746,10 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(_), do: :error
|
def handle_incoming(_, _), do: :error
|
||||||
|
|
||||||
def get_obj_helper(id) do
|
def get_obj_helper(id, options \\ []) do
|
||||||
if object = Object.normalize(id), do: {:ok, object}, else: nil
|
if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
|
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
|
||||||
|
@ -742,7 +771,6 @@ def prepare_object(object) do
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
|> add_emoji_tags
|
|> add_emoji_tags
|
||||||
|> add_attributed_to
|
|> add_attributed_to
|
||||||
|> add_likes
|
|
||||||
|> prepare_attachments
|
|> prepare_attachments
|
||||||
|> set_conversation
|
|> set_conversation
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|
@ -926,22 +954,6 @@ def add_attributed_to(object) do
|
||||||
|> Map.put("attributedTo", attributed_to)
|
|> Map.put("attributedTo", attributed_to)
|
||||||
end
|
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
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
(object["attachment"] || [])
|
(object["attachment"] || [])
|
||||||
|
@ -957,6 +969,7 @@ def prepare_attachments(object) do
|
||||||
defp strip_internal_fields(object) do
|
defp strip_internal_fields(object) do
|
||||||
object
|
object
|
||||||
|> Map.drop([
|
|> Map.drop([
|
||||||
|
"likes",
|
||||||
"like_count",
|
"like_count",
|
||||||
"announcements",
|
"announcements",
|
||||||
"announcement_count",
|
"announcement_count",
|
||||||
|
|
|
@ -251,20 +251,6 @@ def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
|
||||||
|
|
||||||
def insert_full_object(map), do: {:ok, map, nil}
|
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
|
#### Like-related helpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -347,8 +333,7 @@ def update_element_in_object(property, element, object) do
|
||||||
|> Map.put("#{property}_count", length(element))
|
|> Map.put("#{property}_count", length(element))
|
||||||
|> Map.put("#{property}s", element),
|
|> Map.put("#{property}s", element),
|
||||||
changeset <- Changeset.change(object, data: new_data),
|
changeset <- Changeset.change(object, data: new_data),
|
||||||
{:ok, object} <- Object.update_and_set_cache(changeset),
|
{:ok, object} <- Object.update_and_set_cache(changeset) do
|
||||||
_ <- update_object_in_activities(object) do
|
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
alias Pleroma.Keys
|
alias Pleroma.Keys
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
|
@ -173,25 +172,22 @@ def render("followers.json", %{user: user}) do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("outbox.json", %{user: user, max_id: max_qid}) do
|
def render("activity_collection.json", %{iri: iri}) do
|
||||||
params = %{
|
%{
|
||||||
"limit" => "10"
|
"id" => iri,
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"first" => "#{iri}?page=true"
|
||||||
}
|
}
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
params =
|
|
||||||
if max_qid != nil do
|
|
||||||
Map.put(params, "max_id", max_qid)
|
|
||||||
else
|
|
||||||
params
|
|
||||||
end
|
end
|
||||||
|
|
||||||
activities = ActivityPub.fetch_user_activities(user, nil, params)
|
def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
|
||||||
|
# this is sorted chronologically, so first activity is the newest (max)
|
||||||
{max_id, min_id, collection} =
|
{max_id, min_id, collection} =
|
||||||
if length(activities) > 0 do
|
if length(activities) > 0 do
|
||||||
{
|
{
|
||||||
Enum.at(Enum.reverse(activities), 0).id,
|
|
||||||
Enum.at(activities, 0).id,
|
Enum.at(activities, 0).id,
|
||||||
|
Enum.at(Enum.reverse(activities), 0).id,
|
||||||
Enum.map(activities, fn act ->
|
Enum.map(activities, fn act ->
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||||
data
|
data
|
||||||
|
@ -205,71 +201,14 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
iri = "#{user.ap_id}/outbox"
|
%{
|
||||||
|
"id" => "#{iri}?max_id=#{max_id}&page=true",
|
||||||
page = %{
|
|
||||||
"id" => "#{iri}?max_id=#{max_id}",
|
|
||||||
"type" => "OrderedCollectionPage",
|
"type" => "OrderedCollectionPage",
|
||||||
"partOf" => iri,
|
"partOf" => iri,
|
||||||
"orderedItems" => collection,
|
"orderedItems" => collection,
|
||||||
"next" => "#{iri}?max_id=#{min_id}"
|
"next" => "#{iri}?max_id=#{min_id}&page=true"
|
||||||
}
|
|
||||||
|
|
||||||
if max_qid == nil do
|
|
||||||
%{
|
|
||||||
"id" => iri,
|
|
||||||
"type" => "OrderedCollection",
|
|
||||||
"first" => page
|
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
else
|
|
||||||
page |> Map.merge(Utils.make_json_ld_header())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("inbox.json", %{user: user, max_id: max_qid}) do
|
|
||||||
params = %{
|
|
||||||
"limit" => "10"
|
|
||||||
}
|
|
||||||
|
|
||||||
params =
|
|
||||||
if max_qid != nil do
|
|
||||||
Map.put(params, "max_id", max_qid)
|
|
||||||
else
|
|
||||||
params
|
|
||||||
end
|
|
||||||
|
|
||||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
|
||||||
|
|
||||||
min_id = Enum.at(Enum.reverse(activities), 0).id
|
|
||||||
max_id = Enum.at(activities, 0).id
|
|
||||||
|
|
||||||
collection =
|
|
||||||
Enum.map(activities, fn act ->
|
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
|
||||||
data
|
|
||||||
end)
|
|
||||||
|
|
||||||
iri = "#{user.ap_id}/inbox"
|
|
||||||
|
|
||||||
page = %{
|
|
||||||
"id" => "#{iri}?max_id=#{max_id}",
|
|
||||||
"type" => "OrderedCollectionPage",
|
|
||||||
"partOf" => iri,
|
|
||||||
"orderedItems" => collection,
|
|
||||||
"next" => "#{iri}?max_id=#{min_id}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if max_qid == nil do
|
|
||||||
%{
|
|
||||||
"id" => iri,
|
|
||||||
"type" => "OrderedCollection",
|
|
||||||
"first" => page
|
|
||||||
}
|
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|
||||||
else
|
|
||||||
page |> Map.merge(Utils.make_json_ld_header())
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
|
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
import Pleroma.Web.CommonAPI.Utils
|
import Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
@ -284,12 +285,11 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
},
|
},
|
||||||
object: %Object{
|
object: %Object{
|
||||||
data: %{
|
data: %{
|
||||||
"to" => object_to,
|
|
||||||
"type" => "Note"
|
"type" => "Note"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
|
true <- Visibility.is_public?(activity),
|
||||||
%{valid?: true} = info_changeset <-
|
%{valid?: true} = info_changeset <-
|
||||||
User.Info.add_pinnned_activity(user.info, activity),
|
User.Info.add_pinnned_activity(user.info, activity),
|
||||||
changeset <-
|
changeset <-
|
||||||
|
|
|
@ -22,6 +22,18 @@ def init do
|
||||||
refresh_subscriptions()
|
refresh_subscriptions()
|
||||||
end
|
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
|
# Client API
|
||||||
|
|
||||||
def incoming_doc(doc) do
|
def incoming_doc(doc) do
|
||||||
|
|
|
@ -28,7 +28,7 @@ def render("mention.json", %{user: user}) do
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
acct: user.nickname,
|
acct: user.nickname,
|
||||||
username: username_from_nickname(user.nickname),
|
username: username_from_nickname(user.nickname),
|
||||||
url: user.ap_id
|
url: User.profile_url(user)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -71,6 +71,13 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
image = User.avatar_url(user) |> MediaProxy.url()
|
image = User.avatar_url(user) |> MediaProxy.url()
|
||||||
header = User.banner_url(user) |> MediaProxy.url()
|
header = User.banner_url(user) |> MediaProxy.url()
|
||||||
user_info = User.get_cached_user_info(user)
|
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"]
|
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
|
@ -101,11 +108,11 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
locked: user_info.locked,
|
locked: user_info.locked,
|
||||||
created_at: Utils.to_masto_date(user.inserted_at),
|
created_at: Utils.to_masto_date(user.inserted_at),
|
||||||
followers_count: user_info.follower_count,
|
followers_count: followers_count,
|
||||||
following_count: user_info.following_count,
|
following_count: following_count,
|
||||||
statuses_count: user_info.note_count,
|
statuses_count: user_info.note_count,
|
||||||
note: bio || "",
|
note: bio || "",
|
||||||
url: user.ap_id,
|
url: User.profile_url(user),
|
||||||
avatar: image,
|
avatar: image,
|
||||||
avatar_static: image,
|
avatar_static: image,
|
||||||
header: header,
|
header: header,
|
||||||
|
|
|
@ -160,7 +160,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
thread_muted? =
|
thread_muted? =
|
||||||
case activity.thread_muted? do
|
case activity.thread_muted? do
|
||||||
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
|
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
|
end
|
||||||
|
|
||||||
attachment_data = object.data["attachment"] || []
|
attachment_data = object.data["attachment"] || []
|
||||||
|
@ -374,7 +374,7 @@ def render("poll.json", %{object: object} = opts) do
|
||||||
%{
|
%{
|
||||||
# Mastodon uses separate ids for polls, but an object can't have
|
# Mastodon uses separate ids for polls, but an object can't have
|
||||||
# more than one poll embedded so object id is fine
|
# 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),
|
expires_at: Utils.to_masto_date(end_time),
|
||||||
expired: expired,
|
expired: expired,
|
||||||
multiple: multiple,
|
multiple: multiple,
|
||||||
|
|
|
@ -28,17 +28,17 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filename_matches(has_filename, path, url) do
|
def filename_matches(has_filename, path, url) do
|
||||||
filename =
|
filename = url |> MediaProxy.filename()
|
||||||
url
|
|
||||||
|> MediaProxy.filename()
|
|
||||||
|> URI.decode()
|
|
||||||
|
|
||||||
path = URI.decode(path)
|
if has_filename && filename && does_not_match(path, filename) do
|
||||||
|
|
||||||
if has_filename && filename && Path.basename(path) != filename do
|
|
||||||
{:wrong_filename, filename}
|
{:wrong_filename, filename}
|
||||||
else
|
else
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -121,4 +121,6 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
|
||||||
acc ++ rendered_tags
|
acc ++ rendered_tags
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp build_attachments(_), do: []
|
||||||
end
|
end
|
||||||
|
|
|
@ -117,6 +117,8 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp build_attachments(_id, _object), do: []
|
||||||
|
|
||||||
defp player_url(id) do
|
defp player_url(id) do
|
||||||
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
|
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,60 +34,18 @@ def schemas(conn, _params) do
|
||||||
def raw_nodeinfo do
|
def raw_nodeinfo do
|
||||||
stats = Stats.get_stats()
|
stats = Stats.get_stats()
|
||||||
|
|
||||||
mrf_simple =
|
|
||||||
Config.get(:mrf_simple)
|
|
||||||
|> Enum.into(%{})
|
|
||||||
|
|
||||||
# This horror is needed to convert regex sigils to strings
|
|
||||||
mrf_keyword =
|
|
||||||
Config.get(:mrf_keyword, [])
|
|
||||||
|> Enum.map(fn {key, value} ->
|
|
||||||
{key,
|
|
||||||
Enum.map(value, fn
|
|
||||||
{pattern, replacement} ->
|
|
||||||
%{
|
|
||||||
"pattern" =>
|
|
||||||
if not is_binary(pattern) do
|
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end,
|
|
||||||
"replacement" => replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern ->
|
|
||||||
if not is_binary(pattern) do
|
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end
|
|
||||||
end)}
|
|
||||||
end)
|
|
||||||
|> Enum.into(%{})
|
|
||||||
|
|
||||||
mrf_policies =
|
|
||||||
MRF.get_policies()
|
|
||||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
|
||||||
|
|
||||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||||
|
|
||||||
staff_accounts =
|
staff_accounts =
|
||||||
User.all_superusers()
|
User.all_superusers()
|
||||||
|> Enum.map(fn u -> u.ap_id end)
|
|> Enum.map(fn u -> u.ap_id end)
|
||||||
|
|
||||||
mrf_user_allowlist =
|
|
||||||
Config.get([:mrf_user_allowlist], [])
|
|
||||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
|
||||||
|
|
||||||
federation_response =
|
federation_response =
|
||||||
if Config.get([:instance, :mrf_transparency]) do
|
if Config.get([:instance, :mrf_transparency]) do
|
||||||
%{
|
{:ok, data} = MRF.describe()
|
||||||
mrf_policies: mrf_policies,
|
|
||||||
mrf_simple: mrf_simple,
|
data
|
||||||
mrf_keyword: mrf_keyword,
|
|> Map.merge(%{quarantined_instances: quarantined})
|
||||||
mrf_user_allowlist: mrf_user_allowlist,
|
|
||||||
quarantined_instances: quarantined
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
|
|
|
@ -182,6 +182,7 @@ def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_autho
|
||||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
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_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_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"])
|
||||||
|
|
||||||
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
|
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
|
||||||
|
@ -196,7 +197,7 @@ def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_autho
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
|
||||||
{:id, h.(activity.data["id"])},
|
{:id, h.(activity.data["id"])},
|
||||||
{:title, ['#{user.nickname} repeated a notice']},
|
{: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)},
|
{:published, h.(inserted_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:"ostatus:conversation", [ref: h.(activity.data["context"])],
|
{:"ostatus:conversation", [ref: h.(activity.data["context"])],
|
||||||
|
|
|
@ -9,14 +9,18 @@ defmodule Pleroma.Web.OStatus.FollowHandler do
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
|
|
||||||
def handle(entry, doc) do
|
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),
|
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
|
||||||
followed_uri when not is_nil(followed_uri) <-
|
followed_uri when not is_nil(followed_uri) <-
|
||||||
XML.string_from_xpath("/entry/activity:object/id", entry),
|
XML.string_from_xpath("/entry/activity:object/id", entry),
|
||||||
{:ok, followed} <- OStatus.find_or_make_user(followed_uri),
|
{:ok, followed} <- OStatus.find_or_make_user(followed_uri),
|
||||||
|
{:locked, false} <- {:locked, followed.info.locked},
|
||||||
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
|
{:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
|
||||||
User.follow(actor, followed)
|
User.follow(actor, followed)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:locked, true} ->
|
||||||
|
{:error, "It's not possible to follow locked accounts over OStatus"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.Federator
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
|
|
||||||
|
@ -88,14 +89,15 @@ def add_external_url(note, entry) do
|
||||||
Map.put(note, "external_url", url)
|
Map.put(note, "external_url", url)
|
||||||
end
|
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
|
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
|
||||||
activity
|
activity
|
||||||
else
|
else
|
||||||
_e ->
|
_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),
|
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
|
activity
|
||||||
else
|
else
|
||||||
_e -> nil
|
_e -> nil
|
||||||
|
@ -104,15 +106,16 @@ def fetch_replied_to_activity(entry, in_reply_to) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Clean this up a bit.
|
# 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),
|
with id <- XML.string_from_xpath("//id", entry),
|
||||||
activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
|
activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
|
||||||
[author] <- :xmerl_xpath.string('//author[1]', doc),
|
[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),
|
content_html <- OStatus.get_content(entry),
|
||||||
cw <- OStatus.get_cw(entry),
|
cw <- OStatus.get_cw(entry),
|
||||||
in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", 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_object <-
|
||||||
(in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
|
(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,
|
in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.OStatus.UnfollowHandler do
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
|
|
||||||
def handle(entry, doc) do
|
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),
|
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
|
||||||
followed_uri when not is_nil(followed_uri) <-
|
followed_uri when not is_nil(followed_uri) <-
|
||||||
XML.string_from_xpath("/entry/activity:object/id", entry),
|
XML.string_from_xpath("/entry/activity:object/id", entry),
|
||||||
|
|
|
@ -54,9 +54,9 @@ def remote_follow_path do
|
||||||
"#{Web.base_url()}/ostatus_subscribe?acct={uri}"
|
"#{Web.base_url()}/ostatus_subscribe?acct={uri}"
|
||||||
end
|
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 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)
|
do: Pleroma.Instances.set_reachable(actor_user.ap_id)
|
||||||
|
|
||||||
entries = :xmerl_xpath.string('//entry', doc)
|
entries = :xmerl_xpath.string('//entry', doc)
|
||||||
|
@ -91,10 +91,12 @@ def handle_incoming(xml_string) do
|
||||||
_ ->
|
_ ->
|
||||||
case object_type do
|
case object_type do
|
||||||
'http://activitystrea.ms/schema/1.0/note' ->
|
'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' ->
|
'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")
|
Logger.error("Couldn't parse incoming document")
|
||||||
|
@ -118,7 +120,7 @@ def handle_incoming(xml_string) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_share(entry, doc, retweeted_activity) do
|
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),
|
%Object{} = object <- Object.normalize(retweeted_activity),
|
||||||
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
||||||
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
|
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
|
||||||
|
@ -136,7 +138,7 @@ def handle_share(entry, doc) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_favorite(entry, doc, favorited_activity) do
|
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),
|
%Object{} = object <- Object.normalize(favorited_activity),
|
||||||
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
|
||||||
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
|
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
|
||||||
|
@ -262,11 +264,18 @@ def maybe_update_ostatus(doc, user) do
|
||||||
end
|
end
|
||||||
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)
|
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)
|
maybe_update(doc, user)
|
||||||
|
else
|
||||||
|
{:ap_enabled, true} ->
|
||||||
|
{:error, :invalid_protocol}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :unknown_user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -359,7 +368,7 @@ def get_atom_url(body) do
|
||||||
end
|
end
|
||||||
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"),
|
with true <- String.starts_with?(url, "http"),
|
||||||
{:ok, %{body: body, status: code}} when code in 200..299 <-
|
{:ok, %{body: body, status: code}} when code in 200..299 <-
|
||||||
HTTP.get(
|
HTTP.get(
|
||||||
|
@ -367,7 +376,7 @@ def fetch_activity_from_atom_url(url) do
|
||||||
[{:Accept, "application/atom+xml"}]
|
[{:Accept, "application/atom+xml"}]
|
||||||
) do
|
) do
|
||||||
Logger.debug("Got document from #{url}, handling...")
|
Logger.debug("Got document from #{url}, handling...")
|
||||||
handle_incoming(body)
|
handle_incoming(body, options)
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||||
|
@ -375,13 +384,13 @@ def fetch_activity_from_atom_url(url) do
|
||||||
end
|
end
|
||||||
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}")
|
Logger.debug("Trying to fetch #{url}")
|
||||||
|
|
||||||
with true <- String.starts_with?(url, "http"),
|
with true <- String.starts_with?(url, "http"),
|
||||||
{:ok, %{body: body}} <- HTTP.get(url, []),
|
{:ok, %{body: body}} <- HTTP.get(url, []),
|
||||||
{:ok, atom_url} <- get_atom_url(body) do
|
{:ok, atom_url} <- get_atom_url(body) do
|
||||||
fetch_activity_from_atom_url(atom_url)
|
fetch_activity_from_atom_url(atom_url, options)
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
Logger.debug("Couldn't get #{url}: #{inspect(e)}")
|
||||||
|
@ -389,11 +398,11 @@ def fetch_activity_from_html_url(url) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activity_from_url(url) do
|
def fetch_activity_from_url(url, options \\ []) do
|
||||||
with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url) do
|
with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do
|
||||||
{:ok, activities}
|
{:ok, activities}
|
||||||
else
|
else
|
||||||
_e -> fetch_activity_from_html_url(url)
|
_e -> fetch_activity_from_html_url(url, options)
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
|
|
|
@ -684,7 +684,7 @@ defmodule Pleroma.Web.Router do
|
||||||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:oauth_read_or_public)
|
pipe_through(:oauth_read)
|
||||||
get("/web/*path", MastodonAPIController, :index)
|
get("/web/*path", MastodonAPIController, :index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -724,6 +724,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
defmodule Fallback.RedirectController do
|
defmodule Fallback.RedirectController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
require Logger
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Metadata
|
alias Pleroma.Web.Metadata
|
||||||
|
|
||||||
|
@ -750,7 +751,20 @@ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id}
|
||||||
|
|
||||||
def redirector_with_meta(conn, params) do
|
def redirector_with_meta(conn, params) do
|
||||||
{:ok, index_content} = File.read(index_file_path())
|
{:ok, index_content} = File.read(index_file_path())
|
||||||
tags = Metadata.build_tags(params)
|
|
||||||
|
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, "<!--server-generated-meta-->", tags)
|
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -36,6 +36,11 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: color: #d8a070;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Config
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
|
alias Pleroma.Healthcheck
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
|
@ -23,7 +24,8 @@ def help_test(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
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
|
conn
|
||||||
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
||||||
else
|
else
|
||||||
|
@ -98,8 +100,7 @@ def do_remote_follow(conn, %{
|
||||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
%User{} = _followed <- User.get_cached_by_id(id),
|
%User{} = _followed <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
|
||||||
conn
|
conn
|
||||||
|> render("followed.html", %{error: false})
|
|> render("followed.html", %{error: false})
|
||||||
else
|
else
|
||||||
|
@ -120,8 +121,7 @@ def do_remote_follow(conn, %{
|
||||||
|
|
||||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||||
with %User{} = followee <- User.get_cached_by_id(id),
|
with %User{} = followee <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
|
||||||
conn
|
conn
|
||||||
|> render("followed.html", %{error: false})
|
|> render("followed.html", %{error: false})
|
||||||
else
|
else
|
||||||
|
@ -338,20 +338,21 @@ def captcha(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def healthcheck(conn, _params) do
|
def healthcheck(conn, _params) do
|
||||||
info =
|
with true <- Config.get([:instance, :healthcheck]),
|
||||||
if Pleroma.Config.get([:instance, :healthcheck]) do
|
%{healthy: true} = info <- Healthcheck.system_info() do
|
||||||
Pleroma.Healthcheck.system_info()
|
|
||||||
else
|
|
||||||
%{}
|
|
||||||
end
|
|
||||||
|
|
||||||
conn =
|
|
||||||
if info[:healthy] do
|
|
||||||
conn
|
|
||||||
else
|
|
||||||
Plug.Conn.put_status(conn, :service_unavailable)
|
|
||||||
end
|
|
||||||
|
|
||||||
json(conn, info)
|
json(conn, info)
|
||||||
|
else
|
||||||
|
%{healthy: false} = info ->
|
||||||
|
service_unavailable(conn, info)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
service_unavailable(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp service_unavailable(conn, info) do
|
||||||
|
conn
|
||||||
|
|> put_status(:service_unavailable)
|
||||||
|
|> json(info)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
78
mix.exs
78
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("1.0.0"),
|
version: version("1.0.7"),
|
||||||
elixir: "~> 1.7",
|
elixir: "~> 1.7",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
|
@ -95,6 +95,7 @@ defp oauth_deps do
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:phoenix, "~> 1.4.8"},
|
{:phoenix, "~> 1.4.8"},
|
||||||
|
{:tzdata, "~> 0.5.21"},
|
||||||
{:plug_cowboy, "~> 2.0"},
|
{:plug_cowboy, "~> 2.0"},
|
||||||
{:phoenix_pubsub, "~> 1.1"},
|
{:phoenix_pubsub, "~> 1.1"},
|
||||||
{:phoenix_ecto, "~> 4.0"},
|
{:phoenix_ecto, "~> 4.0"},
|
||||||
|
@ -174,22 +175,27 @@ defp aliases do
|
||||||
# Builds a version string made of:
|
# Builds a version string made of:
|
||||||
# * the application version
|
# * the application version
|
||||||
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
|
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
|
||||||
# * build info:
|
# * branch name
|
||||||
|
# * build metadata:
|
||||||
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
|
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
|
||||||
# * the mix environment if different than prod
|
# * the mix environment if different than prod
|
||||||
defp version(version) do
|
defp version(version) do
|
||||||
|
identifier_filter = ~r/[^0-9a-z\-]+/i
|
||||||
|
|
||||||
|
# Pre-release version, denoted from patch version with a hyphen
|
||||||
{git_tag, git_pre_release} =
|
{git_tag, git_pre_release} =
|
||||||
with {tag, 0} <-
|
with {tag, 0} <-
|
||||||
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
|
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
|
||||||
tag = String.trim(tag),
|
tag = String.trim(tag),
|
||||||
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
|
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
|
||||||
describe = String.trim(describe),
|
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))}
|
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
|
{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
|
end
|
||||||
|
|
||||||
if git_tag && version != git_tag do
|
if git_tag && version != git_tag do
|
||||||
|
@ -198,6 +204,23 @@ defp version(version) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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 <-
|
||||||
|
!Enum.all?(["master", "HEAD", "release/", "stable"], fn name ->
|
||||||
|
String.starts_with?(name, branch_name)
|
||||||
|
end) do
|
||||||
|
branch_name =
|
||||||
|
branch_name
|
||||||
|
|> String.trim()
|
||||||
|
|> String.replace(identifier_filter, "-")
|
||||||
|
|
||||||
|
branch_name
|
||||||
|
end
|
||||||
|
|
||||||
build_name =
|
build_name =
|
||||||
cond do
|
cond do
|
||||||
name = Application.get_env(:pleroma, :build_name) -> name
|
name = Application.get_env(:pleroma, :build_name) -> name
|
||||||
|
@ -206,28 +229,37 @@ defp version(version) do
|
||||||
end
|
end
|
||||||
|
|
||||||
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
|
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
|
||||||
|
env_override = System.get_env("PLEROMA_BUILD_ENV")
|
||||||
|
|
||||||
build =
|
env_name =
|
||||||
[build_name, env_name]
|
case env_override do
|
||||||
|> Enum.filter(fn string -> string && string != "" end)
|
nil -> env_name
|
||||||
|> Enum.join("-")
|
env_override when env_override in ["", "prod"] -> nil
|
||||||
|> (fn
|
env_override -> env_override
|
||||||
"" -> nil
|
|
||||||
string -> "+" <> string
|
|
||||||
end).()
|
|
||||||
|
|
||||||
branch_name =
|
|
||||||
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
||||||
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
|
|
||||||
true <- branch_name != "master" do
|
|
||||||
branch_name =
|
|
||||||
String.trim(branch_name)
|
|
||||||
|> String.replace(~r/[^0-9a-z\-\.]+/i, "-")
|
|
||||||
|
|
||||||
"-" <> branch_name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
[version, git_pre_release, branch_name, build]
|
# 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]
|
||||||
|
|> Enum.filter(fn string -> string && string != "" end)
|
||||||
|
|> Enum.join(".")
|
||||||
|
|> (fn
|
||||||
|
"" -> nil
|
||||||
|
string -> "+" <> String.replace(string, identifier_filter, "-")
|
||||||
|
end).()
|
||||||
|
|
||||||
|
[version, pre_release, build_metadata]
|
||||||
|> Enum.filter(fn string -> string && string != "" end)
|
|> Enum.filter(fn string -> string && string != "" end)
|
||||||
|> Enum.join()
|
|> Enum.join()
|
||||||
end
|
end
|
||||||
|
|
14
mix.lock
14
mix.lock
|
@ -6,7 +6,7 @@
|
||||||
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, 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"},
|
"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"},
|
"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"},
|
"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"},
|
"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.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"},
|
||||||
|
@ -33,9 +33,9 @@
|
||||||
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
|
"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"},
|
"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"},
|
"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"},
|
"gen_smtp": {:hex, :gen_smtp, "0.14.0", "39846a03522456077c6429b4badfd1d55e5e7d0fdfb65e935b7c5e38549d9202", [:rebar3], [], "hexpm"},
|
||||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "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"},
|
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [: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.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "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"},
|
"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", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]},
|
||||||
|
@ -76,14 +76,14 @@
|
||||||
"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"},
|
"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"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
"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"},
|
"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"},
|
||||||
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
||||||
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
|
"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"},
|
"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, "0.5.21", "8cbf3607fcce69636c672d5be2bbb08687fe26639a62bdcc283d267277db7cf0", [: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"},
|
"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"},
|
"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.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
defmodule Pleroma.Repo.Migrations.CaseInsensivtivity do
|
defmodule Pleroma.Repo.Migrations.CaseInsensivtivity do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# Two-steps alters are intentional.
|
||||||
|
# When alter of 2 columns is done in a single operation,
|
||||||
|
# inconsistent failures happen because of index on `email` column.
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
execute ("create extension if not exists citext")
|
execute("create extension if not exists citext")
|
||||||
|
|
||||||
alter table(:users) do
|
alter table(:users) do
|
||||||
modify :email, :citext
|
modify(:email, :citext)
|
||||||
modify :nickname, :citext
|
end
|
||||||
|
|
||||||
|
alter table(:users) do
|
||||||
|
modify(:nickname, :citext)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def down do
|
def down do
|
||||||
alter table(:users) do
|
alter table(:users) do
|
||||||
modify :email, :string
|
modify(:email, :string)
|
||||||
modify :nickname, :string
|
|
||||||
end
|
end
|
||||||
execute ("drop extension if exists citext")
|
|
||||||
|
alter table(:users) do
|
||||||
|
modify(:nickname, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute("drop extension if exists citext")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddLikesIndexToObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists index(:objects, ["(data->'likes')"], using: :gin, name: :objects_likes)
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,6 +11,7 @@ end %>
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
|
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
|
||||||
|
http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>],
|
||||||
secret_key_base: "<%= secret %>",
|
secret_key_base: "<%= secret %>",
|
||||||
signing_salt: "<%= signing_salt %>"
|
signing_salt: "<%= signing_salt %>"
|
||||||
|
|
||||||
|
|
|
@ -30,33 +30,67 @@ detect_flavour() {
|
||||||
|
|
||||||
detect_branch() {
|
detect_branch() {
|
||||||
version="$(cut -d' ' -f2 <"$RELEASE_ROOT"/releases/start_erl.data)"
|
version="$(cut -d' ' -f2 <"$RELEASE_ROOT"/releases/start_erl.data)"
|
||||||
branch="$(echo "$version" | cut -d'-' -f 4)"
|
# Expected format: major.minor.patch_version(-number_of_commits_ahead_of_tag-gcommit_hash).branch
|
||||||
|
branch="$(echo "$version" | cut -d'.' -f 4)"
|
||||||
if [ "$branch" = "develop" ]; then
|
if [ "$branch" = "develop" ]; then
|
||||||
echo "develop"
|
echo "develop"
|
||||||
elif [ "$branch" = "" ]; then
|
elif [ "$branch" = "" ]; then
|
||||||
echo "master"
|
echo "stable"
|
||||||
else
|
else
|
||||||
echo "Releases are built only for master and develop branches" >&2
|
# Note: branch name in version is of SemVer format and may only contain [0-9a-zA-Z-] symbols —
|
||||||
|
# if supporting releases for more branches, need to ensure they contain only these symbols.
|
||||||
|
echo "Can't detect the branch automatically, please specify it by using the --branch option." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
update() {
|
update() {
|
||||||
set -e
|
set -e
|
||||||
|
NO_RM=false
|
||||||
|
|
||||||
|
while echo "$1" | grep "^-" >/dev/null; do
|
||||||
|
case "$1" in
|
||||||
|
--zip-url)
|
||||||
|
FULL_URI="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-rm)
|
||||||
|
NO_RM=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--flavour)
|
||||||
|
FLAVOUR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--branch)
|
||||||
|
BRANCH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tmp-dir)
|
||||||
|
TMP_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "invalid option: $1" 1>&2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
RELEASE_ROOT=$(dirname "$SCRIPTPATH")
|
RELEASE_ROOT=$(dirname "$SCRIPTPATH")
|
||||||
uri="${PLEROMA_CTL_URI:-https://git.pleroma.social}"
|
uri="https://git.pleroma.social"
|
||||||
project_id="${PLEROMA_CTL_PROJECT_ID:-2}"
|
project_id="2"
|
||||||
project_branch="$(detect_branch)"
|
project_branch="${BRANCH:-$(detect_branch)}"
|
||||||
flavour="${PLEROMA_CTL_FLAVOUR:-$(detect_flavour)}"
|
flavour="${FLAVOUR:-$(detect_flavour)}"
|
||||||
echo "Detected flavour: $flavour"
|
tmp="${TMP_DIR:-/tmp}"
|
||||||
tmp="${PLEROMA_CTL_TMP_DIR:-/tmp}"
|
|
||||||
artifact="$tmp/pleroma.zip"
|
artifact="$tmp/pleroma.zip"
|
||||||
full_uri="${uri}/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=${flavour}"
|
full_uri="${FULL_URI:-${uri}/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=${flavour}}"
|
||||||
echo "Downloading the artifact from ${full_uri} to ${artifact}"
|
echo "Downloading the artifact from ${full_uri} to ${artifact}"
|
||||||
curl "$full_uri" -o "${artifact}"
|
curl "$full_uri" -o "${artifact}"
|
||||||
echo "Unpacking ${artifact} to ${tmp}"
|
echo "Unpacking ${artifact} to ${tmp}"
|
||||||
unzip -q "$artifact" -d "$tmp"
|
unzip -q "$artifact" -d "$tmp"
|
||||||
echo "Copying files over to $RELEASE_ROOT"
|
echo "Copying files over to $RELEASE_ROOT"
|
||||||
if [ "$1" != "--no-rm" ]; then
|
if [ "$NO_RM" = false ]; then
|
||||||
|
echo "Removing files from the previous release"
|
||||||
rm -r "${RELEASE_ROOT:-?}"/*
|
rm -r "${RELEASE_ROOT:-?}"/*
|
||||||
fi
|
fi
|
||||||
cp -rf "$tmp/release"/* "$RELEASE_ROOT"
|
cp -rf "$tmp/release"/* "$RELEASE_ROOT"
|
||||||
|
@ -83,36 +117,41 @@ if [ -z "$1" ] || [ "$1" = "help" ]; then
|
||||||
Rollback database migrations (needs to be done before downgrading)
|
Rollback database migrations (needs to be done before downgrading)
|
||||||
|
|
||||||
update [OPTIONS]
|
update [OPTIONS]
|
||||||
Update the instance using the latest CI artifact for the current branch.
|
Update the instance.
|
||||||
|
|
||||||
The only supported option is --no-rm, when set the script won't delete the whole directory, but
|
|
||||||
just force copy over files from the new release. This wastes more space, but may be useful if
|
|
||||||
some files are stored inside of the release directories (although you really shouldn't store them
|
|
||||||
there), or if you want to be able to quickly revert a broken update.
|
|
||||||
|
|
||||||
The script will try to detect your architecture and ABI and set a flavour automatically,
|
|
||||||
but if it is wrong, you can overwrite it by setting PLEROMA_CTL_FLAVOUR to the desired flavour.
|
|
||||||
|
|
||||||
By default the artifact will be downloaded from https://git.pleroma.social for pleroma/pleroma (project id: 2)
|
|
||||||
to /tmp/, you can overwrite these settings by setting PLEROMA_CTL_URI, PLEROMA_CTL_PROJECT_ID and PLEROMA_CTL_TMP_DIR
|
|
||||||
respectively.
|
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--branch Update to a specified branch, instead of the latest version of the current one.
|
||||||
|
--flavour Update to a specified flavour (CPU architecture+libc), instead of the current one.
|
||||||
|
--zip-url Get the release from a specified url. If set, renders the previous 2 options inactive.
|
||||||
|
--no-rm Do not erase previous release's files.
|
||||||
|
--tmp-dir Download the temporary files to a specified directory.
|
||||||
|
|
||||||
and any mix tasks under Pleroma namespace, for example \`mix pleroma.user COMMAND\` is
|
and any mix tasks under Pleroma namespace, for example \`mix pleroma.user COMMAND\` is
|
||||||
equivalent to \`$(basename "$0") user COMMAND\`
|
equivalent to \`$(basename "$0") user COMMAND\`
|
||||||
|
|
||||||
By default pleroma_ctl will try calling into a running instance to execute non migration-related commands,
|
By default pleroma_ctl will try calling into a running instance to execute non migration-related commands,
|
||||||
if for some reason this is undesired, set PLEROMA_CTL_RPC_DISABLED environment variable
|
if for some reason this is undesired, set PLEROMA_CTL_RPC_DISABLED environment variable.
|
||||||
|
|
||||||
"
|
"
|
||||||
else
|
else
|
||||||
SCRIPT=$(readlink -f "$0")
|
SCRIPT=$(readlink -f "$0")
|
||||||
SCRIPTPATH=$(dirname "$SCRIPT")
|
SCRIPTPATH=$(dirname "$SCRIPT")
|
||||||
|
|
||||||
if [ "$1" = "update" ]; then
|
FULL_ARGS="$*"
|
||||||
update "$2"
|
|
||||||
elif [ "$1" = "migrate" ] || [ "$1" = "rollback" ] || [ "$1" = "create" ] || [ "$1 $2" = "instance gen" ] || [ -n "$PLEROMA_CTL_RPC_DISABLED" ]; then
|
ACTION="$1"
|
||||||
"$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run("'"$*"'")'
|
shift
|
||||||
|
|
||||||
|
if [ "$(echo \"$1\" | grep \"^-\" >/dev/null)" = false ]; then
|
||||||
|
SUBACTION="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ACTION" = "update" ]; then
|
||||||
|
update "$@"
|
||||||
|
elif [ "$ACTION" = "migrate" ] || [ "$ACTION" = "rollback" ] || [ "$ACTION" = "create" ] || [ "$ACTION $SUBACTION" = "instance gen" ] || [ "$PLEROMA_CTL_RPC_DISABLED" = true ]; then
|
||||||
|
"$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run("'"$FULL_ARGS"'")'
|
||||||
else
|
else
|
||||||
"$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$*"'")'
|
"$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$FULL_ARGS"'")'
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -96,10 +96,10 @@ test "validates signature" do
|
||||||
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "filename_matches matches url encoded paths" do
|
test "filename_matches preserves the encoded or decoded path" do
|
||||||
assert MediaProxyController.filename_matches(
|
assert MediaProxyController.filename_matches(
|
||||||
true,
|
true,
|
||||||
"/Hello%20world.jpg",
|
"/Hello world.jpg",
|
||||||
"http://pleroma.social/Hello world.jpg"
|
"http://pleroma.social/Hello world.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
|
|
||||||
|
@ -108,19 +108,22 @@ test "filename_matches matches url encoded paths" do
|
||||||
"/Hello%20world.jpg",
|
"/Hello%20world.jpg",
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
"http://pleroma.social/Hello%20world.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
|
|
||||||
|
assert MediaProxyController.filename_matches(
|
||||||
|
true,
|
||||||
|
"/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
|
||||||
|
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
|
||||||
|
) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
test "filename_matches matches non-url encoded paths" do
|
test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
|
||||||
assert MediaProxyController.filename_matches(
|
# conn.request_path will return encoded url
|
||||||
true,
|
request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
|
||||||
"/Hello world.jpg",
|
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
|
||||||
) == :ok
|
|
||||||
|
|
||||||
assert MediaProxyController.filename_matches(
|
assert MediaProxyController.filename_matches(
|
||||||
true,
|
true,
|
||||||
"/Hello world.jpg",
|
request_path,
|
||||||
"http://pleroma.social/Hello world.jpg"
|
"https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -531,5 +531,63 @@ test "replying to a deleted post without tagging does not generate a notificatio
|
||||||
|
|
||||||
assert Enum.empty?(Notification.for_user(user))
|
assert Enum.empty?(Notification.for_user(user))
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,4 +64,34 @@ test "users cannot be collided through fake direction spoofing attempts" do
|
||||||
"[error] Could not decode user at fetch https://n1u.moe/users/rye, {:error, :error}"
|
"[error] Could not decode user at fetch https://n1u.moe/users/rye, {:error, :error}"
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Object.FetcherTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
|
import Mock
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
mock(fn
|
mock(fn
|
||||||
|
@ -22,16 +23,31 @@ defmodule Pleroma.Object.FetcherTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "actor origin containment" do
|
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")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
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")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
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")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -798,6 +798,81 @@ def get("http://404.site" <> _, _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
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/httpoison_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/httpoison_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/httpoison_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(url, query, body, headers) do
|
def get(url, query, body, headers) do
|
||||||
{:error,
|
{:error,
|
||||||
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule MRFModuleMock do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{mrf_module_mock: "some config data"}}
|
||||||
|
end
|
|
@ -3,8 +3,11 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
@ -46,4 +49,37 @@ test "following and followers count are updated" do
|
||||||
assert user.info.follower_count == 0
|
assert user.info.follower_count == 0
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -38,7 +38,17 @@ test "running gen" do
|
||||||
"--indexable",
|
"--indexable",
|
||||||
"y",
|
"y",
|
||||||
"--db-configurable",
|
"--db-configurable",
|
||||||
"y"
|
"y",
|
||||||
|
"--rum",
|
||||||
|
"y",
|
||||||
|
"--listen-port",
|
||||||
|
"4000",
|
||||||
|
"--listen-ip",
|
||||||
|
"127.0.0.1",
|
||||||
|
"--uploads-dir",
|
||||||
|
"test/uploads",
|
||||||
|
"--static-dir",
|
||||||
|
"instance/static/"
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -56,10 +66,11 @@ test "running gen" do
|
||||||
assert generated_config =~ "username: \"dbuser\""
|
assert generated_config =~ "username: \"dbuser\""
|
||||||
assert generated_config =~ "password: \"dbpass\""
|
assert generated_config =~ "password: \"dbpass\""
|
||||||
assert generated_config =~ "dynamic_configuration: true"
|
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()
|
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generated_setup_psql do
|
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
|
||||||
end
|
end
|
|
@ -799,6 +799,48 @@ test "blocks domains" do
|
||||||
assert User.blocks?(user, collateral_user)
|
assert User.blocks?(user, collateral_user)
|
||||||
end
|
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
|
test "unblocks domains" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
|
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
|
||||||
|
|
|
@ -308,7 +308,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|> get("/users/#{user.nickname}/inbox")
|
|> get("/users/#{user.nickname}/inbox?page=true")
|
||||||
|
|
||||||
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
||||||
end
|
end
|
||||||
|
@ -393,7 +393,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|> get("/users/#{user.nickname}/outbox")
|
|> get("/users/#{user.nickname}/outbox?page=true")
|
||||||
|
|
||||||
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
assert response(conn, 200) =~ note_activity.data["object"]["content"]
|
||||||
end
|
end
|
||||||
|
@ -405,7 +405,7 @@ test "it returns an announce activity in a collection", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|> get("/users/#{user.nickname}/outbox")
|
|> get("/users/#{user.nickname}/outbox?page=true")
|
||||||
|
|
||||||
assert response(conn, 200) =~ announce_activity.data["object"]
|
assert response(conn, 200) =~ announce_activity.data["object"]
|
||||||
end
|
end
|
||||||
|
|
|
@ -679,9 +679,6 @@ test "adds a like activity to the db" do
|
||||||
assert like_activity == same_like_activity
|
assert like_activity == same_like_activity
|
||||||
assert object.data["likes"] == [user.ap_id]
|
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
|
|
||||||
|
|
||||||
{:ok, _like_activity, object} = ActivityPub.like(user_two, object)
|
{:ok, _like_activity, object} = ActivityPub.like(user_two, object)
|
||||||
assert object.data["like_count"] == 2
|
assert object.data["like_count"] == 2
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
describe "describe/0" do
|
||||||
|
test "it works as expected with noop policy" do
|
||||||
|
expected = %{
|
||||||
|
mrf_policies: ["NoOpPolicy"],
|
||||||
|
exclusions: false
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^expected} = MRF.describe()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works as expected with mock policy" do
|
||||||
|
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock])
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
mrf_policies: ["MRFModuleMock"],
|
||||||
|
mrf_module_mock: "some config data",
|
||||||
|
exclusions: false
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^expected} = MRF.describe()
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -49,6 +49,19 @@ test "has a matching host" do
|
||||||
|
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "when :media_nsfw" do
|
describe "when :media_nsfw" do
|
||||||
|
@ -74,6 +87,20 @@ test "has a matching host" do
|
||||||
|
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
defp build_media_message do
|
defp build_media_message do
|
||||||
|
@ -106,6 +133,15 @@ test "has a matching host" do
|
||||||
assert SimplePolicy.filter(report_message) == {:reject, nil}
|
assert SimplePolicy.filter(report_message) == {:reject, nil}
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
defp build_report_message do
|
defp build_report_message do
|
||||||
|
@ -146,6 +182,27 @@ test "has a matching host" do
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
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
|
test "has a matching host but only as:Public in to" do
|
||||||
{_actor, ftl_message} = build_ftl_actor_and_message()
|
{_actor, ftl_message} = build_ftl_actor_and_message()
|
||||||
|
|
||||||
|
@ -192,6 +249,14 @@ test "has a matching host" do
|
||||||
|
|
||||||
assert SimplePolicy.filter(remote_message) == {:reject, nil}
|
assert SimplePolicy.filter(remote_message) == {:reject, nil}
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "when :accept" do
|
describe "when :accept" do
|
||||||
|
@ -224,6 +289,16 @@ test "has a matching host" do
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
|
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "when :avatar_removal" do
|
describe "when :avatar_removal" do
|
||||||
|
@ -251,6 +326,15 @@ test "has a matching host" do
|
||||||
|
|
||||||
refute filtered["icon"]
|
refute filtered["icon"]
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "when :banner_removal" do
|
describe "when :banner_removal" do
|
||||||
|
@ -278,6 +362,15 @@ test "has a matching host" do
|
||||||
|
|
||||||
refute filtered["image"]
|
refute filtered["image"]
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
defp build_local_message do
|
defp build_local_message do
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy
|
||||||
|
|
||||||
|
describe "accept" do
|
||||||
|
test "it accepts based on parent activity type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Like",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts based on child object type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not accept disallowed child objects" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Article",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not accept disallowed parent types" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "reject" do
|
||||||
|
test "it rejects based on parent activity type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Like",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects based on child object type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it passes through objects that aren't disallowed" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Announce",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,12 +11,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||||
|
|
||||||
|
import Mock
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -46,12 +47,10 @@ test "it fetches replied-to activities if we don't have them" do
|
||||||
data["object"]
|
data["object"]
|
||||||
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
data =
|
data = Map.put(data, "object", object)
|
||||||
data
|
|
||||||
|> Map.put("object", object)
|
|
||||||
|
|
||||||
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
|
||||||
returned_object = Object.normalize(returned_activity.data["object"])
|
|
||||||
|
returned_object = Object.normalize(returned_activity.data["object"], false)
|
||||||
|
|
||||||
assert activity =
|
assert activity =
|
||||||
Activity.get_create_by_object_ap_id(
|
Activity.get_create_by_object_ap_id(
|
||||||
|
@ -61,6 +60,32 @@ test "it fetches replied-to activities if we don't have them" do
|
||||||
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
|
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
|
||||||
end
|
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.data["object"], 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
|
test "it does not crash if the object in inReplyTo can't be fetched" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
@ -390,6 +415,7 @@ test "it ensures that as:Public activities make it to their followers collection
|
||||||
|> Map.put("attributedTo", user.ap_id)
|
|> Map.put("attributedTo", user.ap_id)
|
||||||
|> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
|
|> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
|
||||||
|> Map.put("cc", [])
|
|> Map.put("cc", [])
|
||||||
|
|> Map.put("id", user.ap_id <> "/activities/12345678")
|
||||||
|
|
||||||
data = Map.put(data, "object", object)
|
data = Map.put(data, "object", object)
|
||||||
|
|
||||||
|
@ -413,6 +439,7 @@ test "it ensures that address fields become lists" do
|
||||||
|> Map.put("attributedTo", user.ap_id)
|
|> Map.put("attributedTo", user.ap_id)
|
||||||
|> Map.put("to", nil)
|
|> Map.put("to", nil)
|
||||||
|> Map.put("cc", nil)
|
|> Map.put("cc", nil)
|
||||||
|
|> Map.put("id", user.ap_id <> "/activities/12345678")
|
||||||
|
|
||||||
data = Map.put(data, "object", object)
|
data = Map.put(data, "object", object)
|
||||||
|
|
||||||
|
@ -422,6 +449,27 @@ test "it ensures that address fields become lists" do
|
||||||
assert !is_nil(data["cc"])
|
assert !is_nil(data["cc"])
|
||||||
end
|
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
|
test "it works for incoming update activities" do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
@ -1008,14 +1056,7 @@ test "it strips internal fields of article" do
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["context_id"])
|
assert is_nil(modified["object"]["context_id"])
|
||||||
end
|
assert is_nil(modified["object"]["likes"])
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "the directMessage flag is present" do
|
test "the directMessage flag is present" do
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
test "Renders a user, including the public key" do
|
test "Renders a user, including the public key" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -78,4 +79,35 @@ test "instance users do not expose oAuth endpoints" do
|
||||||
refute result["endpoints"]["oauthTokenEndpoint"]
|
refute result["endpoints"]["oauthTokenEndpoint"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "activity collection page aginates correctly" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
posts =
|
||||||
|
for i <- 0..25 do
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"})
|
||||||
|
activity
|
||||||
|
end
|
||||||
|
|
||||||
|
# outbox sorts chronologically, newest first, with ten per page
|
||||||
|
posts = Enum.reverse(posts)
|
||||||
|
|
||||||
|
%{"next" => next_url} =
|
||||||
|
UserView.render("activity_collection_page.json", %{
|
||||||
|
iri: "#{user.ap_id}/outbox",
|
||||||
|
activities: Enum.take(posts, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
next_id = Enum.at(posts, 9).id
|
||||||
|
assert next_url =~ next_id
|
||||||
|
|
||||||
|
%{"next" => next_url} =
|
||||||
|
UserView.render("activity_collection_page.json", %{
|
||||||
|
iri: "#{user.ap_id}/outbox",
|
||||||
|
activities: Enum.take(Enum.drop(posts, 10), 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
next_id = Enum.at(posts, 19).id
|
||||||
|
assert next_url =~ next_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -188,6 +188,11 @@ test "pin status", %{user: user, activity: activity} do
|
||||||
assert %User{info: %{pinned_activities: [^id]}} = user
|
assert %User{info: %{pinned_activities: [^id]}} = user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "unlisted statuses can be pinned", %{user: user} do
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
|
||||||
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
||||||
|
end
|
||||||
|
|
||||||
test "only self-authored can be pinned", %{activity: activity} do
|
test "only self-authored can be pinned", %{activity: activity} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -213,5 +213,21 @@ test "rejects incoming AP docs with incorrect origin" do
|
||||||
|
|
||||||
:error = Federator.incoming_ap_doc(params)
|
:error = Federator.incoming_ap_doc(params)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
|
||||||
test "Represent a user account" do
|
test "Represent a user account" do
|
||||||
|
@ -275,4 +276,31 @@ test "sanitizes display names" do
|
||||||
result = AccountView.render("account.json", %{user: user})
|
result = AccountView.render("account.json", %{user: user})
|
||||||
refute result.display_name == "<marquee> username </marquee>"
|
refute result.display_name == "<marquee> username </marquee>"
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -2684,8 +2684,10 @@ test "bookmarks" do
|
||||||
|
|
||||||
describe "conversation muting" do
|
describe "conversation muting" do
|
||||||
setup do
|
setup do
|
||||||
|
post_user = insert(:user)
|
||||||
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]
|
[user: user, activity: activity]
|
||||||
end
|
end
|
||||||
|
@ -2877,6 +2879,21 @@ test "redirects not logged-in users to the login page", %{conn: conn, path: path
|
||||||
assert redirected_to(conn) == "/web/login"
|
assert redirected_to(conn) == "/web/login"
|
||||||
end
|
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
|
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
|
||||||
token = insert(:oauth_token)
|
token = insert(:oauth_token)
|
||||||
|
|
||||||
|
@ -3300,7 +3317,7 @@ test "returns poll entity for object id", %{conn: conn} do
|
||||||
|> get("/api/v1/polls/#{object.id}")
|
|> get("/api/v1/polls/#{object.id}")
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
response = json_response(conn, 200)
|
||||||
id = object.id
|
id = to_string(object.id)
|
||||||
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
|
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -361,7 +361,7 @@ test "renders a poll" do
|
||||||
expected = %{
|
expected = %{
|
||||||
emojis: [],
|
emojis: [],
|
||||||
expired: false,
|
expired: false,
|
||||||
id: object.id,
|
id: to_string(object.id),
|
||||||
multiple: false,
|
multiple: false,
|
||||||
options: [
|
options: [
|
||||||
%{title: "absolutely!", votes_count: 0},
|
%{title: "absolutely!", votes_count: 0},
|
||||||
|
|
|
@ -83,4 +83,55 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
|
||||||
|
|
||||||
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
|
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it shows MRF transparency data if enabled", %{conn: conn} do
|
||||||
|
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||||
|
|
||||||
|
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, :rewrite_policy], 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
|
||||||
|
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||||
|
|
||||||
|
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, :rewrite_policy], config)
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency], option)
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
|
||||||
|
Pleroma.Config.put(:mrf_simple, %{})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,8 +11,10 @@ defmodule Pleroma.Web.OStatusTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
import Pleroma.Factory
|
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
|
import Mock
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -196,7 +198,7 @@ test "handle incoming retweets - GS, subscription - local message" do
|
||||||
assert retweeted_activity.data["type"] == "Create"
|
assert retweeted_activity.data["type"] == "Create"
|
||||||
assert retweeted_activity.data["actor"] == user.ap_id
|
assert retweeted_activity.data["actor"] == user.ap_id
|
||||||
assert retweeted_activity.local
|
assert retweeted_activity.local
|
||||||
assert retweeted_activity.data["object"]["announcement_count"] == 1
|
assert Object.normalize(retweeted_activity).data["announcement_count"] == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle incoming retweets - Mastodon, salmon" do
|
test "handle incoming retweets - Mastodon, salmon" do
|
||||||
|
@ -266,10 +268,13 @@ test "handle incoming favorites with locally available object - GS, websub" do
|
||||||
assert favorited_activity.local
|
assert favorited_activity.local
|
||||||
end
|
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")
|
incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity.data["object"], false)
|
||||||
|
|
||||||
assert activity.data["type"] == "Create"
|
assert activity.data["type"] == "Create"
|
||||||
assert object.data["type"] == "Note"
|
assert object.data["type"] == "Note"
|
||||||
|
@ -282,6 +287,23 @@ test "handle incoming replies" do
|
||||||
assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
|
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 "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.data["object"], false)
|
||||||
|
|
||||||
|
refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle incoming follows" do
|
test "handle incoming follows" do
|
||||||
|
@ -302,6 +324,14 @@ test "handle incoming follows" do
|
||||||
assert User.following?(follower, followed)
|
assert User.following?(follower, followed)
|
||||||
end
|
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
|
test "handle incoming unfollows with existing follow" do
|
||||||
incoming_follow = File.read!("test/fixtures/follow.xml")
|
incoming_follow = File.read!("test/fixtures/follow.xml")
|
||||||
{:ok, [_activity]} = OStatus.handle_incoming(incoming_follow)
|
{:ok, [_activity]} = OStatus.handle_incoming(incoming_follow)
|
||||||
|
@ -401,7 +431,7 @@ test "find_or_make_user sets all the nessary input fields" do
|
||||||
}
|
}
|
||||||
end
|
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"
|
uri = "https://social.heldscal.la/user/23211"
|
||||||
|
|
||||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||||
|
@ -414,14 +444,56 @@ test "find_make_or_update_user takes an author element and returns an updated us
|
||||||
|
|
||||||
doc = XML.parse_document(File.read!("test/fixtures/23211.atom"))
|
doc = XML.parse_document(File.read!("test/fixtures/23211.atom"))
|
||||||
[author] = :xmerl_xpath.string('//author[1]', doc)
|
[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.avatar["type"] == "Image"
|
||||||
assert user.name == old_name
|
assert user.name == old_name
|
||||||
assert user.bio == old_bio
|
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
|
assert user_again == user
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "gathering user info from a user id" do
|
describe "gathering user info from a user id" do
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
import Mock
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -227,10 +228,67 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /api/pleroma/healthcheck", %{conn: conn} do
|
describe "GET /api/pleroma/healthcheck" do
|
||||||
conn = get(conn, "/api/pleroma/healthcheck")
|
setup do
|
||||||
|
config_healthcheck = Pleroma.Config.get([:instance, :healthcheck])
|
||||||
|
|
||||||
assert conn.status in [200, 503]
|
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
|
end
|
||||||
|
|
||||||
describe "POST /api/pleroma/disable_account" do
|
describe "POST /api/pleroma/disable_account" do
|
||||||
|
|
Loading…
Reference in New Issue