Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop: (87 commits)
  Add a gopher url port config option
  modify the migrations to use naive_datetime_usec
  Fix filter migration
  Fix migration timestamp type
  Ecto 3.0.5 migration
  Update frontend
  Format
  Use ILIKE to search users
  transmogrifier: upgrade: when upgrading OStatus users to AP, ensure we always use the fake collection
  user: properly cope with actors which do not declare a followers collection
  transmogrifier: when determining followers collection URI, we may need to fetch the actor
  test: add test for list sanitization
  tests: add test for as:Public issues
  activitypub: transmogrifier: do not allow missing lists to be interpreted as nil
  activitypub: transmogrifier: ensure as:Public activities are delivered to followers
  fix UploadTest to use new image_tmp.jpg
  used imageOptim to reduce size of image files
  Document additional pleroma changes to /api/v1/accounts/:id
  Check if the user has indeed not been federated with
  Added support for exclude_types, limit, and min_id in Mastodon notifications.
  ...
This commit is contained in:
Henry Jameson 2019-03-21 20:26:26 +02:00
commit 9bb34a12eb
392 changed files with 2149 additions and 715 deletions

View file

@ -69,8 +69,8 @@
# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
{Credo.Check.Design.TagTODO, exit_status: 2},
{Credo.Check.Design.TagFIXME},
{Credo.Check.Design.TagTODO, exit_status: 0},
{Credo.Check.Design.TagFIXME, exit_status: 0},
{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
@ -81,7 +81,9 @@
{Credo.Check.Readability.ParenthesesOnZeroArityDefs},
{Credo.Check.Readability.ParenthesesInCondition},
{Credo.Check.Readability.PredicateFunctionNames},
{Credo.Check.Readability.PreferImplicitTry},
# lanodan: I think PreferImplicitTry should be consistency, and the behaviour seems
# inconsistent, see: https://github.com/rrrene/credo/issues/224
{Credo.Check.Readability.PreferImplicitTry, false},
{Credo.Check.Readability.RedundantBlankLines},
{Credo.Check.Readability.StringSigils},
{Credo.Check.Readability.TrailingBlankLine},
@ -126,10 +128,6 @@
# Deprecated checks (these will be deleted after a grace period)
{Credo.Check.Readability.Specs, false},
{Credo.Check.Warning.NameRedeclarationByAssignment, false},
{Credo.Check.Warning.NameRedeclarationByCase, false},
{Credo.Check.Warning.NameRedeclarationByDef, false},
{Credo.Check.Warning.NameRedeclarationByFn, false},
# Custom checks can be created using `mix credo.gen.check`.
#

View file

@ -19,6 +19,7 @@ cache:
stages:
- lint
- test
- analysis
before_script:
- mix local.hex --force
@ -37,3 +38,8 @@ unit-testing:
stage: test
script:
- mix test --trace --preload-modules
analysis:
stage: analysis
script:
- mix credo --strict --only=warnings,todo,fixme,consistency,readability

View file

@ -8,8 +8,6 @@ use Mix.Config
# General application configuration
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
config :pleroma, Pleroma.Captcha,
enabled: false,
seconds_valid: 60,
@ -34,7 +32,8 @@ config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [],
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: true,
proxy_remote: false,
proxy_opts: [
redirect_on_failure: false,
@ -256,8 +255,6 @@ config :pleroma, :media_proxy,
config :pleroma, :chat, enabled: false
config :ecto, json_library: Jason
config :phoenix, :format_encoders, json: Jason
config :pleroma, :gopher,
@ -353,6 +350,17 @@ config :auto_linker,
rel: false
]
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost",
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
# 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"

View file

@ -17,6 +17,8 @@ config :pleroma, Pleroma.Captcha,
# Print only warnings and errors during test
config :logger, level: :warn
config :pleroma, Pleroma.Upload, filters: [], link_name: false
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Test

18
docs/Custom-Emoji.md Normal file
View file

@ -0,0 +1,18 @@
# Custom emoji
To add custom emoji:
* Add the image file(s) to `priv/static/emoji/custom`
* In case of conflicts: add the desired shortcode with the path to `config/custom_emoji.txt`, comma-separated and one per line
* Force recompilation (``mix clean && mix compile``)
Example:
image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
content of `config/custom_emoji.txt`:
```
happy, /emoji/custom/happy.png
sad, /emoji/custom/sad.png
```
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.

View file

@ -20,6 +20,26 @@ Has these additional fields under the `pleroma` object:
- `local`: true if the post was made on the local instance.
## Attachments
Has these additional fields under the `pleroma` object:
- `mime_type`: mime type of the attachment.
## Accounts
- `/api/v1/accounts/:id`: The `id` parameter can also be the `nickname` of the user. This only works in this endpoint, not the deeper nested ones for following etc.
Has these additional fields under the `pleroma` object:
- `tags`: Lists an array of tags for the user
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
- `is_moderator`: boolean, true if user is a moderator
- `is_admin`: boolean, true if user is an admin
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
## Notifications
Has these additional fields under the `pleroma` object:
- `is_seen`: true if the notification was read by the user

View file

@ -0,0 +1,119 @@
# Message Rewrite Facility configuration
The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages.
Possible uses include:
* marking incoming messages with media from a given account or instance as sensitive
* rejecting messages from a specific instance
* removing/unlisting messages from the public timelines
* removing media from messages
* sending only public messages to a specific instance
The MRF provides user-configurable policies. The default policy is `NoOpPolicy`, which disables the MRF functionality. Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.
It is possible to use multiple, active MRF policies at the same time.
## Quarantine Instances
You have the ability to prevent from private / followers-only messages from federating with specific instances. Which means they will only get the public or unlisted messages from your instance.
If, for example, you're using `MIX_ENV=prod` aka using production mode, you would open your configuration file located in `config/prod.secret.exs` and edit or add the option under your `:instance` config object. Then you would specify the instance within quotes.
```
config :pleroma, :instance,
[...]
quarantined_instances: ["instance.example", "other.example"]
```
## Using `SimplePolicy`
`SimplePolicy` is capable of handling most common admin tasks.
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
```
config :pleroma, :instance,
[...]
rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
```
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
* `media_removal`: Servers in this group will have media stripped from incoming messages.
* `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media.
* `reject`: Servers in this group will have their messages rejected.
* `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
Servers should be configured as lists.
### Example
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com` and remove messages from `spam.university` from the federated timeline:
```
config :pleroma, :instance,
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
config :pleroma, :mrf_simple,
media_removal: ["illegalporn.biz"],
media_nsfw: ["porn.biz", "porn.business"],
reject: ["spam.com"],
federated_timeline_removal: ["spam.university"]
```
### Use with Care
The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance.
## Writing your own MRF Policy
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
For example, here is a sample policy module which rewrites all messages to "new message content":
```!elixir
# This is a sample MRF policy which rewrites all Notes to have "new message
# content."
defmodule Site.RewritePolicy do
@behavior Pleroma.Web.ActivityPub.MRF
# Catch messages which contain Note objects with actual data to filter.
# Capture the object as `object`, the message content as `content` and the
# message itself as `message`.
@impl true
def filter(%{"type" => Create", "object" => {"type" => "Note", "content" => content} = object} = message)
when is_binary(content) do
# Subject / CW is stored as summary instead of `name` like other AS2 objects
# because of Mastodon doing it that way.
summary = object["summary"]
# Message edits go here.
content = "new message content"
# Assemble the mutated object.
object =
object
|> Map.put("content", content)
|> Map.put("summary", summary)
# Assemble the mutated message.
message = Map.put(message, "object", object)
{:ok, message}
end
# Let all other messages through without modifying them.
@impl true
def filter(message), do: {:ok, message}
end
```
If you save this file as `lib/site/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
```
config :pleroma, :instance,
rewrite_policy: [
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
Site.RewritePolicy
]
```
Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL. As such, you are obligated to release the sources to your custom MRF policy modules upon request.

View file

@ -108,3 +108,11 @@ See [Admin-API](Admin-API.md)
* Response: JSON string. Returns the user flavour or the default one.
* Example response: "glitch"
* Note: This is intended to be used only by mastofe
## `/api/pleroma/notifications/read`
### Mark a single notification as read
* Method `POST`
* Authentication: required
* Params:
* `id`: notifications's id
* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}`

View file

@ -6,6 +6,7 @@ If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherw
## Pleroma.Upload
* `uploader`: Select which `Pleroma.Uploaders` to use
* `filters`: List of `Pleroma.Upload.Filter` to use.
* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host.
* `proxy_remote`: If you\'re using a remote uploader, Pleroma will proxy media requests instead of redirecting to it.
* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
@ -189,6 +190,7 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
* `enabled`: Enables the gopher interface
* `ip`: IP address to bind to
* `port`: Port to bind to
* `dstport`: Port advertised in urls (optional, defaults to `port`)
## :activitypub
* ``accept_blocks``: Whether to accept incoming block activities from other instances
@ -330,3 +332,26 @@ config :auto_linker,
rel: false
]
```
## :ldap
Use LDAP for user authentication. When a user logs in to the Pleroma
instance, the name and password will be verified by trying to authenticate
(bind) to an LDAP server. If a user exists in the LDAP directory but there
is no account with the same name yet on the Pleroma instance then a new
Pleroma account will be created with the same name as the LDAP user name.
* `enabled`: enables LDAP authentication
* `host`: LDAP server hostname
* `port`: LDAP port, e.g. 389 or 636
* `ssl`: true to use SSL, usually implies the port 636
* `sslopts`: additional SSL options
* `tls`: true to start TLS, usually implies the port 389
* `tlsopts`: additional TLS options
* `base`: LDAP base, e.g. "dc=example,dc=com"
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
## Pleroma.Web.Auth.Authenticator
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication

20
docs/static_dir.md Normal file
View file

@ -0,0 +1,20 @@
# Static Directory
Static frontend files are shipped in `priv/static/` and tracked by version control in this repository. If you want to overwrite or update these without the possibility of merge conflicts, you can write your custom versions to `instance/static/`.
```
config :pleroma, :instance,
static_dir: "instance/static/",
```
You can overwrite this value in your configuration to use a different static instance directory.
## robots.txt
By default, the `robots.txt` that ships in `priv/static/` is permissive. It allows well-behaved search engines to index all of your instance's URIs.
If you want to generate a restrictive `robots.txt`, you can run the following mix task. The generated `robots.txt` will be written in your instance static directory.
```
mix pleroma.robots_txt disallow_all
```

View file

@ -4,8 +4,8 @@
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
alias Pleroma.Web.ActivityPub.Relay
alias Mix.Tasks.Pleroma.Common
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@moduledoc """

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RobotsTxt do
use Mix.Task
@shortdoc "Generate robots.txt"
@moduledoc """
Generates robots.txt
## Overwrite robots.txt to disallow all
mix pleroma.robots_txt disallow_all
This will write a robots.txt that will hide all paths on your instance
from search engines and other robots that obey robots.txt
"""
def run(["disallow_all"]) do
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
if !File.exists?(static_dir) do
File.mkdir_p!(static_dir)
end
robots_txt_path = Path.join(static_dir, "robots.txt")
robots_txt_content = "User-Agent: *\nDisallow: /\n"
File.write!(robots_txt_path, robots_txt_content, [:write])
end
end

View file

@ -4,9 +4,9 @@
defmodule Mix.Tasks.Pleroma.Uploads do
use Mix.Task
alias Mix.Tasks.Pleroma.Common
alias Pleroma.Upload
alias Pleroma.Uploaders.Local
alias Mix.Tasks.Pleroma.Common
require Logger
@log_every 50
@ -20,7 +20,6 @@ defmodule Mix.Tasks.Pleroma.Uploads do
Options:
- `--delete` - delete local uploads after migrating them to the target uploader
A list of available uploaders can be seen in config.exs
"""
def run(["migrate_local", target_uploader | args]) do

View file

@ -5,9 +5,9 @@
defmodule Mix.Tasks.Pleroma.User do
use Mix.Task
import Ecto.Changeset
alias Mix.Tasks.Pleroma.Common
alias Pleroma.Repo
alias Pleroma.User
alias Mix.Tasks.Pleroma.Common
@shortdoc "Manages Pleroma users"
@moduledoc """

View file

@ -7,9 +7,9 @@ defmodule Pleroma.PasswordResetToken do
import Ecto.Changeset
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User
schema "password_reset_tokens" do
belongs_to(:user, User, type: Pleroma.FlakeId)

View file

@ -5,9 +5,9 @@
defmodule Pleroma.Activity do
use Ecto.Schema
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Repo
import Ecto.Query
@ -22,6 +22,10 @@ defmodule Pleroma.Activity do
"Like" => "favourite"
}
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
into: %{},
do: {v, k}
schema "activities" do
field(:data, :map)
field(:local, :boolean, default: true)
@ -109,7 +113,8 @@ defmodule Pleroma.Activity do
def delete_by_ap_id(id) when is_binary(id) do
by_object_ap_id(id)
|> Repo.delete_all(returning: true)
|> select([u], u)
|> Repo.delete_all()
|> elem(1)
|> Enum.find(fn
%{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
@ -126,6 +131,10 @@ defmodule Pleroma.Activity do
def mastodon_notification_type(%Activity{}), do: nil
def from_mastodon_notification_type(type) do
Map.get(@mastodon_to_ap_notification_types, type)
end
def all_by_actor_and_id(actor, status_ids \\ [])
def all_by_actor_and_id(_actor, []), do: []

View file

@ -11,10 +11,10 @@ defmodule Pleroma.Application do
@repository Mix.Project.config()[:source_url]
def name, do: @name
def version, do: @version
def named_version(), do: @name <> " " <> @version
def named_version, do: @name <> " " <> @version
def repository, do: @repository
def user_agent() do
def user_agent do
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
named_version() <> "; " <> info
end
@ -48,7 +48,7 @@ defmodule Pleroma.Application do
[
:user_cache,
[
default_ttl: 25000,
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
@ -60,7 +60,7 @@ defmodule Pleroma.Application do
[
:object_cache,
[
default_ttl: 25000,
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
@ -127,7 +127,7 @@ defmodule Pleroma.Application do
Supervisor.start_link(children, opts)
end
def enabled_hackney_pools() do
def enabled_hackney_pools do
[:media] ++
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
[:federation]
@ -142,14 +142,14 @@ defmodule Pleroma.Application do
end
if Mix.env() == :test do
defp streamer_child(), do: []
defp chat_child(), do: []
defp streamer_child, do: []
defp chat_child, do: []
else
defp streamer_child() do
defp streamer_child do
[worker(Pleroma.Web.Streamer, [])]
end
defp chat_child() do
defp chat_child do
if Pleroma.Config.get([:chat, :enabled]) do
[worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
else
@ -158,7 +158,7 @@ defmodule Pleroma.Application do
end
end
defp hackney_pool_children() do
defp hackney_pool_children do
for pool <- enabled_hackney_pools() do
options = Pleroma.Config.get([:hackney_pools, pool])
:hackney_pool.child_spec(pool, options)

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Captcha do
use GenServer
@doc false
def start_link() do
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@ -22,7 +22,7 @@ defmodule Pleroma.Captcha do
@doc """
Ask the configured captcha service for a new captcha
"""
def new() do
def new do
GenServer.call(__MODULE__, :new)
end
@ -73,7 +73,7 @@ defmodule Pleroma.Captcha do
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
# If the time found is less than (current_time - seconds_valid), then the time has already passed.
# If the time found is less than (current_time-seconds_valid) then the time has already passed
# Later we check that the time found is more than the presumed invalidatation time, that means
# that the data is still valid and the captcha can be checked
seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid])

View file

@ -7,7 +7,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
@behaviour Service
@impl Service
def new() do
def new do
endpoint = Pleroma.Config.get!([__MODULE__, :endpoint])
case Tesla.get(endpoint <> "/new") do

View file

@ -7,13 +7,13 @@ defmodule Pleroma.Clippy do
# No software is complete until they have a Clippy implementation.
# A ballmer peak _may_ be required to change this module.
def tip() do
def tip do
tips()
|> Enum.random()
|> puts()
end
def tips() do
def tips do
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
[
@ -92,8 +92,8 @@ defmodule Pleroma.Clippy do
# surrond one/five line clippy with blank lines around to not fuck up the layout
#
# yes this fix sucks but it's good enough, have you ever seen a release of windows wihtout some butched
# features anyway?
# yes this fix sucks but it's good enough, have you ever seen a release of windows
# without some butched features anyway?
lines =
if length(lines) == 1 or length(lines) == 5 do
[""] ++ lines ++ [""]

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Config.DeprecationWarnings do
require Logger
def check_frontend_config_mechanism() do
def check_frontend_config_mechanism do
if Pleroma.Config.get(:fe) do
Logger.warn("""
!!!DEPRECATION WARNING!!!

View file

@ -17,13 +17,13 @@ defmodule Pleroma.Emoji do
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@doc false
def start_link() do
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc "Reloads the emojis from disk."
@spec reload() :: :ok
def reload() do
def reload do
GenServer.call(__MODULE__, :reload)
end
@ -38,7 +38,7 @@ defmodule Pleroma.Emoji do
@doc "Returns all the emojos!!"
@spec get_all() :: [{String.t(), String.t()}, ...]
def get_all() do
def get_all do
:ets.tab2list(@ets)
end
@ -72,7 +72,7 @@ defmodule Pleroma.Emoji do
{:ok, state}
end
defp load() do
defp load do
emojis =
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
load_from_file("config/emoji.txt") ++

View file

@ -8,8 +8,8 @@ defmodule Pleroma.Filter do
import Ecto.Changeset
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
schema "filters" do
belongs_to(:user, User, type: Pleroma.FlakeId)

View file

@ -85,7 +85,7 @@ defmodule Pleroma.FlakeId do
{:ok, FlakeId.from_string(value)}
end
def autogenerate(), do: get()
def autogenerate, do: get()
# -- GenServer API
def start_link do
@ -165,7 +165,7 @@ defmodule Pleroma.FlakeId do
1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
end
defp worker_id() do
defp worker_id do
<<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
worker
end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Formatter do
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
@link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
@auto_linker_config hashtag: true,
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Gopher.Server do
use GenServer
require Logger
def start_link() do
def start_link do
config = Pleroma.Config.get(:gopher, [])
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
port = Keyword.get(config, :port, 1234)
@ -36,12 +36,12 @@ defmodule Pleroma.Gopher.Server do
end
defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
@ -66,7 +66,8 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
def link(name, selector, type \\ 1) do
address = Pleroma.Web.Endpoint.host()
port = Pleroma.Config.get([:gopher, :port], 1234)
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
dstport = Pleroma.Config.get([:gopher, :dstport], port)
"#{type}#{name}\t#{selector}\t#{address}\t#{dstport}\r\n"
end
def render_activities(activities) do

View file

@ -9,7 +9,7 @@ defmodule Pleroma.HTML do
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
def get_scrubbers() do
def get_scrubbers do
Pleroma.Config.get([:markup, :scrub_policy])
|> get_scrubbers
end
@ -95,6 +95,13 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag",
"nofollow",
"noopener",
"noreferrer"
])
# paragraphs and linebreaks
Meta.allow_tag_with_these_attributes("br", [])
Meta.allow_tag_with_these_attributes("p", [])
@ -137,6 +144,13 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag",
"nofollow",
"noopener",
"noreferrer"
])
Meta.allow_tag_with_these_attributes("abbr", ["title"])
Meta.allow_tag_with_these_attributes("b", [])

