Browse Source

Merge remote-tracking branch 'upstream/develop' into feature/openldap-support

tags/v1.1.4
link0ff 5 years ago
parent
commit
19e2b85247
60 changed files with 1605 additions and 887 deletions
  1. +15
    -4
      config/config.exs
  2. +162
    -63
      docs/Admin-API.md
  3. +4
    -0
      docs/Differences-in-MastodonAPI-Responses.md
  4. +27
    -1
      docs/config.md
  5. +18
    -0
      installation/pleroma-apache.conf
  6. +5
    -2
      installation/pleroma.nginx
  7. +59
    -139
      lib/pleroma/formatter.ex
  8. +2
    -1
      lib/pleroma/gopher/server.ex
  9. +130
    -80
      lib/pleroma/user.ex
  10. +0
    -1
      lib/pleroma/user/info.ex
  11. +42
    -62
      lib/pleroma/web/activity_pub/activity_pub.ex
  12. +5
    -4
      lib/pleroma/web/activity_pub/activity_pub_controller.ex
  13. +2
    -1
      lib/pleroma/web/activity_pub/transmogrifier.ex
  14. +56
    -0
      lib/pleroma/web/activity_pub/visibility.ex
  15. +70
    -0
      lib/pleroma/web/admin_api/admin_api_controller.ex
  16. +25
    -0
      lib/pleroma/web/auth/authenticator.ex
  17. +28
    -0
      lib/pleroma/web/auth/pleroma_authenticator.ex
  18. +4
    -24
      lib/pleroma/web/common_api/common_api.ex
  19. +45
    -41
      lib/pleroma/web/common_api/utils.ex
  20. +2
    -1
      lib/pleroma/web/federator/federator.ex
  21. +10
    -9
      lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
  22. +9
    -2
      lib/pleroma/web/mastodon_api/views/account_view.ex
  23. +25
    -0
      lib/pleroma/web/mastodon_api/views/admin/account_view.ex
  24. +1
    -1
      lib/pleroma/web/mastodon_api/views/status_view.ex
  25. +19
    -19
      lib/pleroma/web/mastodon_api/websocket_handler.ex
  26. +1
    -3
      lib/pleroma/web/metadata/twitter_card.ex
  27. +8
    -9
      lib/pleroma/web/oauth/oauth_controller.ex
  28. +5
    -4
      lib/pleroma/web/ostatus/ostatus_controller.ex
  29. +51
    -0
      lib/pleroma/web/rel_me.ex
  30. +3
    -0
      lib/pleroma/web/router.ex
  31. +3
    -3
      lib/pleroma/web/streamer.ex
  32. +5
    -242
      lib/pleroma/web/twitter_api/representers/activity_representer.ex
  33. +1
    -9
      lib/pleroma/web/twitter_api/twitter_api.ex
  34. +4
    -2
      lib/pleroma/web/twitter_api/twitter_api_controller.ex
  35. +3
    -1
      lib/pleroma/web/twitter_api/views/activity_view.ex
  36. +4
    -6
      lib/pleroma/web/twitter_api/views/user_view.ex
  37. +6
    -0
      lib/pleroma/web/web.ex
  38. +6
    -4
      mix.exs
  39. +7
    -6
      mix.lock
  40. +11
    -0
      priv/repo/migrations/20190222104808_data_migration_normalize_scopes.exs
  41. +9
    -0
      priv/repo/migrations/20190301101154_add_default_tags_to_user.exs
  42. +41
    -0
      priv/repo/migrations/20190303120636_update_user_note_counters.exs
  43. +14
    -0
      test/fixtures/rel_me_anchor.html
  44. +14
    -0
      test/fixtures/rel_me_link.html
  45. +13
    -0
      test/fixtures/rel_me_null.html
  46. +40
    -74
      test/formatter_test.exs
  47. +47
    -2
      test/user_test.exs
  48. +58
    -0
      test/web/activity_pub/activity_pub_test.exs
  49. +98
    -0
      test/web/activity_pub/visibilty_test.exs
  50. +150
    -0
      test/web/admin_api/admin_api_controller_test.exs
  51. +37
    -7
      test/web/common_api/common_api_utils_test.exs
  52. +64
    -2
      test/web/mastodon_api/account_view_test.exs
  53. +12
    -4
      test/web/mastodon_api/mastodon_api_controller_test.exs
  54. +16
    -0
      test/web/mastodon_api/status_view_test.exs
  55. +7
    -5
      test/web/oauth/oauth_controller_test.exs
  56. +55
    -0
      test/web/rel_me_test.exs
  57. +1
    -42
      test/web/twitter_api/representers/activity_representer_test.exs
  58. +21
    -6
      test/web/twitter_api/twitter_api_controller_test.exs
  59. +18
    -1
      test/web/twitter_api/views/activity_view_test.exs
  60. +7
    -0
      test/web/twitter_api/views/user_view_test.exs

+ 15
- 4
config/config.exs View File

@@ -93,10 +93,11 @@ config :pleroma, Pleroma.Web.Endpoint,
dispatch: [ dispatch: [
{:_, {:_,
[ [
{"/api/v1/streaming", Elixir.Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
{nil, {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Plug.Adapters.Cowboy.Handler, {Pleroma.Web.Endpoint, []}}
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]} ]}
] ]
], ],
@@ -344,6 +345,16 @@ config :pleroma, Pleroma.Jobs,
federator_outgoing: [max_jobs: 50], federator_outgoing: [max_jobs: 50],
mailer: [max_jobs: 10] mailer: [max_jobs: 10]


config :auto_linker,
opts: [
scheme: true,
extra: true,
class: false,
strip_prefix: false,
new_window: false,
rel: false
]

config :pleroma, :ldap, config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true", enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost", host: System.get_env("LDAP_HOST") || "localhost",


+ 162
- 63
docs/Admin-API.md View File

@@ -1,108 +1,207 @@
# Admin API # Admin API

Authentication is required and the user must be an admin. Authentication is required and the user must be an admin.


## `/api/pleroma/admin/users`

### List users

- Method `GET`
- Params:
- `page`: **integer** page number
- `page_size`: **integer** number of users per page (default is `50`)
- Response:

```JSON
{
"page_size": integer,
"count": integer,
"users": [
{
"deactivated": bool,
"id": integer,
"nickname": string
},
...
]
}
```

## `/api/pleroma/admin/users/search?query={query}&local={local}&page={page}&page_size={page_size}`

### Search users by name or nickname

- Method `GET`
- Params:
- `query`: **string** search term
- `local`: **bool** whether to return only local users
- `page`: **integer** page number
- `page_size`: **integer** number of users per page (default is `50`)
- Response:

```JSON
{
"page_size": integer,
"count": integer,
"users": [
{
"deactivated": bool,
"id": integer,
"nickname": string
},
...
]
}
```

## `/api/pleroma/admin/user` ## `/api/pleroma/admin/user`

### Remove a user ### Remove a user
* Method `DELETE`
* Params:
* `nickname`
* Response: User’s nickname

- Method `DELETE`
- Params:
- `nickname`
- Response: User’s nickname

### Create a user ### Create a user
* Method: `POST`
* Params:
* `nickname`
* `email`
* `password`
* Response: User’s nickname

- Method: `POST`
- Params:
- `nickname`
- `email`
- `password`
- Response: User’s nickname

## `/api/pleroma/admin/users/:nickname/toggle_activation`

### Toggle user activation

- Method: `PATCH`
- Params:
- `nickname`
- Response: User’s object

```JSON
{
"deactivated": bool,
"id": integer,
"nickname": string
}
```


## `/api/pleroma/admin/users/tag` ## `/api/pleroma/admin/users/tag`

### Tag a list of users ### Tag a list of users
* Method: `PUT`
* Params:
* `nickname`
* `tags`

- Method: `PUT`
- Params:
- `nickname`
- `tags`

### Untag a list of users ### Untag a list of users
* Method: `DELETE`
* Params:
* `nickname`
* `tags`

- Method: `DELETE`
- Params:
- `nickname`
- `tags`


## `/api/pleroma/admin/permission_group/:nickname` ## `/api/pleroma/admin/permission_group/:nickname`

### Get user user permission groups membership ### Get user user permission groups membership
* Method: `GET`
* Params: none
* Response:

- Method: `GET`
- Params: none
- Response:

```JSON ```JSON
{ {
"is_moderator": bool,
"is_admin": bool
"is_moderator": bool,
"is_admin": bool
} }
``` ```


## `/api/pleroma/admin/permission_group/:nickname/:permission_group` ## `/api/pleroma/admin/permission_group/:nickname/:permission_group`

Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist. Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.


### Get user user permission groups membership ### Get user user permission groups membership
* Method: `GET`
* Params: none
* Response:

- Method: `GET`
- Params: none
- Response:

```JSON ```JSON
{ {
"is_moderator": bool,
"is_admin": bool
"is_moderator": bool,
"is_admin": bool
} }
``` ```

### Add user in permission group ### Add user in permission group
* Method: `POST`
* Params: none
* Response:
* On failure: ``{"error": "…"}``
* On success: JSON of the ``user.info``

- Method: `POST`
- Params: none
- Response:
- On failure: `{"error": "…"}`
- On success: JSON of the `user.info`

### Remove user from permission group ### Remove user from permission group
* Method: `DELETE`
* Params: none
* Response:
* On failure: ``{"error": "…"}``
* On success: JSON of the ``user.info``
* Note: An admin cannot revoke their own admin status.

- Method: `DELETE`
- Params: none
- Response:
- On failure: `{"error": "…"}`
- On success: JSON of the `user.info`
- Note: An admin cannot revoke their own admin status.


## `/api/pleroma/admin/activation_status/:nickname` ## `/api/pleroma/admin/activation_status/:nickname`


### Active or deactivate a user ### Active or deactivate a user
* Method: `PUT`
* Params:
* `nickname`
* `status` BOOLEAN field, false value means deactivation.

- Method: `PUT`
- Params:
- `nickname`
- `status` BOOLEAN field, false value means deactivation.


## `/api/pleroma/admin/relay` ## `/api/pleroma/admin/relay`

### Follow a Relay ### Follow a Relay
* Methods: `POST`
* Params:
* `relay_url`
* Response:
* On success: URL of the followed relay

- Methods: `POST`
- Params:
- `relay_url`
- Response:
- On success: URL of the followed relay

### Unfollow a Relay ### Unfollow a Relay
* Methods: `DELETE`
* Params:
* `relay_url`
* Response:
* On success: URL of the unfollowed relay

- Methods: `DELETE`
- Params:
- `relay_url`
- Response:
- On success: URL of the unfollowed relay


## `/api/pleroma/admin/invite_token` ## `/api/pleroma/admin/invite_token`

### Get a account registeration invite token ### Get a account registeration invite token
* Methods: `GET`
* Params: none
* Response: invite token (base64 string)

- Methods: `GET`
- Params: none
- Response: invite token (base64 string)


## `/api/pleroma/admin/email_invite` ## `/api/pleroma/admin/email_invite`

### Sends registration invite via email ### Sends registration invite via email
* Methods: `POST`
* Params:
* `email`
* `name`, optionnal

- Methods: `POST`
- Params:
- `email`
- `name`, optionnal


## `/api/pleroma/admin/password_reset` ## `/api/pleroma/admin/password_reset`

### Get a password reset token for a given nickname ### Get a password reset token for a given nickname
* Methods: `GET`
* Params: none
* Response: password reset token (base64 string)

- Methods: `GET`
- Params: none
- Response: password reset token (base64 string)

+ 4
- 0
docs/Differences-in-MastodonAPI-Responses.md View File

@@ -9,3 +9,7 @@ Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mas
## Attachment cap ## Attachment cap


Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting. Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.

## Timelines

Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.

+ 27
- 1
docs/config.md View File

@@ -107,7 +107,7 @@ config :pleroma, Pleroma.Mailer,


An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed: An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
``` ```
config :logger,
config :logger,
backends: [{ExSyslogger, :ex_syslogger}] backends: [{ExSyslogger, :ex_syslogger}]


config :logger, :ex_syslogger, config :logger, :ex_syslogger,
@@ -301,6 +301,32 @@ For each pool, the options are:
* `max_connections` - how much connections a pool can hold * `max_connections` - how much connections a pool can hold
* `timeout` - retention duration for connections * `timeout` - retention duration for connections


## :auto_linker

Configuration for the `auto_linker` library:

* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear
* `rel: "noopener noreferrer"` - override the rel attribute. false to clear
* `new_window: true` - set to false to remove `target='_blank'` attribute
* `scheme: false` - Set to true to link urls with schema `http://google.com`
* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`
* `strip_prefix: true` - Strip the scheme prefix
* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.)

Example:

```exs
config :auto_linker,
opts: [
scheme: true,
extra: true,
class: false,
strip_prefix: false,
new_window: false,
rel: false
]
```

## :ldap ## :ldap


* `enabled`: enables LDAP authentication * `enabled`: enables LDAP authentication


+ 18
- 0
installation/pleroma-apache.conf View File

@@ -1,6 +1,7 @@
# default Apache site config for Pleroma # default Apache site config for Pleroma
# #
# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl # needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl
# optional modules: cache cache_disk
# #
# Simple installation instructions: # Simple installation instructions:
# 1. Install your TLS certificate, possibly using Let's Encrypt. # 1. Install your TLS certificate, possibly using Let's Encrypt.
@@ -8,6 +9,14 @@
# 3. This assumes a Debian style Apache config. Copy this file to # 3. This assumes a Debian style Apache config. Copy this file to
# /etc/apache2/sites-available/ and then add a symlink to it in # /etc/apache2/sites-available/ and then add a symlink to it in
# /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache. # /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache.
#
# Optional: enable disk-based caching for the media proxy
# For details, see https://git.pleroma.social/pleroma/pleroma/wikis/How%20to%20activate%20mediaproxy
#
# 1. Create the directory listed below as the CacheRoot, and make sure
# the Apache user can write to it.
# 2. Configure Apache's htcacheclean to clean the directory periodically.
# 3. Run 'a2enmod cache cache_disk' and restart Apache.


Define servername example.tld Define servername example.tld


@@ -34,6 +43,15 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCompression off SSLCompression off
SSLSessionTickets off SSLSessionTickets off


# uncomment the following to enable mediaproxy caching on disk
# <IfModule mod_cache_disk.c>
# CacheRoot /var/cache/apache2/mod_cache_disk
# CacheDirLevels 1
# CacheDirLength 2
# CacheEnable disk /proxy
# CacheLock on
# </IfModule>

RewriteEngine On RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC] RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Upgrade} websocket [NC]


+ 5
- 2
installation/pleroma.nginx View File

@@ -11,7 +11,9 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac


server { server {
server_name example.tld; server_name example.tld;

listen 80; listen 80;
listen [::]:80;
return 301 https://$server_name$request_uri; return 301 https://$server_name$request_uri;


# Uncomment this if you need to use the 'webroot' method with certbot. Make sure # Uncomment this if you need to use the 'webroot' method with certbot. Make sure
@@ -29,7 +31,10 @@ server {
ssl_session_cache shared:ssl_session_cache:10m; ssl_session_cache shared:ssl_session_cache:10m;


server { server {
server_name example.tld;

listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_session_timeout 5m; ssl_session_timeout 5m;


ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
@@ -48,8 +53,6 @@ server {
ssl_stapling on; ssl_stapling on;
ssl_stapling_verify on; ssl_stapling_verify on;


server_name example.tld;

gzip_vary on; gzip_vary on;
gzip_proxied any; gzip_proxied any;
gzip_comp_level 6; gzip_comp_level 6;


+ 59
- 139
lib/pleroma/formatter.ex View File

@@ -8,33 +8,51 @@ defmodule Pleroma.Formatter do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy


@tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
@link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui


# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
@mentions_regex ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u

def parse_tags(text, data \\ %{}) do
Regex.scan(@tag_regex, text)
|> Enum.map(fn ["#" <> tag = full_tag | _] -> {full_tag, String.downcase(tag)} end)
|> (fn map ->
if data["sensitive"] in [true, "True", "true", "1"],
do: [{"#nsfw", "nsfw"}] ++ map,
else: map
end).()
@auto_linker_config hashtag: true,
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
mention: true,
mention_handler: &Pleroma.Formatter.mention_handler/4

def mention_handler("@" <> nickname, buffer, opts, acc) do
case User.get_cached_by_nickname(nickname) do
%User{id: id} = user ->
ap_id = get_ap_id(user)
nickname_text = get_nickname_text(nickname, opts) |> maybe_escape(opts)

link =
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>@<span>#{
nickname_text
}</span></a></span>"

{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}

_ ->
{buffer, acc}
end
end end


@doc "Parses mentions text and returns list {nickname, user}."
@spec parse_mentions(binary()) :: list({binary(), User.t()})
def parse_mentions(text) do
Regex.scan(@mentions_regex, text)
|> List.flatten()
|> Enum.uniq()
|> Enum.map(fn nickname ->
with nickname <- String.trim_leading(nickname, "@"),
do: {"@" <> nickname, User.get_cached_by_nickname(nickname)}
end)
|> Enum.filter(fn {_match, user} -> user end)
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag)
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
link = "<a class='hashtag' data-tag='#{tag}' href='#{url}' rel='tag'>#{tag_text}</a>"

{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
end

@doc """
Parses a text and replace plain text links with HTML. Returns a tuple with a result text, mentions, and hashtags.
"""
@spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do
options = options ++ @auto_linker_config
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
{text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)

{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end end


def emojify(text) do def emojify(text) do
@@ -48,9 +66,7 @@ defmodule Pleroma.Formatter do
emoji = HTML.strip_tags(emoji) emoji = HTML.strip_tags(emoji)
file = HTML.strip_tags(file) file = HTML.strip_tags(file)


String.replace(
text,
":#{emoji}:",
html =
if not strip do if not strip do
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{ "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
MediaProxy.url(file) MediaProxy.url(file)
@@ -58,8 +74,8 @@ defmodule Pleroma.Formatter do
else else
"" ""
end end
)
|> HTML.filter_tags()
String.replace(text, ":#{emoji}:", html) |> HTML.filter_tags()
end) end)
end end


@@ -75,12 +91,10 @@ defmodule Pleroma.Formatter do


def get_emoji(_), do: [] def get_emoji(_), do: []


@link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui

@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags}
end


# TODO: make it use something other than @link_regex
def html_escape(text, "text/html") do def html_escape(text, "text/html") do
HTML.filter_tags(text) HTML.filter_tags(text)
end end
@@ -94,112 +108,6 @@ defmodule Pleroma.Formatter do
|> Enum.join("") |> Enum.join("")
end end


@doc """
Escapes a special characters in mention names.
"""
@spec mentions_escape(String.t(), list({String.t(), any()})) :: String.t()
def mentions_escape(text, mentions) do
mentions
|> Enum.reduce(text, fn {name, _}, acc ->
escape_name = String.replace(name, @markdown_characters_regex, "\\\\\\1")
String.replace(acc, name, escape_name)
end)
end

@doc "changes scheme:... urls to html links"
def add_links({subs, text}) do
links =
text
|> String.split([" ", "\t", "<br>"])
|> Enum.filter(fn word -> String.starts_with?(word, @valid_schemes) end)
|> Enum.filter(fn word -> Regex.match?(@link_regex, word) end)
|> Enum.map(fn url -> {Ecto.UUID.generate(), url} end)
|> Enum.sort_by(fn {_, url} -> -String.length(url) end)

uuid_text =
links
|> Enum.reduce(text, fn {uuid, url}, acc -> String.replace(acc, url, uuid) end)

subs =
subs ++
Enum.map(links, fn {uuid, url} ->
{uuid, "<a href=\"#{url}\">#{url}</a>"}
end)

{subs, uuid_text}
end

@doc "Adds the links to mentioned users"
def add_user_links({subs, text}, mentions, options \\ []) do
mentions =
mentions
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|> Enum.map(fn {name, user} -> {name, user, Ecto.UUID.generate()} end)

uuid_text =
mentions
|> Enum.reduce(text, fn {match, _user, uuid}, text ->
String.replace(text, match, uuid)
end)

subs =
subs ++
Enum.map(mentions, fn {match, %User{id: id, ap_id: ap_id, info: info}, uuid} ->
ap_id =
if is_binary(info.source_data["url"]) do
info.source_data["url"]
else
ap_id
end

nickname =
if options[:format] == :full do
User.full_nickname(match)
else
User.local_nickname(match)
end

{uuid,
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>" <>
"@<span>#{nickname}</span></a></span>"}
end)

{subs, uuid_text}
end

@doc "Adds the hashtag links"
def add_hashtag_links({subs, text}, tags) do
tags =
tags
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|> Enum.map(fn {name, short} -> {name, short, Ecto.UUID.generate()} end)

uuid_text =
tags
|> Enum.reduce(text, fn {match, _short, uuid}, text ->
String.replace(text, ~r/((?<=[^&])|(\A))#{match}/, uuid)
end)

subs =
subs ++
Enum.map(tags, fn {tag_text, tag, uuid} ->
url =
"<a class='hashtag' data-tag='#{tag}' href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{
tag_text
}</a>"

{uuid, url}
end)

{subs, uuid_text}
end

def finalize({subs, text}) do
Enum.reduce(subs, text, fn {uuid, replacement}, result_text ->
String.replace(result_text, uuid, replacement)
end)
end

def truncate(text, max_length \\ 200, omission \\ "...") do def truncate(text, max_length \\ 200, omission \\ "...") do
# Remove trailing whitespace # Remove trailing whitespace
text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}") text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}")
@@ -211,4 +119,16 @@ defmodule Pleroma.Formatter do
String.slice(text, 0, length_with_omission) <> omission String.slice(text, 0, length_with_omission) <> omission
end end
end end

defp get_ap_id(%User{info: %{source_data: %{"url" => url}}}) when is_binary(url), do: url
defp get_ap_id(%User{ap_id: ap_id}), do: ap_id

defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
defp get_nickname_text(nickname, _), do: User.local_nickname(nickname)

defp maybe_escape(str, %{mentions_escape: true}) do
String.replace(str, @markdown_characters_regex, "\\\\\\1")
end

defp maybe_escape(str, _), do: str
end end

+ 2
- 1
lib/pleroma/gopher/server.ex View File

@@ -37,6 +37,7 @@ end


defmodule Pleroma.Gopher.Server.ProtocolHandler do defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
@@ -110,7 +111,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do


def response("/notices/" <> id) do def response("/notices/" <> id) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.is_public?(activity) do
true <- Visibility.is_public?(activity) do
activities = activities =
ActivityPub.fetch_activities_for_context(activity.data["context"]) ActivityPub.fetch_activities_for_context(activity.data["context"])
|> render_activities |> render_activities


+ 130
- 80
lib/pleroma/user.ex View File

@@ -22,6 +22,7 @@ defmodule Pleroma.User do
alias Pleroma.Web.OAuth alias Pleroma.Web.OAuth
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.RelMe


require Logger require Logger


@@ -547,11 +548,8 @@ defmodule Pleroma.User do
end end


def get_followers_query(user, page) do def get_followers_query(user, page) do
from(
u in get_followers_query(user, nil),
limit: 20,
offset: ^((page - 1) * 20)
)
from(u in get_followers_query(user, nil))
|> paginate(page, 20)
end end


def get_followers_query(user), do: get_followers_query(user, nil) def get_followers_query(user), do: get_followers_query(user, nil)
@@ -577,11 +575,8 @@ defmodule Pleroma.User do
end end


def get_friends_query(user, page) do def get_friends_query(user, page) do
from(
u in get_friends_query(user, nil),
limit: 20,
offset: ^((page - 1) * 20)
)
from(u in get_friends_query(user, nil))
|> paginate(page, 20)
end end


def get_friends_query(user), do: get_friends_query(user, nil) def get_friends_query(user), do: get_friends_query(user, nil)
@@ -613,71 +608,65 @@ defmodule Pleroma.User do
), ),
where: where:
fragment( fragment(
"? @> ?",
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
a.data, a.data,
^%{"object" => user.ap_id}
a.data,
^user.ap_id
) )
) )
end end


def update_follow_request_count(%User{} = user) do
subquery =
def get_follow_requests(%User{} = user) do
users =
user user
|> User.get_follow_requests_query() |> User.get_follow_requests_query()
|> select([a], %{count: count(a.id)})
|> join(:inner, [a], u in User, a.actor == u.ap_id)
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|> group_by([a, u], u.id)
|> select([a, u], u)
|> Repo.all()

{:ok, users}
end


def increase_note_count(%User{} = user) do
User User
|> where(id: ^user.id) |> where(id: ^user.id)
|> join(:inner, [u], s in subquery(subquery))
|> update([u, s],
|> update([u],
set: [ set: [
info: info:
fragment( fragment(
"jsonb_set(?, '{follow_request_count}', ?::varchar::jsonb, true)",
"jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
u.info, u.info,
s.count
u.info
) )
] ]
) )
|> Repo.update_all([], returning: true) |> Repo.update_all([], returning: true)
|> case do |> case do
{1, [user]} -> {:ok, user}
{1, [user]} -> set_cache(user)
_ -> {:error, user} _ -> {:error, user}
end end
end end


def get_follow_requests(%User{} = user) do
q = get_follow_requests_query(user)
reqs = Repo.all(q)

users =
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
|> Enum.filter(fn u -> !is_nil(u) end)
|> Enum.filter(fn u -> !following?(u, user) end)

{:ok, users}
end

def increase_note_count(%User{} = user) do
info_cng = User.Info.add_to_note_count(user.info, 1)

cng =
change(user)
|> put_embed(:info, info_cng)

update_and_set_cache(cng)
end

def decrease_note_count(%User{} = user) do def decrease_note_count(%User{} = user) do
info_cng = User.Info.add_to_note_count(user.info, -1)

cng =
change(user)
|> put_embed(:info, info_cng)

update_and_set_cache(cng)
User
|> where(id: ^user.id)
|> update([u],
set: [
info:
fragment(
"jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
u.info,
u.info
)
]
)
|> Repo.update_all([], returning: true)
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end end


def update_note_count(%User{} = user) do def update_note_count(%User{} = user) do
@@ -701,24 +690,29 @@ defmodule Pleroma.User do


def update_follower_count(%User{} = user) do def update_follower_count(%User{} = user) do
follower_count_query = follower_count_query =
from(
u in User,
where: ^user.follower_address in u.following,
where: u.id != ^user.id,
select: count(u.id)
)

follower_count = Repo.one(follower_count_query)
User
|> where([u], ^user.follower_address in u.following)
|> where([u], u.id != ^user.id)
|> select([u], %{count: count(u.id)})


info_cng =
user.info
|> User.Info.set_follower_count(follower_count)

cng =
change(user)
|> put_embed(:info, info_cng)

update_and_set_cache(cng)
User
|> where(id: ^user.id)
|> join(:inner, [u], s in subquery(follower_count_query))
|> update([u, s],
set: [
info:
fragment(
"jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
u.info,
s.count
)
]
)
|> Repo.update_all([], returning: true)
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end end


def get_users_from_set_query(ap_ids, false) do def get_users_from_set_query(ap_ids, false) do
@@ -755,6 +749,46 @@ defmodule Pleroma.User do
Repo.all(query) Repo.all(query)
end end


@spec search_for_admin(binary(), %{
admin: Pleroma.User.t(),
local: boolean(),
page: number(),
page_size: number()
}) :: {:ok, [Pleroma.User.t()], number()}
def search_for_admin(term, %{admin: admin, local: local, page: page, page_size: page_size}) do
term = String.trim_leading(term, "@")

local_paginated_query =
User
|> maybe_local_user_query(local)
|> paginate(page, page_size)

search_query = fts_search_subquery(term, local_paginated_query)

count =
term
|> fts_search_subquery()
|> maybe_local_user_query(local)
|> Repo.aggregate(:count, :id)

{:ok, do_search(search_query, admin), count}
end

@spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()}
def all_for_admin(page, page_size) do
query = from(u in User, order_by: u.id)

paginated_query =
query
|> paginate(page, page_size)

count =
query
|> Repo.aggregate(:count, :id)

{:ok, Repo.all(paginated_query), count}
end

def search(query, resolve \\ false, for_user \\ nil) do def search(query, resolve \\ false, for_user \\ nil) do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
@@ -788,9 +822,9 @@ defmodule Pleroma.User do
boost_search_results(results, for_user) boost_search_results(results, for_user)
end end


defp fts_search_subquery(query) do
defp fts_search_subquery(term, query \\ User) do
processed_query = processed_query =
query
term
|> String.replace(~r/\W+/, " ") |> String.replace(~r/\W+/, " ")
|> String.trim() |> String.trim()
|> String.split() |> String.split()
@@ -798,7 +832,7 @@ defmodule Pleroma.User do
|> Enum.join(" | ") |> Enum.join(" | ")


from( from(
u in User,
u in query,
select_merge: %{ select_merge: %{
search_rank: search_rank:
fragment( fragment(
@@ -828,19 +862,19 @@ defmodule Pleroma.User do
) )
end end


defp trigram_search_subquery(query) do
defp trigram_search_subquery(term) do
from( from(
u in User, u in User,
select_merge: %{ select_merge: %{
search_rank: search_rank:
fragment( fragment(
"similarity(?, trim(? || ' ' || coalesce(?, '')))", "similarity(?, trim(? || ' ' || coalesce(?, '')))",
^query,
^term,
u.nickname, u.nickname,
u.name u.name
) )
}, },
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^query)
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
) )
end end


@@ -954,6 +988,7 @@ defmodule Pleroma.User do
update_and_set_cache(cng) update_and_set_cache(cng)
end end


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, %{ap_id: ap_id}) do
@@ -997,9 +1032,13 @@ defmodule Pleroma.User do
update_and_set_cache(cng) update_and_set_cache(cng)
end end


def local_user_query do
def maybe_local_user_query(query, local) do
if local, do: local_user_query(query), else: query
end

def local_user_query(query \\ User) do
from( from(
u in User,
u in query,
where: u.local == true, where: u.local == true,
where: not is_nil(u.nickname) where: not is_nil(u.nickname)
) )
@@ -1187,9 +1226,6 @@ defmodule Pleroma.User do
def parse_bio(bio, _user) when bio == "", do: bio def parse_bio(bio, _user) when bio == "", do: bio


def parse_bio(bio, user) do def parse_bio(bio, user) do
mentions = Formatter.parse_mentions(bio)
tags = Formatter.parse_tags(bio)

emoji = emoji =
(user.info.source_data["tag"] || []) (user.info.source_data["tag"] || [])
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
@@ -1197,8 +1233,15 @@ defmodule Pleroma.User do
{String.trim(name, ":"), url} {String.trim(name, ":"), url}
end) end)


# TODO: get profile URLs other than user.ap_id
profile_urls = [user.ap_id]

bio bio
|> CommonUtils.format_input(mentions, tags, "text/plain", user_links: [format: :full])
|> CommonUtils.format_input("text/plain",
mentions_format: :full,
rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
)
|> elem(0)
|> Formatter.emojify(emoji) |> Formatter.emojify(emoji)
end end


@@ -1293,4 +1336,11 @@ defmodule Pleroma.User do
) )
|> Repo.all() |> Repo.all()
end end

defp paginate(query, page, page_size) do
from(u in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
end
end end

+ 0
- 1
lib/pleroma/user/info.ex View File

@@ -12,7 +12,6 @@ defmodule Pleroma.User.Info do
field(:source_data, :map, default: %{}) field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0) field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0) field(:follower_count, :integer, default: 0)
field(:follow_request_count, :integer, default: 0)
field(:locked, :boolean, default: false) field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false) field(:confirmation_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil) field(:confirmation_token, :string, default: nil)


+ 42
- 62
lib/pleroma/web/activity_pub/activity_pub.ex View File

@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


import Ecto.Query import Ecto.Query
import Pleroma.Web.ActivityPub.Utils import Pleroma.Web.ActivityPub.Utils
import Pleroma.Web.ActivityPub.Visibility


require Logger require Logger


@@ -80,6 +81,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


defp check_remote_limit(_), do: true defp check_remote_limit(_), do: true


def increase_note_count_if_public(actor, object) do
if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
end

def decrease_note_count_if_public(actor, object) do
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
end

def insert(map, local \\ true) when is_map(map) do def insert(map, local \\ true) when is_map(map) do
with nil <- Activity.normalize(map), with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map), map <- lazy_put_activity_defaults(map),
@@ -162,7 +171,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
), ),
{:ok, activity} <- insert(create_data, local), {:ok, activity} <- insert(create_data, local),
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
{:ok, _actor} <- User.increase_note_count(actor),
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
@@ -174,8 +183,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object}, with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(actor) do
:ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
end end
@@ -186,8 +194,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object}, with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(actor) do
:ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
end end
@@ -285,8 +292,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def follow(follower, followed, activity_id \\ nil, local \\ true) do def follow(follower, followed, activity_id \\ nil, local \\ true) do
with data <- make_follow_data(follower, followed, activity_id), with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(followed) do
:ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
end end
@@ -296,8 +302,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local), {:ok, activity} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(followed) do
:ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
end end
@@ -315,7 +320,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:ok, _} <- Object.delete(object), with {:ok, _} <- Object.delete(object),
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
{:ok, _actor} <- User.decrease_note_count(user),
{:ok, _actor} <- decrease_note_count_if_public(user, object),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
end end
@@ -420,6 +425,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
@valid_visibilities ~w[direct unlisted public private] @valid_visibilities ~w[direct unlisted public private]


defp restrict_visibility(query, %{visibility: visibility}) defp restrict_visibility(query, %{visibility: visibility})
when is_list(visibility) do
if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
query =
from(
a in query,
where:
fragment(
"activity_visibility(?, ?, ?) = ANY (?)",
a.actor,
a.recipients,
a.data,
^visibility
)
)

Ecto.Adapters.SQL.to_sql(:all, Repo, query)

query
else
Logger.error("Could not restrict visibility to #{visibility}")
end
end

defp restrict_visibility(query, %{visibility: visibility})
when visibility in @valid_visibilities do when visibility in @valid_visibilities do
query = query =
from( from(
@@ -601,6 +630,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


defp restrict_reblogs(query, _), do: query defp restrict_reblogs(query, _), do: query


defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query

defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
mutes = info.mutes mutes = info.mutes


@@ -825,7 +856,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do


date = date =
NaiveDateTime.utc_now() NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")


signature = signature =
Pleroma.Web.HTTPSignatures.sign(actor, %{ Pleroma.Web.HTTPSignatures.sign(actor, %{
@@ -912,57 +943,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
end end


def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false

def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end

def is_private?(activity) do
unless is_public?(activity) do
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
Enum.any?(activity.data["to"], &(&1 == follower_address))
else
false
end
end

def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true

def is_direct?(activity) do
!is_public?(activity) && !is_private?(activity)
end

def visible_for_user?(activity, nil) do
is_public?(activity)
end

def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
y = activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end

# guard
def entire_thread_visible_for_user?(nil, _user), do: false

# child
def entire_thread_visible_for_user?(
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
)
when is_binary(parent_id) do
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
end

# root
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)

# filter out broken threads # filter out broken threads
def contain_broken_threads(%Activity{} = activity, %User{} = user) do def contain_broken_threads(%Activity{} = activity, %User{} = user) do
entire_thread_visible_for_user?(activity, user) entire_thread_visible_for_user?(activity, user)


+ 5
- 4
lib/pleroma/web/activity_pub/activity_pub_controller.ex View File

@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@@ -49,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object(conn, %{"uuid" => uuid}) do def object(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)} do
{_, true} <- {:public?, Visibility.is_public?(object)} do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: object})) |> json(ObjectView.render("object.json", %{object: object}))
@@ -62,7 +63,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)},
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do likes <- Utils.get_object_likes(object) do
{page, _} = Integer.parse(page) {page, _} = Integer.parse(page)


@@ -78,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object_likes(conn, %{"uuid" => uuid}) do def object_likes(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)},
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do likes <- Utils.get_object_likes(object) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
@@ -92,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def activity(conn, %{"uuid" => uuid}) do def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid), with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id), %Activity{} = activity <- Activity.normalize(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(activity)} do
{_, true} <- {:public?, Visibility.is_public?(activity)} do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: activity})) |> json(ObjectView.render("object.json", %{object: activity}))


+ 2
- 1
lib/pleroma/web/activity_pub/transmogrifier.ex View File

@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Repo alias Pleroma.Repo
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 Ecto.Query import Ecto.Query


@@ -489,7 +490,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- get_actor(data), with actor <- get_actor(data),
%User{} = actor <- User.get_or_fetch_by_ap_id(actor), %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
public <- ActivityPub.is_public?(data),
public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity} {:ok, activity}
else else


+ 56
- 0
lib/pleroma/web/activity_pub/visibility.ex View File

@@ -0,0 +1,56 @@
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User

def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false

def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end

def is_private?(activity) do
unless is_public?(activity) do
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
Enum.any?(activity.data["to"], &(&1 == follower_address))
else
false
end
end

def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true

def is_direct?(activity) do
!is_public?(activity) && !is_private?(activity)
end

def visible_for_user?(activity, nil) do
is_public?(activity)
end

def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end

# guard
def entire_thread_visible_for_user?(nil, _user), do: false

# child
def entire_thread_visible_for_user?(
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
)
when is_binary(parent_id) do
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
end

# root
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
end

+ 70
- 0
lib/pleroma/web/admin_api/admin_api_controller.ex View File

@@ -3,9 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only


defmodule Pleroma.Web.AdminAPI.AdminAPIController do defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@users_page_size 50

use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.MastodonAPI.Admin.AccountView


import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]


@@ -41,6 +44,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(user.nickname) |> json(user.nickname)
end end


def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_by_nickname(nickname)

{:ok, updated_user} = User.deactivate(user, !user.info.deactivated)

conn
|> json(AccountView.render("show.json", %{user: updated_user}))
end

def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags), with {:ok, _} <- User.tag(nicknames, tags),
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
@@ -51,6 +63,42 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
end end


def list_users(conn, params) do
{page, page_size} = page_params(params)

with {:ok, users, count} <- User.all_for_admin(page, page_size),
do:
conn
|> json(
AccountView.render("index.json",
users: users,
count: count,
page_size: page_size
)
)
end

def search_users(%{assigns: %{user: admin}} = conn, %{"query" => query} = params) do
{page, page_size} = page_params(params)

with {:ok, users, count} <-
User.search_for_admin(query, %{
admin: admin,
local: params["local"] == "true",
page: page,
page_size: page_size
}),
do:
conn
|> json(
AccountView.render("index.json",
users: users,
count: count,
page_size: page_size
)
)
end

def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_by_nickname(nickname) user = User.get_by_nickname(nickname)
@@ -194,4 +242,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> put_status(500) |> put_status(500)
|> json("Something went wrong") |> json("Something went wrong")
end end

defp page_params(params) do
{get_page(params["page"]), get_page_size(params["page_size"])}
end

defp get_page(page_string) when is_nil(page_string), do: 1

defp get_page(page_string) do
case Integer.parse(page_string) do
{page, _} -> page
:error -> 1
end
end

defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size

defp get_page_size(page_size_string) do
case Integer.parse(page_size_string) do
{page_size, _} -> page_size
:error -> @users_page_size
end
end
end end

+ 25
- 0
lib/pleroma/web/auth/authenticator.ex View File

@@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.Auth.Authenticator do
alias Pleroma.User

def implementation do
Pleroma.Config.get(
Pleroma.Web.Auth.Authenticator,
Pleroma.Web.Auth.PleromaAuthenticator
)
end

@callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
def get_user(plug), do: implementation().get_user(plug)

@callback handle_error(Plug.Conn.t(), any()) :: any()
def handle_error(plug, error), do: implementation().handle_error(plug, error)

@callback auth_template() :: String.t() | nil
def auth_template do
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
end
end

+ 28
- 0
lib/pleroma/web/auth/pleroma_authenticator.ex View File

@@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.User
alias Comeonin.Pbkdf2

@behaviour Pleroma.Web.Auth.Authenticator

def get_user(%Plug.Conn{} = conn) do
%{"authorization" => %{"name" => name, "password" => password}} = conn.params

with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
{:ok, user}
else
error ->
{:error, error}
end
end

def handle_error(%Plug.Conn{} = _conn, error) do
error
end

def auth_template, do: nil
end

+ 4
- 24
lib/pleroma/web/common_api/common_api.ex View File

@@ -82,40 +82,20 @@ defmodule Pleroma.Web.CommonAPI do


def get_visibility(_), do: "public" def get_visibility(_), do: "public"


defp get_content_type(content_type) do
if Enum.member?(Pleroma.Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
"text/plain"
end
end

def post(user, %{"status" => status} = data) do def post(user, %{"status" => status} = data) do
visibility = get_visibility(data) visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit]) limit = Pleroma.Config.get([:instance, :limit])


with status <- String.trim(status), with status <- String.trim(status),
attachments <- attachments_from_ids(data), attachments <- attachments_from_ids(data),
mentions <- Formatter.parse_mentions(status),
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
tags <- Formatter.parse_tags(status, data),
content_html <-
{content_html, mentions, tags} <-
make_content_html( make_content_html(
status, status,
mentions,
attachments, attachments,
tags,
get_content_type(data["content_type"]),
Enum.member?(
[true, "true"],
Map.get(
data,
"no_attachment_links",
Pleroma.Config.get([:instance, :no_attachment_links], false)
)
)
data
), ),
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
context <- make_context(inReplyTo), context <- make_context(inReplyTo),
cw <- data["spoiler_text"], cw <- data["spoiler_text"],
full_payload <- String.trim(status <> (data["spoiler_text"] || "")), full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
@@ -247,7 +227,7 @@ defmodule Pleroma.Web.CommonAPI do
def report(user, data) do def report(user, data) do
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data}, with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
{:account, %User{} = account} <- {:account, User.get_by_id(account_id)}, {:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
{:ok, content_html} <- make_report_content_html(data["comment"]),
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
{:ok, statuses} <- get_report_statuses(account, data), {:ok, statuses} <- get_report_statuses(account, data),
{:ok, activity} <- {:ok, activity} <-
ActivityPub.flag(%{ ActivityPub.flag(%{


+ 45
- 41
lib/pleroma/web/common_api/utils.ex View File

@@ -10,7 +10,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Config
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@@ -100,24 +100,45 @@ defmodule Pleroma.Web.CommonAPI.Utils do


def make_content_html( def make_content_html(
status, status,
mentions,
attachments, attachments,
tags,
content_type,
no_attachment_links \\ false
data
) do ) do
no_attachment_links =
data
|> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links]))
|> Kernel.in([true, "true"])

content_type = get_content_type(data["content_type"])

status status
|> format_input(mentions, tags, content_type)
|> format_input(content_type)
|> maybe_add_attachments(attachments, no_attachment_links) |> maybe_add_attachments(attachments, no_attachment_links)
|> maybe_add_nsfw_tag(data)
end

defp get_content_type(content_type) do
if Enum.member?(Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
"text/plain"
end
end end


defp maybe_add_nsfw_tag({text, mentions, tags}, %{"sensitive" => sensitive})
when sensitive in [true, "True", "true", "1"] do
{text, mentions, [{"#nsfw", "nsfw"} | tags]}
end

defp maybe_add_nsfw_tag(data, _), do: data

def make_context(%Activity{data: %{"context" => context}}), do: context def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id() def make_context(_), do: Utils.generate_context_id()


def maybe_add_attachments(text, _attachments, true = _no_links), do: text
def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed


def maybe_add_attachments(text, attachments, _no_links) do
add_attachments(text, attachments)
def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
text = add_attachments(text, attachments)
{text, mentions, tags}
end end


def add_attachments(text, attachments) do def add_attachments(text, attachments) do
@@ -135,56 +156,39 @@ defmodule Pleroma.Web.CommonAPI.Utils do
Enum.join([text | attachment_text], "<br>") Enum.join([text | attachment_text], "<br>")
end end


def format_input(text, mentions, tags, format, options \\ [])
def format_input(text, format, options \\ [])


@doc """ @doc """
Formatting text to plain text. Formatting text to plain text.
""" """
def format_input(text, mentions, tags, "text/plain", options) do
def format_input(text, "text/plain", options) do
text text
|> Formatter.html_escape("text/plain") |> Formatter.html_escape("text/plain")
|> String.replace(~r/\r?\n/, "<br>")
|> (&{[], &1}).()
|> Formatter.add_links()
|> Formatter.add_user_links(mentions, options[:user_links] || [])
|> Formatter.add_hashtag_links(tags)
|> Formatter.finalize()
|> Formatter.linkify(options)
|> (fn {text, mentions, tags} ->
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
end).()
end end


@doc """ @doc """
Formatting text to html. Formatting text to html.
""" """
def format_input(text, mentions, _tags, "text/html", options) do
def format_input(text, "text/html", options) do
text text
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
|> (&{[], &1}).()
|> Formatter.add_user_links(mentions, options[:user_links] || [])
|> Formatter.finalize()
|> Formatter.linkify(options)
end end


@doc """ @doc """
Formatting text to markdown. Formatting text to markdown.
""" """
def format_input(text, mentions, tags, "text/markdown", options) do
def format_input(text, "text/markdown", options) do
options = Keyword.put(options, :mentions_escape, true)

text text
|> Formatter.mentions_escape(mentions)
|> Earmark.as_html!()
|> Formatter.linkify(options)
|> (fn {text, mentions, tags} -> {Earmark.as_html!(text), mentions, tags} end).()
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
|> (&{[], &1}).()
|> Formatter.add_user_links(mentions, options[:user_links] || [])
|> Formatter.add_hashtag_links(tags)
|> Formatter.finalize()
end

def add_tag_links(text, tags) do
tags =
tags
|> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)

Enum.reduce(tags, text, fn {full, tag}, text ->
url = "<a href='#{Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
String.replace(text, full, url)
end)
end end


def make_note_data( def make_note_data(
@@ -323,13 +327,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do


def maybe_extract_mentions(_), do: [] def maybe_extract_mentions(_), do: []


def make_report_content_html(nil), do: {:ok, nil}
def make_report_content_html(nil), do: {:ok, {nil, [], []}}


def make_report_content_html(comment) do def make_report_content_html(comment) do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)


if String.length(comment) <= max_size do if String.length(comment) <= max_size do
{:ok, format_input(comment, [], [], "text/plain")}
{:ok, format_input(comment, "text/plain")}
else else
{:error, "Comment must be up to #{max_size} characters"} {:error, "Comment must be up to #{max_size} characters"}
end end


+ 2
- 1
lib/pleroma/web/federator/federator.ex View File

@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.Websub alias Pleroma.Web.Websub
alias Pleroma.Web.Salmon alias Pleroma.Web.Salmon
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@@ -94,7 +95,7 @@ defmodule Pleroma.Web.Federator do
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, actor} = WebFinger.ensure_keys_present(actor) {:ok, actor} = WebFinger.ensure_keys_present(actor)


if ActivityPub.is_public?(activity) do
if Visibility.is_public?(activity) do
if OStatus.is_representable?(activity) do if OStatus.is_representable?(activity) do
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end) Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity) Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)


+ 10
- 9
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex View File

@@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.MastodonAPI.ReportView alias Pleroma.Web.MastodonAPI.ReportView
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.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@@ -307,7 +308,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do


def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.visible_for_user?(activity, user) do
true <- Visibility.visible_for_user?(activity, user) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user}) |> try_render("status.json", %{activity: activity, for: user})
@@ -449,7 +450,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname), %User{} = user <- User.get_by_nickname(user.nickname),
true <- ActivityPub.visible_for_user?(activity, user),
true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
@@ -460,7 +461,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname), %User{} = user <- User.get_by_nickname(user.nickname),
true <- ActivityPub.visible_for_user?(activity, user),
true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
@@ -867,7 +868,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
if Regex.match?(~r/https?:/, query) do if Regex.match?(~r/https?:/, query) do
with {:ok, object} <- ActivityPub.fetch_object_from_id(query), with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- ActivityPub.visible_for_user?(activity, user) do
true <- Visibility.visible_for_user?(activity, user) do
[activity] [activity]
else else
_e -> [] _e -> []
@@ -893,7 +894,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end


def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true", user)
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)


statuses = status_search(user, query) statuses = status_search(user, query)


@@ -918,7 +919,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end


def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true", user)
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)


statuses = status_search(user, query) statuses = status_search(user, query)


@@ -940,7 +941,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end


def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true", user)
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)


res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)


@@ -1518,9 +1519,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end


def status_card(conn, %{"id" => status_id}) do
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Repo.get(Activity, status_id), with %Activity{} = activity <- Repo.get(Activity, status_id),
true <- ActivityPub.is_public?(activity) do
true <- Visibility.visible_for_user?(activity, user) do
data = data =
StatusView.render( StatusView.render(
"card.json", "card.json",


+ 9
- 2
lib/pleroma/web/mastodon_api/views/account_view.ex View File

@@ -32,7 +32,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
} }
end end


def render("relationship.json", %{user: user, target: target}) do
def render("relationship.json", %{user: nil, target: _target}) do
%{}
end

def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target) follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)


requested = requested =
@@ -85,6 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do


bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))


relationship = render("relationship.json", %{user: opts[:for], target: user})

%{ %{
id: to_string(user.id), id: to_string(user.id),
username: username_from_nickname(user.nickname), username: username_from_nickname(user.nickname),
@@ -115,7 +121,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
confirmation_pending: user_info.confirmation_pending, confirmation_pending: user_info.confirmation_pending,
tags: user.tags, tags: user.tags,
is_moderator: user.info.is_moderator, is_moderator: user.info.is_moderator,
is_admin: user.info.is_admin
is_admin: user.info.is_admin,
relationship: relationship
} }
} }
end end


+ 25
- 0
lib/pleroma/web/mastodon_api/views/admin/account_view.ex View File

@@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
use Pleroma.Web, :view

alias Pleroma.Web.MastodonAPI.Admin.AccountView

def render("index.json", %{users: users, count: count, page_size: page_size}) do
%{
users: render_many(users, AccountView, "show.json", as: :user),
count: count,
page_size: page_size
}
end

def render("show.json", %{user: user}) do
%{
"id" => user.id,
"nickname" => user.nickname,
"deactivated" => user.info.deactivated
}
end
end

+ 1
- 1
lib/pleroma/web/mastodon_api/views/status_view.ex View File

@@ -168,7 +168,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: present?(repeated), reblogged: present?(repeated),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: CommonAPI.thread_muted?(user, activity),
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user), pinned: pinned?(activity, user),
sensitive: sensitive, sensitive: sensitive,
spoiler_text: object["summary"] || "", spoiler_text: object["summary"] || "",


+ 19
- 19
lib/pleroma/web/mastodon_api/websocket_handler.ex View File

@@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User


@behaviour :cowboy_websocket_handler
@behaviour :cowboy_websocket


@streams [ @streams [
"public", "public",
@@ -26,37 +26,37 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
# Handled by periodic keepalive in Pleroma.Web.Streamer. # Handled by periodic keepalive in Pleroma.Web.Streamer.
@timeout :infinity @timeout :infinity


def init(_type, _req, _opts) do
{:upgrade, :protocol, :cowboy_websocket}
end

def websocket_init(_type, req, _opts) do
with {qs, req} <- :cowboy_req.qs(req),
params <- :cow_qs.parse_qs(qs),
def init(%{qs: qs} = req, state) do
with params <- :cow_qs.parse_qs(qs),
access_token <- List.keyfind(params, "access_token", 0), access_token <- List.keyfind(params, "access_token", 0),
{_, stream} <- List.keyfind(params, "stream", 0), {_, stream} <- List.keyfind(params, "stream", 0),
{:ok, user} <- allow_request(stream, access_token), {:ok, user} <- allow_request(stream, access_token),
topic when is_binary(topic) <- expand_topic(stream, params) do topic when is_binary(topic) <- expand_topic(stream, params) do
send(self(), :subscribe)
{:ok, req, %{user: user, topic: topic}, @timeout}
{:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}}
else else
{:error, code} -> {:error, code} ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(code, req) {:ok, req} = :cowboy_req.reply(code, req)
{:shutdown, req}
{:ok, req, state}


error -> error ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}")
{:shutdown, req}
{:ok, req} = :cowboy_req.reply(400, req)
{:ok, req, state}
end end
end end


def websocket_init(state) do
send(self(), :subscribe)
{:ok, state}
end

# We never receive messages. # We never receive messages.
def websocket_handle(_frame, req, state) do
{:ok, req, state}
def websocket_handle(_frame, state) do
{:ok, state}
end end


def websocket_info(:subscribe, req, state) do
def websocket_info(:subscribe, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} accepted websocket connection for user #{ "#{__MODULE__} accepted websocket connection for user #{
(state.user || %{id: "anonymous"}).id (state.user || %{id: "anonymous"}).id
@@ -64,14 +64,14 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
) )


Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state))
{:ok, req, state}
{:ok, state}
end end


def websocket_info({:text, message}, req, state) do
{:reply, {:text, message}, req, state}
def websocket_info({:text, message}, state) do
{:reply, {:text, message}, state}
end end


def websocket_terminate(reason, _req, state) do
def terminate(reason, _req, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{ "#{__MODULE__} terminating websocket connection for user #{
(state.user || %{id: "anonymous"}).id (state.user || %{id: "anonymous"}).id


+ 1
- 3
lib/pleroma/web/metadata/twitter_card.ex View File

@@ -66,9 +66,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end end
end end


defp build_attachments(id, z = %{data: %{"attachment" => attachments}}) do
IO.puts(inspect(z))

defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
Enum.reduce(attachments, [], fn attachment, acc -> Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags = rendered_tags =
Enum.reduce(attachment["url"], [], fn url, acc -> Enum.reduce(attachment["url"], [], fn url, acc ->


+ 8
- 9
lib/pleroma/web/oauth/oauth_controller.ex View File

@@ -5,6 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthController do defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller use Pleroma.Web, :controller


alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
@@ -24,27 +25,25 @@ defmodule Pleroma.Web.OAuth.OAuthController do
available_scopes = (app && app.scopes) || [] available_scopes = (app && app.scopes) || []
scopes = oauth_scopes(params, nil) || available_scopes scopes = oauth_scopes(params, nil) || available_scopes


render(conn, "show.html", %{
render(conn, Authenticator.auth_template(), %{
response_type: params["response_type"], response_type: params["response_type"],
client_id: params["client_id"], client_id: params["client_id"],
available_scopes: available_scopes, available_scopes: available_scopes,
scopes: scopes, scopes: scopes,
redirect_uri: params["redirect_uri"], redirect_uri: params["redirect_uri"],
state: params["state"]
state: params["state"],
params: params
}) })
end end


def create_authorization(conn, %{ def create_authorization(conn, %{
"authorization" => "authorization" =>
%{ %{
"name" => name,
"password" => password,
"client_id" => client_id, "client_id" => client_id,
"redirect_uri" => redirect_uri "redirect_uri" => redirect_uri
} = auth_params } = auth_params
}) do }) do
with %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash),
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
%App{} = app <- Repo.get_by(App, client_id: client_id), %App{} = app <- Repo.get_by(App, client_id: client_id),
true <- redirect_uri in String.split(app.redirect_uris), true <- redirect_uri in String.split(app.redirect_uris),
scopes <- oauth_scopes(auth_params, []), scopes <- oauth_scopes(auth_params, []),
@@ -53,9 +52,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:missing_scopes, false} <- {:missing_scopes, scopes == []}, {:missing_scopes, false} <- {:missing_scopes, scopes == []},
{:auth_active, true} <- {:auth_active, User.auth_active?(user)}, {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
# Special case: Local MastodonFE.
redirect_uri = redirect_uri =
if redirect_uri == "." do if redirect_uri == "." do
# Special case: Local MastodonFE
mastodon_api_url(conn, :login) mastodon_api_url(conn, :login)
else else
redirect_uri redirect_uri
@@ -97,7 +96,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> authorize(auth_params) |> authorize(auth_params)


error -> error ->
error
Authenticator.handle_error(conn, error)
end end
end end


@@ -114,7 +113,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
refresh_token: token.refresh_token, refresh_token: token.refresh_token,
created_at: DateTime.to_unix(inserted_at), created_at: DateTime.to_unix(inserted_at),
expires_in: 60 * 10, expires_in: 60 * 10,
scope: Enum.join(token.scopes)
scope: Enum.join(token.scopes, " ")
} }


json(conn, response) json(conn, response)


+ 5
- 4
lib/pleroma/web/ostatus/ostatus_controller.ex View File

@@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
@@ -102,7 +103,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
else else
with id <- o_status_url(conn, :object, uuid), with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)}, {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
@@ -127,7 +128,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
else else
with id <- o_status_url(conn, :activity, uuid), with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
@@ -148,7 +149,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do


def notice(conn, %{"id" => id}) do def notice(conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)}, with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do case format = get_format(conn) do
"html" -> "html" ->
@@ -191,7 +192,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
# Returns an HTML embedded <audio> or <video> player suitable for embed iframes. # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
def notice_player(conn, %{"id" => id}) do def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
true <- ActivityPub.is_public?(activity),
true <- Visibility.is_public?(activity),
%Object{} = object <- Object.normalize(activity.data["object"]), %Object{} = object <- Object.normalize(activity.data["object"]),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do


+ 51
- 0
lib/pleroma/web/rel_me.ex View File

@@ -0,0 +1,51 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.RelMe do
@hackney_options [
pool: :media,
timeout: 2_000,
recv_timeout: 2_000,
max_body: 2_000_000
]

if Mix.env() == :test do
def parse(url) when is_binary(url), do: parse_url(url)
else
def parse(url) when is_binary(url) do
Cachex.fetch!(:rel_me_cache, url, fn _ ->
{:commit, parse_url(url)}
end)
rescue
e -> {:error, "Cachex error: #{inspect(e)}"}
end
end

def parse(_), do: {:error, "No URL provided"}

defp parse_url(url) do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)

data =
Floki.attribute(html, "link[rel=me]", "href") ++ Floki.attribute(html, "a[rel=me]", "href")

{:ok, data}
rescue
e -> {:error, "Parsing error: #{inspect(e)}"}
end

def maybe_put_rel_me("http" <> _ = target_page, profile_urls) when is_list(profile_urls) do
{:ok, rel_me_hrefs} = parse(target_page)

true = Enum.any?(rel_me_hrefs, fn x -> x in profile_urls end)

"me"
rescue
_ -> nil
end

def maybe_put_rel_me(_, _) do
nil
end
end

+ 3
- 0
lib/pleroma/web/router.ex View File

@@ -139,7 +139,10 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :oauth_write]) pipe_through([:admin_api, :oauth_write])


get("/users", AdminAPIController, :list_users)
get("/users/search", AdminAPIController, :search_users)
delete("/user", AdminAPIController, :user_delete) delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
post("/user", AdminAPIController, :user_create) post("/user", AdminAPIController, :user_create)
put("/users/tag", AdminAPIController, :tag_users) put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users) delete("/users/tag", AdminAPIController, :untag_users)


+ 3
- 3
lib/pleroma/web/streamer.ex View File

@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Streamer do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility


@keepalive_interval :timer.seconds(30) @keepalive_interval :timer.seconds(30)


@@ -73,7 +73,7 @@ defmodule Pleroma.Web.Streamer do
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
# filter the recipient list if the activity is not public, see #270. # filter the recipient list if the activity is not public, see #270.
recipient_lists = recipient_lists =
case ActivityPub.is_public?(item) do
case Visibility.is_public?(item) do
true -> true ->
Pleroma.List.get_lists_from_activity(item) Pleroma.List.get_lists_from_activity(item)


@@ -82,7 +82,7 @@ defmodule Pleroma.Web.Streamer do
|> Enum.filter(fn list -> |> Enum.filter(fn list ->
owner = Repo.get(User, list.user_id) owner = Repo.get(User, list.user_id)


ActivityPub.visible_for_user?(item, owner)
Visibility.visible_for_user?(item, owner)
end) end)
end end




+ 5
- 242
lib/pleroma/web/twitter_api/representers/activity_representer.ex View File

@@ -6,247 +6,10 @@
# THIS MODULE IS DEPRECATED! DON'T USE IT! # THIS MODULE IS DEPRECATED! DON'T USE IT!
# USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE! # USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE!
defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView

defp user_by_ap_id(user_list, ap_id) do
Enum.find(user_list, fn %{ap_id: user_id} -> ap_id == user_id end)
end

def to_map(
%Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} =
activity,
%{users: users, announced_activity: announced_activity} = opts
) do
user = user_by_ap_id(users, actor)
created_at = created_at |> Utils.date_to_asctime()

text = "#{user.nickname} retweeted a status."

announced_user = user_by_ap_id(users, announced_activity.data["actor"])
retweeted_status = to_map(announced_activity, Map.merge(%{user: announced_user}, opts))

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=note",
"created_at" => created_at,
"retweeted_status" => retweeted_status,
"statusnet_conversation_id" => conversation_id(announced_activity),
"external_url" => activity.data["id"],
"activity_type" => "repeat"
}
end

def to_map(
%Activity{data: %{"type" => "Like", "published" => created_at}} = activity,
%{user: user, liked_activity: liked_activity} = opts
) do
created_at = created_at |> Utils.date_to_asctime()

text = "#{user.nickname} favorited a status."

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
"created_at" => created_at,
"in_reply_to_status_id" => liked_activity.id,
"external_url" => activity.data["id"],
"activity_type" => "like"
}
end

def to_map(
%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity,
%{user: user} = opts
) do
created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at)
created_at = created_at |> Utils.date_to_asctime()

followed = User.get_cached_by_ap_id(followed_id)
text = "#{user.nickname} started following #{followed.nickname}"

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "follow"
}
end

# TODO:
# Make this more proper. Just a placeholder to not break the frontend.
def to_map(
%Activity{
data: %{"type" => "Undo", "published" => created_at, "object" => undid_activity}
} = activity,
%{user: user} = opts
) do
created_at = created_at |> Utils.date_to_asctime()

text = "#{user.nickname} undid the action at #{undid_activity["id"]}"

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "undo"
}
end

def to_map(
%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _}} =
activity,
%{user: user} = opts
) do
created_at = created_at |> Utils.date_to_asctime()

%{
"id" => activity.id,
"uri" => activity.data["object"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => "deleted notice {{tag",
"text" => "deleted notice {{tag",
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "delete"
}
end

def to_map(
%Activity{data: %{"object" => %{"content" => _content} = object}} = activity,
%{user: user} = opts
) do
created_at = object["published"] |> Utils.date_to_asctime()
like_count = object["like_count"] || 0
announcement_count = object["announcement_count"] || 0
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
pinned = activity.id in user.info.pinned_activities

mentions = opts[:mentioned] || []

attentions =
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Enum.map(fn ap_id -> Enum.find(mentions, fn user -> ap_id == user.ap_id end) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)

conversation_id = conversation_id(activity)

tags = activity.data["object"]["tag"] || []
possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw")

tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags

{_summary, content} = ActivityView.render_content(object)

html =
HTML.filter_tags(content, User.html_filter_policy(opts[:for]))
|> Formatter.emojify(object["emoji"])

attachments = object["attachment"] || []

reply_parent = Activity.get_in_reply_to_activity(activity)

reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)

summary = HTML.strip_tags(object["summary"])

card =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)

%{
"id" => activity.id,
"uri" => activity.data["object"]["id"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => html,
"text" => HTML.strip_tags(content),
"is_local" => activity.local,
"is_post_verb" => true,
"created_at" => created_at,
"in_reply_to_status_id" => object["inReplyToStatusId"],
"in_reply_to_screen_name" => reply_user && reply_user.nickname,
"in_reply_to_profileurl" => User.profile_url(reply_user),
"in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
"in_reply_to_user_id" => reply_user && reply_user.id,
"statusnet_conversation_id" => conversation_id,
"attachments" => attachments |> ObjectRepresenter.enum_to_list(opts),
"attentions" => attentions,
"fave_num" => like_count,
"repeat_num" => announcement_count,
"favorited" => to_boolean(favorited),
"repeated" => to_boolean(repeated),
"pinned" => pinned,
"external_url" => object["external_url"] || object["id"],
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object["emoji"]),
"card" => card
}
end

def conversation_id(activity) do
with context when not is_nil(context) <- activity.data["context"] do
TwitterAPI.context_to_conversation_id(context)
else
_e -> nil
end
end

defp to_boolean(false) do
false
end

defp to_boolean(nil) do
false
end

defp to_boolean(_) do
true
def to_map(activity, opts) do
Pleroma.Web.TwitterAPI.ActivityView.render(
"activity.json",
Map.put(opts, :activity, activity)
)
end end
end end

+ 1
- 9
lib/pleroma/web/twitter_api/twitter_api.ex View File

@@ -229,18 +229,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end end
end end


def get_by_id_or_nickname(id_or_nickname) do
if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do
Repo.get_by(User, nickname: id_or_nickname)
else
Repo.get(User, id_or_nickname)
end
end

def get_user(user \\ nil, params) do def get_user(user \\ nil, params) do
case params do case params do
%{"user_id" => user_id} -> %{"user_id" => user_id} ->
case target = get_by_id_or_nickname(user_id) do
case target = User.get_cached_by_nickname_or_id(user_id) do
nil -> nil ->
{:error, "No user with such user_id"} {:error, "No user with such user_id"}




+ 4
- 2
lib/pleroma/web/twitter_api/twitter_api_controller.ex View File

@@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Pleroma.{Repo, Activity, Object, User, Notification} alias Pleroma.{Repo, Activity, Object, User, Notification}
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.ActivityView
@@ -166,6 +167,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
params params
|> Map.put("type", ["Create", "Announce", "Follow", "Like"]) |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put(:visibility, ~w[unlisted public private])


activities = ActivityPub.fetch_activities([user.ap_id], params) activities = ActivityPub.fetch_activities([user.ap_id], params)


@@ -268,7 +270,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do


def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.visible_for_user?(activity, user) do
true <- Visibility.visible_for_user?(activity, user) do
conn conn
|> put_view(ActivityView) |> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user}) |> render("activity.json", %{activity: activity, for: user})
@@ -701,7 +703,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end end


def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
users = User.search(query, true, user)
users = User.search(query, resolve: true, for_user: user)


conn conn
|> put_view(UserView) |> put_view(UserView)


+ 3
- 1
lib/pleroma/web/twitter_api/views/activity_view.ex View File

@@ -10,6 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.ActivityView
@@ -309,7 +310,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"visibility" => StatusView.get_visibility(object), "visibility" => StatusView.get_visibility(object),
"summary" => summary, "summary" => summary,
"summary_html" => summary |> Formatter.emojify(object["emoji"]), "summary_html" => summary |> Formatter.emojify(object["emoji"]),
"card" => card
"card" => card,
"muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user)
} }
end end




+ 4
- 6
lib/pleroma/web/twitter_api/views/user_view.ex View File

@@ -118,7 +118,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"confirmation_pending" => user_info.confirmation_pending, "confirmation_pending" => user_info.confirmation_pending,
"tags" => user.tags "tags" => user.tags
} }
|> maybe_with_follow_request_count(user, for_user)
|> maybe_with_activation_status(user, for_user)
} }


data = data =
@@ -134,13 +134,11 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
end end
end end


defp maybe_with_follow_request_count(data, %User{id: id, info: %{locked: true}} = user, %User{
id: id
}) do
Map.put(data, "follow_request_count", user.info.follow_request_count)
defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
Map.put(data, "deactivated", user.info.deactivated)
end end


defp maybe_with_follow_request_count(data, _, _), do: data
defp maybe_with_activation_status(data, _, _), do: data


defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})


+ 6
- 0
lib/pleroma/web/web.ex View File

@@ -26,6 +26,12 @@ defmodule Pleroma.Web do
import Plug.Conn import Plug.Conn
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers import Pleroma.Web.Router.Helpers

plug(:set_put_layout)

defp set_put_layout(conn, _) do
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
end
end end
end end




+ 6
- 4
mix.exs View File

@@ -55,9 +55,8 @@ defmodule Pleroma.Mixfile do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
# Until Phoenix 1.4.1 is released
{:phoenix, github: "phoenixframework/phoenix", branch: "v1.4"},
{:plug_cowboy, "~> 1.0"},
{:phoenix, "~> 1.4.1"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 3.3"}, {:phoenix_ecto, "~> 3.3"},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
@@ -90,7 +89,10 @@ defmodule Pleroma.Mixfile do
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}, {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
{:floki, "~> 0.20.0"}, {:floki, "~> 0.20.0"},
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"}, {:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
{:timex, "~> 3.5"}
{:timex, "~> 3.5"},
{:auto_linker,
git: "https://git.pleroma.social/pleroma/auto_linker.git",
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"}
] ]
end end




+ 7
- 6
mix.lock View File

@@ -1,4 +1,5 @@
%{ %{
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd", [ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"]},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "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"},
@@ -8,8 +9,8 @@
"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"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
@@ -34,7 +35,7 @@
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
@@ -44,17 +45,17 @@
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "ea22dc50b574178a300ecd19253443960407df93", [branch: "v1.4"]},
"phoenix": {:hex, :phoenix, "1.4.1", "801f9d632808657f1f7c657c8bbe624caaf2ba91429123ebe3801598aea4c3d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "1.0.0", "2e2a7d3409746d335f451218b8bb0858301c3de6d668c3052716c909936eb57a", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "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"},
"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"]},


+ 11
- 0
priv/repo/migrations/20190222104808_data_migration_normalize_scopes.exs View File

@@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.DataMigrationNormalizeScopes do
use Ecto.Migration

def up do
for t <- [:apps, :oauth_authorizations, :oauth_tokens] do
execute "UPDATE #{t} SET scopes = string_to_array(array_to_string(scopes, ' '), ' ');"
end
end

def down, do: :noop
end

+ 9
- 0
priv/repo/migrations/20190301101154_add_default_tags_to_user.exs View File

@@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.AddDefaultTagsToUser do
use Ecto.Migration

def up do
execute "UPDATE users SET tags = array[]::varchar[] where tags IS NULL"
end

def down, do: :noop
end

+ 41
- 0
priv/repo/migrations/20190303120636_update_user_note_counters.exs View File

@@ -0,0 +1,41 @@
defmodule Pleroma.Repo.Migrations.UpdateUserNoteCounters do
use Ecto.Migration

@public "https://www.w3.org/ns/activitystreams#Public"

def up do
execute """
WITH public_note_count AS (
SELECT
data->>'actor' AS actor,
count(id) AS count
FROM objects
WHERE data->>'type' = 'Note' AND (
data->'cc' ? '#{@public}' OR data->'to' ? '#{@public}'
)
GROUP BY data->>'actor'
)
UPDATE users AS u
SET "info" = jsonb_set(u.info, '{note_count}', o.count::varchar::jsonb, true)
FROM public_note_count AS o
WHERE u.ap_id = o.actor
"""
end

def down do
execute """
WITH public_note_count AS (
SELECT
data->>'actor' AS actor,
count(id) AS count
FROM objects
WHERE data->>'type' = 'Note'
GROUP BY data->>'actor'
)
UPDATE users AS u
SET "info" = jsonb_set(u.info, '{note_count}', o.count::varchar::jsonb, true)
FROM public_note_count AS o
WHERE u.ap_id = o.actor
"""
end
end

+ 14
- 0
test/fixtures/rel_me_anchor.html View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Blog</title>
</head>
<body>
<article>
<h1>Lorem ipsum</h1>
<p>Lorem ipsum dolor sit ameph, …</p>
<a rel="me" href="https://social.example.org/users/lain">lain’s account</a>
</article>
</body>
</html>

+ 14
- 0
test/fixtures/rel_me_link.html View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Blog</title>
<link rel="me" href="https://social.example.org/users/lain"/>
</head>
<body>
<article>
<h1>Lorem ipsum</h1>
<p>Lorem ipsum dolor sit ameph, …</p>
</article>
</body>
</html>

+ 13
- 0
test/fixtures/rel_me_null.html View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Blog</title>
</head>
<body>
<article>
<h1>Lorem ipsum</h1>
<p>Lorem ipsum dolor sit ameph, …</p>
</article>
</body>
</html>

+ 40
- 74
test/formatter_test.exs View File

@@ -21,22 +21,16 @@ defmodule Pleroma.FormatterTest do
expected_text = expected_text =
"I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>" "I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"


tags = Formatter.parse_tags(text)

assert expected_text ==
Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize()
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end end


test "does not turn html characters to tags" do test "does not turn html characters to tags" do
text = "Fact #3: pleroma does what mastodon't"
text = "#fact_3: pleroma does what mastodon't"


expected_text = expected_text =
"Fact <a class='hashtag' data-tag='3' href='http://localhost:4001/tag/3' rel='tag'>#3</a>: pleroma does what mastodon't"

tags = Formatter.parse_tags(text)
"<a class='hashtag' data-tag='fact_3' href='http://localhost:4001/tag/fact_3' rel='tag'>#fact_3</a>: pleroma does what mastodon't"


assert expected_text ==
Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize()
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end end
end end


@@ -47,79 +41,79 @@ defmodule Pleroma.FormatterTest do
expected = expected =
"Hey, check out <a href=\"https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla\">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ." "Hey, check out <a href=\"https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla\">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ."


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://mastodon.social/@lambadalambda" text = "https://mastodon.social/@lambadalambda"


expected = expected =
"<a href=\"https://mastodon.social/@lambadalambda\">https://mastodon.social/@lambadalambda</a>" "<a href=\"https://mastodon.social/@lambadalambda\">https://mastodon.social/@lambadalambda</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://mastodon.social:4000/@lambadalambda" text = "https://mastodon.social:4000/@lambadalambda"


expected = expected =
"<a href=\"https://mastodon.social:4000/@lambadalambda\">https://mastodon.social:4000/@lambadalambda</a>" "<a href=\"https://mastodon.social:4000/@lambadalambda\">https://mastodon.social:4000/@lambadalambda</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "@lambadalambda" text = "@lambadalambda"
expected = "@lambadalambda" expected = "@lambadalambda"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "http://www.cs.vu.nl/~ast/intel/" text = "http://www.cs.vu.nl/~ast/intel/"
expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">http://www.cs.vu.nl/~ast/intel/</a>" expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">http://www.cs.vu.nl/~ast/intel/</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"


expected = expected =
"<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>" "<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"


expected = expected =
"<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>" "<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://www.google.co.jp/search?q=Nasim+Aghdam" text = "https://www.google.co.jp/search?q=Nasim+Aghdam"


expected = expected =
"<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>" "<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://en.wikipedia.org/wiki/Duff's_device" text = "https://en.wikipedia.org/wiki/Duff's_device"


expected = expected =
"<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">https://en.wikipedia.org/wiki/Duff's_device</a>" "<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">https://en.wikipedia.org/wiki/Duff's_device</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "https://pleroma.com https://pleroma.com/sucks" text = "https://pleroma.com https://pleroma.com/sucks"


expected = expected =
"<a href=\"https://pleroma.com\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\">https://pleroma.com/sucks</a>" "<a href=\"https://pleroma.com\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\">https://pleroma.com/sucks</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = "xmpp:contact@hacktivis.me" text = "xmpp:contact@hacktivis.me"


expected = "<a href=\"xmpp:contact@hacktivis.me\">xmpp:contact@hacktivis.me</a>" expected = "<a href=\"xmpp:contact@hacktivis.me\">xmpp:contact@hacktivis.me</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)


text = text =
"magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com" "magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"


expected = "<a href=\"#{text}\">#{text}</a>" expected = "<a href=\"#{text}\">#{text}</a>"


assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected
assert {^expected, [], []} = Formatter.linkify(text)
end end
end end


@@ -136,12 +130,9 @@ defmodule Pleroma.FormatterTest do


archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})


mentions = Pleroma.Formatter.parse_mentions(text)

{subs, text} = Formatter.add_user_links({[], text}, mentions)
{text, mentions, []} = Formatter.linkify(text)


assert length(subs) == 3
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
assert length(mentions) == 3


expected_text = expected_text =
"<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{ "<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{
@@ -152,7 +143,7 @@ defmodule Pleroma.FormatterTest do
archaeme_remote.id archaeme_remote.id
}' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>" }' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"


assert expected_text == Formatter.finalize({subs, text})
assert expected_text == text
end end


test "gives a replacement for user links when the user is using Osada" do test "gives a replacement for user links when the user is using Osada" do
@@ -160,48 +151,35 @@ defmodule Pleroma.FormatterTest do


text = "@mike@osada.macgirvin.com test" text = "@mike@osada.macgirvin.com test"


mentions = Formatter.parse_mentions(text)
{text, mentions, []} = Formatter.linkify(text)


{subs, text} = Formatter.add_user_links({[], text}, mentions)

assert length(subs) == 1
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
assert length(mentions) == 1


expected_text = expected_text =
"<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test" "<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"


assert expected_text == Formatter.finalize({subs, text})
assert expected_text == text
end end


test "gives a replacement for single-character local nicknames" do test "gives a replacement for single-character local nicknames" do
text = "@o hi" text = "@o hi"
o = insert(:user, %{nickname: "o"}) o = insert(:user, %{nickname: "o"})


mentions = Formatter.parse_mentions(text)

{subs, text} = Formatter.add_user_links({[], text}, mentions)
{text, mentions, []} = Formatter.linkify(text)


assert length(subs) == 1
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
assert length(mentions) == 1


expected_text = expected_text =
"<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi" "<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"


assert expected_text == Formatter.finalize({subs, text})
assert expected_text == text
end end


test "does not give a replacement for single-character local nicknames who don't exist" do test "does not give a replacement for single-character local nicknames who don't exist" do
text = "@a hi" text = "@a hi"


mentions = Formatter.parse_mentions(text)

{subs, text} = Formatter.add_user_links({[], text}, mentions)

assert Enum.empty?(subs)
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)

expected_text = "@a hi" expected_text = "@a hi"
assert expected_text == Formatter.finalize({subs, text})
assert {^expected_text, [] = _mentions, [] = _tags} = Formatter.linkify(text)
end end
end end


@@ -209,14 +187,14 @@ defmodule Pleroma.FormatterTest do
test "parses tags in the text" do test "parses tags in the text" do
text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。" text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。"


expected = [
expected_tags = [
{"#Test", "test"}, {"#Test", "test"},
{"#working", "working"}, {"#working", "working"},
{"#漢字", "漢字"},
{"#は", "は"}
{"#は", "は"},
{"#漢字", "漢字"}
] ]


assert Formatter.parse_tags(text) == expected
assert {_text, [], ^expected_tags} = Formatter.linkify(text)
end end
end end


@@ -230,15 +208,15 @@ defmodule Pleroma.FormatterTest do
archaeme = insert(:user, %{nickname: "archaeme"}) archaeme = insert(:user, %{nickname: "archaeme"})
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})


expected_result = [
{"@gsimg", gsimg},
expected_mentions = [
{"@archaeme", archaeme}, {"@archaeme", archaeme},
{"@archaeme@archae.me", archaeme_remote}, {"@archaeme@archae.me", archaeme_remote},
{"@o", o},
{"@jimm", jimm}
{"@gsimg", gsimg},
{"@jimm", jimm},
{"@o", o}
] ]


assert Formatter.parse_mentions(text) == expected_result
assert {_text, ^expected_mentions, []} = Formatter.linkify(text)
end end


test "it adds cool emoji" do test "it adds cool emoji" do
@@ -281,22 +259,10 @@ defmodule Pleroma.FormatterTest do
assert Formatter.get_emoji(text) == [] assert Formatter.get_emoji(text) == []
end end


describe "/mentions_escape" do
test "it returns text with escaped mention names" do
text = """
@a_breakin_glass@cybre.space
(also, little voice inside my head thinking "maybe this will encourage people
pronouncing it properly instead of saying _raKEWdo_ ")
"""

escape_text = """
@a\\_breakin\\_glass@cybre\\.space
(also, little voice inside my head thinking \"maybe this will encourage people
pronouncing it properly instead of saying _raKEWdo_ \")
"""

mentions = [{"@a_breakin_glass@cybre.space", %{}}]
assert Formatter.mentions_escape(text, mentions) == escape_text
end
test "it escapes HTML in plain text" do
text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"
expected = "hello &amp; world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"

assert Formatter.html_escape(text, "text/plain") == expected
end end
end end

+ 47
- 2
test/user_test.exs View File

@@ -50,6 +50,34 @@ defmodule Pleroma.UserTest do
assert expected_followers_collection == User.ap_followers(user) assert expected_followers_collection == User.ap_followers(user)
end end


test "returns all pending follow requests" do
unlocked = insert(:user)
locked = insert(:user, %{info: %{locked: true}})
follower = insert(:user)

Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => unlocked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => locked.id})

assert {:ok, []} = User.get_follow_requests(unlocked)
assert {:ok, [activity]} = User.get_follow_requests(locked)

assert activity
end

test "doesn't return already accepted or duplicate follow requests" do
locked = insert(:user, %{info: %{locked: true}})
pending_follower = insert(:user)
accepted_follower = insert(:user)

Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
User.maybe_follow(accepted_follower, locked)

assert {:ok, [activity]} = User.get_follow_requests(locked)
assert activity
end

test "follow_all follows mutliple users" do test "follow_all follows mutliple users" do
user = insert(:user) user = insert(:user)
followed_zero = insert(:user) followed_zero = insert(:user)
@@ -901,7 +929,8 @@ defmodule Pleroma.UserTest do
{:ok, follower} = User.follow(follower, u1) {:ok, follower} = User.follow(follower, u1)
{:ok, u1} = User.follow(u1, friend) {:ok, u1} = User.follow(u1, friend)


assert [friend.id, follower.id, u2.id] == Enum.map(User.search("doe", false, u1), & &1.id)
assert [friend.id, follower.id, u2.id] --
Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
end end


test "finds a user whose name is nil" do test "finds a user whose name is nil" do
@@ -923,7 +952,7 @@ defmodule Pleroma.UserTest do
end end


test "works with URIs" do test "works with URIs" do
results = User.search("http://mastodon.example.org/users/admin", true)
results = User.search("http://mastodon.example.org/users/admin", resolve: true)
result = results |> List.first() result = results |> List.first()


user = User.get_by_ap_id("http://mastodon.example.org/users/admin") user = User.get_by_ap_id("http://mastodon.example.org/users/admin")
@@ -1025,6 +1054,22 @@ defmodule Pleroma.UserTest do


assert expected_text == User.parse_bio(bio, user) assert expected_text == User.parse_bio(bio, user)
end end

test "Adds rel=me on linkbacked urls" do
user = insert(:user, ap_id: "http://social.example.org/users/lain")

bio = "http://example.org/rel_me/null"
expected_text = "<a href=\"#{bio}\">#{bio}</a>"
assert expected_text == User.parse_bio(bio, user)

bio = "http://example.org/rel_me/link"
expected_text = "<a href=\"#{bio}\">#{bio}</a>"
assert expected_text == User.parse_bio(bio, user)

bio = "http://example.org/rel_me/anchor"
expected_text = "<a href=\"#{bio}\">#{bio}</a>"
assert expected_text == User.parse_bio(bio, user)
end
end end


test "bookmarks" do test "bookmarks" do


+ 58
- 0
test/web/activity_pub/activity_pub_test.exs View File

@@ -55,6 +55,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id}) ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})


assert activities == [public_activity] assert activities == [public_activity]

activities =
ActivityPub.fetch_activities([], %{
:visibility => ~w[private public],
"actor_id" => user.ap_id
})

assert activities == [public_activity, private_activity]
end end
end end


@@ -205,6 +213,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.actor == user.ap_id assert activity.actor == user.ap_id
assert activity.recipients == ["user1", "user2", user.ap_id] assert activity.recipients == ["user1", "user2", user.ap_id]
end end

test "increases user note count only for public activities" do
user = insert(:user)

{:ok, _} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "1", "visibility" => "public"})

{:ok, _} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "unlisted"})

{:ok, _} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "private"})

{:ok, _} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "3", "visibility" => "direct"})

user = Repo.get(User, user.id)
assert user.info.note_count == 2
end
end end


describe "fetch activities for recipients" do describe "fetch activities for recipients" do
@@ -291,6 +318,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_three)
refute Enum.member?(activities, activity_one) refute Enum.member?(activities, activity_one)


# Calling with 'with_muted' will deliver muted activities, too.
activities = ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})

assert Enum.member?(activities, activity_two)
assert Enum.member?(activities, activity_three)
assert Enum.member?(activities, activity_one)

{:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})


activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
@@ -633,6 +667,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do


assert Repo.get(Object, object.id).data["type"] == "Tombstone" assert Repo.get(Object, object.id).data["type"] == "Tombstone"
end end

test "decrements user note count only for public activities" do
user = insert(:user, info: %{note_count: 10})

{:ok, a1} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "public"})

{:ok, a2} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "unlisted"})

{:ok, a3} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "private"})

{:ok, a4} =
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "direct"})

{:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
{:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
{:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
{:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()

user = Repo.get(User, user.id)
assert user.info.note_count == 10
end
end end


describe "timeline post-processing" do describe "timeline post-processing" do


+ 98
- 0
test/web/activity_pub/visibilty_test.exs View File

@@ -0,0 +1,98 @@
defmodule Pleroma.Web.ActivityPub.VisibilityTest do
use Pleroma.DataCase

alias Pleroma.Web.CommonAPI
alias Pleroma.Web.ActivityPub.Visibility
import Pleroma.Factory

setup do
user = insert(:user)
mentioned = insert(:user)
following = insert(:user)
unrelated = insert(:user)
{:ok, following} = Pleroma.User.follow(following, user)

{:ok, public} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"})

{:ok, private} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"})

{:ok, direct} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"})

{:ok, unlisted} =
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"})

%{
public: public,
private: private,
direct: direct,
unlisted: unlisted,
user: user,
mentioned: mentioned,
following: following,
unrelated: unrelated
}
end

test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
assert Visibility.is_direct?(direct)
refute Visibility.is_direct?(public)
refute Visibility.is_direct?(private)
refute Visibility.is_direct?(unlisted)
end

test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
refute Visibility.is_public?(direct)
assert Visibility.is_public?(public)
refute Visibility.is_public?(private)
assert Visibility.is_public?(unlisted)
end

test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do
refute Visibility.is_private?(direct)
refute Visibility.is_private?(public)
assert Visibility.is_private?(private)
refute Visibility.is_private?(unlisted)
end

test "visible_for_user?", %{
public: public,
private: private,
direct: direct,
unlisted: unlisted,
user: user,
mentioned: mentioned,
following: following,
unrelated: unrelated
} do
# All visible to author

assert Visibility.visible_for_user?(public, user)
assert Visibility.visible_for_user?(private, user)
assert Visibility.visible_for_user?(unlisted, user)
assert Visibility.visible_for_user?(direct, user)

# All visible to a mentioned user

assert Visibility.visible_for_user?(public, mentioned)
assert Visibility.visible_for_user?(private, mentioned)
assert Visibility.visible_for_user?(unlisted, mentioned)
assert Visibility.visible_for_user?(direct, mentioned)

# DM not visible for just follower

assert Visibility.visible_for_user?(public, following)
assert Visibility.visible_for_user?(private, following)
assert Visibility.visible_for_user?(unlisted, following)
refute Visibility.visible_for_user?(direct, following)

# Public and unlisted visible for unrelated user

assert Visibility.visible_for_user?(public, unrelated)
assert Visibility.visible_for_user?(unlisted, unrelated)
refute Visibility.visible_for_user?(private, unrelated)
refute Visibility.visible_for_user?(direct, unrelated)
end
end

+ 150
- 0
test/web/admin_api/admin_api_controller_test.exs View File

@@ -330,4 +330,154 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do


assert conn.status == 200 assert conn.status == 200
end end

describe "GET /api/pleroma/admin/users" do
test "renders users array for the first page" do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user)

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users?page=1")

assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => [
%{
"deactivated" => admin.info.deactivated,
"id" => admin.id,
"nickname" => admin.nickname
},
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname
}
]
}
end

test "renders empty array for the second page" do
admin = insert(:user, info: %{is_admin: true})
insert(:user)

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users?page=2")

assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => []
}
end
end

test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user)

conn =
build_conn()
|> assign(:user, admin)
|> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")

assert json_response(conn, 200) ==
%{
"deactivated" => !user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname
}
end

describe "GET /api/pleroma/admin/users/search" do
test "regular search" do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user, nickname: "bob")

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users/search?query=bo")

assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname
}
]
}
end

test "regular search with page size" do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user, nickname: "bob")
user2 = insert(:user, nickname: "bo")

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users/search?query=bo&page_size=1&page=1")

assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname
}
]
}

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users/search?query=bo&page_size=1&page=2")

assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [
%{
"deactivated" => user2.info.deactivated,
"id" => user2.id,
"nickname" => user2.nickname
}
]
}
end

test "only local users" do
admin = insert(:user, info: %{is_admin: true}, nickname: "john")
user = insert(:user, nickname: "bob")

insert(:user, nickname: "bobb", local: false)

conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users/search?query=bo&local=true")

assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname
}
]
}
end
end
end end

+ 37
- 7
test/web/common_api/common_api_utils_test.exs View File

@@ -57,19 +57,19 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
assert expected == Utils.emoji_from_profile(user) assert expected == Utils.emoji_from_profile(user)
end end


describe "format_input/4" do
describe "format_input/3" do
test "works for bare text/plain" do test "works for bare text/plain" do
text = "hello world!" text = "hello world!"
expected = "hello world!" expected = "hello world!"


output = Utils.format_input(text, [], [], "text/plain")
{output, [], []} = Utils.format_input(text, "text/plain")


assert output == expected assert output == expected


text = "hello world!\n\nsecond paragraph!" text = "hello world!\n\nsecond paragraph!"
expected = "hello world!<br><br>second paragraph!" expected = "hello world!<br><br>second paragraph!"


output = Utils.format_input(text, [], [], "text/plain")
{output, [], []} = Utils.format_input(text, "text/plain")


assert output == expected assert output == expected
end end
@@ -78,14 +78,14 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
text = "<p>hello world!</p>" text = "<p>hello world!</p>"
expected = "<p>hello world!</p>" expected = "<p>hello world!</p>"


output = Utils.format_input(text, [], [], "text/html")
{output, [], []} = Utils.format_input(text, "text/html")


assert output == expected assert output == expected


text = "<p>hello world!</p>\n\n<p>second paragraph</p>" text = "<p>hello world!</p>\n\n<p>second paragraph</p>"
expected = "<p>hello world!</p>\n\n<p>second paragraph</p>" expected = "<p>hello world!</p>\n\n<p>second paragraph</p>"


output = Utils.format_input(text, [], [], "text/html")
{output, [], []} = Utils.format_input(text, "text/html")


assert output == expected assert output == expected
end end
@@ -94,14 +94,44 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
text = "**hello world**" text = "**hello world**"
expected = "<p><strong>hello world</strong></p>\n" expected = "<p><strong>hello world</strong></p>\n"


output = Utils.format_input(text, [], [], "text/markdown")
{output, [], []} = Utils.format_input(text, "text/markdown")


assert output == expected assert output == expected


text = "**hello world**\n\n*another paragraph*" text = "**hello world**\n\n*another paragraph*"
expected = "<p><strong>hello world</strong></p>\n<p><em>another paragraph</em></p>\n" expected = "<p><strong>hello world</strong></p>\n<p><em>another paragraph</em></p>\n"


output = Utils.format_input(text, [], [], "text/markdown")
{output, [], []} = Utils.format_input(text, "text/markdown")

assert output == expected

text = """
> cool quote

by someone
"""

expected = "<blockquote><p>cool quote</p>\n</blockquote>\n<p>by someone</p>\n"

{output, [], []} = Utils.format_input(text, "text/markdown")

assert output == expected
end

test "works for text/markdown with mentions" do
{:ok, user} =
UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"})

text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"

expected =
"<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{
user.id
}\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{
user.id
}\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n"

{output, _, _} = Utils.format_input(text, "text/markdown")


assert output == expected assert output == expected
end end


+ 64
- 2
test/web/mastodon_api/account_view_test.exs View File

@@ -63,7 +63,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
confirmation_pending: false, confirmation_pending: false,
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false
is_moderator: false,
relationship: %{}
} }
} }


@@ -106,7 +107,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
confirmation_pending: false, confirmation_pending: false,
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false
is_moderator: false,
relationship: %{}
} }
} }


@@ -148,4 +150,64 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do


assert expected == AccountView.render("relationship.json", %{user: user, target: other_user}) assert expected == AccountView.render("relationship.json", %{user: user, target: other_user})
end end

test "represent an embedded relationship" do
user =
insert(:user, %{
info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}},
nickname: "shp@shitposter.club",
inserted_at: ~N[2017-08-15 15:47:06.597036]
})

other_user = insert(:user)

{:ok, other_user} = User.follow(other_user, user)
{:ok, other_user} = User.block(other_user, user)

expected = %{
id: to_string(user.id),
username: "shp",
acct: user.nickname,
display_name: user.name,
locked: false,
created_at: "2017-08-15T15:47:06.000Z",
followers_count: 3,
following_count: 0,
statuses_count: 5,
note: user.bio,
url: user.ap_id,
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
header: "http://localhost:4001/images/banner.png",
header_static: "http://localhost:4001/images/banner.png",
emojis: [],
fields: [],
bot: true,
source: %{
note: "",
privacy: "public",
sensitive: false
},
pleroma: %{
confirmation_pending: false,
tags: [],
is_admin: false,
is_moderator: false,
relationship: %{
id: to_string(user.id),
following: false,
followed_by: false,
blocking: true,
muting: false,
muting_notifications: false,
requested: false,
domain_blocking: false,
showing_reblogs: false,
endorsed: false
}
}
}

assert expected == AccountView.render("account.json", %{user: user, for: other_user})
end
end end

+ 12
- 4
test/web/mastodon_api/mastodon_api_controller_test.exs View File

@@ -946,7 +946,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
other_user = Repo.get(User, other_user.id) other_user = Repo.get(User, other_user.id)


assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
assert user.info.follow_request_count == 1


conn = conn =
build_conn() build_conn()
@@ -960,7 +959,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
other_user = Repo.get(User, other_user.id) other_user = Repo.get(User, other_user.id)


assert User.following?(other_user, user) == true assert User.following?(other_user, user) == true
assert user.info.follow_request_count == 0
end end


test "verify_credentials", %{conn: conn} do test "verify_credentials", %{conn: conn} do
@@ -982,7 +980,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)


user = Repo.get(User, user.id) user = Repo.get(User, user.id)
assert user.info.follow_request_count == 1


conn = conn =
build_conn() build_conn()
@@ -996,7 +993,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
other_user = Repo.get(User, other_user.id) other_user = Repo.get(User, other_user.id)


assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
assert user.info.follow_request_count == 0
end end
end end


@@ -1744,6 +1740,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
} }
} }


# works with private posts
{:ok, activity} =
CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})

response_two =
conn
|> assign(:user, user)
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(200)

assert response_two == response

Pleroma.Config.put([:rich_media, :enabled], false) Pleroma.Config.put([:rich_media, :enabled], false)
end end
end end


+ 16
- 0
test/web/mastodon_api/status_view_test.exs View File

@@ -126,6 +126,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert status == expected assert status == expected
end end


test "tells if the message is muted for some reason" do
user = insert(:user)
other_user = insert(:user)

{:ok, user} = User.mute(user, other_user)

{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = StatusView.render("status.json", %{activity: activity})

assert status.muted == false

status = StatusView.render("status.json", %{activity: activity, for: user})

assert status.muted == true
end

test "a reply" do test "a reply" do
note = insert(:note_activity) note = insert(:note_activity)
user = insert(:user) user = insert(:user)


+ 7
- 5
test/web/oauth/oauth_controller_test.exs View File

@@ -165,10 +165,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do


test "issues a token for request with HTTP basic auth client credentials" do test "issues a token for request with HTTP basic auth client credentials" do
user = insert(:user) user = insert(:user)
app = insert(:oauth_app, scopes: ["scope1", "scope2"])
app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])


{:ok, auth} = Authorization.create_authorization(app, user, ["scope2"])
assert auth.scopes == ["scope2"]
{:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
assert auth.scopes == ["scope1", "scope2"]


app_encoded = app_encoded =
(URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret)) (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
@@ -183,11 +183,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"redirect_uri" => app.redirect_uris "redirect_uri" => app.redirect_uris
}) })


assert %{"access_token" => token} = json_response(conn, 200)
assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)

assert scope == "scope1 scope2"


token = Repo.get_by(Token, token: token) token = Repo.get_by(Token, token: token)
assert token assert token
assert token.scopes == ["scope2"]
assert token.scopes == ["scope1", "scope2"]
end end


test "rejects token exchange with invalid client credentials" do test "rejects token exchange with invalid client credentials" do


+ 55
- 0
test/web/rel_me_test.exs View File

@@ -0,0 +1,55 @@
defmodule Pleroma.Web.RelMeTest do
use ExUnit.Case, async: true

setup do
Tesla.Mock.mock(fn
%{
method: :get,
url: "http://example.com/rel_me/anchor"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")}

%{
method: :get,
url: "http://example.com/rel_me/link"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")}

%{
method: :get,
url: "http://example.com/rel_me/null"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}
end)

:ok
end

test "parse/1" do
hrefs = ["https://social.example.org/users/lain"]

assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/null") == {:ok, []}
assert {:error, _} = Pleroma.Web.RelMe.parse("http://example.com/rel_me/error")

assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/link") == {:ok, hrefs}
assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor") == {:ok, hrefs}
end

test "maybe_put_rel_me/2" do
profile_urls = ["https://social.example.org/users/lain"]
attr = "me"
fallback = nil

assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/null", profile_urls) ==
fallback

assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/error", profile_urls) ==
fallback

assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/anchor", profile_urls) ==
attr

assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/link", profile_urls) ==
attr
end
end

+ 1
- 42
test/web/twitter_api/representers/activity_representer_test.exs View File

@@ -13,36 +13,6 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.TwitterAPI.UserView
import Pleroma.Factory import Pleroma.Factory


test "an announce activity" do
user = insert(:user)
note_activity = insert(:note_activity)
activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"])
object = Object.get_by_ap_id(note_activity.data["object"]["id"])

{:ok, announce_activity, _object} = ActivityPub.announce(user, object)
note_activity = Activity.get_by_ap_id(note_activity.data["id"])

status =
ActivityRepresenter.to_map(announce_activity, %{
users: [user, activity_actor],
announced_activity: note_activity,
for: user
})

assert status["id"] == announce_activity.id
assert status["user"] == UserView.render("show.json", %{user: user, for: user})

retweeted_status =
ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user})

assert retweeted_status["repeated"] == true
assert retweeted_status["id"] == note_activity.id
assert status["statusnet_conversation_id"] == retweeted_status["statusnet_conversation_id"]

assert status["retweeted_status"] == retweeted_status
assert status["activity_type"] == "repeat"
end

test "a like activity" do test "a like activity" do
user = insert(:user) user = insert(:user)
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
@@ -168,6 +138,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
"uri" => activity.data["object"]["id"], "uri" => activity.data["object"]["id"],
"visibility" => "direct", "visibility" => "direct",
"card" => nil, "card" => nil,
"muted" => false,
"summary" => "2hu :2hu:", "summary" => "2hu :2hu:",
"summary_html" => "summary_html" =>
"2hu <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" />" "2hu <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" />"
@@ -180,18 +151,6 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
}) == expected_status }) == expected_status
end end


test "an undo for a follow" do
follower = insert(:user)
followed = insert(:user)

{:ok, _follow} = ActivityPub.follow(follower, followed)
{:ok, unfollow} = ActivityPub.unfollow(follower, followed)

map = ActivityRepresenter.to_map(unfollow, %{user: follower})
assert map["is_post_verb"] == false
assert map["activity_type"] == "undo"
end

test "a delete activity" do test "a delete activity" do
object = insert(:note) object = insert(:note)
user = User.get_by_ap_id(object.data["actor"]) user = User.get_by_ap_id(object.data["actor"])


+ 21
- 6
test/web/twitter_api/twitter_api_controller_test.exs View File

@@ -427,7 +427,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do


test "with credentials", %{conn: conn, user: current_user} do test "with credentials", %{conn: conn, user: current_user} do
{:ok, activity} = {:ok, activity} =
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: current_user})
CommonAPI.post(current_user, %{
"status" => "why is tenshi eating a corndog so cute?",
"visibility" => "public"
})


conn = conn =
conn conn
@@ -445,6 +448,23 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
mentioned: [current_user] mentioned: [current_user]
}) })
end end

test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do
{:ok, _activity} =
CommonAPI.post(current_user, %{
"status" => "Have you guys ever seen how cute tenshi eating a corndog is?",
"visibility" => "direct"
})

conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/mentions.json")

response = json_response(conn, 200)

assert length(response) == 0
end
end end


describe "GET /api/qvitter/statuses/notifications.json" do describe "GET /api/qvitter/statuses/notifications.json" do
@@ -670,7 +690,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
followed = Repo.get(User, followed.id) followed = Repo.get(User, followed.id)


refute User.ap_followers(followed) in current_user.following refute User.ap_followers(followed) in current_user.following
assert followed.info.follow_request_count == 1


assert json_response(conn, 200) == assert json_response(conn, 200) ==
UserView.render("show.json", %{user: followed, for: current_user}) UserView.render("show.json", %{user: followed, for: current_user})
@@ -1737,7 +1756,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
other_user = Repo.get(User, other_user.id) other_user = Repo.get(User, other_user.id)


assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
assert user.info.follow_request_count == 1


conn = conn =
build_conn() build_conn()
@@ -1749,7 +1767,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert relationship = json_response(conn, 200) assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"] assert other_user.id == relationship["id"]
assert relationship["follows_you"] == true assert relationship["follows_you"] == true
assert user.info.follow_request_count == 0
end end
end end


@@ -1764,7 +1781,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
other_user = Repo.get(User, other_user.id) other_user = Repo.get(User, other_user.id)


assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
assert user.info.follow_request_count == 1


conn = conn =
build_conn() build_conn()
@@ -1776,7 +1792,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert relationship = json_response(conn, 200) assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"] assert other_user.id == relationship["id"]
assert relationship["follows_you"] == false assert relationship["follows_you"] == false
assert user.info.follow_request_count == 0
end end
end end




+ 18
- 1
test/web/twitter_api/views/activity_view_test.exs View File

@@ -56,6 +56,22 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
assert result["user"]["id"] == user.id assert result["user"]["id"] == user.id
end end


test "tells if the message is muted for some reason" do
user = insert(:user)
other_user = insert(:user)

{:ok, user} = User.mute(user, other_user)

{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = ActivityView.render("activity.json", %{activity: activity})

assert status["muted"] == false

status = ActivityView.render("activity.json", %{activity: activity, for: user})

assert status["muted"] == true
end

test "a create activity with a html status" do test "a create activity with a html status" do
text = """ text = """
#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg
@@ -149,7 +165,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
"uri" => activity.data["object"]["id"], "uri" => activity.data["object"]["id"],
"user" => UserView.render("show.json", %{user: user}), "user" => UserView.render("show.json", %{user: user}),
"visibility" => "direct", "visibility" => "direct",
"card" => nil
"card" => nil,
"muted" => false
} }


assert result == expected assert result == expected


+ 7
- 0
test/web/twitter_api/views/user_view_test.exs View File

@@ -239,6 +239,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
assert represented["role"] == nil assert represented["role"] == nil
end end


test "A regular user for the admin", %{user: user} do
admin = insert(:user, %{info: %{is_admin: true}})
represented = UserView.render("show.json", %{user: user, for: admin})

assert represented["pleroma"]["deactivated"] == false
end

test "A blocked user for the blocker" do test "A blocked user for the blocker" do
user = insert(:user) user = insert(:user)
blocker = insert(:user) blocker = insert(:user)


Loading…
Cancel
Save