@@ -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`. | |||
# | |||
@@ -1,4 +1,4 @@ | |||
image: elixir:1.7.2 | |||
image: elixir:1.8.1 | |||
services: | |||
- name: postgres:9.6.2 | |||
@@ -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 |
@@ -1,12 +0,0 @@ | |||
Unliking: | |||
- Add a proper undo activity, find out how to ignore those in twitter api. | |||
WEBSUB: | |||
- Add unsubscription | |||
OSTATUS: | |||
- Save and output 'updated' | |||
@@ -133,7 +133,14 @@ config :pleroma, :httpoison, Pleroma.HTTP | |||
config :tesla, adapter: Tesla.Adapter.Hackney | |||
# Configures http settings, upstream proxy etc. | |||
config :pleroma, :http, proxy_url: nil | |||
config :pleroma, :http, | |||
proxy_url: nil, | |||
adapter: [ | |||
ssl_options: [ | |||
# We don't support TLS v1.3 yet | |||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"] | |||
] | |||
] | |||
config :pleroma, :instance, | |||
name: "Pleroma", | |||
@@ -215,6 +222,9 @@ config :pleroma, :frontend_configurations, | |||
scopeCopy: true, | |||
subjectLineBehavior: "email", | |||
alwaysShowSubjectInput: true | |||
}, | |||
masto_fe: %{ | |||
showInstanceSpecificPanel: true | |||
} | |||
config :pleroma, :activitypub, | |||
@@ -345,6 +355,10 @@ config :pleroma, Pleroma.Jobs, | |||
federator_outgoing: [max_jobs: 50], | |||
mailer: [max_jobs: 10] | |||
config :pleroma, :fetch_initial_posts, | |||
enabled: false, | |||
pages: 5 | |||
config :auto_linker, | |||
opts: [ | |||
scheme: true, | |||
@@ -44,6 +44,8 @@ config :web_push_encryption, :vapid_details, | |||
"BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", | |||
private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" | |||
config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock | |||
config :pleroma, Pleroma.Jobs, testing: [max_jobs: 2] | |||
try do | |||
@@ -7,36 +7,11 @@ Authentication is required and the user must be an admin. | |||
### List users | |||
- Method `GET` | |||
- Params: | |||
- `page`: **integer** page number | |||
- `page_size`: **integer** number of users per page (default is `50`) | |||
- Response: | |||
```JSON | |||
{ | |||
"page_size": integer, | |||
"count": integer, | |||
"users": [ | |||
{ | |||
"deactivated": bool, | |||
"id": integer, | |||
"nickname": string | |||
}, | |||
... | |||
] | |||
} | |||
``` | |||
## `/api/pleroma/admin/users/search?query={query}&local={local}&page={page}&page_size={page_size}` | |||
### Search users by name or nickname | |||
- Method `GET` | |||
- Params: | |||
- `query`: **string** search term | |||
- `local`: **bool** whether to return only local users | |||
- `page`: **integer** page number | |||
- `page_size`: **integer** number of users per page (default is `50`) | |||
- Query Params: | |||
- `query`: **string** *optional* search term | |||
- `local_only`: **bool** *optional* whether to return only local users | |||
- `page`: **integer** *optional* page number | |||
- `page_size`: **integer** *optional* number of users per page (default is `50`) | |||
- Response: | |||
```JSON | |||
@@ -4,8 +4,8 @@ Feel free to contact us to be added to this list! | |||
## Desktop | |||
### Roma for Desktop | |||
- Homepage: <http://www.pleroma.com/desktop-app/> | |||
- Source Code: ??? | |||
- Homepage: <https://www.pleroma.com/#desktopApp> | |||
- Source Code: <https://github.com/roma-apps/roma-desktop> | |||
- Platforms: Windows, Mac, (Linux?) | |||
- Features: Streaming Ready | |||
@@ -30,6 +30,12 @@ Feel free to contact us to be added to this list! | |||
- Platforms: iOS | |||
- Features: No Streaming | |||
### Fedilab | |||
- Source Code: <https://gitlab.com/tom79/mastalab/> | |||
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79) | |||
- Platforms: Android | |||
- Features: Streaming Ready | |||
### Nekonium | |||
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/) | |||
- Source: <https://git.gdgd.jp.net/lin/nekonium/> | |||
@@ -37,15 +43,9 @@ Feel free to contact us to be added to this list! | |||
- Platforms: Android | |||
- Features: Streaming Ready | |||
### Mastalab | |||
- Source Code: <https://gitlab.com/tom79/mastalab/> | |||
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79) | |||
- Platforms: Android | |||
- Features: Streaming Ready | |||
### Roma | |||
- Homepage: <http://www.pleroma.com/> | |||
- Source Code: ??? | |||
- Homepage: <https://www.pleroma.com/#mobileApps> | |||
- Source Code: [iOS](https://github.com/roma-apps/roma-ios), [Android](https://github.com/roma-apps/roma-android) | |||
- Platforms: iOS, Android | |||
- Features: No Streaming | |||
@@ -13,3 +13,13 @@ Some apps operate under the assumption that no more than 4 attachments can be re | |||
## Timelines | |||
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users. | |||
## Statuses | |||
Has these additional fields under the `pleroma` object: | |||
- `local`: true if the post was made on the local instance. | |||
## 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. |
@@ -129,7 +129,7 @@ See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_s | |||
## :frontend_configurations | |||
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` are configured. | |||
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. | |||
Frontends can access these settings at `/api/pleroma/frontend_configurations` | |||
@@ -285,6 +285,10 @@ This config contains two queues: `federator_incoming` and `federator_outgoing`. | |||
## :rich_media | |||
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews | |||
## :fetch_initial_posts | |||
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts | |||
* `pages`: the amount of pages to fetch | |||
## :hackney_pools | |||
Advanced. Tweaks Hackney (http client) connections pools. | |||
@@ -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 """ | |||
@@ -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 | |||
@@ -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 """ | |||
@@ -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) | |||
@@ -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 | |||
@@ -107,6 +107,18 @@ defmodule Pleroma.Activity do | |||
def get_in_reply_to_activity(_), do: nil | |||
def delete_by_ap_id(id) when is_binary(id) do | |||
by_object_ap_id(id) | |||
|> Repo.delete_all(returning: true) | |||
|> elem(1) | |||
|> Enum.find(fn | |||
%{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id | |||
_ -> nil | |||
end) | |||
end | |||
def delete_by_ap_id(_), do: nil | |||
for {ap_type, type} <- @mastodon_notification_types do | |||
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}), | |||
do: unquote(type) | |||
@@ -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) | |||
@@ -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]) | |||
@@ -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 | |||
@@ -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 ++ [""] | |||
@@ -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!!! | |||
@@ -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") ++ | |||
@@ -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) | |||
@@ -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 | |||
@@ -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, | |||
@@ -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]) | |||
@@ -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 | |||
@@ -8,8 +8,8 @@ defmodule Pleroma.HTTP.Connection do | |||
""" | |||
@hackney_options [ | |||
timeout: 10000, | |||
recv_timeout: 20000, | |||
connect_timeout: 2_000, | |||
recv_timeout: 20_000, | |||
follow_redirect: true, | |||
pool: :federation | |||
] | |||
@@ -31,6 +31,10 @@ defmodule Pleroma.HTTP.Connection do | |||
# | |||
defp hackney_options(opts) do | |||
options = Keyword.get(opts, :adapter, []) | |||
@hackney_options ++ options | |||
adapter_options = Pleroma.Config.get([:http, :adapter], []) | |||
@hackney_options | |||
|> Keyword.merge(adapter_options) | |||
|> Keyword.merge(options) | |||
end | |||
end |
@@ -27,21 +27,29 @@ defmodule Pleroma.HTTP do | |||
""" | |||
def request(method, url, body \\ "", headers \\ [], options \\ []) do | |||
options = | |||
process_request_options(options) | |||
|> process_sni_options(url) | |||
params = Keyword.get(options, :params, []) | |||
%{} | |||
|> Builder.method(method) | |||
|> Builder.headers(headers) | |||
|> Builder.opts(options) | |||
|> Builder.url(url) | |||
|> Builder.add_param(:body, :body, body) | |||
|> Builder.add_param(:query, :query, params) | |||
|> Enum.into([]) | |||
|> (&Tesla.request(Connection.new(), &1)).() | |||
try do | |||
options = | |||
process_request_options(options) | |||
|> process_sni_options(url) | |||
params = Keyword.get(options, :params, []) | |||
%{} | |||
|> Builder.method(method) | |||
|> Builder.headers(headers) | |||
|> Builder.opts(options) | |||
|> Builder.url(url) | |||
|> Builder.add_param(:body, :body, body) | |||
|> Builder.add_param(:query, :query, params) | |||
|> Enum.into([]) | |||
|> (&Tesla.request(Connection.new(options), &1)).() | |||
rescue | |||
e -> | |||
{:error, e} | |||
catch | |||
:exit, e -> | |||
{:error, e} | |||
end | |||
end | |||
defp process_sni_options(options, nil), do: options | |||
@@ -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 | |||
@@ -5,12 +5,12 @@ | |||
defmodule Pleroma.Notification do | |||
use Ecto.Schema | |||
alias Pleroma.User | |||
alias Pleroma.Activity | |||
alias Pleroma.Notification | |||
alias Pleroma.Repo | |||
alias Pleroma.Web.CommonAPI.Utils | |||
alias Pleroma.User | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.CommonAPI.Utils | |||
import Ecto.Query | |||
@@ -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 | |||
@@ -86,9 +86,9 @@ defmodule Pleroma.Object do | |||
def delete(%Object{data: %{"id" => id}} = object) do | |||
with {:ok, _obj} = swap_object_with_tombstone(object), | |||
Repo.delete_all(Activity.by_object_ap_id(id)), | |||
deleted_activity = Activity.delete_by_ap_id(id), | |||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do | |||
{:ok, object} | |||
{:ok, object, deleted_activity} | |||
end | |||
end | |||
@@ -34,13 +34,16 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
defp csp_string do | |||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] | |||
websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws") | |||
static_url = Pleroma.Web.Endpoint.static_url() | |||
websocket_url = String.replace(static_url, "http", "ws") | |||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}" | |||
connect_src = | |||
if Mix.env() == :dev do | |||
"connect-src 'self' http://localhost:3035/ " <> websocket_url | |||
connect_src <> " http://localhost:3035/" | |||
else | |||
"connect-src 'self' " <> websocket_url | |||
connect_src | |||
end | |||
script_src = | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 -> | |||
@@ -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__) | |||
@@ -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 | |||
@@ -85,6 +85,10 @@ defmodule Pleroma.Upload do | |||
end | |||
end | |||
def char_unescaped?(char) do | |||
URI.char_unreserved?(char) or char == ?/ | |||
end | |||
defp get_opts(opts) do | |||
{size_limit, activity_type} = | |||
case Keyword.get(opts, :type) do | |||
@@ -218,9 +222,7 @@ defmodule Pleroma.Upload do | |||
defp url_from_spec(base_url, {:file, path}) do | |||
path = | |||
path | |||
|> URI.encode() | |||
|> String.replace("?", "%3F") | |||
|> String.replace(":", "%3A") | |||
|> URI.encode(&char_unescaped?/1) | |||
[base_url, "media", path] | |||
|> Path.join() | |||
@@ -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) | |||
@@ -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) | |||
@@ -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()) :: | |||
@@ -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.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
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.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]+$/ | |||
@@ -285,7 +286,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 | |||
@@ -435,7 +436,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, "/")) | |||
@@ -532,6 +534,10 @@ defmodule Pleroma.User do | |||
_e -> | |||
with [_nick, _domain] <- String.split(nickname, "@"), | |||
{:ok, user} <- fetch_by_nickname(nickname) do | |||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do | |||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) | |||
end | |||
user | |||
else | |||
_e -> nil | |||
@@ -539,6 +545,17 @@ defmodule Pleroma.User do | |||
end | |||
end | |||
@doc "Fetch some posts when the user has just been federated with" | |||
def fetch_initial_posts(user) do | |||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) | |||
Enum.each( | |||
# Insert all the posts in reverse order, so they're in the right order on the timeline | |||
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)), | |||
&Pleroma.Web.Federator.incoming_ap_doc/1 | |||
) | |||
end | |||
def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do | |||
from( | |||
u in User, | |||
@@ -749,13 +766,41 @@ defmodule Pleroma.User do | |||
Repo.all(query) | |||
end | |||
@spec search_for_admin(binary(), %{ | |||
@spec search_for_admin(%{ | |||
local: boolean(), | |||
page: number(), | |||
page_size: number() | |||
}) :: {: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) | |||
|> maybe_local_user_query(local) | |||
paginated_query = | |||
query | |||
|> paginate(page, page_size) | |||
count = | |||
query | |||
|> Repo.aggregate(:count, :id) | |||
{:ok, Repo.all(paginated_query), count} | |||
end | |||
@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(term, %{admin: admin, local: local, page: page, page_size: page_size}) do | |||
def search_for_admin(%{ | |||
query: term, | |||
admin: admin, | |||
local: local, | |||
page: page, | |||
page_size: page_size | |||
}) do | |||
term = String.trim_leading(term, "@") | |||
local_paginated_query = | |||
@@ -774,21 +819,6 @@ defmodule Pleroma.User do | |||
{:ok, do_search(search_query, admin), count} | |||
end | |||
@spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()} | |||
def all_for_admin(page, page_size) do | |||
query = from(u in User, order_by: u.id) | |||
paginated_query = | |||
query | |||
|> paginate(page, page_size) | |||
count = | |||
query | |||
|> Repo.aggregate(:count, :id) | |||
{:ok, Repo.all(paginated_query), count} | |||
end | |||
def search(query, resolve \\ false, for_user \\ nil) do | |||
# Strip the beginning @ off if there is a query | |||
query = String.trim_leading(query, "@") | |||
@@ -1108,24 +1138,36 @@ defmodule Pleroma.User do | |||
def html_filter_policy(_), do: @default_scrubbers | |||
def fetch_by_ap_id(ap_id) do | |||
ap_try = ActivityPub.make_user_from_ap_id(ap_id) | |||
case ap_try do | |||
{:ok, user} -> | |||
user | |||
_ -> | |||
case OStatus.make_user(ap_id) do | |||
{:ok, user} -> user | |||
_ -> {:error, "Could not fetch by AP id"} | |||
end | |||
end | |||
end | |||
def get_or_fetch_by_ap_id(ap_id) do | |||
user = get_by_ap_id(ap_id) | |||
if !is_nil(user) and !User.needs_update?(user) do | |||
user | |||
else | |||
ap_try = ActivityPub.make_user_from_ap_id(ap_id) | |||
case ap_try do | |||
{:ok, user} -> | |||
user | |||
user = fetch_by_ap_id(ap_id) | |||
_ -> | |||
case OStatus.make_user(ap_id) do | |||
{:ok, user} -> user | |||
_ -> {:error, "Could not fetch by AP id"} | |||
end | |||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do | |||
with %User{} = user do | |||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) | |||
end | |||
end | |||
user | |||
end | |||
end | |||
@@ -1300,7 +1342,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 | |||
@@ -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: %{}) | |||
@@ -250,4 +252,11 @@ 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 | |||
end |
@@ -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 |
@@ -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) | |||
@@ -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,17 +310,19 @@ 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"] || [] | |||
data = %{ | |||
"type" => "Delete", | |||
"actor" => actor, | |||
"object" => id, | |||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"] | |||
} | |||
with {:ok, _} <- Object.delete(object), | |||
with {:ok, object, activity} <- Object.delete(object), | |||
data <- %{ | |||
"type" => "Delete", | |||
"actor" => actor, | |||
"object" => id, | |||
"to" => to, | |||
"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} | |||
@@ -501,7 +504,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 | |||
@@ -511,7 +514,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 | |||
@@ -520,14 +523,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 | |||
@@ -600,7 +603,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 | |||
@@ -609,7 +612,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 | |||
@@ -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 | |||
@@ -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() | |||
@@ -23,15 +23,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do | |||
defp score_displayname(_), do: 0.0 | |||
defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do | |||
# nickname will always be a binary string because it's generated by Pleroma. | |||
nick_score = | |||
nickname | |||
|> String.downcase() | |||
|> score_nickname() | |||
# displayname will either be a binary string or nil, if a displayname isn't set. | |||
name_score = | |||
displayname | |||
|> String.downcase() | |||
|> score_displayname() | |||
if is_binary(displayname) do | |||
displayname | |||
|> String.downcase() | |||
|> score_displayname() | |||
else | |||
0.0 | |||
end | |||
nick_score + name_score | |||
end | |||
@@ -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} -> | |||
{String.replace(content_acc, pattern, replacement), | |||
String.replace(summary_acc, pattern, replacement)} | |||
end) | |||
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 | |||
) | |||
{:ok, | |||
message | |||
@@ -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 | |||
@@ -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 | |||
@@ -650,10 +650,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 | |||
@@ -736,6 +736,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
def prepare_outgoing(%{"type" => _type} = data) do | |||
data = | |||
data | |||
|> strip_internal_fields | |||
|> maybe_fix_object_url | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
@@ -829,10 +830,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 | |||
@@ -870,7 +871,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"announcements", | |||
"announcement_count", | |||
"emoji", | |||
"context_id" | |||
"context_id", | |||
"deleted_activity_id" | |||
]) | |||
end | |||
@@ -3,16 +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 Ecto.Changeset | |||
alias Ecto.UUID | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Notification | |||
alias Pleroma.Web.Router.Helpers | |||
alias Pleroma.Object | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Web | |||
alias Pleroma.Web.ActivityPub.Visibility | |||
alias Pleroma.Web.Endpoint | |||
alias Ecto.Changeset | |||
alias Ecto.UUID | |||
alias Pleroma.Web.Router.Helpers | |||
import Ecto.Query | |||
@@ -274,13 +275,31 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
Repo.all(query) | |||
end | |||
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do | |||
def make_like_data( | |||
%User{ap_id: ap_id} = actor, | |||
%{data: %{"actor" => object_actor_id, "id" => id}} = object, | |||
activity_id | |||
) do | |||
object_actor = User.get_cached_by_ap_id(object_actor_id) | |||
to = | |||
if Visibility.is_public?(object) do | |||
[actor.follower_address, object.data["actor"]] | |||
else | |||
[object.data["actor"]] | |||
end | |||
cc = | |||
(object.data["to"] ++ (object.data["cc"] || [])) | |||
|> List.delete(actor.ap_id) | |||
|> List.delete(object_actor.follower_address) | |||
data = %{ | |||
"type" => "Like", | |||
"actor" => ap_id, | |||
"object" => id, | |||
"to" => [actor.follower_address, object.data["actor"]], | |||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], | |||
"to" => to, | |||
"cc" => cc, | |||
"context" => object.data["context"] | |||
} | |||
@@ -614,4 +633,43 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
} | |||
|> Map.merge(additional) | |||
end | |||
@doc """ | |||
Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after | |||
the first one to `pages_left` pages. | |||
If the amount of pages is higher than the collection has, it returns whatever was there. | |||
""" | |||
def fetch_ordered_collection(from, pages_left, acc \\ []) do | |||
with {:ok, response} <- Tesla.get(from), | |||
{:ok, collection} <- Poison.decode(response.body) do | |||
case collection["type"] do | |||
"OrderedCollection" -> | |||
# If we've encountered the OrderedCollection and not the page, | |||
# just call the same function on the page address | |||
fetch_ordered_collection(collection["first"], pages_left) | |||
"OrderedCollectionPage" -> | |||
if pages_left > 0 do | |||
# There are still more pages | |||
if Map.has_key?(collection, "next") do | |||
# There are still more pages, go deeper saving what we have into the accumulator | |||
fetch_ordered_collection( | |||
collection["next"], | |||
pages_left - 1, | |||
acc ++ collection["orderedItems"] | |||
) | |||
else | |||
# No more pages left, just return whatever we already have | |||
acc ++ collection["orderedItems"] | |||
end | |||
else | |||
# Got the amount of pages needed, add them all to the accumulator | |||
acc ++ collection["orderedItems"] | |||
end | |||
_ -> | |||
{:error, "Not an OrderedCollection or OrderedCollectionPage"} | |||
end | |||
end | |||
end | |||
end |
@@ -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 | |||
@@ -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] | |||
@@ -63,28 +63,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
do: json_response(conn, :no_content, "") | |||
end | |||
def list_users(conn, params) do | |||
{page, page_size} = page_params(params) | |||
with {:ok, users, count} <- User.all_for_admin(page, page_size), | |||
do: | |||
conn | |||
|> json( | |||
AccountView.render("index.json", | |||
users: users, | |||
count: count, | |||
page_size: page_size | |||
) | |||
) | |||
end | |||
def search_users(%{assigns: %{user: admin}} = conn, %{"query" => query} = params) do | |||
def list_users(%{assigns: %{user: admin}} = conn, params) do | |||
{page, page_size} = page_params(params) | |||
with {:ok, users, count} <- | |||
User.search_for_admin(query, %{ | |||
User.search_for_admin(%{ | |||
query: params["query"], | |||
admin: admin, | |||
local: params["local"] == "true", | |||
local: params["local_only"] == "true", | |||
page: page, | |||
page_size: page_size | |||
}), | |||
@@ -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 |
@@ -3,8 +3,8 @@ | |||
# 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 | |||
@@ -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 | |||
@@ -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 |
@@ -3,21 +3,70 @@ | |||
# 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 | |||
def follow(follower, followed) do | |||
with {:ok, follower} <- User.maybe_direct_follow(follower, followed), | |||
{:ok, activity} <- ActivityPub.follow(follower, followed), | |||
{:ok, follower, followed} <- | |||
User.wait_and_refresh( | |||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), | |||
follower, | |||
followed | |||
) do | |||
{:ok, follower, followed, activity} | |||
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), | |||
true <- user.info.is_moderator || user.ap_id == object.data["actor"], | |||
true <- User.superuser?(user) || user.ap_id == object.data["actor"], | |||
{:ok, _} <- unpin(activity_id, user), | |||
{:ok, delete} <- ActivityPub.delete(object) do | |||
{:ok, delete} | |||
@@ -75,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 | |||
@@ -88,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), | |||
@@ -107,7 +156,7 @@ defmodule Pleroma.Web.CommonAPI do | |||
context, | |||
content_html, | |||
attachments, | |||
inReplyTo, | |||
in_reply_to, | |||
tags, | |||
cw, | |||
cc | |||
@@ -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 | |||
@@ -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 | |||
@@ -26,6 +26,7 @@ defmodule Pleroma.Web.Endpoint do | |||
from: :pleroma, | |||
only: | |||
~w(index.html 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 | |||
@@ -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 | |||
@@ -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 |
@@ -1 +1,63 @@ | |||
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do | |||
import Ecto.Query | |||
import Ecto.Changeset | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
@default_limit 20 | |||
def get_followers(user, params \\ %{}) do | |||
user | |||
|> User.get_followers_query() | |||
|> paginate(params) | |||
|> Repo.all() | |||
end | |||
def get_friends(user, params \\ %{}) do | |||
user | |||
|> User.get_friends_query() | |||
|> paginate(params) | |||
|> Repo.all() | |||
end | |||
def paginate(query, 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)) | |||
end | |||
def cast_params(params) do | |||
param_types = %{ | |||
max_id: :string, | |||
since_id: :string, | |||
limit: :integer | |||
} | |||
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) | |||
query | |||
|> limit(^limit) | |||
end | |||
defp restrict(query, _, _), do: query | |||
end |
@@ -4,6 +4,7 @@ | |||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Activity | |||
alias Pleroma.Config | |||
alias Pleroma.Filter | |||
@@ -13,21 +14,17 @@ 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.Push | |||
alias Push.Subscription | |||
alias Pleroma.Web.MastodonAPI.AccountView | |||
alias Pleroma.Web.MastodonAPI.FilterView | |||
alias Pleroma.Web.MastodonAPI.ListView | |||
alias Pleroma.Web.MastodonAPI.MastodonAPI | |||
alias Pleroma.Web.MastodonAPI.MastodonView | |||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView | |||
alias Pleroma.Web.MastodonAPI.StatusView | |||
alias Pleroma.Web.MastodonAPI.ReportView | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.ActivityPub.Visibility | |||
alias Pleroma.Web.MastodonAPI.StatusView | |||
alias Pleroma.Web.MediaProxy | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
@@ -134,8 +131,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
json(conn, account) | |||
end | |||
def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do | |||
with %User{} = user <- Repo.get(User, id), | |||
def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do | |||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id), | |||
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do | |||
account = AccountView.render("account.json", %{user: user, for: for_user}) | |||
json(conn, account) | |||
@@ -193,6 +190,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do | |||
params = | |||
conn.params | |||
|> Map.drop(["since_id", "max_id"]) | |||
|> Map.merge(params) | |||
last = List.last(activities) | |||
first = List.first(activities) | |||
@@ -292,13 +294,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do | |||
query = | |||
ActivityPub.fetch_activities_query( | |||
[user.ap_id], | |||
Map.merge(params, %{"type" => "Create", visibility: "direct"}) | |||
) | |||
params = | |||
params | |||
|> Map.put("type", "Create") | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("user", user) | |||
|> Map.put(:visibility, "direct") | |||
activities = Repo.all(query) | |||
activities = | |||
[user.ap_id] | |||
|> ActivityPub.fetch_activities_query(params) | |||
|> Repo.all() | |||
conn | |||
|> add_link_headers(:dm_timeline, activities) | |||
@@ -646,9 +652,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
|> render("index.json", %{activities: activities, for: user, as: :activity}) | |||
end | |||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do | |||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do | |||
with %User{} = user <- Repo.get(User, id), | |||
{:ok, followers} <- User.get_followers(user) do | |||
followers <- MastodonAPI.get_followers(user, params) do | |||
followers = | |||
cond do | |||
for_user && user.id == for_user.id -> followers | |||
@@ -657,14 +663,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
conn | |||
|> add_link_headers(:followers, followers, user) | |||
|> put_view(AccountView) | |||
|> render("accounts.json", %{users: followers, as: :user}) | |||
end | |||
end | |||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do | |||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do | |||
with %User{} = user <- Repo.get(User, id), | |||
{:ok, followers} <- User.get_friends(user) do | |||
followers <- MastodonAPI.get_friends(user, params) do | |||
followers = | |||
cond do | |||
for_user && user.id == for_user.id -> followers | |||
@@ -673,6 +680,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
conn | |||
|> add_link_headers(:following, followers, user) | |||
|> put_view(AccountView) | |||
|> render("accounts.json", %{users: followers, as: :user}) | |||
end | |||
@@ -688,16 +696,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}) | |||
@@ -711,15 +710,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}) | |||
@@ -733,14 +724,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do | |||
with %User{} = followed <- Repo.get(User, id), | |||
{:ok, follower} <- User.maybe_direct_follow(follower, followed), | |||
{:ok, _activity} <- ActivityPub.follow(follower, followed), | |||
{:ok, follower, followed} <- | |||
User.wait_and_refresh( | |||
Config.get([:activitypub, :follow_handshake_timeout]), | |||
follower, | |||
followed | |||
) do | |||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do | |||
conn | |||
|> put_view(AccountView) | |||
|> render("relationship.json", %{user: follower, target: followed}) | |||
@@ -754,8 +738,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do | |||
with %User{} = followed <- Repo.get_by(User, nickname: uri), | |||
{:ok, follower} <- User.maybe_direct_follow(follower, followed), | |||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do | |||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do | |||
conn | |||
|> put_view(AccountView) | |||
|> render("account.json", %{user: followed, for: follower}) | |||
@@ -769,8 +752,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}) | |||
@@ -1128,7 +1110,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: [ | |||
@@ -1273,7 +1256,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"] | |||
@@ -1424,37 +1407,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
json(conn, %{}) | |||
end | |||
def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do | |||
true = Push.enabled() | |||
Subscription.delete_if_exists(user, token) | |||
{:ok, subscription} = Subscription.create(user, token, params) | |||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do | |||
true = Push.enabled() | |||
subscription = Subscription.get(user, token) | |||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
def update_push_subscription( | |||
%{assigns: %{user: user, token: token}} = conn, | |||
params | |||
) do | |||
true = Push.enabled() | |||
{:ok, subscription} = Subscription.update(user, token, params) | |||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do | |||
true = Push.enabled() | |||
{:ok, _response} = Subscription.delete(user, token) | |||
json(conn, %{}) | |||
end | |||
# fallback action | |||
# | |||
def errors(conn, _) do | |||
conn | |||
|> put_status(500) | |||
@@ -1483,7 +1437,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
url, | |||
[], | |||
adapter: [ | |||
timeout: timeout, | |||
recv_timeout: timeout, | |||
pool: :default | |||
] | |||
@@ -0,0 +1,71 @@ | |||
# 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.SubscriptionController do | |||
@moduledoc "The module represents functions to manage user subscriptions." | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Web.Push | |||
alias Pleroma.Web.Push.Subscription | |||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View | |||
action_fallback(:errors) | |||
# Creates PushSubscription | |||
# POST /api/v1/push/subscription | |||
# | |||
def create(%{assigns: %{user: user, token: token}} = conn, params) do | |||
with true <- Push.enabled(), | |||
{:ok, _} <- Subscription.delete_if_exists(user, token), | |||
{:ok, subscription} <- Subscription.create(user, token, params) do | |||
view = View.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
end | |||
# Gets PushSubscription | |||
# GET /api/v1/push/subscription | |||
# | |||
def get(%{assigns: %{user: user, token: token}} = conn, _params) do | |||
with true <- Push.enabled(), | |||
{:ok, subscription} <- Subscription.get(user, token) do | |||
view = View.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
end | |||
# Updates PushSubscription | |||
# PUT /api/v1/push/subscription | |||
# | |||
def update(%{assigns: %{user: user, token: token}} = conn, params) do | |||
with true <- Push.enabled(), | |||
{:ok, subscription} <- Subscription.update(user, token, params) do | |||
view = View.render("push_subscription.json", subscription: subscription) | |||
json(conn, view) | |||
end | |||
end | |||
# Deletes PushSubscription | |||
# DELETE /api/v1/push/subscription | |||
# | |||
def delete(%{assigns: %{user: user, token: token}} = conn, _params) do | |||
with true <- Push.enabled(), | |||
{:ok, _response} <- Subscription.delete(user, token), | |||
do: json(conn, %{}) | |||
end | |||
# fallback action | |||
# | |||
def errors(conn, {:error, :not_found}) do | |||
conn | |||
|> put_status(404) | |||
|> json("Not found") | |||
end | |||
def errors(conn, _) do | |||
conn | |||
|> put_status(500) | |||
|> json("Something went wrong") | |||
end | |||
end |
@@ -4,6 +4,7 @@ | |||
defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do | |||
use Pleroma.Web, :view | |||
alias Pleroma.Web.Push | |||
def render("push_subscription.json", %{subscription: subscription}) do | |||
%{ | |||
@@ -14,7 +15,5 @@ defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do | |||
} | |||
end | |||
defp server_key do | |||
Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key) | |||
end | |||
defp server_key, do: Keyword.get(Push.vapid_config(), :public_key) | |||
end |
@@ -102,7 +102,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
website: nil | |||
}, | |||
language: nil, | |||
emojis: [] | |||
emojis: [], | |||
pleroma: %{ | |||
local: activity.local | |||
} | |||
} | |||
end | |||
@@ -181,7 +184,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
website: nil | |||
}, | |||
language: nil, | |||
emojis: build_emojis(activity.data["object"]["emoji"]) | |||
emojis: build_emojis(activity.data["object"]["emoji"]), | |||
pleroma: %{ | |||
local: activity.local | |||
} | |||
} | |||
end | |||
@@ -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 | |||
@@ -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. | |||
@@ -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" -> | |||
[ | |||
@@ -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"], []}, | |||
@@ -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 | |||
@@ -17,14 +17,14 @@ defmodule Pleroma.Web.Metadata.Utils do | |||
|> Formatter.truncate() | |||
end | |||
def scrub_html_and_truncate(content) when is_binary(content) do | |||
def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do | |||
content | |||
# html content comes from DB already encoded, decode first and scrub after | |||
|> HtmlEntities.decode() | |||
|> String.replace(~r/<br\s?\/?>/, " ") | |||
|> HTML.strip_tags() | |||
|> Formatter.demojify() | |||
|> Formatter.truncate() | |||
|> Formatter.truncate(max_length) | |||
end | |||
def attachment_url(url) do | |||
@@ -1 +0,0 @@ | |||
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Config | |||
alias Pleroma.Repo | |||
alias Pleroma.Stats | |||
alias Pleroma.User | |||
alias Pleroma.Web | |||
@@ -86,8 +85,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do | |||
end | |||
staff_accounts = | |||
User.moderator_user_query() | |||
|> Repo.all() | |||
User.all_superusers() | |||
|> Enum.map(fn u -> u.ap_id end) | |||
mrf_user_allowlist = | |||
@@ -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 | |||
@@ -5,12 +5,12 @@ | |||
defmodule Pleroma.Web.OAuth.OAuthController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Web.Auth.Authenticator | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] | |||
@@ -7,11 +7,11 @@ 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) | |||
@@ -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 | |||
@@ -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 | |||
@@ -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), | |||
@@ -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), | |||
@@ -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(%{ | |||
@@ -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), | |||
@@ -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.WebFinger | |||
alias Pleroma.Web.Websub | |||
alias Pleroma.Web.OStatus.DeleteHandler | |||
alias Pleroma.Web.OStatus.FollowHandler | |||
alias Pleroma.Web.OStatus.UnfollowHandler | |||
alias Pleroma.Web.OStatus.NoteHandler | |||
alias Pleroma.Web.OStatus.DeleteHandler | |||
alias Pleroma.Web.OStatus.UnfollowHandler | |||
alias Pleroma.Web.WebFinger | |||
alias Pleroma.Web.Websub | |||
def is_representable?(%Activity{data: data}) do | |||
object = Object.normalize(data["object"]) | |||
@@ -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]) | |||
@@ -0,0 +1,133 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
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.Web.Metadata.Utils | |||
alias Pleroma.Web.Push.Subscription | |||
require Logger | |||
import Ecto.Query | |||
@types ["Create", "Follow", "Announce", "Like"] | |||
@doc "Performs sending notifications for user subscriptions" | |||
@spec perform_send(Notification.t()) :: list(any) | |||
def perform_send( | |||
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} = | |||
notif | |||
) | |||
when activity_type in @types do | |||
actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) | |||
type = Activity.mastodon_notification_type(notif.activity) | |||
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key) | |||
avatar_url = User.avatar_url(actor) | |||
for subscription <- fetch_subsriptions(user_id), | |||
get_in(subscription.data, ["alerts", type]) do | |||
%{ | |||
title: format_title(notif), | |||
access_token: subscription.token.token, | |||
body: format_body(notif, actor), | |||
notification_id: notif.id, | |||
notification_type: type, | |||
icon: avatar_url, | |||
preferred_locale: "en", | |||
pleroma: %{ | |||
activity_id: activity_id | |||
} | |||
} | |||
|> Jason.encode!() | |||
|> push_message(build_sub(subscription), gcm_api_key, subscription) | |||
end | |||
end | |||
def perform_send(_) do | |||
Logger.warn("Unknown notification type") | |||
:error | |||
end | |||
@doc "Push message to web" | |||
def push_message(body, sub, api_key, subscription) do | |||
case WebPushEncryption.send_web_push(body, sub, api_key) do | |||
{:ok, %{status_code: code}} when 400 <= code and code < 500 -> | |||
Logger.debug("Removing subscription record") | |||
Repo.delete!(subscription) | |||
:ok | |||
{:ok, %{status_code: code}} when 200 <= code and code < 300 -> | |||
:ok | |||
{:ok, %{status_code: code}} -> | |||
Logger.error("Web Push Notification failed with code: #{code}") | |||
:error | |||
_ -> | |||
Logger.error("Web Push Notification failed with unknown error") | |||
:error | |||
end | |||
end | |||
@doc "Gets user subscriptions" | |||
def fetch_subsriptions(user_id) do | |||
Subscription | |||
|> where(user_id: ^user_id) | |||
|> preload(:token) | |||
|> Repo.all() | |||
end | |||
def build_sub(subscription) do | |||
%{ | |||
keys: %{ | |||
p256dh: subscription.key_p256dh, | |||
auth: subscription.key_auth | |||
}, | |||
endpoint: subscription.endpoint | |||
} | |||
end | |||
def format_body( | |||
%{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, | |||
actor | |||
) do | |||
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" | |||
end | |||
def format_body( | |||
%{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, | |||
actor | |||
) do | |||
%Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id) | |||
%Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id) | |||
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" | |||
end | |||
def format_body( | |||
%{activity: %{data: %{"type" => type}}}, | |||
actor | |||
) | |||
when type in ["Follow", "Like"] do | |||
case type do | |||
"Follow" -> "@#{actor.nickname} has followed you" | |||
"Like" -> "@#{actor.nickname} has favorited your post" | |||
end | |||
end | |||
def format_title(%{activity: %{data: %{"type" => type}}}) do | |||
case type do | |||
"Create" -> "New Mention" | |||
"Follow" -> "New Follower" | |||
"Announce" -> "New Repeat" | |||
"Like" -> "New Favorite" | |||
end | |||
end | |||
end |
@@ -5,24 +5,23 @@ | |||
defmodule Pleroma.Web.Push do | |||
use GenServer | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Web.Push.Subscription | |||
alias Pleroma.Web.Push.Impl | |||
require Logger | |||
import Ecto.Query | |||
@types ["Create", "Follow", "Announce", "Like"] | |||
############## | |||
# 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 | |||
@@ -30,14 +29,18 @@ defmodule Pleroma.Web.Push do | |||
end | |||
end | |||
def send(notification) do | |||
if enabled() do | |||
GenServer.cast(Pleroma.Web.Push, {:send, notification}) | |||
end | |||
end | |||
def send(notification), | |||
do: GenServer.cast(__MODULE__, {:send, notification}) | |||
#################### | |||
# Server Callbacks # | |||
#################### | |||
@impl true | |||
def init(:ok) do | |||
if !enabled() do | |||
if enabled() do | |||
{:ok, nil} | |||
else | |||
Logger.warn(""" | |||
VAPID key pair is not found. If you wish to enabled web push, please run | |||
@@ -47,93 +50,15 @@ defmodule Pleroma.Web.Push do | |||
""") | |||
:ignore | |||
else | |||
{:ok, nil} | |||
end | |||
end | |||
def handle_cast( | |||
{:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification}, | |||
state | |||
) | |||
when type in @types do | |||
actor = User.get_cached_by_ap_id(notification.activity.data["actor"]) | |||
type = Pleroma.Activity.mastodon_notification_type(notification.activity) | |||
Subscription | |||
|> where(user_id: ^user_id) | |||
|> preload(:token) | |||
|> Repo.all() | |||
|> Enum.filter(fn subscription -> | |||
get_in(subscription.data, ["alerts", type]) || false | |||
end) | |||
|> Enum.each(fn subscription -> | |||
sub = %{ | |||
keys: %{ | |||
p256dh: subscription.key_p256dh, | |||
auth: subscription.key_auth | |||
}, | |||
endpoint: subscription.endpoint | |||
} | |||
body = | |||
Jason.encode!(%{ | |||
title: format_title(notification), | |||
access_token: subscription.token.token, | |||
body: format_body(notification, actor), | |||
notification_id: notification.id, | |||
notification_type: type, | |||
icon: User.avatar_url(actor), | |||
preferred_locale: "en" | |||
}) | |||
case WebPushEncryption.send_web_push( | |||
body, | |||
sub, | |||
Application.get_env(:web_push_encryption, :gcm_api_key) | |||
) do | |||
{:ok, %{status_code: code}} when 400 <= code and code < 500 -> | |||
Logger.debug("Removing subscription record") | |||
Repo.delete!(subscription) | |||
:ok | |||
{:ok, %{status_code: code}} when 200 <= code and code < 300 -> | |||
:ok | |||
{:ok, %{status_code: code}} -> | |||
Logger.error("Web Push Notification failed with code: #{code}") | |||
:error | |||
_ -> | |||
Logger.error("Web Push Notification failed with unknown error") | |||
:error | |||
end | |||
end) | |||
{:noreply, state} | |||
end | |||
def handle_cast({:send, _}, state) do | |||
Logger.warn("Unknown notification type") | |||
{:noreply, state} | |||
end | |||
defp format_title(%{activity: %{data: %{"type" => type}}}) do | |||
case type do | |||
"Create" -> "New Mention" | |||
"Follow" -> "New Follower" | |||
"Announce" -> "New Repeat" | |||
"Like" -> "New Favorite" | |||
@impl true | |||
def handle_cast({:send, notification}, state) do | |||
if enabled() do | |||
Impl.perform_send(notification) | |||
end | |||
end | |||
defp format_body(%{activity: %{data: %{"type" => type}}}, actor) do | |||
case type do | |||
"Create" -> "@#{actor.nickname} has mentioned you" | |||
"Follow" -> "@#{actor.nickname} has followed you" | |||
"Announce" -> "@#{actor.nickname} has repeated your post" | |||
"Like" -> "@#{actor.nickname} has favorited your post" | |||
end | |||
{:noreply, state} | |||
end | |||
end |
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.Push.Subscription do | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.Push.Subscription | |||
@type t :: %__MODULE__{} | |||
schema "push_subscriptions" do | |||
belongs_to(:user, User, type: Pleroma.FlakeId) | |||
belongs_to(:token, Token) | |||
@@ -50,30 +52,38 @@ defmodule Pleroma.Web.Push.Subscription do | |||
}) | |||
end | |||
@doc "Gets subsciption by user & token" | |||
@spec get(User.t(), Token.t()) :: {:ok, t()} | {:error, :not_found} | |||
def get(%User{id: user_id}, %Token{id: token_id}) do | |||
Repo.get_by(Subscription, user_id: user_id, token_id: token_id) | |||
case Repo.get_by(Subscription, user_id: user_id, token_id: token_id) do | |||
nil -> {:error, :not_found} | |||
subscription -> {:ok, subscription} | |||
end | |||
end | |||
def update(user, token, params) do | |||
get(user, token) | |||
|> change(data: alerts(params)) | |||
|> Repo.update() | |||
with {:ok, subscription} <- get(user, token) do | |||
subscription | |||
|> change(data: alerts(params)) | |||
|> Repo.update() | |||
end | |||
end | |||
def delete(user, token) do | |||
Repo.delete(get(user, token)) | |||
with {:ok, subscription} <- get(user, token), | |||
do: Repo.delete(subscription) | |||
end | |||
def delete_if_exists(user, token) do | |||
case get(user, token) do | |||
nil -> {:ok, nil} | |||
sub -> Repo.delete(sub) | |||
{:error, _} -> {:ok, nil} | |||
{:ok, sub} -> Repo.delete(sub) | |||
end | |||
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("+", "-") | |||
@@ -5,7 +5,6 @@ | |||
defmodule Pleroma.Web.RelMe do | |||
@hackney_options [ | |||
pool: :media, | |||
timeout: 2_000, | |||
recv_timeout: 2_000, | |||
max_body: 2_000_000 | |||
] | |||
@@ -28,7 +27,8 @@ defmodule Pleroma.Web.RelMe do | |||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) | |||
data = | |||
Floki.attribute(html, "link[rel=me]", "href") ++ Floki.attribute(html, "a[rel=me]", "href") | |||
Floki.attribute(html, "link[rel~=me]", "href") ++ | |||
Floki.attribute(html, "a[rel~=me]", "href") | |||
{:ok, data} | |||
rescue | |||
@@ -4,14 +4,28 @@ | |||
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 | |||
if AutoLinker.Parser.is_url?(page_url, true) do | |||
URI.parse(page_url) |> validate_page_url | |||
else | |||
:error | |||
end | |||
end | |||
defp validate_page_url(%URI{authority: nil}), do: :error | |||
defp validate_page_url(%URI{scheme: nil}), do: :error | |||
defp validate_page_url(%URI{}), do: :ok | |||
defp validate_page_url(_), do: :error | |||
def fetch_data_for_activity(%Activity{} = activity) do | |||
with true <- Pleroma.Config.get([:rich_media, :enabled]), | |||
%Object{} = object <- Object.normalize(activity.data["object"]), | |||
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), | |||
:ok <- validate_page_url(page_url), | |||
{:ok, rich_media} <- Parser.parse(page_url) do | |||
%{page_url: page_url, rich_media: rich_media} | |||
else | |||
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.RichMedia.Parser do | |||
@hackney_options [ | |||
pool: :media, | |||
timeout: 2_000, | |||
recv_timeout: 2_000, | |||
max_body: 2_000_000 | |||
] | |||
@@ -140,7 +140,6 @@ defmodule Pleroma.Web.Router do | |||
pipe_through([:admin_api, :oauth_write]) | |||
get("/users", AdminAPIController, :list_users) | |||
get("/users/search", AdminAPIController, :search_users) | |||
delete("/user", AdminAPIController, :user_delete) | |||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) | |||
post("/user", AdminAPIController, :user_create) | |||
@@ -304,10 +303,10 @@ defmodule Pleroma.Web.Router do | |||
scope [] do | |||
pipe_through(:oauth_push) | |||
post("/push/subscription", MastodonAPIController, :create_push_subscription) | |||
get("/push/subscription", MastodonAPIController, :get_push_subscription) | |||
put("/push/subscription", MastodonAPIController, :update_push_subscription) | |||
delete("/push/subscription", MastodonAPIController, :delete_push_subscription) | |||
post("/push/subscription", SubscriptionController, :create) | |||
get("/push/subscription", SubscriptionController, :get) | |||
put("/push/subscription", SubscriptionController, :update) | |||
delete("/push/subscription", SubscriptionController, :delete) | |||
end | |||
end | |||
@@ -632,8 +631,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 | |||
@@ -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} | |||
@@ -5,11 +5,11 @@ | |||
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.Visibility | |||
@keepalive_interval :timer.seconds(30) | |||
@@ -197,10 +197,12 @@ defmodule Pleroma.Web.Streamer do | |||
if socket.assigns[:user] do | |||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) | |||
blocks = user.info.blocks || [] | |||
mutes = user.info.mutes || [] | |||
parent = Object.normalize(item.data["object"]) | |||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do | |||
unless is_nil(parent) or item.actor in blocks or item.actor in mutes or | |||
parent.data["actor"] in blocks or parent.data["actor"] in mutes do | |||
send(socket.transport_pid, {:text, represent_update(item, user)}) | |||
end | |||
else | |||
@@ -209,23 +211,28 @@ defmodule Pleroma.Web.Streamer do | |||
end) | |||
end | |||
def push_to_socket(topics, topic, %Activity{id: id, data: %{"type" => "Delete"}}) do | |||
def push_to_socket(topics, topic, %Activity{ | |||
data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} | |||
}) do | |||
Enum.each(topics[topic] || [], fn socket -> | |||
send( | |||
socket.transport_pid, | |||
{:text, %{event: "delete", payload: to_string(id)} |> Jason.encode!()} | |||
{:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} | |||
) | |||
end) | |||
end | |||
def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop | |||
def push_to_socket(topics, topic, item) do | |||
Enum.each(topics[topic] || [], fn socket -> | |||
# Get the current user so we have up-to-date blocks etc. | |||
if socket.assigns[:user] do | |||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) | |||
blocks = user.info.blocks || [] | |||
mutes = user.info.mutes || [] | |||
unless item.actor in blocks do | |||
unless item.actor in blocks or item.actor in mutes do | |||
send(socket.transport_pid, {:text, represent_update(item, user)}) | |||
end | |||
else | |||
@@ -8,75 +8,145 @@ | |||
</title> | |||
<style> | |||
body { | |||
background-color: #282c37; | |||
background-color: #121a24; | |||
font-family: sans-serif; | |||
color:white; | |||
color: #b9b9ba; | |||
text-align: center; | |||
} | |||
.container { | |||
margin: 50px auto; | |||
max-width: 320px; | |||
padding: 0; | |||
padding: 40px 40px 40px 40px; | |||
background-color: #313543; | |||
max-width: 420px; | |||
padding: 20px; | |||
background-color: #182230; | |||
border-radius: 4px; | |||
margin: auto; | |||
margin-top: 10vh; | |||
box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5); | |||
} | |||
h1 { | |||
margin: 0; | |||
font-size: 24px; | |||
} | |||
h2 { | |||
color: #9baec8; | |||
color: #b9b9ba; | |||
font-weight: normal; | |||
font-size: 20px; | |||
margin-bottom: 40px; | |||
font-size: 18px; | |||
margin-bottom: 20px; | |||
} | |||
form { | |||
width: 100%; | |||
} | |||
.input { | |||
text-align: left; | |||
color: #89898a; | |||
display: flex; | |||
flex-direction: column; | |||
} | |||
input { | |||
box-sizing: border-box; | |||
width: 100%; | |||
box-sizing: content-box; | |||
padding: 10px; | |||
margin-top: 20px; | |||
background-color: rgba(0,0,0,.1); | |||
color: white; | |||
margin-top: 5px; | |||
margin-bottom: 10px; | |||
background-color: #121a24; | |||
color: #b9b9ba; | |||
border: 0; | |||
border-bottom: 2px solid #9baec8; | |||
transition-property: border-bottom; | |||
transition-duration: 0.35s; | |||
border-bottom: 2px solid #2a384a; | |||
font-size: 14px; | |||
} | |||
input:focus { | |||
border-bottom: 2px solid #4b8ed8; | |||
.scopes-input { | |||
display: flex; | |||
margin-top: 1em; | |||
text-align: left; | |||
color: #89898a; | |||
} | |||
.scopes-input label:first-child { | |||
flex-basis: 40%; | |||
} | |||
input[type="checkbox"] { | |||
width: auto; | |||
.scopes { | |||
display: flex; | |||
flex-wrap: wrap; | |||
text-align: left; | |||
color: #b9b9ba; | |||
} | |||
.scope { | |||
flex-basis: 100%; | |||
display: flex; | |||
height: 2em; | |||
align-items: center; | |||
} | |||
[type="checkbox"] + label { | |||
margin: 0.5em; | |||
} | |||
[type="checkbox"] { | |||
display: none; | |||
} | |||
[type="checkbox"] + label:before { | |||
display: inline-block; | |||
color: white; | |||
background-color: #121a24; | |||
border: 4px solid #121a24; | |||
box-sizing: border-box; | |||
width: 1.2em; | |||
height: 1.2em; | |||
margin-right: 1.0em; | |||
content: ""; | |||
transition-property: background-color; | |||
transition-duration: 0.35s; | |||
color: #121a24; | |||
margin-bottom: -0.2em; | |||
border-radius: 2px; | |||
} | |||
[type="checkbox"]:checked + label:before { | |||
background-color: #d8a070; | |||
} | |||
input:focus { | |||
outline: none; | |||
border-bottom: 2px solid #d8a070; | |||
} | |||
button { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: white; | |||
background-color: #419bdd; | |||
background-color: #1c2a3a; | |||
color: #b9b9ba; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 30px; | |||
text-transform: uppercase; | |||
font-weight: 500; | |||
font-size: 16px; | |||
box-shadow: 0px 0px 2px 0px black, | |||
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, | |||
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; | |||
} | |||
button:hover { | |||
cursor: pointer; | |||
box-shadow: 0px 0px 0px 1px #d8a070, | |||
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, | |||
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; | |||
} | |||
.alert-danger { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #D8000C; | |||
background-color: #FFD2D2; | |||
background-color: #931014; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
@@ -88,20 +158,32 @@ | |||
.alert-info { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #00529B; | |||
background-color: #BDE5F8; | |||
border-radius: 4px; | |||
border: none; | |||
border: 1px solid #7d796a; | |||
padding: 10px; | |||
margin-top: 20px; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
@media all and (max-width: 440px) { | |||
.container { | |||
margin-top: 0 | |||
} | |||
.scopes-input { | |||
flex-direction: column; | |||
} | |||
.scope { | |||
flex-basis: 50%; | |||
} | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="container"> | |||
<h1>Pleroma</h1> | |||
<h1><%= Application.get_env(:pleroma, :instance)[:name] %></h1> | |||
<%= render @view_module, @view_template, assigns %> | |||
</div> | |||
</body> | |||
@@ -6,23 +6,26 @@ | |||
<% end %> | |||
<h2>OAuth Authorization</h2> | |||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> | |||
<%= label f, :name, "Name or email" %> | |||
<%= text_input f, :name %> | |||
<br> | |||
<br> | |||
<%= label f, :password, "Password" %> | |||
<%= password_input f, :password %> | |||
<br> | |||
<br> | |||
<div class="input"> | |||
<%= label f, :name, "Name or email" %> | |||
<%= text_input f, :name %> | |||
</div> | |||
<div class="input"> | |||
<%= label f, :password, "Password" %> | |||
<%= password_input f, :password %> | |||
</div> | |||
<div class="scopes-input"> | |||
<%= label f, :scope, "Permissions" %> | |||
<br> | |||
<%= for scope <- @available_scopes do %> | |||
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> | |||
<%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> | |||
<%= label f, :"scope_#{scope}", String.capitalize(scope) %> | |||
<br> | |||
<% end %> | |||
<div class="scopes"> | |||
<%= for scope <- @available_scopes do %> | |||
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> | |||
<div class="scope"> | |||
<%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> | |||
<%= label f, :"scope_#{scope}", String.capitalize(scope) %> | |||
</div> | |||
<% end %> | |||
</div> | |||
</div> | |||
<%= hidden_input f, :client_id, value: @client_id %> | |||
<%= hidden_input f, :response_type, value: @response_type %> | |||
@@ -10,13 +10,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do | |||
alias Comeonin.Pbkdf2 | |||
alias Pleroma.Emoji | |||
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}), | |||