View file

@ -2,8 +2,8 @@ defmodule Pleroma.Instances.Instance do
@moduledoc "Instance."
alias Pleroma.Instances
alias Pleroma.Repo
alias Pleroma.Instances.Instance
alias Pleroma.Repo
use Ecto.Schema
@ -12,7 +12,7 @@ defmodule Pleroma.Instances.Instance do
schema "instances" do
field(:host, :string)
field(:unreachable_since, :naive_datetime)
field(:unreachable_since, :naive_datetime_usec)
timestamps()
end

View file

@ -5,14 +5,16 @@
defmodule Pleroma.Notification do
use Ecto.Schema
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
import Ecto.Query
import Ecto.Changeset
schema "notifications" do
field(:seen, :boolean, default: false)
@ -22,36 +24,22 @@ defmodule Pleroma.Notification do
timestamps()
end
# TODO: Make generic and unify (see activity_pub.ex)
defp restrict_max(query, %{"max_id" => max_id}) do
from(activity in query, where: activity.id < ^max_id)
def changeset(%Notification{} = notification, attrs) do
notification
|> cast(attrs, [:seen])
end
defp restrict_max(query, _), do: query
defp restrict_since(query, %{"since_id" => since_id}) do
from(activity in query, where: activity.id > ^since_id)
def for_user_query(user) do
Notification
|> where(user_id: ^user.id)
|> join(:inner, [n], activity in assoc(n, :activity))
|> preload(:activity)
end
defp restrict_since(query, _), do: query
def for_user(user, opts \\ %{}) do
query =
from(
n in Notification,
where: n.user_id == ^user.id,
order_by: [desc: n.id],
join: activity in assoc(n, :activity),
preload: [activity: activity],
limit: 20
)
query =
query
|> restrict_since(opts)
|> restrict_max(opts)
Repo.all(query)
user
|> for_user_query()
|> Pagination.fetch_paginated(opts)
end
def set_read_up_to(%{id: user_id} = _user, id) do
@ -68,6 +56,14 @@ defmodule Pleroma.Notification do
Repo.update_all(query, [])
end
def read_one(%User{} = user, notification_id) do
with {:ok, %Notification{} = notification} <- get(user, notification_id) do
notification
|> changeset(%{seen: true})
|> Repo.update()
end
end
def get(%{id: user_id} = _user, id) do
query =
from(

View file

@ -5,11 +5,11 @@
defmodule Pleroma.Object do
use Ecto.Schema
alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.ObjectTombstone
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
import Ecto.Changeset

78
lib/pleroma/pagination.ex Normal file
View file

@ -0,0 +1,78 @@
defmodule Pleroma.Pagination do
@moduledoc """
Implements Mastodon-compatible pagination.
"""
import Ecto.Query
import Ecto.Changeset
alias Pleroma.Repo
@default_limit 20
def fetch_paginated(query, params) do
options = cast_params(params)
query
|> paginate(options)
|> Repo.all()
|> enforce_order(options)
end
def paginate(query, options) do
query
|> restrict(:min_id, options)
|> restrict(:since_id, options)
|> restrict(:max_id, options)
|> restrict(:order, options)
|> restrict(:limit, options)
end
defp cast_params(params) do
param_types = %{
min_id: :string,
since_id: :string,
max_id: :string,
limit: :integer
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
changeset.changes
end
defp restrict(query, :min_id, %{min_id: min_id}) do
where(query, [q], q.id > ^min_id)
end
defp restrict(query, :since_id, %{since_id: since_id}) do
where(query, [q], q.id > ^since_id)
end
defp restrict(query, :max_id, %{max_id: max_id}) do
where(query, [q], q.id < ^max_id)
end
defp restrict(query, :order, %{min_id: _}) do
order_by(query, [u], fragment("? asc nulls last", u.id))
end
defp restrict(query, :order, _options) do
order_by(query, [u], fragment("? desc nulls last", u.id))
end
defp restrict(query, :limit, options) do
limit = Map.get(options, :limit, @default_limit)
query
|> limit(^limit)
end
defp restrict(query, _, _), do: query
defp enforce_order(result, %{min_id: _}) do
result
|> Enum.reverse()
end
defp enforce_order(result, _), do: result
end

View file

@ -3,8 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
alias Pleroma.Web.HTTPSignatures
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.HTTPSignatures
import Plug.Conn
require Logger

View file

@ -21,7 +21,8 @@ defmodule Pleroma.Plugs.InstanceStatic do
end
end
@only ~w(index.html static emoji packs sounds images instance favicon.png sw.js sw-pleroma.js)
@only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js
sw-pleroma.js)
def init(opts) do
opts

View file

@ -6,8 +6,8 @@ defmodule Pleroma.Plugs.OAuthPlug do
import Plug.Conn
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
@ -38,6 +38,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
preload: [user: user]
)
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do
{:ok, user, token_record}
end

