Merge branch 'develop' into feature/compat/push-subscriptions
# Conflicts: # lib/mix/tasks/sample_config.eex # lib/pleroma/web/twitter_api/controllers/util_controller.ex # mix.exs # mix.lock
This commit is contained in:
commit
8b4397c704
@ -1,4 +1,4 @@
|
||||
image: elixir:1.6.4
|
||||
image: elixir:1.7.2
|
||||
|
||||
services:
|
||||
- postgres:9.6.2
|
||||
@ -9,6 +9,11 @@ variables:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
DB_HOST: postgres
|
||||
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- deps
|
||||
- _build
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
|
106
CONFIGURATION.md
106
CONFIGURATION.md
@ -1,106 +0,0 @@
|
||||
# Configuring Pleroma
|
||||
|
||||
In the `config/` directory, you will find the following relevant files:
|
||||
|
||||
* `config.exs`: default base configuration
|
||||
* `dev.exs`: default additional configuration for `MIX_ENV=dev`
|
||||
* `prod.exs`: default additional configuration for `MIX_ENV=prod`
|
||||
|
||||
|
||||
Do not modify files in the list above.
|
||||
Instead, overload the settings by editing the following files:
|
||||
|
||||
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
|
||||
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
|
||||
|
||||
## Uploads configuration
|
||||
|
||||
To configure where to upload files, and wether or not
|
||||
you want to remove automatically EXIF data from pictures
|
||||
being uploaded.
|
||||
|
||||
config :pleroma, Pleroma.Upload,
|
||||
uploads: "uploads",
|
||||
strip_exif: false
|
||||
|
||||
* `uploads`: where to put the uploaded files, relative to pleroma's main directory.
|
||||
* `strip_exif`: whether or not to remove EXIF data from uploaded pics automatically.
|
||||
This needs Imagemagick installed on the system ( apt install imagemagick ).
|
||||
|
||||
|
||||
## Block functionality
|
||||
|
||||
config :pleroma, :activitypub,
|
||||
accept_blocks: true,
|
||||
unfollow_blocked: true,
|
||||
outgoing_blocks: true
|
||||
|
||||
config :pleroma, :user, deny_follow_blocked: true
|
||||
|
||||
* `accept_blocks`: whether to accept incoming block activities from
|
||||
other instances
|
||||
* `unfollow_blocked`: whether blocks result in people getting
|
||||
unfollowed
|
||||
* `outgoing_blocks`: whether to federate blocks to other instances
|
||||
* `deny_follow_blocked`: whether to disallow following an account that
|
||||
has blocked the user in question
|
||||
|
||||
## Message Rewrite Filters (MRFs)
|
||||
|
||||
Modify incoming and outgoing posts.
|
||||
|
||||
config :pleroma, :instance,
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy
|
||||
|
||||
`rewrite_policy` specifies which MRF policies to apply.
|
||||
It can either be a single policy or a list of policies.
|
||||
Currently, MRFs availible by default are:
|
||||
|
||||
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`
|
||||
|
||||
Some policies, such as SimplePolicy and RejectNonPublic,
|
||||
can be additionally configured in their respective sections.
|
||||
|
||||
### NoOpPolicy
|
||||
|
||||
Does not modify posts (this is the default `rewrite_policy`)
|
||||
|
||||
### DropPolicy
|
||||
|
||||
Drops all posts.
|
||||
It generally does not make sense to use this in production.
|
||||
|
||||
### SimplePolicy
|
||||
|
||||
Restricts the visibility of posts from certain instances.
|
||||
|
||||
config :pleroma, :mrf_simple,
|
||||
media_removal: [],
|
||||
media_nsfw: [],
|
||||
federated_timeline_removal: [],
|
||||
reject: [],
|
||||
accept: []
|
||||
|
||||
* `media_removal`: posts from these instances will have attachments
|
||||
removed
|
||||
* `media_nsfw`: posts from these instances will have attachments marked
|
||||
as nsfw
|
||||
* `federated_timeline_removal`: posts from these instances will be
|
||||
marked as unlisted
|
||||
* `reject`: posts from these instances will be dropped
|
||||
* `accept`: if not empty, only posts from these instances will be accepted
|
||||
|
||||
### RejectNonPublic
|
||||
|
||||
Drops posts with non-public visibility settings.
|
||||
|
||||
config :pleroma :mrf_rejectnonpublic
|
||||
allow_followersonly: false,
|
||||
allow_direct: false,
|
||||
|
||||
* `allow_followersonly`: whether to allow follower-only posts through
|
||||
the filter
|
||||
* `allow_direct`: whether to allow direct messages through the filter
|
33
README.md
33
README.md
@ -2,11 +2,13 @@
|
||||
|
||||
## About Pleroma
|
||||
|
||||
Pleroma is an OStatus-compatible social networking server written in Elixir, compatible with GNU Social and Mastodon. It is high-performance and can run on small devices like a Raspberry Pi.
|
||||
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support the same federation standards (OStatus and ActivityPub). What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement either OStatus or ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
||||
|
||||
Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.
|
||||
|
||||
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
|
||||
|
||||
Mobile clients that are known to work well:
|
||||
Client applications that are known to work well:
|
||||
|
||||
* Twidere
|
||||
* Tusky
|
||||
@ -15,6 +17,7 @@ Mobile clients that are known to work well:
|
||||
* Amaroq (iOS)
|
||||
* Tootdon (Android + iOS)
|
||||
* Tootle (iOS)
|
||||
* Whalebird (Windows + Mac + Linux)
|
||||
|
||||
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org.
|
||||
|
||||
@ -36,7 +39,7 @@ While we don't provide docker files, other people have written very good ones. T
|
||||
|
||||
* Run `mix generate_config`. This will ask you a few questions about your instance and generate a configuration file in `config/generated_config.exs`. Check that and copy it to either `config/dev.secret.exs` or `config/prod.secret.exs`. It will also create a `config/setup_db.psql`; you may want to double-check this file in case you wanted a different username, or database name than the default. Then you need to run the script as PostgreSQL superuser (i.e. `sudo su postgres -c "psql -f config/setup_db.psql"`). It will create a pleroma db user, database and will setup needed extensions that need to be set up. Postgresql super-user privileges are only needed for this step.
|
||||
|
||||
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`.
|
||||
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [``config/config.md``](config/config.md)
|
||||
|
||||
* Run `mix ecto.migrate` to run the database migrations. You will have to do this again after certain updates.
|
||||
|
||||
@ -45,8 +48,6 @@ While we don't provide docker files, other people have written very good ones. T
|
||||
* The common and convenient way for adding HTTPS is by using Nginx as a reverse proxy. You can look at example Nginx configuration in `installation/pleroma.nginx`. If you need TLS/SSL certificates for HTTPS, you can look get some for free with letsencrypt: https://letsencrypt.org/
|
||||
The simplest way to obtain and install a certificate is to use [Certbot.](https://certbot.eff.org) Depending on your specific setup, certbot may be able to get a certificate and configure your web server automatically.
|
||||
|
||||
* [Not tested with system reboot yet!] You'll also want to set up Pleroma to be run as a systemd service. Example .service file can be found in `installation/pleroma.service` you can put it in `/etc/systemd/system/`.
|
||||
|
||||
## Running
|
||||
|
||||
* By default, it listens on port 4000 (TCP), so you can access it on http://localhost:4000/ (if you are on the same machine). In case of an error it will restart automatically.
|
||||
@ -55,9 +56,15 @@ While we don't provide docker files, other people have written very good ones. T
|
||||
Pleroma comes with two frontends. The first one, Pleroma FE, can be reached by normally visiting the site. The other one, based on the Mastodon project, can be found by visiting the /web path of your site.
|
||||
|
||||
### As systemd service (with provided .service file)
|
||||
Example .service file can be found in `installation/pleroma.service` you can put it in `/etc/systemd/system/`.
|
||||
Running `service pleroma start`
|
||||
Logs can be watched by using `journalctl -fu pleroma.service`
|
||||
|
||||
### As OpenRC service (with provided RC file)
|
||||
Copy ``installation/init.d/pleroma`` to ``/etc/init.d/pleroma``.
|
||||
You can add it to the services ran by default with:
|
||||
``rc-update add pleroma``
|
||||
|
||||
### Standalone/run by other means
|
||||
Run `mix phx.server` in repository's root, it will output log into stdout/stderr
|
||||
|
||||
@ -70,22 +77,6 @@ Add the following to your `dev.secret.exs` or `prod.secret.exs` if you want to p
|
||||
|
||||
This is useful for running pleroma inside Tor or i2p.
|
||||
|
||||
## Admin Tasks
|
||||
|
||||
### Register a User
|
||||
|
||||
Run `mix register_user <name> <nickname> <email> <bio> <password>`. The `name` appears on statuses, while the nickname corresponds to the user, e.g. `@nickname@instance.tld`
|
||||
|
||||
### Password reset
|
||||
|
||||
Run `mix generate_password_reset username` to generate a password reset link that you can then send to the user.
|
||||
|
||||
### Moderators
|
||||
|
||||
You can make users moderators. They will then be able to delete any post.
|
||||
|
||||
Run `mix set_moderator username [true|false]` to make user a moderator or not.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No incoming federation
|
||||
|
@ -20,17 +20,39 @@ config :pleroma, Pleroma.Uploaders.Local,
|
||||
|
||||
config :pleroma, Pleroma.Uploaders.S3,
|
||||
bucket: nil,
|
||||
public_endpoint: "https://s3.amazonaws.com"
|
||||
public_endpoint: "https://s3.amazonaws.com",
|
||||
force_media_proxy: false
|
||||
|
||||
config :pleroma, Pleroma.Uploaders.MDII,
|
||||
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
||||
files: "https://mdii.sakura.ne.jp"
|
||||
|
||||
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
|
||||
|
||||
config :pleroma, :uri_schemes, additionnal_schemes: []
|
||||
config :pleroma, :uri_schemes,
|
||||
valid_schemes: [
|
||||
"https",
|
||||
"http",
|
||||
"dat",
|
||||
"dweb",
|
||||
"gopher",
|
||||
"ipfs",
|
||||
"ipns",
|
||||
"irc",
|
||||
"ircs",
|
||||
"magnet",
|
||||
"mailto",
|
||||
"mumble",
|
||||
"ssb",
|
||||
"xmpp"
|
||||
]
|
||||
|
||||
# Configures the endpoint
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "localhost"],
|
||||
protocol: "https",
|
||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||
signing_salt: "CqaoopA2",
|
||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||
pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
|
||||
secure_cookie_flag: true
|
||||
@ -51,30 +73,32 @@ config :pleroma, :websub, Pleroma.Web.Websub
|
||||
config :pleroma, :ostatus, Pleroma.Web.OStatus
|
||||
config :pleroma, :httpoison, Pleroma.HTTP
|
||||
|
||||
version =
|
||||
with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
||||
"Pleroma #{Mix.Project.config()[:version]} #{String.trim(version)}"
|
||||
else
|
||||
_ -> "Pleroma #{Mix.Project.config()[:version]} dev"
|
||||
end
|
||||
|
||||
# Configures http settings, upstream proxy etc.
|
||||
config :pleroma, :http, proxy_url: nil
|
||||
|
||||
config :pleroma, :instance,
|
||||
version: version,
|
||||
name: "Pleroma",
|
||||
email: "example@example.com",
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
limit: 5000,
|
||||
upload_limit: 16_000_000,
|
||||
avatar_upload_limit: 2_000_000,
|
||||
background_upload_limit: 4_000_000,
|
||||
banner_upload_limit: 4_000_000,
|
||||
registrations_open: true,
|
||||
federating: true,
|
||||
allow_relay: true,
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||
public: true,
|
||||
quarantined_instances: [],
|
||||
managed_config: true
|
||||
managed_config: true,
|
||||
allowed_post_formats: [
|
||||
"text/plain",
|
||||
"text/html",
|
||||
"text/markdown"
|
||||
],
|
||||
finmoji_enabled: true,
|
||||
mrf_transparency: true
|
||||
|
||||
config :pleroma, :markup,
|
||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||
@ -98,12 +122,16 @@ config :pleroma, :fe,
|
||||
redirect_root_login: "/main/friends",
|
||||
show_instance_panel: true,
|
||||
scope_options_enabled: false,
|
||||
collapse_message_with_subject: false
|
||||
formatting_options_enabled: false,
|
||||
collapse_message_with_subject: false,
|
||||
hide_post_stats: false,
|
||||
hide_user_stats: false
|
||||
|
||||
config :pleroma, :activitypub,
|
||||
accept_blocks: true,
|
||||
unfollow_blocked: true,
|
||||
outgoing_blocks: true
|
||||
outgoing_blocks: true,
|
||||
follow_handshake_timeout: 500
|
||||
|
||||
config :pleroma, :user, deny_follow_blocked: true
|
||||
|
||||
@ -145,6 +173,27 @@ config :pleroma, :suggestions,
|
||||
limit: 23,
|
||||
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
|
||||
|
||||
config :pleroma, :http_security,
|
||||
enabled: true,
|
||||
sts: false,
|
||||
sts_max_age: 31_536_000,
|
||||
ct_max_age: 2_592_000,
|
||||
referrer_policy: "same-origin"
|
||||
|
||||
config :cors_plug,
|
||||
max_age: 86_400,
|
||||
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
|
||||
expose: [
|
||||
"Link",
|
||||
"X-RateLimit-Reset",
|
||||
"X-RateLimit-Limit",
|
||||
"X-RateLimit-Remaining",
|
||||
"X-Request-Id",
|
||||
"Idempotency-Key"
|
||||
],
|
||||
credentials: true,
|
||||
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
102
config/config.md
Normal file
102
config/config.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Configuration
|
||||
|
||||
This file describe the configuration, it is recommended to edit the relevant *.secret.exs file instead of the others founds in the ``config`` directory.
|
||||
If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.
|
||||
|
||||
## Pleroma.Upload
|
||||
* `uploader`: Select which `Pleroma.Uploaders` to use
|
||||
* `strip_exif`: boolean, uses ImageMagick(!) to strip exif.
|
||||
|
||||
## Pleroma.Uploaders.Local
|
||||
* `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory
|
||||
* `uploads_url`: The URL to access a user-uploaded file, ``{{base_url}}`` is replaced to the instance URL and ``{{file}}`` to the filename. Useful when you want to proxy the media files via another host.
|
||||
|
||||
## :uri_schemes
|
||||
* `valid_schemes`: List of the scheme part that is considered valid to be an URL
|
||||
|
||||
## :instance
|
||||
* `name`: The instance’s name
|
||||
* `email`: Email used to reach an Administrator/Moderator of the instance
|
||||
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``
|
||||
* `limit`: Posts character limit (CW/Subject included in the counter)
|
||||
* `upload_limit`: File size limit of uploads (except for avatar, background, banner)
|
||||
* `avatar_upload_limit`: File size limit of user’s profile avatars
|
||||
* `background_upload_limit`: File size limit of user’s profile backgrounds
|
||||
* `banner_upload_limit`: File size limit of user’s profile backgrounds
|
||||
* `registerations_open`: Enable registerations for anyone, invitations can be used when false.
|
||||
* `federating`
|
||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
|
||||
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
||||
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
|
||||
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production
|
||||
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See ``:mrf_simple`` section)
|
||||
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
|
||||
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
|
||||
* `finmoji_enabled`: Whenether to enable the finmojis in the custom emojis.
|
||||
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||
|
||||
## :fe
|
||||
This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:instance`` is set to false.
|
||||
|
||||
* `theme`: Which theme to use, they are defined in ``styles.json``
|
||||
* `logo`: URL of the logo, defaults to Pleroma’s logo
|
||||
* `logo_mask`: Whenether to mask the logo
|
||||
* `logo_margin`: What margin to use around the logo
|
||||
* `background`: URL of the background, unless viewing a user profile with a background that is set
|
||||
* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.
|
||||
* `redirect_root_login`: relative URL which indicates where to redirect when a user is logged in.
|
||||
* `show_instance_panel`: Whenether to show the instance’s specific panel.
|
||||
* `scope_options_enabled`: Enable setting an notice visibility and subject/CW when posting
|
||||
* `formatting_options_enabled`: Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to ``:instance, allowed_post_formats``
|
||||
* `collapse_message_with_subjects`: When a message has a subject(aka Content Warning), collapse it by default
|
||||
* `hide_post_stats`: Hide notices statistics(repeats, favorites, …)
|
||||
* `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)
|
||||
|
||||
## :mrf_simple
|
||||
* `media_removal`: List of instances to remove medias from
|
||||
* `media_nsfw`: List of instances to put medias as NSFW(sensitive) from
|
||||
* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline
|
||||
* `reject`: List of instances to reject any activities from
|
||||
* `accept`: List of instances to accept any activities from
|
||||
|
||||
## :mrf_rejectnonpublic
|
||||
* `allow_followersonly`: whether to allow followers-only posts
|
||||
* `allow_direct`: whether to allow direct messages
|
||||
|
||||
## :media_proxy
|
||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||
* `redirect_on_failure`: Use the original URL when Media Proxy fails to get it
|
||||
|
||||
## :gopher
|
||||
* `enabled`: Enables the gopher interface
|
||||
* `ip`: IP address to bind to
|
||||
* `port`: Port to bind to
|
||||
|
||||
## :activitypub
|
||||
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
||||
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
||||
|
||||
## :http_security
|
||||
* ``enabled``: Whether the managed content security policy is enabled
|
||||
* ``sts``: Whether to additionally send a `Strict-Transport-Security` header
|
||||
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
|
||||
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
||||
|
||||
## :mrf_user_allowlist
|
||||
|
||||
The keys in this section are the domain names that the policy should apply to.
|
||||
Each key should be assigned a list of users that should be allowed through by
|
||||
their ActivityPub ID.
|
||||
|
||||
An example:
|
||||
|
||||
```
|
||||
config :pleroma, :mrf_user_allowlist,
|
||||
"example.org": ["https://example.org/users/admin"]
|
||||
```
|
@ -1,4 +1,10 @@
|
||||
social.domain.tld {
|
||||
# default Caddyfile config for Pleroma
|
||||
#
|
||||
# Simple installation instructions:
|
||||
# 1. Replace 'example.tld' with your instance's domain wherever it appears.
|
||||
# 2. Copy this section into your Caddyfile and restart Caddy.
|
||||
|
||||
example.tld {
|
||||
log /var/log/caddy/pleroma_access.log
|
||||
errors /var/log/caddy/pleroma_error.log
|
||||
|
||||
@ -9,34 +15,12 @@ social.domain.tld {
|
||||
transparent
|
||||
}
|
||||
|
||||
tls user@domain.tld {
|
||||
tls {
|
||||
# Remove the rest of the lines in here, if you want to support older devices
|
||||
key_type p256
|
||||
ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256
|
||||
}
|
||||
|
||||
header / {
|
||||
X-XSS-Protection "1; mode=block"
|
||||
X-Frame-Options "DENY"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "same-origin"
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains;"
|
||||
Expect-CT "enforce, max-age=2592000"
|
||||
Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://social.domain.tld; upgrade-insecure-requests;"
|
||||
}
|
||||
|
||||
# If you do not want remote frontends to be able to access your Pleroma backend server, remove these lines.
|
||||
# If you want to allow all origins access, remove the origin lines.
|
||||
# To use this directive, you need the http.cors plugin for Caddy.
|
||||
cors / {
|
||||
origin https://halcyon.domain.tld
|
||||
origin https://pinafore.domain.tld
|
||||
methods POST,PUT,DELETE,GET,PATCH,OPTIONS
|
||||
allowed_headers Authorization,Content-Type,Idempotency-Key
|
||||
exposed_headers Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id
|
||||
}
|
||||
# Stop removing lines here.
|
||||
|
||||
# If you do not want to use the mediaproxy function, remove these lines.
|
||||
# To use this directive, you need the http.cache plugin for Caddy.
|
||||
cache {
|
||||
|
@ -1,24 +1,31 @@
|
||||
#Example configuration for when Apache httpd and Pleroma are on the same host.
|
||||
#Needed modules: headers proxy proxy_http proxy_wstunnel rewrite ssl
|
||||
#This assumes a Debian style Apache config. Put this in /etc/apache2/sites-available
|
||||
#Install your TLS certificate, possibly using Let's Encrypt.
|
||||
#Replace 'pleroma.example.com' with your instance's domain wherever it appears
|
||||
# default Apache site config for Pleroma
|
||||
#
|
||||
# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl
|
||||
#
|
||||
# Simple installation instructions:
|
||||
# 1. Install your TLS certificate, possibly using Let's Encrypt.
|
||||
# 2. Replace 'example.tld' with your instance's domain wherever it appears.
|
||||
# 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-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache.
|
||||
|
||||
ServerName pleroma.example.com
|
||||
Define servername example.tld
|
||||
|
||||
ServerName ${servername}
|
||||
ServerTokens Prod
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
<VirtualHost *:80>
|
||||
Redirect permanent / https://pleroma.example.com
|
||||
Redirect permanent / https://${servername}
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/pleroma.example.com/cert.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/pleroma.example.com/privkey.pem
|
||||
SSLCertificateChainFile /etc/letsencrypt/live/pleroma.example.com/fullchain.pem
|
||||
SSLCertificateFile /etc/letsencrypt/live/${servername}/cert.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${servername}/privkey.pem
|
||||
SSLCertificateChainFile /etc/letsencrypt/live/${servername}/fullchain.pem
|
||||
|
||||
# Mozilla modern configuration, tweak to your needs
|
||||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||
@ -27,15 +34,6 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
SSLCompression off
|
||||
SSLSessionTickets off
|
||||
|
||||
Header always set X-Xss-Protection "1; mode=block"
|
||||
Header always set X-Frame-Options "DENY"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set Referrer-Policy same-origin
|
||||
Header always set Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://pleroma.example.tld; upgrade-insecure-requests;"
|
||||
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Connection} Upgrade [NC]
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
@ -45,7 +43,7 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
ProxyPass / http://localhost:4000/
|
||||
ProxyPassReverse / http://localhost:4000/
|
||||
|
||||
RequestHeader set Host "pleroma.example.com"
|
||||
RequestHeader set Host ${servername}
|
||||
ProxyPreserveHost On
|
||||
</VirtualHost>
|
||||
|
||||
|
@ -10,8 +10,8 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac
|
||||
inactive=720m use_temp_path=off;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.tld;
|
||||
listen 80;
|
||||
return 301 https://$server_name$request_uri;
|
||||
|
||||
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure
|
||||
@ -60,28 +60,6 @@ server {
|
||||
client_max_body_size 16m;
|
||||
|
||||
location / {
|
||||
# if you do not want remote frontends to be able to access your Pleroma backend
|
||||
# server, remove these lines.
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
|
||||
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
# stop removing lines here.
|
||||
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
add_header X-Download-Options "noopen" always;
|
||||
add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://example.tld; upgrade-insecure-requests;" always;
|
||||
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
@ -6,10 +6,21 @@ After=network.target postgresql.service
|
||||
User=pleroma
|
||||
WorkingDirectory=/home/pleroma/pleroma
|
||||
Environment="HOME=/home/pleroma"
|
||||
Environment="MIX_ENV=prod"
|
||||
ExecStart=/usr/local/bin/mix phx.server
|
||||
ExecReload=/bin/kill $MAINPID
|
||||
KillMode=process
|
||||
Restart=on-failure
|
||||
|
||||
; Some security directives.
|
||||
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||
PrivateTmp=true
|
||||
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
||||
ProtectSystem=full
|
||||
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
||||
PrivateDevices=false
|
||||
; Ensures that the service process and all its children can never gain new privileges through execve().
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@ -119,13 +119,3 @@ sub vcl_pipe {
|
||||
set bereq.http.connection = req.http.connection;
|
||||
}
|
||||
}
|
||||
|
||||
sub vcl_deliver {
|
||||
set resp.http.X-Frame-Options = "DENY";
|
||||
set resp.http.X-XSS-Protection = "1; mode=block";
|
||||
set resp.http.X-Content-Type-Options = "nosniff";
|
||||
set resp.http.Referrer-Policy = "same-origin";
|
||||
set resp.http.Content-Security-Policy = "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://" + req.http.host + "; upgrade-insecure-requests;";
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# set resp.http.Strict-Transport-Security= "max-age=31536000; includeSubDomains";
|
||||
}
|
||||
|
@ -2,7 +2,13 @@ defmodule Mix.Tasks.DeactivateUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Toggle deactivation status for a user"
|
||||
@moduledoc """
|
||||
Deactivates a user (local or remote)
|
||||
|
||||
Usage: ``mix deactivate_user <nickname>``
|
||||
|
||||
Example: ``mix deactivate_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
@ -1,7 +1,15 @@
|
||||
defmodule Mix.Tasks.GenerateConfig do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generates a new config"
|
||||
@moduledoc """
|
||||
Generate a new config
|
||||
|
||||
## Usage
|
||||
``mix generate_config``
|
||||
|
||||
This mix task is interactive, and will overwrite the config present at ``config/generated_config.exs``.
|
||||
"""
|
||||
|
||||
def run(_) do
|
||||
IO.puts("Answer a few questions to generate a new config\n")
|
||||
IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
|
||||
|
@ -1,7 +1,14 @@
|
||||
defmodule Mix.Tasks.GenerateInviteToken do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generate invite token for user"
|
||||
@moduledoc """
|
||||
Generates invite token
|
||||
|
||||
This is in the form of a URL to be used by the Invited user to register themselves.
|
||||
|
||||
## Usage
|
||||
``mix generate_invite_token``
|
||||
"""
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
@ -2,7 +2,13 @@ defmodule Mix.Tasks.GeneratePasswordReset do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Generate password reset link for user"
|
||||
@moduledoc """
|
||||
Generate password reset link for user
|
||||
|
||||
Usage: ``mix generate_password_reset <nickname>``
|
||||
|
||||
Example: ``mix generate_password_reset lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
|
@ -1,9 +1,16 @@
|
||||
defmodule Mix.Tasks.SetModerator do
|
||||
@moduledoc """
|
||||
Set moderator to a local user
|
||||
|
||||
Usage: ``mix set_moderator <nickname>``
|
||||
|
||||
Example: ``mix set_moderator lain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Set moderator status"
|
||||
def run([nickname | rest]) do
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
|
19
lib/mix/tasks/reactivate_user.ex
Normal file
19
lib/mix/tasks/reactivate_user.ex
Normal file
@ -0,0 +1,19 @@
|
||||
defmodule Mix.Tasks.ReactivateUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@moduledoc """
|
||||
Reactivate a user
|
||||
|
||||
Usage: ``mix reactivate_user <nickname>``
|
||||
|
||||
Example: ``mix reactivate_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with user <- User.get_by_nickname(nickname) do
|
||||
User.deactivate(user, false)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,4 +1,12 @@
|
||||
defmodule Mix.Tasks.RegisterUser do
|
||||
@moduledoc """
|
||||
Manually register a local user
|
||||
|
||||
Usage: ``mix register_user <name> <nickname> <email> <bio> <password>``
|
||||
|
||||
Example: ``mix register_user 仮面の告白 lain lain@example.org "blushy-crushy fediverse idol + pleroma dev" pleaseDontHeckLain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
|
@ -4,12 +4,21 @@ defmodule Mix.Tasks.RelayFollow do
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Follows a remote relay"
|
||||
@moduledoc """
|
||||
Follows a remote relay
|
||||
|
||||
Usage: ``mix relay_follow <relay_url>``
|
||||
|
||||
Example: ``mix relay_follow https://example.org/relay``
|
||||
"""
|
||||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
:ok = Relay.follow(target)
|
||||
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
with {:ok, activity} <- Relay.follow(target) do
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
else
|
||||
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,13 +3,21 @@ defmodule Mix.Tasks.RelayUnfollow do
|
||||
require Logger
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Follows a remote relay"
|
||||
@moduledoc """
|
||||
Unfollows a remote relay
|
||||
|
||||
Usage: ``mix relay_follow <relay_url>``
|
||||
|
||||
Example: ``mix relay_follow https://example.org/relay``
|
||||
"""
|
||||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
:ok = Relay.unfollow(target)
|
||||
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
with {:ok, activity} <- Relay.follow(target) do
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
else
|
||||
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,12 +2,18 @@ defmodule Mix.Tasks.RmUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@shortdoc "Permanently delete a user"
|
||||
@moduledoc """
|
||||
Permanently deletes a user
|
||||
|
||||
Usage: ``mix rm_user [nickname]``
|
||||
|
||||
Example: ``mix rm_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
User.delete(user)
|
||||
{:ok, _} = User.delete(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -31,6 +31,10 @@ config :web_push_encryption, :vapid_details,
|
||||
public_key: "<%= web_push_public_key %>",
|
||||
private_key: "<%= web_push_private_key %>"
|
||||
|
||||
# Enable Strict-Transport-Security once SSL is working:
|
||||
# config :pleroma, :http_security,
|
||||
# sts: true
|
||||
|
||||
# Configure S3 support if desired.
|
||||
# The public S3 endpoint is different depending on region and provider,
|
||||
# consult your S3 provider's documentation for details on what to use.
|
||||
|
@ -1,8 +1,5 @@
|
||||
CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
|
||||
-- in case someone runs this second time accidentally
|
||||
ALTER USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB;
|
||||
CREATE DATABASE pleroma_dev;
|
||||
ALTER DATABASE pleroma_dev OWNER TO pleroma;
|
||||
CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>';
|
||||
CREATE DATABASE pleroma_dev OWNER pleroma;
|
||||
\c pleroma_dev;
|
||||
--Extensions made by ecto.migrate that need superuser access
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
|
32
lib/mix/tasks/set_admin.ex
Normal file
32
lib/mix/tasks/set_admin.ex
Normal file
@ -0,0 +1,32 @@
|
||||
defmodule Mix.Tasks.SetAdmin do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@doc """
|
||||
Sets admin status
|
||||
Usage: set_admin nickname [true|false]
|
||||
"""
|
||||
def run([nickname | rest]) do
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
status =
|
||||
case rest do
|
||||
[status] -> status == "true"
|
||||
_ -> true
|
||||
end
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_admin", !!status)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
IO.puts("Admin status of #{nickname}: #{user.info["is_admin"]}")
|
||||
else
|
||||
_ ->
|
||||
IO.puts("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
end
|
@ -1,9 +1,18 @@
|
||||
defmodule Mix.Tasks.SetLocked do
|
||||
@moduledoc """
|
||||
Lock a local user
|
||||
|
||||
The local user will then have to manually accept/reject followers. This can also be done by the user into their settings.
|
||||
|
||||
Usage: ``mix set_locked <username>``
|
||||
|
||||
Example: ``mix set_locked lain``
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Set locked status"
|
||||
def run([nickname | rest]) do
|
||||
ensure_started(Repo, [])
|
||||
|
||||
|
38
lib/mix/tasks/unsubscribe_user.ex
Normal file
38
lib/mix/tasks/unsubscribe_user.ex
Normal file
@ -0,0 +1,38 @@
|
||||
defmodule Mix.Tasks.UnsubscribeUser do
|
||||
use Mix.Task
|
||||
alias Pleroma.{User, Repo}
|
||||
require Logger
|
||||
|
||||
@moduledoc """
|
||||
Deactivate and Unsubscribe local users from a user
|
||||
|
||||
Usage: ``mix unsubscribe_user <nickname>``
|
||||
|
||||
Example: ``mix unsubscribe_user lain``
|
||||
"""
|
||||
def run([nickname]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
||||
Logger.info("Deactivating #{user.nickname}")
|
||||
User.deactivate(user)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn friend ->
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
Logger.info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
User.unfollow(user, friend)
|
||||
end)
|
||||
|
||||
:timer.sleep(500)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
if length(user.following) == 0 do
|
||||
Logger.info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -82,4 +82,10 @@ defmodule Pleroma.Activity do
|
||||
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
|
||||
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do
|
||||
get_create_activity_by_object_ap_id(ap_id)
|
||||
end
|
||||
|
||||
def get_in_reply_to_activity(_), do: nil
|
||||
end
|
||||
|
@ -1,8 +1,15 @@
|
||||
defmodule Pleroma.Application do
|
||||
use Application
|
||||
|
||||
@name "Pleroma"
|
||||
@version Mix.Project.config()[:version]
|
||||
def name, do: @name
|
||||
def version, do: @version
|
||||
def named_version(), do: @name <> " " <> @version
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@env Mix.env()
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec
|
||||
import Cachex.Spec
|
||||
@ -12,18 +19,35 @@ defmodule Pleroma.Application do
|
||||
[
|
||||
# Start the Ecto repository
|
||||
supervisor(Pleroma.Repo, []),
|
||||
worker(Pleroma.Emoji, []),
|
||||
# Start the endpoint when the application starts
|
||||
supervisor(Pleroma.Web.Endpoint, []),
|
||||
# Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3)
|
||||
# worker(Pleroma.Worker, [arg1, arg2, arg3]),
|
||||
worker(Cachex, [
|
||||
:user_cache,
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
]),
|
||||
:user_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
],
|
||||
id: :cachex_user
|
||||
),
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
:object_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
],
|
||||
id: :cachex_object
|
||||
),
|
||||
worker(
|
||||
Cachex,
|
||||
[
|
||||
@ -40,11 +64,12 @@ defmodule Pleroma.Application do
|
||||
id: :cachex_idem
|
||||
),
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||
worker(Pleroma.Gopher.Server, []),
|
||||
worker(Pleroma.Stats, []),
|
||||
worker(Pleroma.Web.Push, [])
|
||||
] ++
|
||||
if Mix.env() == :test,
|
||||
if @env == :test,
|
||||
do: [],
|
||||
else:
|
||||
[worker(Pleroma.Web.Streamer, [])] ++
|
||||
|
42
lib/pleroma/config.ex
Normal file
42
lib/pleroma/config.ex
Normal file
@ -0,0 +1,42 @@
|
||||
defmodule Pleroma.Config do
|
||||
defmodule Error do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
def get(key), do: get(key, nil)
|
||||
|
||||
def get([key], default), do: get(key, default)
|
||||
|
||||
def get([parent_key | keys], default) do
|
||||
Application.get_env(:pleroma, parent_key)
|
||||
|> get_in(keys) || default
|
||||
end
|
||||
|
||||
def get(key, default) do
|
||||
Application.get_env(:pleroma, key, default)
|
||||
end
|
||||
|
||||
def get!(key) do
|
||||
value = get(key, nil)
|
||||
|
||||
if value == nil do
|
||||
raise(Error, message: "Missing configuration value: #{inspect(key)}")
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def put([key], value), do: put(key, value)
|
||||
|
||||
def put([parent_key | keys], value) do
|
||||
parent =
|
||||
Application.get_env(:pleroma, parent_key)
|
||||
|> put_in(keys, value)
|
||||
|
||||
Application.put_env(:pleroma, parent_key, parent)
|
||||
end
|
||||
|
||||
def put(key, value) do
|
||||
Application.put_env(:pleroma, key, value)
|
||||
end
|
||||
end
|
194
lib/pleroma/emoji.ex
Normal file
194
lib/pleroma/emoji.ex
Normal file
@ -0,0 +1,194 @@
|
||||
defmodule Pleroma.Emoji do
|
||||
@moduledoc """
|
||||
The emojis are loaded from:
|
||||
|
||||
* the built-in Finmojis (if enabled in configuration),
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths
|
||||
|
||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||
"""
|
||||
use GenServer
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:set, :protected, :named_table, {:read_concurrency, true}]
|
||||
|
||||
@doc false
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Reloads the emojis from disk."
|
||||
@spec reload() :: :ok
|
||||
def reload() do
|
||||
GenServer.call(__MODULE__, :reload)
|
||||
end
|
||||
|
||||
@doc "Returns the path of the emoji `name`."
|
||||
@spec get(String.t()) :: String.t() | nil
|
||||
def get(name) do
|
||||
case :ets.lookup(@ets, name) do
|
||||
[{_, path}] -> path
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Returns all the emojos!!"
|
||||
@spec get_all() :: [{String.t(), String.t()}, ...]
|
||||
def get_all() do
|
||||
:ets.tab2list(@ets)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init(_) do
|
||||
@ets = :ets.new(@ets, @ets_options)
|
||||
GenServer.cast(self(), :reload)
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(:reload, state) do
|
||||
load()
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:reload, _from, state) do
|
||||
load()
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_, _) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old_vsn, state, _extra) do
|
||||
load()
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp load() do
|
||||
emojis =
|
||||
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
|
||||
load_from_file("config/emoji.txt") ++
|
||||
load_from_file("config/custom_emoji.txt") ++
|
||||
load_from_globs(
|
||||
Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
|
||||
))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
:ok
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
"a_trusted_friend",
|
||||
"alandislands",
|
||||
"association",
|
||||
"auroraborealis",
|
||||
"baby_in_a_box",
|
||||
"bear",
|
||||
"black_gold",
|
||||
"christmasparty",
|
||||
"crosscountryskiing",
|
||||
"cupofcoffee",
|
||||
"education",
|
||||
"fashionista_finns",
|
||||
"finnishlove",
|
||||
"flag",
|
||||
"forest",
|
||||
"four_seasons_of_bbq",
|
||||
"girlpower",
|
||||
"handshake",
|
||||
"happiness",
|
||||
"headbanger",
|
||||
"icebreaker",
|
||||
"iceman",
|
||||
"joulutorttu",
|
||||
"kaamos",
|
||||
"kalsarikannit_f",
|
||||
"kalsarikannit_m",
|
||||
"karjalanpiirakka",
|
||||
"kicksled",
|
||||
"kokko",
|
||||
"lavatanssit",
|
||||
"losthopes_f",
|
||||
"losthopes_m",
|
||||
"mattinykanen",
|
||||
"meanwhileinfinland",
|
||||
"moominmamma",
|
||||
"nordicfamily",
|
||||
"out_of_office",
|
||||
"peacemaker",
|
||||
"perkele",
|
||||
"pesapallo",
|
||||
"polarbear",
|
||||
"pusa_hispida_saimensis",
|
||||
"reindeer",
|
||||
"sami",
|
||||
"sauna_f",
|
||||
"sauna_m",
|
||||
"sauna_whisk",
|
||||
"sisu",
|
||||
"stuck",
|
||||
"suomimainittu",
|
||||
"superfood",
|
||||
"swan",
|
||||
"the_cap",
|
||||
"the_conductor",
|
||||
"the_king",
|
||||
"the_voice",
|
||||
"theoriginalsanta",
|
||||
"tomoffinland",
|
||||
"torillatavataan",
|
||||
"unbreakable",
|
||||
"waiting",
|
||||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
defp load_finmoji(true) do
|
||||
Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
end
|
||||
|
||||
defp load_finmoji(_), do: []
|
||||
|
||||
defp load_from_file(file) do
|
||||
if File.exists?(file) do
|
||||
load_from_file_stream(File.stream!(file))
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp load_from_file_stream(stream) do
|
||||
stream
|
||||
|> Stream.map(&String.strip/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] -> {name, file}
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
end
|
||||
|
||||
defp load_from_globs(globs) do
|
||||
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||
|
||||
paths =
|
||||
Enum.map(globs, fn glob ->
|
||||
Path.join(static_path, glob)
|
||||
|> Path.wildcard()
|
||||
end)
|
||||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
end)
|
||||
end
|
||||
end
|
@ -36,6 +36,34 @@ defmodule Pleroma.Filter do
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do
|
||||
# If filter_id wasn't given, use the max filter_id for this user plus 1.
|
||||
# XXX This could result in a race condition if a user tries to add two
|
||||
# different filters for their account from two different clients at the
|
||||
# same time, but that should be unlikely.
|
||||
|
||||
max_id_query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
where: f.user_id == ^user_id,
|
||||
select: max(f.filter_id)
|
||||
)
|
||||
|
||||
filter_id =
|
||||
case Repo.one(max_id_query) do
|
||||
# Start allocating from 1
|
||||
nil ->
|
||||
1
|
||||
|
||||
max_id ->
|
||||
max_id + 1
|
||||
end
|
||||
|
||||
filter
|
||||
|> Map.put(:filter_id, filter_id)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def create(%Pleroma.Filter{} = filter) do
|
||||
Repo.insert(filter)
|
||||
end
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Formatter do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Emoji
|
||||
|
||||
@tag_regex ~r/\#\w+/u
|
||||
def parse_tags(text, data \\ %{}) do
|
||||
@ -28,119 +29,10 @@ defmodule Pleroma.Formatter do
|
||||
|> Enum.filter(fn {_match, user} -> user end)
|
||||
end
|
||||
|
||||
@finmoji [
|
||||
"a_trusted_friend",
|
||||
"alandislands",
|
||||
"association",
|
||||
"auroraborealis",
|
||||
"baby_in_a_box",
|
||||
"bear",
|
||||
"black_gold",
|
||||
"christmasparty",
|
||||
"crosscountryskiing",
|
||||
"cupofcoffee",
|
||||
"education",
|
||||
"fashionista_finns",
|
||||
"finnishlove",
|
||||
"flag",
|
||||
"forest",
|
||||
"four_seasons_of_bbq",
|
||||
"girlpower",
|
||||
"handshake",
|
||||
"happiness",
|
||||
"headbanger",
|
||||
"icebreaker",
|
||||
"iceman",
|
||||
"joulutorttu",
|
||||
"kaamos",
|
||||
"kalsarikannit_f",
|
||||
"kalsarikannit_m",
|
||||
"karjalanpiirakka",
|
||||
"kicksled",
|
||||
"kokko",
|
||||
"lavatanssit",
|
||||
"losthopes_f",
|
||||
"losthopes_m",
|
||||
"mattinykanen",
|
||||
"meanwhileinfinland",
|
||||
"moominmamma",
|
||||
"nordicfamily",
|
||||
"out_of_office",
|
||||
"peacemaker",
|
||||
"perkele",
|
||||
"pesapallo",
|
||||
"polarbear",
|
||||
"pusa_hispida_saimensis",
|
||||
"reindeer",
|
||||
"sami",
|
||||
"sauna_f",
|
||||
"sauna_m",
|
||||
"sauna_whisk",
|
||||
"sisu",
|
||||
"stuck",
|
||||
"suomimainittu",
|
||||
"superfood",
|
||||
"swan",
|
||||
"the_cap",
|
||||
"the_conductor",
|
||||
"the_king",
|
||||
"the_voice",
|
||||
"theoriginalsanta",
|
||||
"tomoffinland",
|
||||
"torillatavataan",
|
||||
"unbreakable",
|
||||
"waiting",
|
||||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
def emojify(text) do
|
||||
emojify(text, Emoji.get_all())
|
||||
end
|
||||
|
||||
@finmoji_with_filenames Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
end)
|
||||
|
||||
@emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do
|
||||
custom =
|
||||
with {:ok, custom} <- File.read("config/custom_emoji.txt") do
|
||||
custom
|
||||
else
|
||||
_e -> ""
|
||||
end
|
||||
|
||||
(default <> "\n" <> custom)
|
||||
|> String.trim()
|
||||
|> String.split(~r/\n+/)
|
||||
|> Enum.map(fn line ->
|
||||
[name, file] = String.split(line, ~r/,\s*/)
|
||||
{name, file}
|
||||
end)
|
||||
else
|
||||
_ -> []
|
||||
end)
|
||||
|
||||
@emoji_from_globs (
|
||||
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||
|
||||
globs =
|
||||
Application.get_env(:pleroma, :emoji, [])
|
||||
|> Keyword.get(:shortcode_globs, [])
|
||||
|
||||
paths =
|
||||
Enum.map(globs, fn glob ->
|
||||
Path.join(static_path, glob)
|
||||
|> Path.wildcard()
|
||||
end)
|
||||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
end)
|
||||
)
|
||||
|
||||
@emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
|
||||
|
||||
def emojify(text, emoji \\ @emoji)
|
||||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji) do
|
||||
@ -160,39 +52,22 @@ defmodule Pleroma.Formatter do
|
||||
end
|
||||
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(@emoji, fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
def get_custom_emoji() do
|
||||
@emoji
|
||||
end
|
||||
|
||||
@link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui
|
||||
|
||||
# IANA got a list https://www.iana.org/assignments/uri-schemes/ but
|
||||
# Stuff like ipfs isn’t in it
|
||||
# There is very niche stuff
|
||||
@uri_schemes [
|
||||
"https://",
|
||||
"http://",
|
||||
"dat://",
|
||||
"dweb://",
|
||||
"gopher://",
|
||||
"ipfs://",
|
||||
"ipns://",
|
||||
"irc:",
|
||||
"ircs:",
|
||||
"magnet:",
|
||||
"mailto:",
|
||||
"mumble:",
|
||||
"ssb://",
|
||||
"xmpp:"
|
||||
]
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
# TODO: make it use something other than @link_regex
|
||||
def html_escape(text) do
|
||||
def html_escape(text, "text/html") do
|
||||
HTML.filter_tags(text)
|
||||
end
|
||||
|
||||
def html_escape(text, "text/plain") do
|
||||
Regex.split(@link_regex, text, include_captures: true)
|
||||
|> Enum.map_every(2, fn chunk ->
|
||||
{:safe, part} = Phoenix.HTML.html_escape(chunk)
|
||||
@ -203,14 +78,10 @@ defmodule Pleroma.Formatter do
|
||||
|
||||
@doc "changes scheme:... urls to html links"
|
||||
def add_links({subs, text}) do
|
||||
additionnal_schemes =
|
||||
Application.get_env(:pleroma, :uri_schemes, [])
|
||||
|> Keyword.get(:additionnal_schemes, [])
|
||||
|
||||
links =
|
||||
text
|
||||
|> String.split([" ", "\t", "<br>"])
|
||||
|> Enum.filter(fn word -> String.starts_with?(word, @uri_schemes ++ additionnal_schemes) end)
|
||||
|> 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)
|
||||
@ -222,13 +93,7 @@ defmodule Pleroma.Formatter do
|
||||
subs =
|
||||
subs ++
|
||||
Enum.map(links, fn {uuid, url} ->
|
||||
{:safe, link} = Phoenix.HTML.Link.link(url, to: url)
|
||||
|
||||
link =
|
||||
link
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{uuid, link}
|
||||
{uuid, "<a href=\"#{url}\">#{url}</a>"}
|
||||
end)
|
||||
|
||||
{subs, uuid_text}
|
||||
@ -250,7 +115,12 @@ defmodule Pleroma.Formatter do
|
||||
subs =
|
||||
subs ++
|
||||
Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} ->
|
||||
ap_id = info["source_data"]["url"] || ap_id
|
||||
ap_id =
|
||||
if is_binary(info["source_data"]["url"]) do
|
||||
info["source_data"]["url"]
|
||||
else
|
||||
ap_id
|
||||
end
|
||||
|
||||
short_match = String.split(match, "@") |> tl() |> hd()
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
defmodule Pleroma.Gopher.Server do
|
||||
use GenServer
|
||||
require Logger
|
||||
@gopher Application.get_env(:pleroma, :gopher)
|
||||
|
||||
def start_link() do
|
||||
ip = Keyword.get(@gopher, :ip, {0, 0, 0, 0})
|
||||
port = Keyword.get(@gopher, :port, 1234)
|
||||
config = Pleroma.Config.get(:gopher, [])
|
||||
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
|
||||
port = Keyword.get(config, :port, 1234)
|
||||
GenServer.start_link(__MODULE__, [ip, port], [])
|
||||
end
|
||||
|
||||
def init([ip, port]) do
|
||||
if Keyword.get(@gopher, :enabled, false) do
|
||||
if Pleroma.Config.get([:gopher, :enabled], false) do
|
||||
Logger.info("Starting gopher server on #{port}")
|
||||
|
||||
:ranch.start_listener(
|
||||
@ -37,9 +37,6 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.HTML
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@gopher Application.get_env(:pleroma, :gopher)
|
||||
|
||||
def start_link(ref, socket, transport, opts) do
|
||||
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
|
||||
{:ok, pid}
|
||||
@ -62,7 +59,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
|
||||
def link(name, selector, type \\ 1) do
|
||||
address = Pleroma.Web.Endpoint.host()
|
||||
port = Keyword.get(@gopher, :port, 1234)
|
||||
port = Pleroma.Config.get([:gopher, :port], 1234)
|
||||
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
|
||||
end
|
||||
|
||||
@ -85,7 +82,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
end
|
||||
|
||||
def response("") do
|
||||
info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <>
|
||||
info("Welcome to #{Pleroma.Config.get([:instance, :name], "Pleroma")}!") <>
|
||||
link("Public Timeline", "/main/public") <>
|
||||
link("Federated Timeline", "/main/all") <> ".\r\n"
|
||||
end
|
||||
|
@ -1,14 +1,12 @@
|
||||
defmodule Pleroma.HTML do
|
||||
alias HtmlSanitizeEx.Scrubber
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
|
||||
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
|
||||
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
||||
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
||||
|
||||
def get_scrubbers() do
|
||||
Keyword.get(@markup, :scrub_policy)
|
||||
Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|> get_scrubbers
|
||||
end
|
||||
|
||||
@ -36,11 +34,13 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||
paragraphs, breaks and links are allowed through the filter.
|
||||
"""
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
require HtmlSanitizeEx.Scrubber.Meta
|
||||
alias HtmlSanitizeEx.Scrubber.Meta
|
||||
|
||||
@valid_schemes ["http", "https"]
|
||||
|
||||
Meta.remove_cdata_sections_before_scrub()
|
||||
Meta.strip_comments()
|
||||
|
||||
@ -56,11 +56,11 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||
Meta.allow_tag_with_these_attributes("span", [])
|
||||
|
||||
# allow inline images for custom emoji
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||
|
||||
if @allow_inline_images do
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], @valid_schemes)
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
@ -79,7 +79,9 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||
require HtmlSanitizeEx.Scrubber.Meta
|
||||
alias HtmlSanitizeEx.Scrubber.Meta
|
||||
|
||||
@valid_schemes ["http", "https"]
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
|
||||
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
|
||||
|
||||
Meta.remove_cdata_sections_before_scrub()
|
||||
Meta.strip_comments()
|
||||
@ -87,6 +89,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||
Meta.allow_tag_with_uri_attributes("a", ["href"], @valid_schemes)
|
||||
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("b", [])
|
||||
Meta.allow_tag_with_these_attributes("blockquote", [])
|
||||
Meta.allow_tag_with_these_attributes("br", [])
|
||||
@ -103,11 +107,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||
Meta.allow_tag_with_these_attributes("u", [])
|
||||
Meta.allow_tag_with_these_attributes("ul", [])
|
||||
|
||||
@markup Application.get_env(:pleroma, :markup)
|
||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||
|
||||
if @allow_inline_images do
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], @valid_schemes)
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
@ -173,6 +177,8 @@ defmodule Pleroma.HTML.Transform.MediaProxy do
|
||||
{"img", attributes, children}
|
||||
end
|
||||
|
||||
def scrub({:comment, children}), do: ""
|
||||
|
||||
def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
||||
def scrub({tag, children}), do: children
|
||||
def scrub(text), do: text
|
||||
|
@ -22,6 +22,7 @@ defmodule Pleroma.HTTP do
|
||||
def process_request_options(options) do
|
||||
config = Application.get_env(:pleroma, :http, [])
|
||||
proxy = Keyword.get(config, :proxy_url, nil)
|
||||
options = options ++ [hackney: [pool: :default]]
|
||||
|
||||
case proxy do
|
||||
nil -> options
|
||||
|
@ -69,6 +69,25 @@ defmodule Pleroma.List do
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
# Get lists to which the account belongs.
|
||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||
user = Repo.get(User, account_id)
|
||||
|
||||
query =
|
||||
from(
|
||||
l in Pleroma.List,
|
||||
where:
|
||||
l.user_id == ^owner.id and
|
||||
fragment(
|
||||
"? = ANY(?)",
|
||||
^user.follower_address,
|
||||
l.following
|
||||
)
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def rename(%Pleroma.List{} = list, title) do
|
||||
list
|
||||
|> title_changeset(%{title: title})
|
||||
|
@ -1,6 +1,6 @@
|
||||
defmodule Pleroma.Notification do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.{User, Activity, Notification, Repo}
|
||||
alias Pleroma.{User, Activity, Notification, Repo, Object}
|
||||
import Ecto.Query
|
||||
|
||||
schema "notifications" do
|
||||
@ -42,6 +42,20 @@ defmodule Pleroma.Notification do
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def set_read_up_to(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
n in Notification,
|
||||
where: n.user_id == ^user_id,
|
||||
where: n.id <= ^id,
|
||||
update: [
|
||||
set: [seen: true]
|
||||
]
|
||||
)
|
||||
|
||||
Repo.update_all(query, [])
|
||||
end
|
||||
|
||||
def get(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
@ -81,7 +95,7 @@ defmodule Pleroma.Notification do
|
||||
|
||||
def create_notifications(%Activity{id: _, data: %{"to" => _, "type" => type}} = activity)
|
||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
users = User.get_notified_from_activity(activity)
|
||||
users = get_notified_from_activity(activity)
|
||||
|
||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||
{:ok, notifications}
|
||||
@ -100,4 +114,64 @@ defmodule Pleroma.Notification do
|
||||
notification
|
||||
end
|
||||
end
|
||||
|
||||
def get_notified_from_activity(activity, local_only \\ true)
|
||||
|
||||
def get_notified_from_activity(
|
||||
%Activity{data: %{"to" => _, "type" => type} = data} = activity,
|
||||
local_only
|
||||
)
|
||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
recipients =
|
||||
[]
|
||||
|> maybe_notify_to_recipients(activity)
|
||||
|> maybe_notify_mentioned_recipients(activity)
|
||||
|> Enum.uniq()
|
||||
|
||||
User.get_users_from_set(recipients, local_only)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(_, local_only), do: []
|
||||
|
||||
defp maybe_notify_to_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => to, "type" => type}} = activity
|
||||
) do
|
||||
recipients ++ to
|
||||
end
|
||||
|
||||
defp maybe_notify_mentioned_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => to, "type" => type} = data} = activity
|
||||
)
|
||||
when type == "Create" do
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
object_data =
|
||||
cond do
|
||||
!is_nil(object) ->
|
||||
object.data
|
||||
|
||||
is_map(data["object"]) ->
|
||||
data["object"]
|
||||
|
||||
true ->
|
||||
%{}
|
||||
end
|
||||
|
||||
tagged_mentions = maybe_extract_mentions(object_data)
|
||||
|
||||
recipients ++ tagged_mentions
|
||||
end
|
||||
|
||||
defp maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
||||
|
||||
defp maybe_extract_mentions(%{"tag" => tag}) do
|
||||
tag
|
||||
|> Enum.filter(fn x -> is_map(x) end)
|
||||
|> Enum.filter(fn x -> x["type"] == "Mention" end)
|
||||
|> Enum.map(fn x -> x["href"] end)
|
||||
end
|
||||
|
||||
defp maybe_extract_mentions(_), do: []
|
||||
end
|
||||
|
@ -1,6 +1,6 @@
|
||||
defmodule Pleroma.Object do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.{Repo, Object}
|
||||
alias Pleroma.{Repo, Object, Activity}
|
||||
import Ecto.{Query, Changeset}
|
||||
|
||||
schema "objects" do
|
||||
@ -31,13 +31,15 @@ defmodule Pleroma.Object do
|
||||
def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
if Mix.env() == :test do
|
||||
if Mix.env() == :test do
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
get_by_ap_id(ap_id)
|
||||
else
|
||||
end
|
||||
else
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
key = "object:#{ap_id}"
|
||||
|
||||
Cachex.fetch!(:user_cache, key, fn _ ->
|
||||
Cachex.fetch!(:object_cache, key, fn _ ->
|
||||
object = get_by_ap_id(ap_id)
|
||||
|
||||
if object do
|
||||
@ -52,4 +54,12 @@ defmodule Pleroma.Object do
|
||||
def context_mapping(context) do
|
||||
Object.change(%Object{}, %{data: %{"id" => context}})
|
||||
end
|
||||
|
||||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
18
lib/pleroma/plugs/federating_plug.ex
Normal file
18
lib/pleroma/plugs/federating_plug.ex
Normal file
@ -0,0 +1,18 @@
|
||||
defmodule Pleroma.Web.FederatingPlug do
|
||||
import Plug.Conn
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(conn, opts) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> Phoenix.Controller.render(Pleroma.Web.ErrorView, "404.json")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
63
lib/pleroma/plugs/http_security_plug.ex
Normal file
63
lib/pleroma/plugs/http_security_plug.ex
Normal file
@ -0,0 +1,63 @@
|
||||
defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||
alias Pleroma.Config
|
||||
import Plug.Conn
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, options) do
|
||||
if Config.get([:http_security, :enabled]) do
|
||||
conn =
|
||||
merge_resp_headers(conn, headers())
|
||||
|> maybe_send_sts_header(Config.get([:http_security, :sts]))
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp headers do
|
||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
||||
|
||||
[
|
||||
{"x-xss-protection", "1; mode=block"},
|
||||
{"x-permitted-cross-domain-policies", "none"},
|
||||
{"x-frame-options", "DENY"},
|
||||
{"x-content-type-options", "nosniff"},
|
||||
{"referrer-policy", referrer_policy},
|
||||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
]
|
||||
end
|
||||
|
||||
defp csp_string do
|
||||
protocol = Config.get([Pleroma.Web.Endpoint, :protocol])
|
||||
|
||||
[
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"img-src 'self' data: https:",
|
||||
"media-src 'self' https:",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"script-src 'self'",
|
||||
"connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||
"manifest-src 'self'",
|
||||
if @protocol == "https" do
|
||||
"upgrade-insecure-requests"
|
||||
end
|
||||
]
|
||||
|> Enum.join("; ")
|
||||
end
|
||||
|
||||
defp maybe_send_sts_header(conn, true) do
|
||||
max_age_sts = Config.get([:http_security, :sts_max_age])
|
||||
max_age_ct = Config.get([:http_security, :ct_max_age])
|
||||
|
||||
merge_resp_headers(conn, [
|
||||
{"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
|
||||
{"expect-ct", "enforce, max-age=#{max_age_ct}"}
|
||||
])
|
||||
end
|
||||
|
||||
defp maybe_send_sts_header(conn, _), do: conn
|
||||
end
|
19
lib/pleroma/plugs/user_is_admin_plug.ex
Normal file
19
lib/pleroma/plugs/user_is_admin_plug.ex
Normal file
@ -0,0 +1,19 @@
|
||||
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
||||
import Plug.Conn
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{info: %{"is_admin" => true}}}} = conn, _) do
|
||||
conn
|
||||
end
|
||||
|
||||
def call(conn, _) do
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{error: "User is not admin."}))
|
||||
|> halt
|
||||
end
|
||||
end
|
@ -1,64 +1,74 @@
|
||||
defmodule Pleroma.Upload do
|
||||
alias Ecto.UUID
|
||||
|
||||
@storage_backend Application.get_env(:pleroma, Pleroma.Upload)
|
||||
|> Keyword.fetch!(:uploader)
|
||||
def check_file_size(path, nil), do: true
|
||||
|
||||
def store(%Plug.Upload{} = file, should_dedupe) do
|
||||
content_type = get_content_type(file.path)
|
||||
|
||||
uuid = get_uuid(file, should_dedupe)
|
||||
name = get_name(file, uuid, content_type, should_dedupe)
|
||||
|
||||
strip_exif_data(content_type, file.path)
|
||||
|
||||
{:ok, url_path} =
|
||||
@storage_backend.put_file(name, uuid, file.path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Document",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
def check_file_size(path, size_limit) do
|
||||
{:ok, %{size: size}} = File.stat(path)
|
||||
size <= size_limit
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
|
||||
def store(file, should_dedupe, size_limit \\ nil)
|
||||
|
||||
def store(%Plug.Upload{} = file, should_dedupe, size_limit) do
|
||||
content_type = get_content_type(file.path)
|
||||
|
||||
with uuid <- get_uuid(file, should_dedupe),
|
||||
name <- get_name(file, uuid, content_type, should_dedupe),
|
||||
true <- check_file_size(file.path, size_limit) do
|
||||
strip_exif_data(content_type, file.path)
|
||||
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, file.path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Document",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe, size_limit) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"], ignore: :whitespace)
|
||||
|
||||
tmp_path = tempfile_for_image(data)
|
||||
with tmp_path <- tempfile_for_image(data),
|
||||
uuid <- UUID.generate(),
|
||||
true <- check_file_size(tmp_path, size_limit) do
|
||||
content_type = get_content_type(tmp_path)
|
||||
strip_exif_data(content_type, tmp_path)
|
||||
|
||||
uuid = UUID.generate()
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
)
|
||||
|
||||
content_type = get_content_type(tmp_path)
|
||||
strip_exif_data(content_type, tmp_path)
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, tmp_path, content_type, should_dedupe)
|
||||
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
)
|
||||
|
||||
{:ok, url_path} = @storage_backend.put_file(name, uuid, tmp_path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@ -152,7 +162,13 @@ defmodule Pleroma.Upload do
|
||||
"audio/mpeg"
|
||||
|
||||
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
"audio/ogg"
|
||||
case IO.binread(f, 27) do
|
||||
<<_::size(160), 0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61>> ->
|
||||
"video/ogg"
|
||||
|
||||
_ ->
|
||||
"audio/ogg"
|
||||
end
|
||||
|
||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||
"audio/wav"
|
||||
@ -167,4 +183,8 @@ defmodule Pleroma.Upload do
|
||||
_e -> "application/octet-stream"
|
||||
end
|
||||
end
|
||||
|
||||
defp uploader() do
|
||||
Pleroma.Config.get!([Pleroma.Upload, :uploader])
|
||||
end
|
||||
end
|
||||
|
26
lib/pleroma/uploaders/mdii.ex
Normal file
26
lib/pleroma/uploaders/mdii.ex
Normal file
@ -0,0 +1,26 @@
|
||||
defmodule Pleroma.Uploaders.MDII do
|
||||
alias Pleroma.Config
|
||||
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def put_file(name, uuid, path, content_type, should_dedupe) do
|
||||
cgi = Pleroma.Config.get([Pleroma.Uploaders.MDII, :cgi])
|
||||
files = Pleroma.Config.get([Pleroma.Uploaders.MDII, :files])
|
||||
|
||||
{:ok, file_data} = File.read(path)
|
||||
|
||||
extension = String.split(name, ".") |> List.last()
|
||||
query = "#{cgi}?#{extension}"
|
||||
|
||||
with {:ok, %{status_code: 200, body: body}} <- @httpoison.post(query, file_data) do
|
||||
File.rm!(path)
|
||||
remote_file_name = String.split(body) |> List.first()
|
||||
public_url = "#{files}/#{remote_file_name}.#{extension}"
|
||||
{:ok, public_url}
|
||||
else
|
||||
_ -> Pleroma.Uploaders.Local.put_file(name, uuid, path, content_type, should_dedupe)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,16 +1,19 @@
|
||||
defmodule Pleroma.Uploaders.S3 do
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
|
||||
def put_file(name, uuid, path, content_type, _should_dedupe) do
|
||||
settings = Application.get_env(:pleroma, Pleroma.Uploaders.S3)
|
||||
bucket = Keyword.fetch!(settings, :bucket)
|
||||
public_endpoint = Keyword.fetch!(settings, :public_endpoint)
|
||||
force_media_proxy = Keyword.fetch!(settings, :force_media_proxy)
|
||||
|
||||
{:ok, file_data} = File.read(path)
|
||||
|
||||
File.rm!(path)
|
||||
|
||||
s3_name = "#{uuid}/#{name}"
|
||||
s3_name = "#{uuid}/#{encode(name)}"
|
||||
|
||||
{:ok, _} =
|
||||
ExAws.S3.put_object(bucket, s3_name, file_data, [
|
||||
@ -19,6 +22,19 @@ defmodule Pleroma.Uploaders.S3 do
|
||||
])
|
||||
|> ExAws.request()
|
||||
|
||||
{:ok, "#{public_endpoint}/#{bucket}/#{s3_name}"}
|
||||
url_base = "#{public_endpoint}/#{bucket}/#{s3_name}"
|
||||
|
||||
public_url =
|
||||
if force_media_proxy do
|
||||
MediaProxy.url(url_base)
|
||||
else
|
||||
url_base
|
||||
end
|
||||
|
||||
{:ok, public_url}
|
||||
end
|
||||
|
||||
defp encode(name) do
|
||||
String.replace(name, ~r/[^0-9a-zA-Z!.*'()_-]/, "-")
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,9 @@
|
||||
defmodule Pleroma.Uploaders.Swift.Keystone do
|
||||
use HTTPoison.Base
|
||||
|
||||
@settings Application.get_env(:pleroma, Pleroma.Uploaders.Swift)
|
||||
|
||||
def process_url(url) do
|
||||
Enum.join(
|
||||
[Keyword.fetch!(@settings, :auth_url), url],
|
||||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :auth_url]), url],
|
||||
"/"
|
||||
)
|
||||
end
|
||||
@ -16,9 +14,10 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
||||
end
|
||||
|
||||
def get_token() do
|
||||
username = Keyword.fetch!(@settings, :username)
|
||||
password = Keyword.fetch!(@settings, :password)
|
||||
tenant_id = Keyword.fetch!(@settings, :tenant_id)
|
||||
settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
|
||||
username = Keyword.fetch!(settings, :username)
|
||||
password = Keyword.fetch!(settings, :password)
|
||||
tenant_id = Keyword.fetch!(settings, :tenant_id)
|
||||
|
||||
case post(
|
||||
"/tokens",
|
||||
|
@ -1,17 +1,15 @@
|
||||
defmodule Pleroma.Uploaders.Swift.Client do
|
||||
use HTTPoison.Base
|
||||
|
||||
@settings Application.get_env(:pleroma, Pleroma.Uploaders.Swift)
|
||||
|
||||
def process_url(url) do
|
||||
Enum.join(
|
||||
[Keyword.fetch!(@settings, :storage_url), url],
|
||||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :storage_url]), url],
|
||||
"/"
|
||||
)
|
||||
end
|
||||
|
||||
def upload_file(filename, body, content_type) do
|
||||
object_url = Keyword.fetch!(@settings, :object_url)
|
||||
object_url = Pleroma.Config.get!([Pleroma.Uploaders.Swift, :object_url])
|
||||
token = Pleroma.Uploaders.Swift.Keystone.get_token()
|
||||
|
||||
case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do
|
||||
|
@ -4,7 +4,7 @@ defmodule Pleroma.User do
|
||||
import Ecto.{Changeset, Query}
|
||||
alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Web.{OStatus, Websub}
|
||||
alias Pleroma.Web.{OStatus, Websub, OAuth}
|
||||
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
|
||||
|
||||
schema "users" do
|
||||
@ -42,6 +42,10 @@ defmodule Pleroma.User do
|
||||
end
|
||||
end
|
||||
|
||||
def profile_url(%User{info: %{"source_data" => %{"url" => url}}}), do: url
|
||||
def profile_url(%User{ap_id: ap_id}), do: ap_id
|
||||
def profile_url(_), do: nil
|
||||
|
||||
def ap_id(%User{nickname: nickname}) do
|
||||
"#{Web.base_url()}/users/#{nickname}"
|
||||
end
|
||||
@ -132,6 +136,9 @@ defmodule Pleroma.User do
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
OAuth.Token.delete_user_tokens(struct)
|
||||
OAuth.Authorization.delete_user_authorizations(struct)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
|
||||
@ -184,33 +191,16 @@ defmodule Pleroma.User do
|
||||
|
||||
def needs_update?(_), do: true
|
||||
|
||||
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
|
||||
user_config = Application.get_env(:pleroma, :user)
|
||||
deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do
|
||||
{:ok, follower}
|
||||
end
|
||||
|
||||
user_info = user_info(followed)
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
||||
follow(follower, followed)
|
||||
end
|
||||
|
||||
should_direct_follow =
|
||||
cond do
|
||||
# if the account is locked, don't pre-create the relationship
|
||||
user_info[:locked] == true ->
|
||||
false
|
||||
|
||||
# if the users are blocking each other, we shouldn't even be here, but check for it anyway
|
||||
deny_follow_blocked and
|
||||
(User.blocks?(follower, followed) or User.blocks?(followed, follower)) ->
|
||||
false
|
||||
|
||||
# if OStatus, then there is no three-way handshake to follow
|
||||
User.ap_enabled?(followed) != true ->
|
||||
true
|
||||
|
||||
# if there are no other reasons not to, just pre-create the relationship
|
||||
true ->
|
||||
true
|
||||
end
|
||||
|
||||
if should_direct_follow do
|
||||
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
||||
if !User.ap_enabled?(followed) do
|
||||
follow(follower, followed)
|
||||
else
|
||||
{:ok, follower}
|
||||
@ -305,6 +295,7 @@ defmodule Pleroma.User do
|
||||
def invalidate_cache(user) do
|
||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
Cachex.del(:user_cache, "user_info:#{user.id}")
|
||||
end
|
||||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
@ -473,36 +464,25 @@ defmodule Pleroma.User do
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def get_notified_from_activity_query(to) do
|
||||
def get_users_from_set_query(ap_ids, false) do
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^to,
|
||||
where: u.ap_id in ^ap_ids
|
||||
)
|
||||
end
|
||||
|
||||
def get_users_from_set_query(ap_ids, true) do
|
||||
query = get_users_from_set_query(ap_ids, false)
|
||||
|
||||
from(
|
||||
u in query,
|
||||
where: u.local == true
|
||||
)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{recipients: to, data: %{"type" => "Announce"} = data}) do
|
||||
object = Object.normalize(data["object"])
|
||||
actor = User.get_cached_by_ap_id(data["actor"])
|
||||
|
||||
# ensure that the actor who published the announced object appears only once
|
||||
to =
|
||||
if actor.nickname != nil do
|
||||
to ++ [object.data["actor"]]
|
||||
else
|
||||
to
|
||||
end
|
||||
|> Enum.uniq()
|
||||
|
||||
query = get_notified_from_activity_query(to)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(%Activity{recipients: to}) do
|
||||
query = get_notified_from_activity_query(to)
|
||||
|
||||
Repo.all(query)
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
get_users_from_set_query(ap_ids, local_only)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||
@ -518,7 +498,7 @@ defmodule Pleroma.User do
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def search(query, resolve) do
|
||||
def search(query, resolve \\ false) do
|
||||
# strip the beginning @ off if there is a query
|
||||
query = String.trim_leading(query, "@")
|
||||
|
||||
@ -632,8 +612,8 @@ defmodule Pleroma.User do
|
||||
)
|
||||
end
|
||||
|
||||
def deactivate(%User{} = user) do
|
||||
new_info = Map.put(user.info, "deactivated", true)
|
||||
def deactivate(%User{} = user, status \\ true) do
|
||||
new_info = Map.put(user.info, "deactivated", status)
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
@ -666,7 +646,7 @@ defmodule Pleroma.User do
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
def html_filter_policy(%User{info: %{"no_rich_text" => true}}) do
|
||||
@ -753,6 +733,7 @@ defmodule Pleroma.User do
|
||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
||||
end
|
||||
|
||||
def ap_enabled?(%User{local: true}), do: true
|
||||
def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
|
||||
def ap_enabled?(_), do: false
|
||||
|
||||
@ -763,4 +744,28 @@ defmodule Pleroma.User do
|
||||
get_or_fetch_by_nickname(uri_or_nickname)
|
||||
end
|
||||
end
|
||||
|
||||
# wait a period of time and return newest version of the User structs
|
||||
# this is because we have synchronous follow APIs and need to simulate them
|
||||
# with an async handshake
|
||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||
with %User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||
with :ok <- :timer.sleep(timeout),
|
||||
%User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,8 +10,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
|
||||
# For Announce activities, we filter the recipients based on following status for any actors
|
||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||
@ -44,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
defp check_actor_is_active(actor) do
|
||||
if not is_nil(actor) do
|
||||
with user <- User.get_cached_by_ap_id(actor),
|
||||
nil <- user.info["deactivated"] do
|
||||
false <- !!user.info["deactivated"] do
|
||||
:ok
|
||||
else
|
||||
_e -> :reject
|
||||
@ -273,8 +271,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
with Repo.delete(object),
|
||||
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
|
||||
with {:ok, _} <- Object.delete(object),
|
||||
{:ok, activity} <- insert(data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
{:ok, _actor} <- User.decrease_note_count(user) do
|
||||
@ -575,9 +572,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
def upload(file) do
|
||||
data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media])
|
||||
Repo.insert(%Object{data: data})
|
||||
def upload(file, size_limit \\ nil) do
|
||||
with data <-
|
||||
Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media], size_limit),
|
||||
false <- is_nil(data) do
|
||||
Repo.insert(%Object{data: data})
|
||||
end
|
||||
end
|
||||
|
||||
def user_data_from_user_object(data) do
|
||||
@ -628,9 +628,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, %{status_code: 200, body: body}} <-
|
||||
@httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
|
||||
user_data_from_user_object(data)
|
||||
else
|
||||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||
@ -657,14 +655,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
end
|
||||
|
||||
@quarantined_instances Keyword.get(@instance, :quarantined_instances, [])
|
||||
|
||||
def should_federate?(inbox, public) do
|
||||
if public do
|
||||
true
|
||||
else
|
||||
inbox_info = URI.parse(inbox)
|
||||
inbox_info.host not in @quarantined_instances
|
||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
end
|
||||
end
|
||||
|
||||
@ -683,7 +679,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|
||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn %{info: %{"source_data" => data}} ->
|
||||
(data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"]
|
||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
@ -734,28 +730,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
else
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
id,
|
||||
[Accept: "application/activity+json"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
||||
nil <- Object.normalize(data),
|
||||
params <- %{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["attributedTo"],
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"object" => data
|
||||
},
|
||||
:ok <- Transmogrifier.contain_origin(id, params),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, Object.normalize(activity.data["object"])}
|
||||
else
|
||||
{:error, {:reject, nil}} ->
|
||||
{:reject, nil}
|
||||
|
||||
object = %Object{} ->
|
||||
{:ok, object}
|
||||
|
||||
@ -770,6 +760,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(id) do
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
id,
|
||||
[Accept: "application/activity+json"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
:ok <- Transmogrifier.contain_origin_from_id(id, data) do
|
||||
{:ok, data}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def is_public?(activity) do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
||||
(activity.data["cc"] || []))
|
||||
@ -784,4 +795,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
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
|
||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
||||
entire_thread_visible_for_user?(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a specific activity
|
||||
def contain_activity(%Activity{} = activity, %User{} = user) do
|
||||
contain_broken_threads(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a timeline
|
||||
def contain_timeline(timeline, user) do
|
||||
timeline
|
||||
|> Enum.filter(fn activity ->
|
||||
contain_activity(activity, user)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
@ -4,12 +4,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
require Logger
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
|
||||
plug(:relay_active? when action in [:relay])
|
||||
|
||||
def relay_active?(conn, _) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "not found"})
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
|
||||
def user(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
|
||||
@ -87,25 +102,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||
outbox(conn, %{"nickname" => nickname, "max_id" => nil})
|
||||
end
|
||||
|
||||
# TODO: Ensure that this inbox is a recipient of the message
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
true <- Utils.recipient_in_message(user.ap_id, params),
|
||||
params <- Utils.maybe_splice_recipient(user.ap_id, params) do
|
||||
Federator.enqueue(:incoming_ap_doc, params)
|
||||
json(conn, "ok")
|
||||
end
|
||||
end
|
||||
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||
Federator.enqueue(:incoming_ap_doc, params)
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
# only accept relayed Creates
|
||||
def inbox(conn, %{"type" => "Create"} = params) do
|
||||
Logger.info(
|
||||
"Signature missing or not from author, relayed Create message, fetching object from source"
|
||||
)
|
||||
|
||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def inbox(conn, params) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
|
||||
if !String.contains?(headers["signature"] || "", params["actor"]) do
|
||||
Logger.info("Signature not from author, relayed message, fetching from source")
|
||||
ActivityPub.fetch_object_from_id(params["object"]["id"])
|
||||
else
|
||||
Logger.info("Signature error - make sure you are forwarding the HTTP Host header!")
|
||||
Logger.info("Could not validate #{params["actor"]}")
|
||||
if String.contains?(headers["signature"], params["actor"]) do
|
||||
Logger.info(
|
||||
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
|
||||
)
|
||||
|
||||
Logger.info(inspect(conn.req_headers))
|
||||
end
|
||||
|
||||
json(conn, "ok")
|
||||
json(conn, "error")
|
||||
end
|
||||
|
||||
def relay(conn, params) do
|
||||
|
@ -3,10 +3,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_normalize_markup Application.get_env(:pleroma, :mrf_normalize_markup)
|
||||
|
||||
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
|
||||
scrub_policy = Keyword.get(@mrf_normalize_markup, :scrub_policy)
|
||||
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
||||
|
||||
child = object["object"]
|
||||
|
||||
|
@ -2,10 +2,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_rejectnonpublic Application.get_env(:pleroma, :mrf_rejectnonpublic)
|
||||
@allow_followersonly Keyword.get(@mrf_rejectnonpublic, :allow_followersonly)
|
||||
@allow_direct Keyword.get(@mrf_rejectnonpublic, :allow_direct)
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create"} = object) do
|
||||
user = User.get_cached_by_ap_id(object["actor"])
|
||||
@ -20,6 +16,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
true -> "direct"
|
||||
end
|
||||
|
||||
policy = Pleroma.Config.get(:mrf_rejectnonpublic)
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
{:ok, object}
|
||||
@ -28,14 +26,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
{:ok, object}
|
||||
|
||||
"followers" ->
|
||||
with true <- @allow_followersonly do
|
||||
with true <- Keyword.get(policy, :allow_followersonly) do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
end
|
||||
|
||||
"direct" ->
|
||||
with true <- @allow_direct do
|
||||
with true <- Keyword.get(policy, :allow_direct) do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
|
@ -2,60 +2,76 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_policy Application.get_env(:pleroma, :mrf_simple)
|
||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||
accepts = Pleroma.Config.get([:mrf_simple, :accept])
|
||||
|
||||
@accept Keyword.get(@mrf_policy, :accept)
|
||||
defp check_accept(%{host: actor_host} = actor_info, object)
|
||||
when length(@accept) > 0 and not (actor_host in @accept) do
|
||||
{:reject, nil}
|
||||
cond do
|
||||
accepts == [] -> {:ok, object}
|
||||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
||||
Enum.member?(accepts, actor_host) -> {:ok, object}
|
||||
true -> {:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_accept(actor_info, object), do: {:ok, object}
|
||||
|
||||
@reject Keyword.get(@mrf_policy, :reject)
|
||||
defp check_reject(%{host: actor_host} = actor_info, object) when actor_host in @reject do
|
||||
{:reject, nil}
|
||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do
|
||||
{:reject, nil}
|
||||
else
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_reject(actor_info, object), do: {:ok, object}
|
||||
defp check_media_removal(
|
||||
%{host: actor_host} = _actor_info,
|
||||
%{"type" => "Create", "object" => %{"attachement" => child_attachment}} = object
|
||||
)
|
||||
when length(child_attachment) > 0 do
|
||||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do
|
||||
child_object = Map.delete(object["object"], "attachment")
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
object
|
||||
end
|
||||
|
||||
@media_removal Keyword.get(@mrf_policy, :media_removal)
|
||||
defp check_media_removal(%{host: actor_host} = actor_info, %{"type" => "Create"} = object)
|
||||
when actor_host in @media_removal do
|
||||
child_object = Map.delete(object["object"], "attachment")
|
||||
object = Map.put(object, "object", child_object)
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_media_removal(actor_info, object), do: {:ok, object}
|
||||
defp check_media_removal(_actor_info, object), do: {:ok, object}
|
||||
|
||||
@media_nsfw Keyword.get(@mrf_policy, :media_nsfw)
|
||||
defp check_media_nsfw(
|
||||
%{host: actor_host} = actor_info,
|
||||
%{host: actor_host} = _actor_info,
|
||||
%{
|
||||
"type" => "Create",
|
||||
"object" => %{"attachment" => child_attachment} = child_object
|
||||
} = object
|
||||
)
|
||||
when actor_host in @media_nsfw and length(child_attachment) > 0 do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
object = Map.put(object, "object", child_object)
|
||||
when length(child_attachment) > 0 do
|
||||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
object
|
||||
end
|
||||
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_media_nsfw(actor_info, object), do: {:ok, object}
|
||||
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
||||
|
||||
@ftl_removal Keyword.get(@mrf_policy, :federated_timeline_removal)
|
||||
defp check_ftl_removal(%{host: actor_host} = actor_info, object)
|
||||
when actor_host in @ftl_removal do
|
||||
user = User.get_by_ap_id(object["actor"])
|
||||
|
||||
# flip to/cc relationship to make the post unlisted
|
||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||
object =
|
||||
if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and
|
||||
user.follower_address in object["cc"] do
|
||||
with true <-
|
||||
Enum.member?(
|
||||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
|
||||
actor_host
|
||||
),
|
||||
user <- User.get_cached_by_ap_id(object["actor"]),
|
||||
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"],
|
||||
true <- user.follower_address in object["cc"] do
|
||||
to =
|
||||
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
|
||||
[user.follower_address]
|
||||
@ -68,14 +84,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
else
|
||||
object
|
||||
_ -> object
|
||||
end
|
||||
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
defp check_ftl_removal(actor_info, object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def filter(object) do
|
||||
actor_info = URI.parse(object["actor"])
|
||||
|
23
lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
Normal file
23
lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
Normal file
@ -0,0 +1,23 @@
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
|
||||
alias Pleroma.Config
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp filter_by_list(object, []), do: {:ok, object}
|
||||
|
||||
defp filter_by_list(%{"actor" => actor} = object, allow_list) do
|
||||
if actor in allow_list do
|
||||
{:ok, object}
|
||||
else
|
||||
{:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object) do
|
||||
actor_info = URI.parse(object["actor"])
|
||||
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
|
||||
|
||||
filter_by_list(object, allow_list)
|
||||
end
|
||||
end
|
@ -12,11 +12,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
||||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def unfollow(target_instance) do
|
||||
@ -24,11 +25,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
||||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
|
||||
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||
|
@ -21,18 +21,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
if is_binary(Enum.at(actor, 0)) do
|
||||
Enum.at(actor, 0)
|
||||
else
|
||||
Enum.find(actor, fn %{"type" => type} -> type == "Person" end)
|
||||
Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
|
||||
|> Map.get("id")
|
||||
end
|
||||
end
|
||||
|
||||
def get_actor(%{"actor" => actor}) when is_map(actor) do
|
||||
actor["id"]
|
||||
def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
|
||||
id
|
||||
end
|
||||
|
||||
def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
|
||||
get_actor(%{"actor" => actor})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks that an imported AP object's actor matches the domain it came from.
|
||||
"""
|
||||
def contain_origin(id, %{"actor" => nil}), do: :error
|
||||
|
||||
def contain_origin(id, %{"actor" => actor} = params) do
|
||||
id_uri = URI.parse(id)
|
||||
actor_uri = URI.parse(get_actor(params))
|
||||
@ -44,6 +50,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
end
|
||||
|
||||
def contain_origin_from_id(id, %{"id" => nil}), do: :error
|
||||
|
||||
def contain_origin_from_id(id, %{"id" => other_id} = params) do
|
||||
id_uri = URI.parse(id)
|
||||
other_uri = URI.parse(other_id)
|
||||
|
||||
if id_uri.host == other_uri.host do
|
||||
:ok
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||
"""
|
||||
@ -51,6 +70,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
object
|
||||
|> fix_actor
|
||||
|> fix_attachments
|
||||
|> fix_url
|
||||
|> fix_context
|
||||
|> fix_in_reply_to
|
||||
|> fix_emoji
|
||||
@ -96,9 +116,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
object
|
||||
end
|
||||
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
||||
when not is_nil(in_reply_to_id) do
|
||||
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
|
||||
when not is_nil(in_reply_to) do
|
||||
in_reply_to_id =
|
||||
cond do
|
||||
is_bitstring(in_reply_to) ->
|
||||
in_reply_to
|
||||
|
||||
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
|
||||
in_reply_to["id"]
|
||||
|
||||
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
|
||||
Enum.at(in_reply_to, 0)
|
||||
|
||||
# Maybe I should output an error too?
|
||||
true ->
|
||||
""
|
||||
end
|
||||
|
||||
case fetch_obj_helper(in_reply_to_id) do
|
||||
{:ok, replied_object} ->
|
||||
with %Activity{} = activity <-
|
||||
Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
|
||||
@ -110,12 +146,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
else
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
end
|
||||
@ -130,9 +166,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> Map.put("conversation", context)
|
||||
end
|
||||
|
||||
def fix_attachments(object) do
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
attachment
|
||||
|> Enum.map(fn data ->
|
||||
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
|
||||
Map.put(data, "url", url)
|
||||
@ -142,21 +178,41 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
def fix_emoji(object) do
|
||||
tags = object["tag"] || []
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
|
||||
Map.put(object, "attachment", [attachment])
|
||||
|> fix_attachments()
|
||||
end
|
||||
|
||||
def fix_attachments(object), do: object
|
||||
|
||||
def fix_url(%{"url" => url} = object) when is_map(url) do
|
||||
object
|
||||
|> Map.put("url", url["href"])
|
||||
end
|
||||
|
||||
def fix_url(%{"url" => url} = object) when is_list(url) do
|
||||
first_element = Enum.at(url, 0)
|
||||
|
||||
url_string =
|
||||
cond do
|
||||
is_bitstring(first_element) -> first_element
|
||||
is_map(first_element) -> first_element["href"] || ""
|
||||
true -> ""
|
||||
end
|
||||
|
||||
object
|
||||
|> Map.put("url", url_string)
|
||||
end
|
||||
|
||||
def fix_url(object), do: object
|
||||
|
||||
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
|
||||
emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|
||||
|
||||
emoji =
|
||||
emoji
|
||||
|> Enum.reduce(%{}, fn data, mapping ->
|
||||
name = data["name"]
|
||||
|
||||
name =
|
||||
if String.starts_with?(name, ":") do
|
||||
name |> String.slice(1..-2)
|
||||
else
|
||||
name
|
||||
end
|
||||
name = String.trim(data["name"], ":")
|
||||
|
||||
mapping |> Map.put(name, data["icon"]["url"])
|
||||
end)
|
||||
@ -168,18 +224,37 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> Map.put("emoji", emoji)
|
||||
end
|
||||
|
||||
def fix_tag(object) do
|
||||
def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
|
||||
name = String.trim(tag["name"], ":")
|
||||
emoji = %{name => tag["icon"]["url"]}
|
||||
|
||||
object
|
||||
|> Map.put("emoji", emoji)
|
||||
end
|
||||
|
||||
def fix_emoji(object), do: object
|
||||
|
||||
def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|
||||
tags =
|
||||
(object["tag"] || [])
|
||||
tag
|
||||
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
||||
|> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
|
||||
|
||||
combined = (object["tag"] || []) ++ tags
|
||||
combined = tag ++ tags
|
||||
|
||||
object
|
||||
|> Map.put("tag", combined)
|
||||
end
|
||||
|
||||
def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
|
||||
combined = [tag, String.slice(hashtag, 1..-1)]
|
||||
|
||||
object
|
||||
|> Map.put("tag", combined)
|
||||
end
|
||||
|
||||
def fix_tag(object), do: object
|
||||
|
||||
# content map usually only has one language so this will do for now.
|
||||
def fix_content_map(%{"contentMap" => content_map} = object) do
|
||||
content_groups = Map.to_list(content_map)
|
||||
@ -201,7 +276,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
# - tags
|
||||
# - emoji
|
||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
|
||||
when objtype in ["Article", "Note", "Video"] do
|
||||
when objtype in ["Article", "Note", "Video", "Page"] do
|
||||
actor = get_actor(data)
|
||||
|
||||
data =
|
||||
@ -285,8 +360,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
def handle_incoming(
|
||||
%{"type" => "Accept", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.accept(%{
|
||||
@ -309,8 +386,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
def handle_incoming(
|
||||
%{"type" => "Reject", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.accept(%{
|
||||
@ -329,11 +408,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data
|
||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@ -342,11 +421,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = _data
|
||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@ -388,15 +467,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Make secure.
|
||||
# TODO: We presently assume that any actor on the same origin domain as the object being
|
||||
# deleted has the rights to delete that object. A better way to validate whether or not
|
||||
# the object should be deleted is to refetch the object URI, which should return either
|
||||
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
||||
# place.
|
||||
def handle_incoming(
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data
|
||||
) do
|
||||
object_id = Utils.get_ap_id(object_id)
|
||||
|
||||
with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
:ok <- contain_origin(actor.ap_id, object.data),
|
||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@ -410,11 +494,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
"object" => %{"type" => "Announce", "object" => object_id},
|
||||
"actor" => actor,
|
||||
"id" => id
|
||||
} = _data
|
||||
} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@ -440,9 +524,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
end
|
||||
|
||||
@ap_config Application.get_env(:pleroma, :activitypub)
|
||||
@accept_blocks Keyword.get(@ap_config, :accept_blocks)
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
@ -451,7 +532,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
"id" => id
|
||||
} = _data
|
||||
) do
|
||||
with true <- @accept_blocks,
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker <- User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||
@ -465,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
def handle_incoming(
|
||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = data
|
||||
) do
|
||||
with true <- @accept_blocks,
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||
@ -483,11 +564,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
"object" => %{"type" => "Like", "object" => object_id},
|
||||
"actor" => actor,
|
||||
"id" => id
|
||||
} = _data
|
||||
} = data
|
||||
) do
|
||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
with actor <- get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
@ -497,6 +578,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|
||||
def handle_incoming(_), do: :error
|
||||
|
||||
def fetch_obj_helper(id) when is_bitstring(id), do: ActivityPub.fetch_object_from_id(id)
|
||||
def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_id(obj["id"])
|
||||
|
||||
def get_obj_helper(id) do
|
||||
if object = Object.normalize(id), do: {:ok, object}, else: nil
|
||||
end
|
||||
@ -523,6 +607,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
|> set_reply_to_uri
|
||||
|> strip_internal_fields
|
||||
|> strip_internal_tags
|
||||
end
|
||||
|
||||
# @doc
|
||||
@ -538,7 +624,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
@ -557,7 +643,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
@ -575,7 +661,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
@ -585,14 +671,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
data =
|
||||
data
|
||||
|> maybe_fix_object_url
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def maybe_fix_object_url(data) do
|
||||
if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
|
||||
case ActivityPub.fetch_object_from_id(data["object"]) do
|
||||
case fetch_obj_helper(data["object"]) do
|
||||
{:ok, relative_object} ->
|
||||
if relative_object.data["external_url"] do
|
||||
_data =
|
||||
@ -627,12 +713,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
end
|
||||
|
||||
def add_mention_tags(object) do
|
||||
recipients = object["to"] ++ (object["cc"] || [])
|
||||
|
||||
mentions =
|
||||
recipients
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.filter(& &1)
|
||||
object
|
||||
|> Utils.get_notified_from_object()
|
||||
|> Enum.map(fn user ->
|
||||
%{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
|
||||
end)
|
||||
@ -692,6 +775,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|> Map.put("attachment", attachments)
|
||||
end
|
||||
|
||||
defp strip_internal_fields(object) do
|
||||
object
|
||||
|> Map.drop([
|
||||
"likes",
|
||||
"like_count",
|
||||
"announcements",
|
||||
"announcement_count",
|
||||
"emoji",
|
||||
"context_id"
|
||||
])
|
||||
end
|
||||
|
||||
defp strip_internal_tags(%{"tag" => tags} = object) do
|
||||
tags =
|
||||
tags
|
||||
|> Enum.filter(fn x -> is_map(x) end)
|
||||
|
||||
object
|
||||
|> Map.put("tag", tags)
|
||||
end
|
||||
|
||||
defp strip_internal_tags(object), do: object
|
||||
|
||||
defp user_upgrade_task(user) do
|
||||
old_follower_address = User.ap_followers(user)
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
alias Pleroma.{Repo, Web, Object, Activity, User}
|
||||
alias Pleroma.{Repo, Web, Object, Activity, User, Notification}
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Ecto.{Changeset, UUID}
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
def get_ap_id(object) do
|
||||
@ -19,22 +21,58 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
Map.put(params, "actor", get_ap_id(params["actor"]))
|
||||
end
|
||||
|
||||
defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll
|
||||
defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll
|
||||
defp recipient_in_collection(_, _), do: false
|
||||
|
||||
def recipient_in_message(ap_id, params) do
|
||||
cond do
|
||||
recipient_in_collection(ap_id, params["to"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["cc"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["bto"]) ->
|
||||
true
|
||||
|
||||
recipient_in_collection(ap_id, params["bcc"]) ->
|
||||
true
|
||||
|
||||
# if the message is unaddressed at all, then assume it is directly addressed
|
||||
# to the recipient
|
||||
!params["to"] && !params["cc"] && !params["bto"] && !params["bcc"] ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_list(target) when is_binary(target), do: [target]
|
||||
defp extract_list(lst) when is_list(lst), do: lst
|
||||
defp extract_list(_), do: []
|
||||
|
||||
def maybe_splice_recipient(ap_id, params) do
|
||||
need_splice =
|
||||
!recipient_in_collection(ap_id, params["to"]) &&
|
||||
!recipient_in_collection(ap_id, params["cc"])
|
||||
|
||||
cc_list = extract_list(params["cc"])
|
||||
|
||||
if need_splice do
|
||||
params
|
||||
|> Map.put("cc", [ap_id | cc_list])
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def make_json_ld_header do
|
||||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld"
|
||||
]
|
||||
}
|
||||
end
|
||||
@ -59,6 +97,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
|
||||
end
|
||||
|
||||
def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
|
||||
fake_create_activity = %{
|
||||
"to" => object["to"],
|
||||
"cc" => object["cc"],
|
||||
"type" => "Create",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false)
|
||||
end
|
||||
|
||||
def get_notified_from_object(object) do
|
||||
Notification.get_notified_from_activity(%Activity{data: object}, false)
|
||||
end
|
||||
|
||||
def create_context(context) do
|
||||
context = context || generate_id("contexts")
|
||||
changeset = Object.context_mapping(context)
|
||||
@ -128,7 +181,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
||||
when is_map(object_data) and type in ["Article", "Note", "Video"] do
|
||||
when is_map(object_data) and type in @supported_object_types do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
end
|
||||
@ -247,11 +300,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"actor" => follower_id,
|
||||
"to" => [followed_id],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => followed_id
|
||||
"object" => followed_id,
|
||||
"state" => "pending"
|
||||
}
|
||||
|
||||
data = if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data
|
||||
|
||||
data
|
||||
end
|
||||
|
@ -1,27 +1,34 @@
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.{Object, Activity}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def render("object.json", %{object: object}) do
|
||||
base = %{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
%{
|
||||
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
|
||||
"sensitive" => "as:sensitive",
|
||||
"Hashtag" => "as:Hashtag",
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"Emoji" => "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
||||
def render("object.json", %{object: %Object{} = object}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
|
||||
additional = Transmogrifier.prepare_object(object.data)
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
|
||||
def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|> Map.put("object", Transmogrifier.prepare_object(object.data))
|
||||
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
|
||||
def render("object.json", %{object: %Activity{} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|> Map.put("object", object.data["id"])
|
||||
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
end
|
||||
|
@ -17,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||
public_key = :public_key.pem_encode([public_key])
|
||||
|
||||
%{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => user.ap_id,
|
||||
"type" => "Application",
|
||||
"following" => "#{user.ap_id}/following",
|
||||
@ -36,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
|
||||
}
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
|
158
lib/pleroma/web/admin_api/admin_api_controller.ex
Normal file
158
lib/pleroma/web/admin_api/admin_api_controller.ex
Normal file
@ -0,0 +1,158 @@
|
||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
require Logger
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
def user_delete(conn, %{"nickname" => nickname}) do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
if user.local == true do
|
||||
User.delete(user)
|
||||
else
|
||||
User.delete(user)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json(nickname)
|
||||
end
|
||||
|
||||
def user_create(
|
||||
conn,
|
||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||
) do
|
||||
new_user = %{
|
||||
nickname: nickname,
|
||||
name: nickname,
|
||||
email: email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
bio: "."
|
||||
}
|
||||
|
||||
User.register_changeset(%User{}, new_user)
|
||||
|> Repo.insert!()
|
||||
|
||||
conn
|
||||
|> json(new_user.nickname)
|
||||
end
|
||||
|
||||
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_" <> permission_group, true)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
|
||||
def right_get(conn, %{"nickname" => nickname}) do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
|
||||
def right_add(conn, _) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "No such permission_group"})
|
||||
end
|
||||
|
||||
def right_delete(
|
||||
%{assigns: %{user: %User{:nickname => admin_nickname}}} = conn,
|
||||
%{
|
||||
"permission_group" => permission_group,
|
||||
"nickname" => nickname
|
||||
}
|
||||
)
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
if admin_nickname == nickname do
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> json(%{error: "You can't revoke your own admin status."})
|
||||
else
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_" <> permission_group, false)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
end
|
||||
|
||||
def right_delete(conn, _) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "No such permission_group"})
|
||||
end
|
||||
|
||||
def relay_follow(conn, %{"relay_url" => target}) do
|
||||
{status, message} = Relay.follow(target)
|
||||
|
||||
if status == :ok do
|
||||
conn
|
||||
|> json(target)
|
||||
else
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
def relay_unfollow(conn, %{"relay_url" => target}) do
|
||||
{status, message} = Relay.unfollow(target)
|
||||
|
||||
if status == :ok do
|
||||
conn
|
||||
|> json(target)
|
||||
else
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
@shortdoc "Get a account registeration invite token (base64 string)"
|
||||
def get_invite_token(conn, _params) do
|
||||
{:ok, token} = Pleroma.UserInviteToken.create_token()
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
end
|
||||
|
||||
@shortdoc "Get a password reset token (base64 string) for given nickname"
|
||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||
(%User{local: true} = user) = User.get_by_nickname(nickname)
|
||||
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json("Invalid parameters")
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json("Something went wrong")
|
||||
end
|
||||
end
|
@ -4,9 +4,7 @@ defmodule Pleroma.Web.UserSocket do
|
||||
|
||||
## Channels
|
||||
# channel "room:*", Pleroma.Web.RoomChannel
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||
end
|
||||
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||
|
||||
## Transports
|
||||
transport(:websocket, Phoenix.Transports.WebSocket)
|
||||
@ -24,7 +22,8 @@ defmodule Pleroma.Web.UserSocket do
|
||||
# See `Phoenix.Token` documentation for examples in
|
||||
# performing token verification on connect.
|
||||
def connect(%{"token" => token}, socket) do
|
||||
with {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
with true <- Pleroma.Config.get([:chat, :enabled]),
|
||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
%User{} = user <- Pleroma.Repo.get(User, user_id) do
|
||||
{:ok, assign(socket, :user_name, user.nickname)}
|
||||
else
|
||||
|
@ -36,7 +36,6 @@ defmodule Pleroma.Web.CommonAPI do
|
||||
|
||||
def favorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
false <- activity.data["actor"] == user.ap_id,
|
||||
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||
ActivityPub.like(user, object)
|
||||
else
|
||||
@ -47,7 +46,6 @@ defmodule Pleroma.Web.CommonAPI do
|
||||
|
||||
def unfavorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
false <- activity.data["actor"] == user.ap_id,
|
||||
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||
ActivityPub.unlike(user, object)
|
||||
else
|
||||
@ -72,22 +70,37 @@ defmodule Pleroma.Web.CommonAPI do
|
||||
|
||||
def get_visibility(_), do: "public"
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@limit Keyword.get(@instance, :limit)
|
||||
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
|
||||
visibility = get_visibility(data)
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
with status <- String.trim(status),
|
||||
length when length in 1..@limit <- String.length(status),
|
||||
attachments <- attachments_from_ids(data["media_ids"]),
|
||||
mentions <- Formatter.parse_mentions(status),
|
||||
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 <-
|
||||
make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
|
||||
make_content_html(
|
||||
status,
|
||||
mentions,
|
||||
attachments,
|
||||
tags,
|
||||
get_content_type(data["content_type"]),
|
||||
data["no_attachment_links"]
|
||||
),
|
||||
context <- make_context(inReplyTo),
|
||||
cw <- data["spoiler_text"],
|
||||
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
|
||||
length when length in 1..limit <- String.length(full_payload),
|
||||
object <-
|
||||
make_note_data(
|
||||
user.ap_id,
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
alias Pleroma.{Repo, Object, Formatter, Activity}
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.User
|
||||
alias Calendar.Strftime
|
||||
alias Comeonin.Pbkdf2
|
||||
@ -18,6 +19,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
end
|
||||
end
|
||||
|
||||
def get_replied_to_activity(""), do: nil
|
||||
|
||||
def get_replied_to_activity(id) when not is_nil(id) do
|
||||
Repo.get(Activity, id)
|
||||
end
|
||||
@ -31,21 +34,29 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
cc = [user.follower_address | mentioned_users]
|
||||
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
|
||||
cc = [user.follower_address]
|
||||
|
||||
if inReplyTo do
|
||||
{to, Enum.uniq([inReplyTo.data["actor"] | cc])}
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "unlisted") do
|
||||
{to, cc} = to_for_user_and_mentions(user, mentions, inReplyTo, "public")
|
||||
{cc, to}
|
||||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
|
||||
|
||||
to = [user.follower_address | mentioned_users]
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
|
||||
@ -63,9 +74,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
end
|
||||
end
|
||||
|
||||
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
|
||||
def make_content_html(
|
||||
status,
|
||||
mentions,
|
||||
attachments,
|
||||
tags,
|
||||
content_type,
|
||||
no_attachment_links \\ false
|
||||
) do
|
||||
status
|
||||
|> format_input(mentions, tags)
|
||||
|> format_input(mentions, tags, content_type)
|
||||
|> maybe_add_attachments(attachments, no_attachment_links)
|
||||
end
|
||||
|
||||
@ -81,8 +99,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
def add_attachments(text, attachments) do
|
||||
attachment_text =
|
||||
Enum.map(attachments, fn
|
||||
%{"url" => [%{"href" => href} | _]} ->
|
||||
name = URI.decode(Path.basename(href))
|
||||
%{"url" => [%{"href" => href} | _]} = attachment ->
|
||||
name = attachment["name"] || URI.decode(Path.basename(href))
|
||||
href = MediaProxy.url(href)
|
||||
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
|
||||
|
||||
_ ->
|
||||
@ -92,9 +111,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
Enum.join([text | attachment_text], "<br>")
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags) do
|
||||
def format_input(text, mentions, tags, "text/plain") do
|
||||
text
|
||||
|> Formatter.html_escape()
|
||||
|> Formatter.html_escape("text/plain")
|
||||
|> String.replace(~r/\r?\n/, "<br>")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_links()
|
||||
@ -103,6 +122,26 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags, "text/html") do
|
||||
text
|
||||
|> Formatter.html_escape("text/html")
|
||||
|> String.replace(~r/\r?\n/, "<br>")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_user_links(mentions)
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def format_input(text, mentions, tags, "text/markdown") do
|
||||
text
|
||||
|> Earmark.as_html!()
|
||||
|> Formatter.html_escape("text/html")
|
||||
|> String.replace(~r/\r?\n/, "")
|
||||
|> (&{[], &1}).()
|
||||
|> Formatter.add_user_links(mentions)
|
||||
|> Formatter.add_hashtag_links(tags)
|
||||
|> Formatter.finalize()
|
||||
end
|
||||
|
||||
def add_tag_links(text, tags) do
|
||||
tags =
|
||||
tags
|
||||
|
@ -1,9 +1,7 @@
|
||||
defmodule Pleroma.Web.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
end
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
|
||||
socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket)
|
||||
|
||||
@ -11,13 +9,17 @@ defmodule Pleroma.Web.Endpoint do
|
||||
#
|
||||
# You should set gzip to true if you are running phoenix.digest
|
||||
# when deploying your static files in production.
|
||||
plug(CORSPlug)
|
||||
plug(Pleroma.Plugs.HTTPSecurityPlug)
|
||||
|
||||
plug(Plug.Static, at: "/media", from: Pleroma.Uploaders.Local.upload_path(), gzip: false)
|
||||
|
||||
plug(
|
||||
Plug.Static,
|
||||
at: "/",
|
||||
from: :pleroma,
|
||||
only: ~w(index.html static finmoji emoji packs sounds images instance sw.js favicon.png)
|
||||
only:
|
||||
~w(index.html static finmoji emoji packs sounds images instance sw.js favicon.png schemas)
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
@ -42,14 +44,19 @@ defmodule Pleroma.Web.Endpoint do
|
||||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
||||
cookie_name =
|
||||
if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
do: "__Host-pleroma_key",
|
||||
else: "pleroma_key"
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
plug(
|
||||
Plug.Session,
|
||||
store: :cookie,
|
||||
key: "_pleroma_key",
|
||||
signing_salt: "CqaoopA2",
|
||||
key: cookie_name,
|
||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||
http_only: true,
|
||||
secure:
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
|
@ -3,17 +3,17 @@ defmodule Pleroma.Web.Federator do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.OStatus
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
@ostatus Application.get_env(:pleroma, :ostatus)
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@federating Keyword.get(@instance, :federating)
|
||||
@max_jobs 20
|
||||
|
||||
def init(args) do
|
||||
@ -65,15 +65,17 @@ defmodule Pleroma.Web.Federator do
|
||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||
|
||||
if ActivityPub.is_public?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
if OStatus.is_representable?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
end
|
||||
|
||||
if Mix.env() != :test do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||
Pleroma.Web.ActivityPub.Relay.publish(activity)
|
||||
Relay.publish(activity)
|
||||
end
|
||||
end
|
||||
|
||||
@ -100,44 +102,46 @@ defmodule Pleroma.Web.Federator do
|
||||
|
||||
params = Utils.normalize_params(params)
|
||||
|
||||
# NOTE: we use the actor ID to do the containment, this is fine because an
|
||||
# actor shouldn't be acting on objects outside their own AP server.
|
||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||
nil <- Activity.normalize(params["id"]),
|
||||
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
|
||||
:ok <- Transmogrifier.contain_origin_from_id(params["actor"], params),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, activity}
|
||||
else
|
||||
%Activity{} ->
|
||||
Logger.info("Already had #{params["id"]}")
|
||||
:error
|
||||
|
||||
_e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, pretty: 2))
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle(:publish_single_ap, params) do
|
||||
ActivityPub.publish_one(params)
|
||||
case ActivityPub.publish_one(params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, _} ->
|
||||
RetryQueue.enqueue(params, ActivityPub)
|
||||
end
|
||||
end
|
||||
|
||||
def handle(:publish_single_websub, %{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
||||
signature = @websub.sign(secret || "", xml)
|
||||
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
def handle(
|
||||
:publish_single_websub,
|
||||
%{xml: xml, topic: topic, callback: callback, secret: secret} = params
|
||||
) do
|
||||
case Websub.publish_one(params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
with {:ok, %{status_code: code}} <-
|
||||
@httpoison.post(
|
||||
callback,
|
||||
xml,
|
||||
[
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.debug(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||
{:error, _} ->
|
||||
RetryQueue.enqueue(params, Websub)
|
||||
end
|
||||
end
|
||||
|
||||
@ -146,11 +150,15 @@ defmodule Pleroma.Web.Federator do
|
||||
{:error, "Don't know what to do with this"}
|
||||
end
|
||||
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if @federating do
|
||||
if Mix.env() == :test do
|
||||
if Mix.env() == :test do
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if Pleroma.Config.get([:instance, :federating]) do
|
||||
handle(type, payload)
|
||||
else
|
||||
end
|
||||
end
|
||||
else
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if Pleroma.Config.get([:instance, :federating]) do
|
||||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
||||
end
|
||||
end
|
||||
|
71
lib/pleroma/web/federator/retry_queue.ex
Normal file
71
lib/pleroma/web/federator/retry_queue.ex
Normal file
@ -0,0 +1,71 @@
|
||||
defmodule Pleroma.Web.Federator.RetryQueue do
|
||||
use GenServer
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
@ostatus Application.get_env(:pleroma, :websub)
|
||||
@httpoison Application.get_env(:pleroma, :websub)
|
||||
@instance Application.get_env(:pleroma, :websub)
|
||||
# initial timeout, 5 min
|
||||
@initial_timeout 30_000
|
||||
@max_retries 5
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, %{delivered: 0, dropped: 0}, name: __MODULE__)
|
||||
end
|
||||
|
||||
def enqueue(data, transport, retries \\ 0) do
|
||||
GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1})
|
||||
end
|
||||
|
||||
def get_retry_params(retries) do
|
||||
if retries > @max_retries do
|
||||
{:drop, "Max retries reached"}
|
||||
else
|
||||
{:retry, growth_function(retries)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast({:maybe_enqueue, data, transport, retries}, %{dropped: drop_count} = state) do
|
||||
case get_retry_params(retries) do
|
||||
{:retry, timeout} ->
|
||||
Process.send_after(
|
||||
__MODULE__,
|
||||
{:send, data, transport, retries},
|
||||
growth_function(retries)
|
||||
)
|
||||
|
||||
{:noreply, state}
|
||||
|
||||
{:drop, message} ->
|
||||
Logger.debug(message)
|
||||
{:noreply, %{state | dropped: drop_count + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:send, data, transport, retries}, %{delivered: delivery_count} = state) do
|
||||
case transport.publish_one(data) do
|
||||
{:ok, _} ->
|
||||
{:noreply, %{state | delivered: delivery_count + 1}}
|
||||
|
||||
{:error, reason} ->
|
||||
enqueue(data, transport, retries)
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(unknown, state) do
|
||||
Logger.debug("RetryQueue: don't know what to do with #{inspect(unknown)}, ignoring")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp growth_function(retries) do
|
||||
round(@initial_timeout * :math.pow(retries, 3))
|
||||
end
|
||||
end
|
@ -35,6 +35,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
original_user = user
|
||||
|
||||
avatar_upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:avatar_upload_limit)
|
||||
|
||||
banner_upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:banner_upload_limit)
|
||||
|
||||
params =
|
||||
if bio = params["note"] do
|
||||
Map.put(params, "bio", bio)
|
||||
@ -52,7 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
user =
|
||||
if avatar = params["avatar"] do
|
||||
with %Plug.Upload{} <- avatar,
|
||||
{:ok, object} <- ActivityPub.upload(avatar),
|
||||
{:ok, object} <- ActivityPub.upload(avatar, avatar_upload_limit),
|
||||
change = Ecto.Changeset.change(user, %{avatar: object.data}),
|
||||
{:ok, user} = User.update_and_set_cache(change) do
|
||||
user
|
||||
@ -66,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
user =
|
||||
if banner = params["header"] do
|
||||
with %Plug.Upload{} <- banner,
|
||||
{:ok, object} <- ActivityPub.upload(banner),
|
||||
{:ok, object} <- ActivityPub.upload(banner, banner_upload_limit),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
@ -124,22 +132,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@mastodon_api_level "2.5.0"
|
||||
|
||||
def masto_instance(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
|
||||
response = %{
|
||||
uri: Web.base_url(),
|
||||
title: Keyword.get(@instance, :name),
|
||||
description: Keyword.get(@instance, :description),
|
||||
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
|
||||
email: Keyword.get(@instance, :email),
|
||||
title: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
||||
email: Keyword.get(instance, :email),
|
||||
urls: %{
|
||||
streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
|
||||
},
|
||||
stats: Stats.get_stats(),
|
||||
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
max_toot_chars: Keyword.get(instance, :limit)
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
@ -150,7 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Formatter.get_custom_emoji()
|
||||
Pleroma.Emoji.get_all()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
@ -223,6 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
@ -268,9 +278,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, _params) do
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
query =
|
||||
ActivityPub.fetch_activities_query([user.ap_id], %{"type" => "Create", visibility: "direct"})
|
||||
ActivityPub.fetch_activities_query(
|
||||
[user.ap_id],
|
||||
Map.merge(params, %{"type" => "Create", visibility: "direct"})
|
||||
)
|
||||
|
||||
activities = Repo.all(query)
|
||||
|
||||
@ -282,7 +295,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
@ -345,7 +358,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
{:ok, activity} =
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
||||
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
@ -361,28 +374,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
|
||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do
|
||||
render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
@ -434,6 +447,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
|
||||
end
|
||||
|
||||
# Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
|
||||
def relationships(%{assigns: %{user: user}} = conn, _) do
|
||||
conn
|
||||
|> json([])
|
||||
end
|
||||
|
||||
def update_media(%{assigns: %{user: _}} = conn, data) do
|
||||
with %Object{} = object <- Repo.get(Object, data["id"]),
|
||||
true <- is_binary(data["description"]),
|
||||
@ -499,6 +518,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("tag", String.downcase(params["tag"]))
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_public_activities(params)
|
||||
@ -574,7 +594,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followed),
|
||||
{:ok, follower, followed} <-
|
||||
User.wait_and_refresh(
|
||||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
|
||||
follower,
|
||||
followed
|
||||
) do
|
||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||
else
|
||||
{:error, message} ->
|
||||
@ -765,6 +791,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
|
||||
lists = Pleroma.List.get_lists_account_belongs(user, account_id)
|
||||
res = ListView.render("lists.json", lists: lists)
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
{:ok, _list} <- Pleroma.List.delete(list) do
|
||||
@ -859,6 +891,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
accounts =
|
||||
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
|
||||
|
||||
@ -878,7 +912,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
auto_play_gif: false,
|
||||
display_sensitive_media: false,
|
||||
reduce_motion: false,
|
||||
max_toot_chars: Keyword.get(@instance, :limit)
|
||||
max_toot_chars: limit
|
||||
},
|
||||
rights: %{
|
||||
delete_others_notice: !!user.info["is_moderator"]
|
||||
@ -938,7 +972,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
push_subscription: nil,
|
||||
accounts: accounts,
|
||||
custom_emojis: mastodon_emoji,
|
||||
char_limit: Keyword.get(@instance, :limit)
|
||||
char_limit: limit
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
@ -964,9 +998,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
def login(conn, %{"code" => code}) do
|
||||
with {:ok, app} <- get_or_make_app(),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
conn
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> redirect(to: "/web/getting-started")
|
||||
end
|
||||
end
|
||||
|
||||
def login(conn, _) do
|
||||
conn
|
||||
|> render(MastodonView, "login.html", %{error: false})
|
||||
with {:ok, app} <- get_or_make_app() do
|
||||
path =
|
||||
o_auth_path(conn, :authorize,
|
||||
response_type: "code",
|
||||
client_id: app.client_id,
|
||||
redirect_uri: ".",
|
||||
scope: app.scopes
|
||||
)
|
||||
|
||||
conn
|
||||
|> redirect(to: path)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_make_app() do
|
||||
@ -985,22 +1039,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do
|
||||
with %User{} = user <- User.get_by_nickname_or_email(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
{:ok, app} <- get_or_make_app(),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
conn
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> redirect(to: "/web/getting-started")
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|> render(MastodonView, "login.html", %{error: "Wrong username or password"})
|
||||
end
|
||||
end
|
||||
|
||||
def logout(conn, _) do
|
||||
conn
|
||||
|> clear_session
|
||||
@ -1173,18 +1211,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
|> json("Something went wrong")
|
||||
end
|
||||
|
||||
@suggestions Application.get_env(:pleroma, :suggestions)
|
||||
|
||||
def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||
if Keyword.get(@suggestions, :enabled, false) do
|
||||
api = Keyword.get(@suggestions, :third_party_engine, "")
|
||||
timeout = Keyword.get(@suggestions, :timeout, 5000)
|
||||
limit = Keyword.get(@suggestions, :limit, 23)
|
||||
suggestions = Pleroma.Config.get(:suggestions)
|
||||
|
||||
host =
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
|
||||
|> Keyword.get(:url)
|
||||
|> Keyword.get(:host)
|
||||
if Keyword.get(suggestions, :enabled, false) do
|
||||
api = Keyword.get(suggestions, :third_party_engine, "")
|
||||
timeout = Keyword.get(suggestions, :timeout, 5000)
|
||||
limit = Keyword.get(suggestions, :limit, 23)
|
||||
|
||||
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
|
||||
|
||||
user = user.nickname
|
||||
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
|
||||
@ -1220,4 +1255,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
json(conn, [])
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, renderer, target, params)
|
||||
when is_binary(target) do
|
||||
res = render(conn, renderer, target, params)
|
||||
|
||||
if res == nil do
|
||||
conn
|
||||
|> put_status(501)
|
||||
|> json(%{error: "Can't display this activity"})
|
||||
else
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, _, _, _) do
|
||||
conn
|
||||
|> put_status(501)
|
||||
|> json(%{error: "Can't display this activity"})
|
||||
end
|
||||
end
|
||||
|
@ -11,9 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
def connect(params, socket) do
|
||||
with token when not is_nil(token) <- params["access_token"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
def connect(%{"access_token" => token} = params, socket) do
|
||||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
stream
|
||||
when stream in [
|
||||
@ -26,15 +25,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
||||
"list",
|
||||
"hashtag"
|
||||
] <- params["stream"] do
|
||||
topic = if stream == "list", do: "list:#{params["list"]}", else: stream
|
||||
socket_stream = if stream == "hashtag", do: "hashtag:#{params["tag"]}", else: stream
|
||||
topic =
|
||||
case stream do
|
||||
"hashtag" -> "hashtag:#{params["tag"]}"
|
||||
"list" -> "list:#{params["list"]}"
|
||||
_ -> stream
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:topic, topic)
|
||||
|> assign(:user, user)
|
||||
|
||||
Pleroma.Web.Streamer.add_socket(socket_stream, socket)
|
||||
Pleroma.Web.Streamer.add_socket(topic, socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def connect(%{"stream" => stream} = params, socket)
|
||||
when stream in ["public", "public:local", "hashtag"] do
|
||||
topic =
|
||||
case stream do
|
||||
"hashtag" -> "hashtag:#{params["tag"]}"
|
||||
_ -> stream
|
||||
end
|
||||
|
||||
with socket =
|
||||
socket
|
||||
|> assign(:topic, topic) do
|
||||
Pleroma.Web.Streamer.add_socket(topic, socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
_e -> :error
|
||||
|
@ -72,6 +72,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||
end
|
||||
|
||||
def render("relationship.json", %{user: user, target: target}) do
|
||||
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
|
||||
|
||||
requested =
|
||||
if follow_activity do
|
||||
follow_activity.data["state"] == "pending"
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: User.following?(user, target),
|
||||
@ -79,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||
blocking: User.blocks?(user, target),
|
||||
muting: false,
|
||||
muting_notifications: false,
|
||||
requested: false,
|
||||
requested: requested,
|
||||
domain_blocking: false,
|
||||
showing_reblogs: false,
|
||||
endorsed: false
|
||||
|
@ -34,6 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
"status.json",
|
||||
Map.put(opts, :replied_to_activities, replied_to_activities)
|
||||
)
|
||||
|> Enum.filter(fn x -> not is_nil(x) end)
|
||||
end
|
||||
|
||||
def render(
|
||||
@ -60,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
reblog: reblogged,
|
||||
content: reblogged[:content],
|
||||
content: reblogged[:content] || "",
|
||||
created_at: created_at,
|
||||
reblogs_count: 0,
|
||||
replies_count: 0,
|
||||
@ -158,10 +159,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
}
|
||||
end
|
||||
|
||||
def render("status.json", _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def render("attachment.json", %{attachment: attachment}) do
|
||||
[attachment_url | _] = attachment["url"]
|
||||
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
|
||||
href = attachment_url["href"]
|
||||
href = attachment_url["href"] |> MediaProxy.url()
|
||||
|
||||
type =
|
||||
cond do
|
||||
@ -175,9 +180,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
|
||||
%{
|
||||
id: to_string(attachment["id"] || hash_id),
|
||||
url: MediaProxy.url(href),
|
||||
url: href,
|
||||
remote_url: href,
|
||||
preview_url: MediaProxy.url(href),
|
||||
preview_url: href,
|
||||
text_url: href,
|
||||
type: type,
|
||||
description: attachment["name"]
|
||||
@ -225,24 +230,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
if !!name and name != "" do
|
||||
"<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
object["content"] || ""
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def render_content(%{"type" => "Article"} = object) do
|
||||
def render_content(%{"type" => object_type} = object) when object_type in ["Article", "Page"] do
|
||||
summary = object["name"]
|
||||
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
if !!summary and summary != "" and is_bitstring(object["url"]) do
|
||||
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
object["content"] || ""
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def render_content(object), do: object["content"]
|
||||
def render_content(object), do: object["content"] || ""
|
||||
end
|
||||
|
@ -11,15 +11,47 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
||||
error: "public, must-revalidate, max-age=160"
|
||||
}
|
||||
|
||||
def remote(conn, %{"sig" => sig, "url" => url}) do
|
||||
# Content-types that will not be returned as content-disposition attachments
|
||||
# Override with :media_proxy, :safe_content_types in the configuration
|
||||
@safe_content_types [
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"video/webm",
|
||||
"video/mp4"
|
||||
]
|
||||
|
||||
def remote(conn, params = %{"sig" => sig, "url" => url}) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
|
||||
with true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||
{:ok, content_type, body} <- proxy_request(url) do
|
||||
filename <- Path.basename(URI.parse(url).path),
|
||||
true <-
|
||||
if(Map.get(params, "filename"),
|
||||
do: filename == Path.basename(conn.request_path),
|
||||
else: true
|
||||
),
|
||||
{:ok, content_type, body} <- proxy_request(url),
|
||||
safe_content_type <-
|
||||
Enum.member?(
|
||||
Keyword.get(config, :safe_content_types, @safe_content_types),
|
||||
content_type
|
||||
) do
|
||||
conn
|
||||
|> put_resp_content_type(content_type)
|
||||
|> set_cache_header(:default)
|
||||
|> put_resp_header(
|
||||
"content-security-policy",
|
||||
"default-src 'none'; style-src 'unsafe-inline'; media-src data:; img-src 'self' data:"
|
||||
)
|
||||
|> put_resp_header("x-xss-protection", "1; mode=block")
|
||||
|> put_resp_header("x-content-type-options", "nosniff")
|
||||
|> put_attachement_header(safe_content_type, filename)
|
||||
|> send_resp(200, body)
|
||||
else
|
||||
false ->
|
||||
@ -92,6 +124,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
||||
# TODO: the body is passed here as well because some hosts do not provide a content-type.
|
||||
# At some point we may want to use magic numbers to discover the content-type and reply a proper one.
|
||||
defp proxy_request_content_type(headers, _body) do
|
||||
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
|
||||
headers["Content-Type"] || headers["content-type"] || "application/octet-stream"
|
||||
end
|
||||
|
||||
defp put_attachement_header(conn, true, _), do: conn
|
||||
|
||||
defp put_attachement_header(conn, false, filename) do
|
||||
put_resp_header(conn, "content-disposition", "attachment; filename='#{filename}'")
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,8 @@ defmodule Pleroma.Web.MediaProxy do
|
||||
|
||||
def url(nil), do: nil
|
||||
|
||||
def url(""), do: nil
|
||||
|
||||
def url(url = "/" <> _), do: url
|
||||
|
||||
def url(url) do
|
||||
@ -15,7 +17,10 @@ defmodule Pleroma.Web.MediaProxy do
|
||||
base64 = Base.url_encode64(url, @base64_opts)
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url()) <> "/proxy/#{sig64}/#{base64}"
|
||||
filename = if path = URI.parse(url).path, do: "/" <> Path.basename(path), else: ""
|
||||
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url()) <>
|
||||
"/proxy/#{sig64}/#{base64}#{filename}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -4,6 +4,10 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
def schemas(conn, _params) do
|
||||
response = %{
|
||||
@ -27,16 +31,69 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||
gopher = Application.get_env(:pleroma, :gopher)
|
||||
stats = Stats.get_stats()
|
||||
|
||||
mrf_simple =
|
||||
Application.get_env(:pleroma, :mrf_simple)
|
||||
|> Enum.into(%{})
|
||||
|
||||
mrf_policies =
|
||||
MRF.get_policies()
|
||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||
|
||||
quarantined = Keyword.get(instance, :quarantined_instances)
|
||||
|
||||
quarantined =
|
||||
if is_list(quarantined) do
|
||||
quarantined
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
staff_accounts =
|
||||
User.moderator_user_query()
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn u -> u.ap_id end)
|
||||
|
||||
mrf_user_allowlist =
|
||||
Config.get([:mrf_user_allowlist], [])
|
||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
||||
|
||||
mrf_transparency = Keyword.get(instance, :mrf_transparency)
|
||||
|
||||
federation_response =
|
||||
if mrf_transparency do
|
||||
%{
|
||||
mrf_policies: mrf_policies,
|
||||
mrf_simple: mrf_simple,
|
||||
mrf_user_allowlist: mrf_user_allowlist,
|
||||
quarantined_instances: quarantined
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
features = [
|
||||
"pleroma_api",
|
||||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
if Keyword.get(media_proxy, :enabled) do
|
||||
"media_proxy"
|
||||
end,
|
||||
if Keyword.get(gopher, :enabled) do
|
||||
"gopher"
|
||||
end,
|
||||
if Keyword.get(chat, :enabled) do
|
||||
"chat"
|
||||
end,
|
||||
if Keyword.get(suggestions, :enabled) do
|
||||
"suggestions"
|
||||
end
|
||||
]
|
||||
|
||||
response = %{
|
||||
version: "2.0",
|
||||
software: %{
|
||||
name: "pleroma",
|
||||
version: Keyword.get(instance, :version)
|
||||
name: Pleroma.Application.name(),
|
||||
version: Pleroma.Application.version()
|
||||
},
|
||||
protocols: ["ostatus", "activitypub"],
|
||||
services: %{
|
||||
@ -53,7 +110,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||
metadata: %{
|
||||
nodeName: Keyword.get(instance, :name),
|
||||
nodeDescription: Keyword.get(instance, :description),
|
||||
mediaProxy: Keyword.get(media_proxy, :enabled),
|
||||
private: !Keyword.get(instance, :public, true),
|
||||
suggestions: %{
|
||||
enabled: Keyword.get(suggestions, :enabled, false),
|
||||
@ -63,8 +119,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||
web: Keyword.get(suggestions, :web, "")
|
||||
},
|
||||
staffAccounts: staff_accounts,
|
||||
chat: Keyword.get(chat, :enabled),
|
||||
gopher: Keyword.get(gopher, :enabled)
|
||||
federation: federation_response,
|
||||
postFormats: Keyword.get(instance, :allowed_post_formats),
|
||||
uploadLimits: %{
|
||||
general: Keyword.get(instance, :upload_limit),
|
||||
avatar: Keyword.get(instance, :avatar_upload_limit),
|
||||
banner: Keyword.get(instance, :banner_upload_limit),
|
||||
background: Keyword.get(instance, :background_upload_limit)
|
||||
},
|
||||
features: features
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.OAuth.{Authorization, App}
|
||||
|
||||
import Ecto.{Changeset}
|
||||
import Ecto.{Changeset, Query}
|
||||
|
||||
schema "oauth_authorizations" do
|
||||
field(:token, :string)
|
||||
@ -45,4 +45,12 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||
end
|
||||
|
||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
|
||||
def delete_user_authorizations(%User{id: user_id}) do
|
||||
from(
|
||||
a in Pleroma.Web.OAuth.Authorization,
|
||||
where: a.user_id == ^user_id
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
end
|
||||
|
@ -33,25 +33,35 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user) do
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
# Special case: Local MastodonFE.
|
||||
redirect_uri =
|
||||
if redirect_uri == "." do
|
||||
mastodon_api_url(conn, :login)
|
||||
else
|
||||
redirect_uri
|
||||
end
|
||||
|
||||
url_params =
|
||||
if params["state"] do
|
||||
Map.put(url_params, :state, params["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
cond do
|
||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
true ->
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
||||
redirect(conn, external: url)
|
||||
url_params =
|
||||
if params["state"] do
|
||||
Map.put(url_params, :state, params["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -133,8 +143,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
end
|
||||
end
|
||||
|
||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||
# decoding it. Investigate sometime.
|
||||
defp fix_padding(token) do
|
||||
token
|
||||
|> URI.decode()
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64()
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
defmodule Pleroma.Web.OAuth.Token do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.OAuth.{Token, App, Authorization}
|
||||
|
||||
@ -35,4 +37,12 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||
|
||||
Repo.insert(token)
|
||||
end
|
||||
|
||||
def delete_user_tokens(%User{id: user_id}) do
|
||||
from(
|
||||
t in Pleroma.Web.OAuth.Token,
|
||||
where: t.user_id == ^user_id
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
end
|
||||
|
@ -11,6 +11,21 @@ defmodule Pleroma.Web.OStatus do
|
||||
alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler}
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
def is_representable?(%Activity{data: data}) do
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
cond do
|
||||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["type"] == "Note" ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def feed_path(user) do
|
||||
"#{user.ap_id}/feed.atom"
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.{User, Activity}
|
||||
alias Pleroma.{User, Activity, Object}
|
||||
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.{OStatus, Federator}
|
||||
@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
alias Pleroma.Web.ActivityPub.ActivityPubController
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
|
||||
action_fallback(:errors)
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
@ -135,7 +136,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
"html" ->
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv/static/index.html")
|
||||
|> send_file(200, Application.app_dir(:pleroma, "priv/static/index.html"))
|
||||
|
||||
_ ->
|
||||
represent_activity(conn, format, activity, user)
|
||||
@ -152,10 +153,21 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
end
|
||||
end
|
||||
|
||||
defp represent_activity(conn, "activity+json", activity, user) do
|
||||
defp represent_activity(
|
||||
conn,
|
||||
"activity+json",
|
||||
%Activity{data: %{"type" => "Create"}} = activity,
|
||||
user
|
||||
) do
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(ObjectView.render("object.json", %{object: activity}))
|
||||
|> json(ObjectView.render("object.json", %{object: object}))
|
||||
end
|
||||
|
||||
defp represent_activity(conn, "activity+json", _, _) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
defp represent_activity(conn, _, activity, user) do
|
||||
|
@ -3,12 +3,6 @@ defmodule Pleroma.Web.Router do
|
||||
|
||||
alias Pleroma.{Repo, User, Web.Router}
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@federating Keyword.get(@instance, :federating)
|
||||
@allow_relay Keyword.get(@instance, :allow_relay)
|
||||
@public Keyword.get(@instance, :public)
|
||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||
|
||||
pipeline :api do
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
@ -37,6 +31,21 @@ defmodule Pleroma.Web.Router do
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
end
|
||||
|
||||
pipeline :admin_api do
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
|
||||
plug(Pleroma.Plugs.UserFetcherPlug)
|
||||
plug(Pleroma.Plugs.SessionAuthenticationPlug)
|
||||
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug)
|
||||
plug(Pleroma.Plugs.UserEnabledPlug)
|
||||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.UserIsAdminPlug)
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
@ -85,6 +94,23 @@ defmodule Pleroma.Web.Router do
|
||||
get("/emoji", UtilController, :emoji)
|
||||
end
|
||||
|
||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||
pipe_through(:admin_api)
|
||||
delete("/user", AdminAPIController, :user_delete)
|
||||
post("/user", AdminAPIController, :user_create)
|
||||
|
||||
get("/permission_group/:nickname", AdminAPIController, :right_get)
|
||||
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
|
||||
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
|
||||
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
|
||||
|
||||
post("/relay", AdminAPIController, :relay_follow)
|
||||
delete("/relay", AdminAPIController, :relay_unfollow)
|
||||
|
||||
get("/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/password_reset", AdminAPIController, :get_password_reset)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
pipe_through(:pleroma_html)
|
||||
get("/ostatus_subscribe", UtilController, :remote_follow)
|
||||
@ -119,6 +145,7 @@ defmodule Pleroma.Web.Router do
|
||||
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
|
||||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
||||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
|
||||
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
||||
|
||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
||||
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
|
||||
@ -247,11 +274,7 @@ defmodule Pleroma.Web.Router do
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
if @public do
|
||||
pipe_through(:api)
|
||||
else
|
||||
pipe_through(:authenticated_api)
|
||||
end
|
||||
pipe_through(:api)
|
||||
|
||||
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
||||
|
||||
@ -264,7 +287,12 @@ defmodule Pleroma.Web.Router do
|
||||
get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
scope "/api", Pleroma.Web, as: :twitter_api_search do
|
||||
pipe_through(:api)
|
||||
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
|
||||
@ -284,8 +312,13 @@ defmodule Pleroma.Web.Router do
|
||||
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
|
||||
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
|
||||
|
||||
# XXX: this is really a pleroma API, but we want to keep the pleroma namespace clean
|
||||
# for now.
|
||||
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
|
||||
|
||||
post("/statuses/update", TwitterAPI.Controller, :status_update)
|
||||
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
|
||||
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
|
||||
@ -335,12 +368,10 @@ defmodule Pleroma.Web.Router do
|
||||
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
|
||||
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
|
||||
|
||||
if @federating do
|
||||
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
end
|
||||
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
|
||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
@ -357,31 +388,27 @@ defmodule Pleroma.Web.Router do
|
||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||
end
|
||||
|
||||
if @federating do
|
||||
if @allow_relay do
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_relay)
|
||||
get("/", ActivityPubController, :relay)
|
||||
end
|
||||
end
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_relay)
|
||||
get("/", ActivityPubController, :relay)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:activitypub)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:activitypub)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through(:well_known)
|
||||
scope "/.well-known", Pleroma.Web do
|
||||
pipe_through(:well_known)
|
||||
|
||||
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
|
||||
end
|
||||
get("/host-meta", WebFinger.WebFingerController, :host_meta)
|
||||
get("/webfinger", WebFinger.WebFingerController, :webfinger)
|
||||
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
|
||||
end
|
||||
|
||||
scope "/nodeinfo", Pleroma.Web do
|
||||
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
|
||||
end
|
||||
scope "/nodeinfo", Pleroma.Web do
|
||||
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.MastodonAPI do
|
||||
@ -394,12 +421,12 @@ defmodule Pleroma.Web.Router do
|
||||
end
|
||||
|
||||
pipeline :remote_media do
|
||||
plug(:accepts, ["html"])
|
||||
end
|
||||
|
||||
scope "/proxy/", Pleroma.Web.MediaProxy do
|
||||
pipe_through(:remote_media)
|
||||
get("/:sig/:url", MediaProxyController, :remote)
|
||||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
||||
end
|
||||
|
||||
scope "/", Fallback do
|
||||
@ -414,11 +441,9 @@ defmodule Fallback.RedirectController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
def redirector(conn, _params) do
|
||||
if Mix.env() != :test do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv/static/index.html")
|
||||
end
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, Application.app_dir(:pleroma, "priv/static/index.html"))
|
||||
end
|
||||
|
||||
def registration_page(conn, params) do
|
||||
|
@ -73,7 +73,8 @@ defmodule Pleroma.Web.Streamer do
|
||||
Pleroma.List.get_lists_from_activity(item)
|
||||
|> Enum.filter(fn list ->
|
||||
owner = Repo.get(User, list.user_id)
|
||||
author.follower_address in owner.following
|
||||
|
||||
ActivityPub.visible_for_user?(item, owner)
|
||||
end)
|
||||
end
|
||||
|
||||
@ -169,16 +170,33 @@ defmodule Pleroma.Web.Streamer do
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
defp represent_update(%Activity{} = activity) do
|
||||
%{
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
"status.json",
|
||||
activity: activity
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
if socket.assigns[:user] do
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
|
||||
parent = Object.normalize(item.data["object"])
|
||||
parent = Object.normalize(item.data["object"])
|
||||
|
||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
else
|
||||
send(socket.transport_pid, {:text, represent_update(item)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
@ -186,11 +204,15 @@ defmodule Pleroma.Web.Streamer do
|
||||
def push_to_socket(topics, topic, item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
if socket.assigns[:user] do
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
|
||||
unless item.actor in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
unless item.actor in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
else
|
||||
send(socket.transport_pid, {:text, represent_update(item)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -2,7 +2,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8 />
|
||||
<title>Pleroma</title>
|
||||
<title>
|
||||
<%= Application.get_env(:pleroma, :instance)[:name] %>
|
||||
</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #282c37;
|
||||
|
@ -1,11 +0,0 @@
|
||||
<h2>Login to Mastodon Frontend</h2>
|
||||
<%= if @error do %>
|
||||
<h2><%= @error %></h2>
|
||||
<% end %>
|
||||
<%= form_for @conn, mastodon_api_path(@conn, :login), [as: "authorization"], fn f -> %>
|
||||
<%= text_input f, :name, placeholder: "Username or email" %>
|
||||
<br>
|
||||
<%= password_input f, :password, placeholder: "Password" %>
|
||||
<br>
|
||||
<%= submit "Log in" %>
|
||||
<% end %>
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.{Formatter, Emoji}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.{Repo, PasswordResetToken, User}
|
||||
|
||||
@ -134,19 +134,20 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
end
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@instance_fe Application.get_env(:pleroma, :fe)
|
||||
@instance_chat Application.get_env(:pleroma, :chat)
|
||||
def config(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
instance_fe = Pleroma.Config.get(:fe)
|
||||
instance_chat = Pleroma.Config.get(:chat)
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
response = """
|
||||
<config>
|
||||
<site>
|
||||
<name>#{Keyword.get(@instance, :name)}</name>
|
||||
<name>#{Keyword.get(instance, :name)}</name>
|
||||
<site>#{Web.base_url()}</site>
|
||||
<textlimit>#{Keyword.get(@instance, :limit)}</textlimit>
|
||||
<closed>#{!Keyword.get(@instance, :registrations_open)}</closed>
|
||||
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
|
||||
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
|
||||
</site>
|
||||
</config>
|
||||
"""
|
||||
@ -160,30 +161,33 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
|
||||
|
||||
data = %{
|
||||
name: Keyword.get(@instance, :name),
|
||||
description: Keyword.get(@instance, :description),
|
||||
name: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
server: Web.base_url(),
|
||||
textlimit: to_string(Keyword.get(@instance, :limit)),
|
||||
closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"),
|
||||
private: if(Keyword.get(@instance, :public, true), do: "0", else: "1"),
|
||||
textlimit: to_string(Keyword.get(instance, :limit)),
|
||||
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
|
||||
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
|
||||
vapidPublicKey: vapid_public_key
|
||||
}
|
||||
|
||||
pleroma_fe = %{
|
||||
theme: Keyword.get(@instance_fe, :theme),
|
||||
background: Keyword.get(@instance_fe, :background),
|
||||
logo: Keyword.get(@instance_fe, :logo),
|
||||
logoMask: Keyword.get(@instance_fe, :logo_mask),
|
||||
logoMargin: Keyword.get(@instance_fe, :logo_margin),
|
||||
redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login),
|
||||
redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login),
|
||||
chatDisabled: !Keyword.get(@instance_chat, :enabled),
|
||||
showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
|
||||
scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
|
||||
collapseMessageWithSubject: Keyword.get(@instance_fe, :collapse_message_with_subject)
|
||||
theme: Keyword.get(instance_fe, :theme),
|
||||
background: Keyword.get(instance_fe, :background),
|
||||
logo: Keyword.get(instance_fe, :logo),
|
||||
logoMask: Keyword.get(instance_fe, :logo_mask),
|
||||
logoMargin: Keyword.get(instance_fe, :logo_margin),
|
||||
redirectRootNoLogin: Keyword.get(instance_fe, :redirect_root_no_login),
|
||||
redirectRootLogin: Keyword.get(instance_fe, :redirect_root_login),
|
||||
chatDisabled: !Keyword.get(instance_chat, :enabled),
|
||||
showInstanceSpecificPanel: Keyword.get(instance_fe, :show_instance_panel),
|
||||
scopeOptionsEnabled: Keyword.get(instance_fe, :scope_options_enabled),
|
||||
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
|
||||
collapseMessageWithSubject: Keyword.get(instance_fe, :collapse_message_with_subject),
|
||||
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
|
||||
hideUserStats: Keyword.get(instance_fe, :hide_user_stats)
|
||||
}
|
||||
|
||||
managed_config = Keyword.get(@instance, :managed_config)
|
||||
managed_config = Keyword.get(instance, :managed_config)
|
||||
|
||||
data =
|
||||
if managed_config do
|
||||
@ -197,7 +201,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
version = Keyword.get(@instance, :version)
|
||||
version = Pleroma.Application.named_version()
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
@ -213,7 +217,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
|
||||
json(conn, Enum.into(Emoji.get_all(), %{}))
|
||||
end
|
||||
|
||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||
@ -226,7 +230,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||
|> Enum.map(fn account ->
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
|
||||
%User{} = followed <- User.get_or_fetch(account),
|
||||
{:ok, follower} <- User.follow(follower, followed) do
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed) do
|
||||
ActivityPub.follow(follower, followed)
|
||||
else
|
||||
err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}")
|
||||
|
@ -180,6 +180,10 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
||||
|
||||
attachments = (object["attachment"] || []) ++ video
|
||||
|
||||
reply_parent = Activity.get_in_reply_to_activity(activity)
|
||||
|
||||
reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"uri" => activity.data["object"]["id"],
|
||||
@ -190,6 +194,10 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
|
||||
"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,
|
||||
|
@ -3,11 +3,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.TwitterAPI.UserView
|
||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||
alias Pleroma.Web.MediaProxy
|
||||
import Ecto.Query
|
||||
|
||||
@instance Application.get_env(:pleroma, :instance)
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@registrations_open Keyword.get(@instance, :registrations_open)
|
||||
|
||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||
CommonAPI.post(user, data)
|
||||
@ -23,7 +22,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
def follow(%User{} = follower, params) do
|
||||
with {:ok, %User{} = followed} <- get_user(params),
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||
{:ok, follower, followed} <-
|
||||
User.wait_and_refresh(
|
||||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
|
||||
follower,
|
||||
followed
|
||||
) do
|
||||
{:ok, follower, followed, activity}
|
||||
else
|
||||
err -> err
|
||||
@ -92,7 +97,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
{:ok, object} = ActivityPub.upload(file)
|
||||
|
||||
url = List.first(object.data["url"])
|
||||
href = url["href"]
|
||||
href = url["href"] |> MediaProxy.url()
|
||||
type = url["mediaType"]
|
||||
|
||||
case format do
|
||||
@ -133,18 +138,20 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
password_confirmation: params["confirm"]
|
||||
}
|
||||
|
||||
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
|
||||
|
||||
# no need to query DB if registration is open
|
||||
token =
|
||||
unless @registrations_open || is_nil(tokenString) do
|
||||
unless registrations_open || is_nil(tokenString) do
|
||||
Repo.get_by(UserInviteToken, %{token: tokenString})
|
||||
end
|
||||
|
||||
cond do
|
||||
@registrations_open || (!is_nil(token) && !token.used) ->
|
||||
registrations_open || (!is_nil(token) && !token.used) ->
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
|
||||
with {:ok, user} <- Repo.insert(changeset) do
|
||||
!@registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||
{:ok, user}
|
||||
else
|
||||
{:error, changeset} ->
|
||||
@ -155,10 +162,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
{:error, %{error: errors}}
|
||||
end
|
||||
|
||||
!@registrations_open && is_nil(token) ->
|
||||
!registrations_open && is_nil(token) ->
|
||||
{:error, "Invalid token"}
|
||||
|
||||
!@registrations_open && token.used ->
|
||||
!registrations_open && token.used ->
|
||||
{:error, "Expired token"}
|
||||
end
|
||||
end
|
||||
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|
||||
require Logger
|
||||
|
||||
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
|
||||
action_fallback(:errors)
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
|
||||
@ -79,7 +80,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|
||||
conn
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
@ -123,6 +126,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
query =
|
||||
ActivityPub.fetch_activities_query(
|
||||
[user.ap_id],
|
||||
Map.merge(params, %{"type" => "Create", "user" => user, visibility: "direct"})
|
||||
)
|
||||
|
||||
activities = Repo.all(query)
|
||||
|
||||
conn
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
@ -130,6 +146,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|> render(NotificationView, "notification.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
|
||||
Notification.set_read_up_to(user, latest_id)
|
||||
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
conn
|
||||
|> render(NotificationView, "notification.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def notifications_read(%{assigns: %{user: user}} = conn, _) do
|
||||
bad_request_reply(conn, "You need to specify latest_id")
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: user}} = conn, params) do
|
||||
case TwitterAPI.follow(user, params) do
|
||||
{:ok, user, followed, _activity} ->
|
||||
@ -261,7 +290,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
end
|
||||
|
||||
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
||||
{:ok, object} = ActivityPub.upload(params)
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:avatar_upload_limit)
|
||||
|
||||
{:ok, object} = ActivityPub.upload(params, upload_limit)
|
||||
change = Changeset.change(user, %{avatar: object.data})
|
||||
{:ok, user} = User.update_and_set_cache(change)
|
||||
CommonAPI.update(user)
|
||||
@ -270,7 +303,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}),
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:banner_upload_limit)
|
||||
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, upload_limit),
|
||||
new_info <- Map.put(user.info, "banner", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
@ -284,7 +321,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
end
|
||||
|
||||
def update_background(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(params),
|
||||
upload_limit =
|
||||
Application.get_env(:pleroma, :instance)
|
||||
|> Keyword.fetch(:background_upload_limit)
|
||||
|
||||
with {:ok, object} <- ActivityPub.upload(params, upload_limit),
|
||||
new_info <- Map.put(user.info, "background", object.data),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, _user} <- User.update_and_set_cache(change) do
|
||||
@ -423,7 +464,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
|
||||
bio_html = CommonUtils.format_input(bio, mentions, tags)
|
||||
bio_html = CommonUtils.format_input(bio, mentions, tags, "text/plain")
|
||||
Map.put(params, "bio", bio_html |> Formatter.emojify(emoji))
|
||||
else
|
||||
params
|
||||
@ -488,6 +529,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
|
||||
users = User.search(query, true)
|
||||
|
||||
conn
|
||||
|> render(UserView, "index.json", %{users: users, for: user})
|
||||
end
|
||||
|
||||
defp bad_request_reply(conn, error_message) do
|
||||
json = error_json(conn, error_message)
|
||||
json_reply(conn, 400, json)
|
||||
@ -504,6 +552,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||
json_reply(conn, 403, json)
|
||||
end
|
||||
|
||||
def only_if_public_instance(conn = %{conn: %{assigns: %{user: _user}}}, _), do: conn
|
||||
|
||||
def only_if_public_instance(conn, _) do
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> forbidden_json_reply("Invalid credentials.")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp error_json(conn, error_message) do
|
||||
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
|
||||
end
|
||||
|
@ -236,6 +236,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||
HTML.filter_tags(content, User.html_filter_policy(opts[:for]))
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
|
||||
reply_parent = Activity.get_in_reply_to_activity(activity)
|
||||
|
||||
reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
|
||||
|
||||
%{
|
||||
"id" => activity.id,
|
||||
"uri" => activity.data["object"]["id"],
|
||||
@ -246,6 +250,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||
"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" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
|
||||
"attentions" => attentions,
|
||||
@ -275,11 +283,11 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||
{summary, content}
|
||||
end
|
||||
|
||||
def render_content(%{"type" => "Article"} = object) do
|
||||
def render_content(%{"type" => object_type} = object) when object_type in ["Article", "Page"] do
|
||||
summary = object["name"] || object["summary"]
|
||||
|
||||
content =
|
||||
if !!summary and summary != "" do
|
||||
if !!summary and summary != "" and is_bitstring(object["url"]) do
|
||||
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
|
||||
else
|
||||
object["content"]
|
||||
|
@ -37,6 +37,13 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
||||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
|
||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
||||
fields =
|
||||
(user.info["source_data"]["attachment"] || [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
data = %{
|
||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||
@ -48,8 +55,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
||||
"statusnet_blocking" => statusnet_blocking,
|
||||
"friends_count" => user_info[:following_count],
|
||||
"id" => user.id,
|
||||
"name" => user.name,
|
||||
"name_html" => HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
"name" => user.name || user.nickname,
|
||||
"name_html" =>
|
||||
if(user.name,
|
||||
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
else: user.nickname
|
||||
),
|
||||
"profile_image_url" => image,
|
||||
"profile_image_url_https" => image,
|
||||
"profile_image_url_profile_size" => image,
|
||||
@ -65,7 +76,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
||||
"is_local" => user.local,
|
||||
"locked" => !!user.info["locked"],
|
||||
"default_scope" => user.info["default_scope"] || "public",
|
||||
"no_rich_text" => user.info["no_rich_text"] || false
|
||||
"no_rich_text" => user.info["no_rich_text"] || false,
|
||||
"fields" => fields
|
||||
}
|
||||
|
||||
if assigns[:token] do
|
||||
|
@ -3,6 +3,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|
||||
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
def host_meta(conn, _params) do
|
||||
xml = WebFinger.host_meta()
|
||||
|
||||
|
@ -252,4 +252,29 @@ defmodule Pleroma.Web.Websub do
|
||||
Pleroma.Web.Federator.enqueue(:request_subscription, sub)
|
||||
end)
|
||||
end
|
||||
|
||||
def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
||||
signature = sign(secret || "", xml)
|
||||
Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
|
||||
with {:ok, %{status_code: code}} <-
|
||||
@httpoison.post(
|
||||
callback,
|
||||
xml,
|
||||
[
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||
{:ok, code}
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,15 @@ defmodule Pleroma.Web.Websub.WebsubController do
|
||||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||
require Logger
|
||||
|
||||
plug(
|
||||
Pleroma.Web.FederatingPlug
|
||||
when action in [
|
||||
:websub_subscription_request,
|
||||
:websub_subscription_confirmation,
|
||||
:websub_incoming
|
||||
]
|
||||
)
|
||||
|
||||
def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
|
||||
user = User.get_cached_by_nickname(nickname)
|
||||
|
||||
|
66
mix.exs
66
mix.exs
@ -4,13 +4,25 @@ defmodule Pleroma.Mixfile do
|
||||
def project do
|
||||
[
|
||||
app: :pleroma,
|
||||
version: "0.9.0",
|
||||
version: version("0.9.0"),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps()
|
||||
deps: deps(),
|
||||
|
||||
# Docs
|
||||
name: "Pleroma",
|
||||
source_url: "https://git.pleroma.social/pleroma/pleroma",
|
||||
source_url_pattern:
|
||||
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
|
||||
homepage_url: "https://pleroma.social/",
|
||||
docs: [
|
||||
logo: "priv/static/static/logo.png",
|
||||
extras: ["README.md", "config/config.md"],
|
||||
main: "readme"
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
@ -48,11 +60,14 @@ defmodule Pleroma.Mixfile do
|
||||
{:mogrify, "~> 0.6.1"},
|
||||
{:ex_aws, "~> 2.0"},
|
||||
{:ex_aws_s3, "~> 2.0"},
|
||||
{:earmark, "~> 1.2"},
|
||||
{:ex_machina, "~> 2.2", only: :test},
|
||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
||||
{:mock, "~> 0.3.1", only: :test},
|
||||
{:crypt,
|
||||
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
||||
{:cors_plug, "~> 1.5"},
|
||||
{:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false},
|
||||
{:web_push_encryption, "~> 0.2.1"}
|
||||
]
|
||||
end
|
||||
@ -70,4 +85,51 @@ defmodule Pleroma.Mixfile do
|
||||
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||
]
|
||||
end
|
||||
|
||||
# Builds a version string made of:
|
||||
# * the application version
|
||||
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
|
||||
# * build info:
|
||||
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
|
||||
# * the mix environment if different than prod
|
||||
defp version(version) do
|
||||
{git_tag, git_pre_release} =
|
||||
with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"]),
|
||||
tag = String.trim(tag),
|
||||
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
|
||||
describe = String.trim(describe),
|
||||
ahead <- String.replace(describe, tag, "") do
|
||||
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
|
||||
else
|
||||
_ -> {nil, nil}
|
||||
end
|
||||
|
||||
if git_tag && version != git_tag do
|
||||
Mix.shell().error(
|
||||
"Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}"
|
||||
)
|
||||
end
|
||||
|
||||
build_name =
|
||||
cond do
|
||||
name = Application.get_env(:pleroma, :build_name) -> name
|
||||
name = System.get_env("PLEROMA_BUILD_NAME") -> name
|
||||
true -> nil
|
||||
end
|
||||
|
||||
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
|
||||
|
||||
build =
|
||||
[build_name, env_name]
|
||||
|> Enum.filter(fn string -> string && string != "" end)
|
||||
|> Enum.join("-")
|
||||
|> (fn
|
||||
"" -> nil
|
||||
string -> "+" <> string
|
||||
end).()
|
||||
|
||||
[version, git_pre_release, build]
|
||||
|> Enum.filter(fn string -> string && string != "" end)
|
||||
|> Enum.join()
|
||||
end
|
||||
end
|
||||
|
6
mix.lock
6
mix.lock
@ -6,16 +6,19 @@
|
||||
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "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"},
|
||||
"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"},
|
||||
"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"]},
|
||||
"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"},
|
||||
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
@ -24,6 +27,8 @@
|
||||
"idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"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"},
|
||||
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
|
||||
@ -31,6 +36,7 @@
|
||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
|
||||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
|
||||
"phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f73fcf466fd253b0e5c6.js></script><script type=text/javascript src=/static/js/vendor.04ec6dccb5e616b6deac.js></script><script type=text/javascript src=/static/js/app.daf013e442326e175355.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.34667c2817916147413f.js></script><script type=text/javascript src=/static/js/vendor.32c621c7157f34c20923.js></script><script type=text/javascript src=/static/js/app.065638d22ade92dea420.js></script></body></html>
|
23
priv/static/schemas/litepub-0.1.jsonld
Normal file
23
priv/static/schemas/litepub-0.1.jsonld
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"Emoji": "toot:Emoji",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"conversation": {
|
||||
"@id": "ostatus:conversation",
|
||||
"@type": "@id"
|
||||
},
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"schema": "http://schema.org",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"totalItems": "as:totalItems",
|
||||
"value": "schema:value",
|
||||
"sensitive": "as:sensitive"
|
||||
}
|
||||
]
|
||||
}
|
@ -10,5 +10,8 @@
|
||||
"showInstanceSpecificPanel": false,
|
||||
"scopeOptionsEnabled": false,
|
||||
"formattingOptionsEnabled": false,
|
||||
"collapseMessageWithSubject": false
|
||||
"collapseMessageWithSubject": false,
|
||||
"hidePostStats": false,
|
||||
"hideUserStats": false,
|
||||
"loginMethod": "password"
|
||||
}
|
||||
|
11
priv/static/static/js/app.065638d22ade92dea420.js
Normal file
11
priv/static/static/js/app.065638d22ade92dea420.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/static/js/app.065638d22ade92dea420.js.map
Normal file
1
priv/static/static/js/app.065638d22ade92dea420.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"04ec6dccb5e616b6deac",2:"daf013e442326e175355"}[e]+".js",n.appendChild(c)}},t.m=e,t.c=n,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.f73fcf466fd253b0e5c6.js.map
|
||||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"32c621c7157f34c20923",2:"065638d22ade92dea420"}[e]+".js",n.appendChild(c)}},t.m=e,t.c=n,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.34667c2817916147413f.js.map
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user