View file

@ -24,6 +24,18 @@ defmodule Pleroma.Plugs.UploadedMedia do
end
def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
conn =
case fetch_query_params(conn) do
%{query_params: %{"name" => name}} = conn ->
name = String.replace(name, "\"", "\\\"")
conn
|> put_resp_header("content-disposition", "filename=\"#{name}\"")
conn ->
conn
end
config = Pleroma.Config.get([Pleroma.Upload])
with uploader <- Keyword.fetch!(config, :uploader),

View file

@ -3,8 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserFetcherPlug do
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
import Plug.Conn

View file

@ -3,7 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo do
use Ecto.Repo, otp_app: :pleroma
use Ecto.Repo,
otp_app: :pleroma,
adapter: Ecto.Adapters.Postgres,
migration_timestamps: [type: :naive_datetime_usec]
@doc """
Dynamically loads the repository url from the

View file

@ -3,10 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since if-unmodified-since if-none-match if-range range)
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range)
@resp_cache_headers ~w(etag date last-modified cache-control)
@keep_resp_headers @resp_cache_headers ++
~w(content-type content-disposition content-encoding content-range accept-ranges vary)
~w(content-type content-disposition content-encoding content-range) ++
~w(accept-ranges vary)
@default_cache_control_header "public, max-age=1209600"
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
@ -282,8 +284,8 @@ defmodule Pleroma.ReverseProxy do
headers
has_cache? ->
# There's caching header present but no cache-control -- we need to explicitely override it to public
# as Plug defaults to "max-age=0, private, must-revalidate"
# There's caching header present but no cache-control -- we need to explicitely override it
# to public as Plug defaults to "max-age=0, private, must-revalidate"
List.keystore(headers, "cache-control", 0, {"cache-control", "public"})
true ->
@ -309,7 +311,25 @@ defmodule Pleroma.ReverseProxy do
end
if attachment? do
disposition = "attachment; filename=" <> Keyword.get(opts, :attachment_name, "attachment")
name =
try do
{{"content-disposition", content_disposition_string}, _} =
List.keytake(headers, "content-disposition", 0)
[name | _] =
Regex.run(
~r/filename="((?:[^"\\]|\\.)*)"/u,
content_disposition_string || "",
capture: :all_but_first
)
name
rescue
MatchError -> Keyword.get(opts, :attachment_name, "attachment")
end
disposition = "attachment; filename=\"#{name}\""
List.keystore(headers, "content-disposition", 0, {"content-disposition", disposition})
else
headers

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Stats do
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)

View file

@ -4,7 +4,11 @@
defmodule Pleroma.ThreadMute do
use Ecto.Schema
alias Pleroma.{Repo, User, ThreadMute}
alias Pleroma.Repo
alias Pleroma.ThreadMute
alias Pleroma.User
require Ecto.Query
schema "thread_mutes" do

View file

@ -70,7 +70,7 @@ defmodule Pleroma.Upload do
%{
"type" => "Link",
"mediaType" => upload.content_type,
"href" => url_from_spec(opts.base_url, url_spec)
"href" => url_from_spec(upload, opts.base_url, url_spec)
}
],
"name" => Map.get(opts, :description) || upload.name
@ -219,14 +219,18 @@ defmodule Pleroma.Upload do
tmp_path
end
defp url_from_spec(base_url, {:file, path}) do
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
path =
path
|> URI.encode(&char_unescaped?/1)
URI.encode(path, &char_unescaped?/1) <>
if Pleroma.Config.get([__MODULE__, :link_name], false) do
"?name=#{URI.encode(name, &char_unescaped?/1)}"
else
""
end
[base_url, "media", path]
|> Path.join()
end
defp url_from_spec(_base_url, {:url, url}), do: url
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
end

View file

@ -6,7 +6,8 @@ defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader
require Logger
# The file name is re-encoded with S3's constraints here to comply with previous links with less strict filenames
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
def get_file(file) do
config = Pleroma.Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|> Poison.decode!()
end
def get_token() do
def get_token do
settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
username = Keyword.fetch!(settings, :username)
password = Keyword.fetch!(settings, :password)

View file

@ -29,7 +29,6 @@ defmodule Pleroma.Uploaders.Uploader do
* `{:error, String.t}` error information if the file failed to be saved to the backend.
* `:wait_callback` will wait for an http post request at `/api/pleroma/upload_callback/:upload_path` and call the uploader's `http_callback/3` method.
"""
@type file_spec :: {:file | :url, String.t()}
@callback put_file(Pleroma.Upload.t()) ::

View file

@ -8,21 +8,21 @@ defmodule Pleroma.User do
import Ecto.Changeset
import Ecto.Query
alias Comeonin.Pbkdf2
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web
alias Pleroma.Activity
alias Pleroma.Notification
alias Comeonin.Pbkdf2
alias Pleroma.Formatter
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.OStatus
alias Pleroma.Web.Websub
alias Pleroma.Web.OAuth
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.OAuth
alias Pleroma.Web.OStatus
alias Pleroma.Web.RelMe
alias Pleroma.Web.Websub
require Logger
@ -30,6 +30,7 @@ defmodule Pleroma.User do
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
@ -51,7 +52,7 @@ defmodule Pleroma.User do
field(:search_rank, :float, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:bookmarks, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime)
field(:last_refreshed_at, :naive_datetime_usec)
has_many(:notifications, Notification)
embeds_one(:info, Pleroma.User.Info)
@ -103,9 +104,8 @@ defmodule Pleroma.User do
"#{Web.base_url()}/users/#{nickname}"
end
def ap_followers(%User{} = user) do
"#{ap_id(user)}/followers"
end
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0
@ -285,7 +285,7 @@ defmodule Pleroma.User do
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
def needs_update?(%User{local: false} = user) do
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86400
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
end
def needs_update?(_), do: true
@ -334,10 +334,11 @@ defmodule Pleroma.User do
^followed_addresses
)
]
]
],
select: u
)
{1, [follower]} = Repo.update_all(q, [], returning: true)
{1, [follower]} = Repo.update_all(q, [])
Enum.each(followeds, fn followed ->
update_follower_count(followed)
@ -367,10 +368,11 @@ defmodule Pleroma.User do
q =
from(u in User,
where: u.id == ^follower.id,
update: [push: [following: ^ap_followers]]
update: [push: [following: ^ap_followers]],
select: u
)
{1, [follower]} = Repo.update_all(q, [], returning: true)
{1, [follower]} = Repo.update_all(q, [])
{:ok, _} = update_follower_count(followed)
@ -385,10 +387,11 @@ defmodule Pleroma.User do
q =
from(u in User,
where: u.id == ^follower.id,
update: [pull: [following: ^ap_followers]]
update: [pull: [following: ^ap_followers]],
select: u
)
{1, [follower]} = Repo.update_all(q, [], returning: true)
{1, [follower]} = Repo.update_all(q, [])
{:ok, followed} = update_follower_count(followed)
@ -435,7 +438,8 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
# This is mostly an SPC migration fix. This guesses the user nickname (by taking the last part of the ap_id and the domain) and tries to get that user
# This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
# of the ap_id and the domain and tries to get that user
def get_by_guessed_nickname(ap_id) do
domain = URI.parse(ap_id).host
name = List.last(String.split(ap_id, "/"))
@ -635,7 +639,7 @@ defmodule Pleroma.User do
users =
user
|> User.get_follow_requests_query()
|> join(:inner, [a], u in User, a.actor == u.ap_id)
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|> group_by([a, u], u.id)
|> select([a, u], u)
@ -657,7 +661,8 @@ defmodule Pleroma.User do
)
]
)
|> Repo.update_all([], returning: true)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
@ -677,7 +682,8 @@ defmodule Pleroma.User do
)
]
)
|> Repo.update_all([], returning: true)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
@ -723,7 +729,8 @@ defmodule Pleroma.User do
)
]
)
|> Repo.update_all([], returning: true)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
@ -771,7 +778,7 @@ defmodule Pleroma.User do
}) :: {:ok, [Pleroma.User.t()], number()}
def search_for_admin(%{query: nil, local: local, page: page, page_size: page_size}) do
query =
from(u in User, order_by: u.id)
from(u in User, order_by: u.nickname)
|> maybe_local_user_query(local)
paginated_query =
@ -787,34 +794,27 @@ defmodule Pleroma.User do
@spec search_for_admin(%{
query: binary(),
admin: Pleroma.User.t(),
local: boolean(),
page: number(),
page_size: number()
}) :: {:ok, [Pleroma.User.t()], number()}
def search_for_admin(%{
query: term,
admin: admin,
local: local,
page: page,
page_size: page_size
}) do
term = String.trim_leading(term, "@")
maybe_local_query = User |> maybe_local_user_query(local)
local_paginated_query =
User
|> maybe_local_user_query(local)
search_query = from(u in maybe_local_query, where: ilike(u.nickname, ^"%#{term}%"))
count = search_query |> Repo.aggregate(:count, :id)
results =
search_query
|> paginate(page, page_size)
|> Repo.all()
search_query = fts_search_subquery(term, local_paginated_query)
count =
term
|> fts_search_subquery()
|> maybe_local_user_query(local)
|> Repo.aggregate(:count, :id)
{:ok, do_search(search_query, admin), count}
{:ok, results, count}
end
def search(query, resolve \\ false, for_user \\ nil) do
@ -1157,9 +1157,12 @@ defmodule Pleroma.User do
if !is_nil(user) and !User.needs_update?(user) do
user
else
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
user = fetch_by_ap_id(ap_id)
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
if should_fetch_initial do
with %User{} = user do
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
end
@ -1340,7 +1343,7 @@ defmodule Pleroma.User do
|> Enum.map(&String.downcase(&1))
end
defp local_nickname_regex() do
defp local_nickname_regex do
if Pleroma.Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex
else
@ -1383,4 +1386,8 @@ defmodule Pleroma.User do
offset: ^((page - 1) * page_size)
)
end
def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.info.muted_reblogs
end
end

View file

@ -6,6 +6,8 @@ defmodule Pleroma.User.Info do
use Ecto.Schema
import Ecto.Changeset
alias Pleroma.User.Info
embedded_schema do
field(:banner, :map, default: %{})
field(:background, :map, default: %{})
@ -19,6 +21,7 @@ defmodule Pleroma.User.Info do
field(:blocks, {:array, :string}, default: [])
field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:muted_reblogs, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false)
@ -250,4 +253,23 @@ defmodule Pleroma.User.Info do
cast(info, params, [:pinned_activities])
end
def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do
%{
admin: is_admin,
moderator: is_moderator
}
end
def add_reblog_mute(info, ap_id) do
params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]}
cast(info, params, [:muted_reblogs])
end
def remove_reblog_mute(info, ap_id) do
params = %{muted_reblogs: List.delete(info.muted_reblogs, ap_id)}
cast(info, params, [:muted_reblogs])
end
end

View file

@ -14,7 +14,7 @@ defmodule Pleroma.User.WelcomeMessage do
end
end
defp welcome_user() do
defp welcome_user do
with nickname when is_binary(nickname) <-
Pleroma.Config.get([:instance, :welcome_user_nickname]),
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
@ -24,7 +24,7 @@ defmodule Pleroma.User.WelcomeMessage do
end
end
defp welcome_message() do
defp welcome_message do
Pleroma.Config.get([:instance, :welcome_message])
end
end

View file

@ -7,8 +7,8 @@ defmodule Pleroma.UserInviteToken do
import Ecto.Changeset
alias Pleroma.UserInviteToken
alias Pleroma.Repo
alias Pleroma.UserInviteToken
schema "user_invite_tokens" do
field(:token, :string)

View file

@ -4,17 +4,17 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.Instances
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Instances
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.WebFinger
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
import Ecto.Query
import Pleroma.Web.ActivityPub.Utils
@ -170,7 +170,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
additional
),
{:ok, activity} <- insert(create_data, local),
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
@ -309,7 +310,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor)
to = object.data["to"] || [] ++ object.data["cc"] || []
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
with {:ok, object, activity} <- Object.delete(object),
data <- %{
@ -320,7 +321,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"deleted_activity_id" => activity && activity.id
},
{:ok, activity} <- insert(data, local),
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
{:ok, _actor} <- decrease_note_count_if_public(user, object),
:ok <- maybe_federate(activity) do
{:ok, activity}
@ -368,20 +370,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
content: content
} = params
) do
additional = params[:additional] || %{}
# only accept false as false value
local = !(params[:local] == false)
forward = !(params[:forward] == false)
%{
additional = params[:additional] || %{}
params = %{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content
}
|> make_flag_data(additional)
|> insert(local)
additional =
if forward do
Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
else
Map.merge(additional, %{"to" => [], "cc" => []})
end
with flag_data <- make_flag_data(params, additional),
{:ok, activity} <- insert(flag_data, local),
:ok <- maybe_federate(activity) do
Enum.each(User.all_superusers(), fn superuser ->
superuser
|> Pleroma.AdminEmail.report(actor, account, statuses, content)
|> Pleroma.Mailer.deliver_async()
end)
{:ok, activity}
end
end
def fetch_activities_for_context(context, opts \\ %{}) do
@ -502,7 +522,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
when is_list(tag_reject) and tag_reject != [] do
from(
activity in query,
where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject)
where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject)
)
end
@ -512,7 +532,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
when is_list(tag_all) and tag_all != [] do
from(
activity in query,
where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all)
where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all)
)
end
@ -521,14 +541,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
from(
activity in query,
where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag)
where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag)
)
end
defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
from(
activity in query,
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data)
)
end
@ -601,7 +621,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
from(
activity in query,
where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
)
end
@ -610,7 +630,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
from(
activity in query,
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[])
)
end
@ -677,6 +697,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_pinned(query, _), do: query
defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
muted_reblogs = info.muted_reblogs || []
from(
activity in query,
where: fragment("not ?->>'type' = 'Announce'", activity.data),
where: fragment("not ? = ANY(?)", activity.actor, ^muted_reblogs)
)
end
defp restrict_muted_reblogs(query, _), do: query
def fetch_activities_query(recipients, opts \\ %{}) do
base_query =
from(
@ -704,6 +736,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_replies(opts)
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
|> restrict_muted_reblogs(opts)
end
def fetch_activities(recipients, opts \\ %{}) do

View file

@ -6,15 +6,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
require Logger

View file

@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
end)
end
def get_policies() do
def get_policies do
Application.get_env(:pleroma, :instance, [])
|> Keyword.get(:rewrite_policy, [])
|> get_policies()

View file

@ -45,13 +45,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
{content, summary} =
Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
replacement},
{content_acc,
summary_acc} ->
Enum.reduce(
Pleroma.Config.get([:mrf_keyword, :replace]),
{content, summary},
fn {pattern, replacement}, {content_acc, summary_acc} ->
{String.replace(content_acc, pattern, replacement),
String.replace(summary_acc, pattern, replacement)}
end)
end
)
{:ok,
message

View file

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
require Logger

View file

@ -7,9 +7,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@ -86,10 +86,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def fix_addressing_list(map, field) do
if is_binary(map[field]) do
map
|> Map.put(field, [map[field]])
else
cond do
is_binary(map[field]) ->
Map.put(map, field, [map[field]])
is_nil(map[field]) ->
Map.put(map, field, [])
true ->
map
end
end
@ -128,13 +132,42 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_explicit_addressing(explicit_mentions)
end
# if as:Public is addressed, then make sure the followers collection is also addressed
# so that the activities will be delivered to local users.
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
recipients = to ++ cc
if followers_collection not in recipients do
cond do
"https://www.w3.org/ns/activitystreams#Public" in cc ->
to = to ++ [followers_collection]
Map.put(object, "to", to)
"https://www.w3.org/ns/activitystreams#Public" in to ->
cc = cc ++ [followers_collection]
Map.put(object, "cc", cc)
true ->
object
end
else
object
end
end
def fix_implicit_addressing(object, _), do: object
def fix_addressing(object) do
%User{} = user = User.get_or_fetch_by_ap_id(object["actor"])
followers_collection = User.ap_followers(user)
object
|> fix_addressing_list("to")
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
|> fix_explicit_addressing
|> fix_implicit_addressing(followers_collection)
end
def fix_actor(%{"attributedTo" => actor} = object) do
@ -355,6 +388,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
# Reduce the object list to find the reported user.
%User{} = account <-
Enum.reduce_while(objects, nil, fn ap_id, _ ->
with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
{:halt, user}
else
_ -> {:cont, nil}
end
end),
# Remove the reported user from the object list.
statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
params = %{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content,
additional: %{
"cc" => [account.ap_id]
}
}
ActivityPub.flag(params)
end
end
# disallow objects with bogus IDs
def handle_incoming(%{"id" => nil}), do: :error
def handle_incoming(%{"id" => ""}), do: :error
@ -650,10 +717,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if object = Object.normalize(id), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do
with false <- String.starts_with?(inReplyTo, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
with false <- String.starts_with?(in_reply_to, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do
Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to)
else
_e -> object
end
@ -830,10 +897,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def add_attributed_to(object) do
attributedTo = object["attributedTo"] || object["actor"]
attributed_to = object["attributedTo"] || object["actor"]
object
|> Map.put("attributedTo", attributedTo)
|> Map.put("attributedTo", attributed_to)
end
def add_likes(%{"id" => id, "like_count" => likes} = object) do
@ -888,7 +955,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_tags(object), do: object
defp user_upgrade_task(user) do
old_follower_address = User.ap_followers(user)
# we pass a fake user so that the followers collection is stripped away
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
q =
from(

View file

@ -3,17 +3,17 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.Repo
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint
alias Ecto.Changeset
alias Ecto.UUID
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router.Helpers
import Ecto.Query
@ -621,7 +621,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
#### Flag-related helpers
def make_flag_data(params, additional) do
status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"])
status_ap_ids =
Enum.map(params.statuses || [], fn
%Activity{} = act -> act.data["id"]
act when is_map(act) -> act["id"]
act when is_binary(act) -> act
end)
object = [params.account.ap_id] ++ status_ap_ids
%{

View file

@ -5,15 +5,15 @@
defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Salmon
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
import Ecto.Query

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.MastodonAPI.Admin.AccountView
alias Pleroma.Web.AdminAPI.AccountView
import Pleroma.Web.ControllerHelper, only: [json_response: 3]

View file

@ -2,10 +2,11 @@
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
defmodule Pleroma.Web.AdminAPI.AccountView do
use Pleroma.Web, :view
alias Pleroma.Web.MastodonAPI.Admin.AccountView
alias Pleroma.User.Info
alias Pleroma.Web.AdminAPI.AccountView
def render("index.json", %{users: users, count: count, page_size: page_size}) do
%{
@ -19,7 +20,10 @@ defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
%{
"id" => user.id,
"nickname" => user.nickname,
"deactivated" => user.info.deactivated
"deactivated" => user.info.deactivated,
"local" => user.local,
"roles" => Info.roles(user.info),
"tags" => user.tags || []
}
end
end

View file

@ -0,0 +1,143 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
alias Pleroma.User
require Logger
@behaviour Pleroma.Web.Auth.Authenticator
@connection_timeout 10_000
@search_timeout 10_000
def get_user(%Plug.Conn{} = conn) do
if Pleroma.Config.get([:ldap, :enabled]) do
{name, password} =
case conn.params do
%{"authorization" => %{"name" => name, "password" => password}} ->
{name, password}
%{"grant_type" => "password", "username" => name, "password" => password} ->
{name, password}
end
case ldap_user(name, password) do
%User{} = user ->
{:ok, user}
{:error, {:ldap_connection_error, _}} ->
# When LDAP is unavailable, try default authenticator
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
error ->
error
end
else
# Fall back to default authenticator
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
end
end
def handle_error(%Plug.Conn{} = _conn, error) do
error
end
def auth_template, do: nil
defp ldap_user(name, password) do
ldap = Pleroma.Config.get(:ldap, [])
host = Keyword.get(ldap, :host, "localhost")
port = Keyword.get(ldap, :port, 389)
ssl = Keyword.get(ldap, :ssl, false)
sslopts = Keyword.get(ldap, :sslopts, [])
options =
[{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
if sslopts != [], do: [{:sslopts, sslopts}], else: []
case :eldap.open([to_charlist(host)], options) do
{:ok, connection} ->
try do
if Keyword.get(ldap, :tls, false) do
:application.ensure_all_started(:ssl)
case :eldap.start_tls(
connection,
Keyword.get(ldap, :tlsopts, []),
@connection_timeout
) do
:ok ->
:ok
error ->
Logger.error("Could not start TLS: #{inspect(error)}")
end
end
bind_user(connection, ldap, name, password)
after
:eldap.close(connection)
end
{:error, error} ->
Logger.error("Could not open LDAP connection: #{inspect(error)}")
{:error, {:ldap_connection_error, error}}
end
end
defp bind_user(connection, ldap, name, password) do
uid = Keyword.get(ldap, :uid, "cn")
base = Keyword.get(ldap, :base)
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
:ok ->
case User.get_by_nickname_or_email(name) do
%User{} = user ->
user
_ ->
register_user(connection, base, uid, name, password)
end
error ->
error
end
end
defp register_user(connection, base, uid, name, password) do
case :eldap.search(connection, [
{:base, to_charlist(base)},
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
{:scope, :eldap.wholeSubtree()},
{:attributes, ['mail', 'email']},
{:timeout, @search_timeout}
]) do
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
params = %{
email: :erlang.list_to_binary(mail),
name: name,
nickname: name,
password: password,
password_confirmation: password
}
changeset = User.register_changeset(%User{}, params)
case User.register(changeset) do
{:ok, user} -> user
error -> error
end
else
_ ->
Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
{:error, :ldap_registration_missing_attributes}
end
error ->
error
end
end
end

View file

@ -3,13 +3,20 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.User
alias Comeonin.Pbkdf2
alias Pleroma.User
@behaviour Pleroma.Web.Auth.Authenticator
def get_user(%Plug.Conn{} = conn) do
%{"authorization" => %{"name" => name, "password" => password}} = conn.params
{name, password} =
case conn.params do
%{"authorization" => %{"name" => name, "password" => password}} ->
{name, password}
%{"grant_type" => "password", "username" => name, "password" => password} ->
{name, password}
end
with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do

View file

@ -23,7 +23,7 @@ defmodule Pleroma.Web.UserSocket do
# performing token verification on connect.
def connect(%{"token" => token}, socket) do
with true <- Pleroma.Config.get([:chat, :enabled]),
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
%User{} = user <- Pleroma.Repo.get(User, user_id) do
{:ok, assign(socket, :user_name, user.nickname)}
else

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Web.ChatChannel do
use Phoenix.Channel
alias Pleroma.Web.ChatChannel.ChatChannelState
alias Pleroma.User
alias Pleroma.Web.ChatChannel.ChatChannelState
def join("chat:public", _message, socket) do
send(self(), :after_join)
@ -48,7 +48,7 @@ defmodule Pleroma.Web.ChatChannel.ChatChannelState do
end)
end
def messages() do
def messages do
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end)
end
end

View file

@ -3,14 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ThreadMute
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Formatter
import Pleroma.Web.CommonAPI.Utils
@ -27,6 +27,42 @@ defmodule Pleroma.Web.CommonAPI do
end
end
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
{:ok, follower}
end
end
def accept_follow_request(follower, followed) do
with {:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Accept"
}) do
{:ok, follower}
end
end
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Reject"
}) do
{:ok, follower}
end
end
def delete(activity_id, user) do
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
%Object{} = object <- Object.normalize(object_id),
@ -88,8 +124,8 @@ defmodule Pleroma.Web.CommonAPI do
nil ->
"public"
inReplyTo ->
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
in_reply_to ->
Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"])
end
end
@ -101,15 +137,15 @@ defmodule Pleroma.Web.CommonAPI do
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
{content_html, mentions, tags} <-
make_content_html(
status,
attachments,
data
),
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
context <- make_context(inReplyTo),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to),
cw <- data["spoiler_text"],
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
length when length in 1..limit <- String.length(full_payload),
@ -120,7 +156,7 @@ defmodule Pleroma.Web.CommonAPI do
context,
content_html,
attachments,
inReplyTo,
in_reply_to,
tags,
cw,
cc
@ -248,14 +284,9 @@ defmodule Pleroma.Web.CommonAPI do
actor: user,
account: account,
statuses: statuses,
content: content_html
content: content_html,
forward: data["forward"] || false
}) do
Enum.each(User.all_superusers(), fn superuser ->
superuser
|> Pleroma.AdminEmail.report(user, account, statuses, content_html)
|> Pleroma.Mailer.deliver_async()
end)
{:ok, activity}
else
{:error, err} -> {:error, err}
@ -263,4 +294,24 @@ defmodule Pleroma.Web.CommonAPI do
{:account, nil} -> {:error, "Account not found"}
end
end
def hide_reblogs(user, muted) do
ap_id = muted.ap_id
if ap_id not in user.info.muted_reblogs do
info_changeset = User.Info.add_reblog_mute(user.info, ap_id)
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end
end
def show_reblogs(user, muted) do
ap_id = muted.ap_id
if ap_id in user.info.muted_reblogs do
info_changeset = User.Info.remove_reblog_mute(user.info, ap_id)
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end
end
end

View file

@ -6,14 +6,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Calendar.Strftime
alias Comeonin.Pbkdf2
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.ActivityPub.Utils
# This is a hack for twidere.
def get_by_id_or_ap_id(id) do

View file

@ -6,7 +6,8 @@ defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller
def oauth_scopes(params, default) do
# Note: `scopes` is used by Mastodon — supporting it but sticking to OAuth's standard `scope` wherever we control it
# Note: `scopes` is used by Mastodon — supporting it but sticking to
# OAuth's standard `scope` wherever we control it
Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default)
end

View file

@ -25,7 +25,8 @@ defmodule Pleroma.Web.Endpoint do
at: "/",
from: "priv_sid/static",
only:
~w(index.html static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
)
# Code reloading can be explicitly enabled under the

View file

@ -4,27 +4,27 @@
defmodule Pleroma.Web.Federator do
alias Pleroma.Activity
alias Pleroma.Jobs
alias Pleroma.User
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.Salmon
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator.RetryQueue
alias Pleroma.Web.OStatus
alias Pleroma.Jobs
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
require Logger
@websub Application.get_env(:pleroma, :websub)
@ostatus Application.get_env(:pleroma, :ostatus)
def init() do
def init do
# 1 minute
Process.sleep(1000 * 60 * 1)
Process.sleep(1000 * 60)
refresh_subscriptions()
end
@ -58,7 +58,7 @@ defmodule Pleroma.Web.Federator do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
end
def refresh_subscriptions() do
def refresh_subscriptions do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
end

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
{:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}}
end
def start_link() do
def start_link do
enabled =
if Mix.env() == :test, do: true, else: Pleroma.Config.get([__MODULE__, :enabled], false)
@ -39,11 +39,11 @@ defmodule Pleroma.Web.Federator.RetryQueue do
GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1})
end
def get_stats() do
def get_stats do
GenServer.call(__MODULE__, :get_stats)
end
def reset_stats() do
def reset_stats do
GenServer.call(__MODULE__, :reset_stats)
end
@ -55,7 +55,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
end
end
def get_retry_timer_interval() do
def get_retry_timer_interval do
Pleroma.Config.get([:retry_queue, :interval], 1000)
end
@ -231,7 +231,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
end
end
defp maybe_kickoff_timer() do
defp maybe_kickoff_timer do
GenServer.cast(__MODULE__, :kickoff_timer)
end
end

View file

@ -2,61 +2,49 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Repo
@default_limit 20
def get_followers(user, params \\ %{}) do
user
|> User.get_followers_query()
|> paginate(params)
|> Repo.all()
|> Pagination.fetch_paginated(params)
end
def get_friends(user, params \\ %{}) do
user
|> User.get_friends_query()
|> paginate(params)
|> Repo.all()
|> Pagination.fetch_paginated(params)
end
def paginate(query, params \\ %{}) do
def get_notifications(user, params \\ %{}) do
options = cast_params(params)
query
|> restrict(:max_id, options)
|> restrict(:since_id, options)
|> restrict(:limit, options)
|> order_by([u], fragment("? desc nulls last", u.id))
user
|> Notification.for_user_query()
|> restrict(:exclude_types, options)
|> Pagination.fetch_paginated(params)
end
def cast_params(params) do
defp cast_params(params) do
param_types = %{
max_id: :string,
since_id: :string,
limit: :integer
exclude_types: {:array, :string}
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
changeset.changes
end
defp restrict(query, :max_id, %{max_id: max_id}) do
query
|> where([q], q.id < ^max_id)
end
defp restrict(query, :since_id, %{since_id: since_id}) do
query
|> where([q], q.id > ^since_id)
end
defp restrict(query, :limit, options) do
limit = Map.get(options, :limit, @default_limit)
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
ap_types =
mastodon_types
|> Enum.map(&Activity.from_mastodon_notification_type/1)
|> Enum.filter(& &1)
query
|> limit(^limit)
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
end
defp restrict(query, _, _), do: query

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Filter
@ -13,19 +14,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MastodonAPI.ReportView
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.MastodonAPI.MastodonView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.ReportView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
@ -502,21 +502,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params)
result =
notifications
|> Enum.map(fn x -> render_notification(user, x) end)
|> Enum.filter(& &1)
notifications = MastodonAPI.get_notifications(user, params)
conn
|> add_link_headers(:notifications, notifications)
|> json(result)
|> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
end
def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
with {:ok, notification} <- Notification.get(user, id) do
json(conn, render_notification(user, notification))
conn
|> put_view(NotificationView)
|> render("show.json", %{notification: notification, for: user})
else
{:error, reason} ->
conn
@ -697,16 +695,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- Repo.get(User, id),
{:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Accept"
}) do
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: followed, target: follower})
@ -720,15 +709,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- Repo.get(User, id),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Reject"
}) do
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: followed, target: follower})
@ -742,11 +723,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
false <- User.following?(follower, followed),
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: follower, target: followed})
else
true ->
followed = User.get_cached_by_id(id)
{:ok, follower} =
case conn.params["reblogs"] do
true -> CommonAPI.show_reblogs(follower, followed)
false -> CommonAPI.hide_reblogs(follower, followed)
end
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: follower, target: followed})
{:error, message} ->
conn
|> put_resp_content_type("application/json")
@ -770,8 +765,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
{:ok, _activity} <- ActivityPub.unfollow(follower, followed),
{:ok, follower, _} <- User.unfollow(follower, followed) do
{:ok, follower} <- CommonAPI.unfollow(follower, followed) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: follower, target: followed})
@ -1129,7 +1123,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
compose: %{
me: "#{user.id}",
default_privacy: user.info.default_scope,
default_sensitive: false
default_sensitive: false,
allow_content_types: Config.get([:instance, :allowed_post_formats])
},
media_attachments: %{
accept_content_types: [
@ -1274,7 +1269,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
defp get_or_make_app() do
defp get_or_make_app do
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
scopes = ["read", "write", "follow", "push"]
@ -1327,45 +1322,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
end
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
actor = User.get_cached_by_ap_id(activity.data["actor"])
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity)
response = %{
id: to_string(id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(created_at),
account: AccountView.render("account.json", %{user: actor, for: user})
}
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: activity, for: user})
})
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
"follow" ->
response
_ ->
nil
end
end
def get_filters(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
res = FilterView.render("filters.json", filters: filters)

View file

@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
muting_notifications: false,
requested: requested,
domain_blocking: false,
showing_reblogs: false,
showing_reblogs: User.showing_reblogs?(user, target),
endorsed: false
}
end

View file

@ -0,0 +1,64 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationView do
use Pleroma.Web, :view
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
def render("index.json", %{notifications: notifications, for: user}) do
render_many(notifications, NotificationView, "show.json", %{for: user})
end
def render("show.json", %{
notification: %Notification{activity: activity} = notification,
for: user
}) do
actor = User.get_cached_by_ap_id(activity.data["actor"])
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity)
response = %{
id: to_string(notification.id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: AccountView.render("account.json", %{user: actor, for: user}),
pleroma: %{
is_seen: notification.seen
}
}
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: activity, for: user})
})
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
})
"follow" ->
response
_ ->
nil
end
end
end

View file

@ -257,7 +257,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
preview_url: href,
text_url: href,
type: type,
description: attachment["name"]
description: attachment["name"],
pleroma: %{mime_type: media_type}
}
end

View file

@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
require Logger
alias Pleroma.Web.OAuth.Token
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
@behaviour :cowboy_websocket

View file

@ -19,7 +19,8 @@ defmodule Pleroma.Web.MediaProxy do
else
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
# Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580)
# Must preserve `%2F` for compatibility with S3
# https://git.pleroma.social/pleroma/pleroma/issues/580
replacement = get_replacement(url, ":2F:")
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.

View file

@ -88,7 +88,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
# TODO: Add additional properties to objects when we have the data available.
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
# object when a Video or GIF is attached it will display that in the Whatsapp Rich Preview.
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
case media_type do
"audio" ->
[

View file

@ -97,7 +97,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
| acc
]
# TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio
# TODO: Need the true width and height values here or Twitter renders an iFrame with
# a bad aspect ratio
"video" ->
[
{:meta, [property: "twitter:card", content: "player"], []},

View file

@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
# Copyright \xc2\xa9 2017-2019 Pleroma Authors <https://pleroma.social/>
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Utils do
alias Pleroma.HTML
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Web.MediaProxy
def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do

View file

@ -1 +0,0 @@

View file

@ -5,10 +5,10 @@
defmodule Pleroma.Web.OAuth.Authorization do
use Ecto.Schema
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
import Ecto.Changeset
import Ecto.Query
@ -16,7 +16,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
schema "oauth_authorizations" do
field(:token, :string)
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime)
field(:valid_until, :naive_datetime_usec)
field(:used, :boolean, default: false)
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
belongs_to(:app, App)

View file

@ -5,13 +5,12 @@
defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Repo
alias Pleroma.User
alias Comeonin.Pbkdf2
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
@ -105,6 +104,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
fixed_token = fix_padding(params["code"]),
%Authorization{} = auth <-
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
%User{} = user <- Repo.get(User, auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth),
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
response = %{
@ -113,7 +113,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
refresh_token: token.refresh_token,
created_at: DateTime.to_unix(inserted_at),
expires_in: 60 * 10,
scope: Enum.join(token.scopes, " ")
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
json(conn, response)
@ -126,11 +127,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def token_exchange(
conn,
%{"grant_type" => "password", "username" => name, "password" => password} = params
%{"grant_type" => "password"} = params
) do
with %App{} = app <- get_app_from_request(conn, params),
%User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash),
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
%App{} = app <- get_app_from_request(conn, params),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
scopes <- oauth_scopes(params, app.scopes),
[] <- scopes -- app.scopes,
@ -142,7 +142,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: 60 * 10,
scope: Enum.join(token.scopes, " ")
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
json(conn, response)

View file

@ -7,17 +7,17 @@ defmodule Pleroma.Web.OAuth.Token do
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Token
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
schema "oauth_tokens" do
field(:token, :string)
field(:refresh_token, :string)
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime)
field(:valid_until, :naive_datetime_usec)
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
belongs_to(:app, App)

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.OStatus.UserRepresenter
require Logger

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Web.OStatus.FeedRepresenter do
alias Pleroma.User
alias Pleroma.Web.OStatus
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.UserRepresenter

View file

@ -4,9 +4,9 @@
defmodule Pleroma.Web.OStatus.DeleteHandler do
require Logger
alias Pleroma.Web.XML
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.XML
def handle_delete(entry, _doc \\ nil) do
with id <- XML.string_from_xpath("//id", entry),

View file

@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.FollowHandler do
alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
def handle(entry, doc) do
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),

View file

@ -4,13 +4,14 @@
defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
@doc """
Get the context for this note. Uses this:
@ -18,13 +19,13 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
2. The conversation reference in the ostatus xml
3. A newly generated context id.
"""
def get_context(entry, inReplyTo) do
def get_context(entry, in_reply_to) do
context =
(XML.string_from_xpath("//ostatus:conversation[1]", entry) ||
XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry) || "")
|> String.trim()
with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(inReplyTo) do
with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(in_reply_to) do
context
else
_e ->
@ -87,14 +88,14 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
Map.put(note, "external_url", url)
end
def fetch_replied_to_activity(entry, inReplyTo) do
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(inReplyTo) do
def fetch_replied_to_activity(entry, in_reply_to) do
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
activity
else
_e ->
with inReplyToHref when not is_nil(inReplyToHref) <-
with in_reply_to_href when not is_nil(in_reply_to_href) <-
XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(inReplyToHref) do
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do
activity
else
_e -> nil
@ -110,11 +111,12 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
{:ok, actor} <- OStatus.find_make_or_update_user(author),
content_html <- OStatus.get_content(entry),
cw <- OStatus.get_cw(entry),
inReplyTo <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
inReplyToActivity <- fetch_replied_to_activity(entry, inReplyTo),
inReplyTo <- (inReplyToActivity && inReplyToActivity.data["object"]["id"]) || inReplyTo,
in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to),
in_reply_to <-
(in_reply_to_activity && in_reply_to_activity.data["object"]["id"]) || in_reply_to,
attachments <- OStatus.get_attachments(entry),
context <- get_context(entry, inReplyTo),
context <- get_context(entry, in_reply_to),
tags <- OStatus.get_tags(entry),
mentions <- get_mentions(entry),
to <- make_to_list(actor, mentions),
@ -128,7 +130,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
context,
content_html,
attachments,
inReplyToActivity,
in_reply_to_activity,
[],
cw
),
@ -140,8 +142,8 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
# TODO: Handle this case in make_note_data
note <-
if(
inReplyTo && !inReplyToActivity,
do: note |> Map.put("inReplyTo", inReplyTo),
in_reply_to && !in_reply_to_activity,
do: note |> Map.put("inReplyTo", in_reply_to),
else: note
) do
ActivityPub.create(%{

View file

@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.UnfollowHandler do
alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
def handle(entry, doc) do
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),

View file

@ -9,19 +9,19 @@ defmodule Pleroma.Web.OStatus do
import Pleroma.Web.XML
require Logger
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.OStatus.DeleteHandler
alias Pleroma.Web.OStatus.FollowHandler
alias Pleroma.Web.OStatus.NoteHandler
alias Pleroma.Web.OStatus.UnfollowHandler
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.OStatus.FollowHandler
alias Pleroma.Web.OStatus.UnfollowHandler
alias Pleroma.Web.OStatus.NoteHandler
alias Pleroma.Web.OStatus.DeleteHandler
def is_representable?(%Activity{data: data}) do
object = Object.normalize(data["object"])

View file

@ -9,13 +9,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])

View file

@ -5,13 +5,13 @@
defmodule Pleroma.Web.Push.Impl do
@moduledoc "The module represents implementation push web notification"
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.Push.Subscription
alias Pleroma.Web.Metadata.Utils
alias Pleroma.Notification
alias Pleroma.Web.Push.Subscription
require Logger
import Ecto.Query

View file

@ -13,15 +13,15 @@ defmodule Pleroma.Web.Push do
# Client API #
##############
def start_link() do
def start_link do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def vapid_config() do
def vapid_config do
Application.get_env(:web_push_encryption, :vapid_details, [])
end
def enabled() do
def enabled do
case vapid_config() do
[] -> false
list when is_list(list) -> true

View file

@ -82,8 +82,8 @@ defmodule Pleroma.Web.Push.Subscription do
end
# Some webpush clients (e.g. iOS Toot!) use an non urlsafe base64 as an encoding for the key.
# However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library we use
# requires the key to be properly encoded. So we just convert base64 to urlsafe base64.
# However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library
# we use requires the key to be properly encoded. So we just convert base64 to urlsafe base64.
defp ensure_base64_urlsafe(string) do
string
|> String.replace("+", "-")

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
defp validate_page_url(page_url) when is_binary(page_url) do

View file

@ -190,6 +190,12 @@ defmodule Pleroma.Web.Router do
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
scope [] do
pipe_through(:oauth_read)
post("/notifications/read", UtilController, :notifications_read)
end
end
scope "/oauth", Pleroma.Web.OAuth do
@ -631,8 +637,8 @@ end
defmodule Fallback.RedirectController do
use Pleroma.Web, :controller
alias Pleroma.Web.Metadata
alias Pleroma.User
alias Pleroma.Web.Metadata
def redirector(conn, _params, code \\ 200) do
conn

View file

@ -9,8 +9,8 @@ defmodule Pleroma.Web.Salmon do
alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.XML
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.XML
require Logger
@ -86,10 +86,10 @@ defmodule Pleroma.Web.Salmon do
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
try do
_ = :public_key.generate_key({:rsa, 2048, 65537})
_ = :public_key.generate_key({:rsa, 2048, 65_537})
def generate_rsa_pem do
key = :public_key.generate_key({:rsa, 2048, 65537})
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
{:ok, pem}

View file

@ -5,12 +5,14 @@
defmodule Pleroma.Web.Streamer do
use GenServer
require Logger
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.MastodonAPI.NotificationView
@keepalive_interval :timer.seconds(30)
@ -106,10 +108,10 @@ defmodule Pleroma.Web.Streamer do
%{
event: "notification",
payload:
Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(
socket.assigns["user"],
item
)
NotificationView.render("show.json", %{
notification: item,
for: socket.assigns["user"]
})
|> Jason.encode!()
}
|> Jason.encode!()
@ -198,10 +200,12 @@ defmodule Pleroma.Web.Streamer do
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
blocks = user.info.blocks || []
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
parent = Object.normalize(item.data["object"])
unless is_nil(parent) or item.actor in blocks or item.actor in mutes or
item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or
parent.data["actor"] in blocks or parent.data["actor"] in mutes do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
@ -232,7 +236,8 @@ defmodule Pleroma.Web.Streamer do
blocks = user.info.blocks || []
mutes = user.info.mutes || []
unless item.actor in blocks or item.actor in mutes do
unless item.actor in blocks or item.actor in mutes or
not ActivityPub.contain_activity(item, user) do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
else

View file

@ -9,14 +9,15 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Comeonin.Pbkdf2
alias Pleroma.Emoji
alias Pleroma.Notification
alias Pleroma.PasswordResetToken
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
alias Pleroma.Web.ActivityPub.ActivityPub
def show_password_reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
@ -142,6 +143,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, _} <- Notification.read_one(user, notification_id) do
json(conn, %{status: "success"})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def config(conn, _params) do
instance = Pleroma.Config.get(:instance)
instance_fe = Pleroma.Config.get(:fe)

View file

@ -3,16 +3,16 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.UserInviteToken
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.UserEmail
alias Pleroma.Mailer
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserEmail
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.UserView
import Ecto.Query
@ -35,11 +35,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
def unfollow(%User{} = follower, params) do
with {:ok, %User{} = unfollowed} <- get_user(params),
{:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
{:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do
{:ok, follower, unfollowed}
else
err -> err
end
end
@ -133,7 +130,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
def register_user(params) do
tokenString = params["token"]
token_string = params["token"]
params = %{
nickname: params["nickname"],
@ -170,8 +167,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
# no need to query DB if registration is open
token =
unless registrations_open || is_nil(tokenString) do
Repo.get_by(UserInviteToken, %{token: tokenString})
unless registrations_open || is_nil(token_string) do
Repo.get_by(UserInviteToken, %{token: token_string})
end
cond do

View file

@ -8,23 +8,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Ecto.Changeset
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView, TokenView}
alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, Object, User, Notification}
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TokenView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
require Logger
@ -590,16 +587,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- Repo.get(User, uid),
{:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Accept"
}) do
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})
@ -611,15 +599,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- Repo.get(User, uid),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: follow_activity.data["id"],
type: "Reject"
}) do
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})

Some files were not shown because too many files have changed in this diff Show more