forked from AkkomaGang/akkoma
Merge branch 'develop' into feature/user-status-subscriptions
This commit is contained in:
commit
be8350baa2
73 changed files with 2771 additions and 258 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -35,3 +35,6 @@ erl_crash.dump
|
|||
|
||||
# Editor config
|
||||
/.vscode/
|
||||
|
||||
# Prevent committing docs files
|
||||
/priv/static/doc/*
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
# General application configuration
|
||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
types: Pleroma.PostgresTypes,
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter]
|
||||
|
||||
config :pleroma, Pleroma.Captcha,
|
||||
enabled: false,
|
||||
seconds_valid: 60,
|
||||
|
@ -54,7 +58,13 @@
|
|||
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
||||
files: "https://mdii.sakura.ne.jp"
|
||||
|
||||
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
|
||||
config :pleroma, :emoji,
|
||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||
groups: [
|
||||
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
|
||||
Finmoji: "/finmoji/128px/*-128.png",
|
||||
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||
]
|
||||
|
||||
config :pleroma, :uri_schemes,
|
||||
valid_schemes: [
|
||||
|
@ -87,6 +97,7 @@
|
|||
|
||||
# Configures the endpoint
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
|
||||
url: [host: "localhost"],
|
||||
http: [
|
||||
dispatch: [
|
||||
|
@ -356,7 +367,10 @@
|
|||
config :pleroma_job_queue, :queues,
|
||||
federator_incoming: 50,
|
||||
federator_outgoing: 50,
|
||||
mailer: 10
|
||||
web_push: 50,
|
||||
mailer: 10,
|
||||
transmogrifier: 20,
|
||||
scheduled_activities: 10
|
||||
|
||||
config :pleroma, :fetch_initial_posts,
|
||||
enabled: false,
|
||||
|
@ -383,8 +397,31 @@
|
|||
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
||||
uid: System.get_env("LDAP_UID") || "cn"
|
||||
|
||||
oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
|
||||
|
||||
ueberauth_providers =
|
||||
for strategy <- oauth_consumer_strategies do
|
||||
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
|
||||
strategy_module = String.to_atom(strategy_module_name)
|
||||
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
|
||||
end
|
||||
|
||||
config :ueberauth,
|
||||
Ueberauth,
|
||||
base_path: "/oauth",
|
||||
providers: ueberauth_providers
|
||||
|
||||
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
|
||||
|
||||
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
||||
|
||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||
|
||||
config :pleroma, Pleroma.ScheduledActivity,
|
||||
daily_user_limit: 25,
|
||||
total_user_limit: 300,
|
||||
enabled: true
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
|
||||
],
|
||||
protocol: "http",
|
||||
secure_cookie_flag: false,
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
check_origin: false,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
firefox, /emoji/Firefox.gif
|
||||
blank, /emoji/blank.png
|
||||
firefox, /emoji/Firefox.gif, Gif,Fun
|
||||
blank, /emoji/blank.png, Fun
|
||||
f_00b, /emoji/f_00b.png
|
||||
f_00b11b, /emoji/f_00b11b.png
|
||||
f_00b33b, /emoji/f_00b33b.png
|
||||
|
@ -28,4 +28,3 @@ f_33b00b, /emoji/f_33b00b.png
|
|||
f_33b22b, /emoji/f_33b22b.png
|
||||
f_33h, /emoji/f_33h.png
|
||||
f_33t, /emoji/f_33t.png
|
||||
|
||||
|
|
|
@ -50,6 +50,11 @@
|
|||
|
||||
config :pleroma_job_queue, disabled: true
|
||||
|
||||
config :pleroma, Pleroma.ScheduledActivity,
|
||||
daily_user_limit: 2,
|
||||
total_user_limit: 3,
|
||||
enabled: false
|
||||
|
||||
try do
|
||||
import_config "test.secret.exs"
|
||||
rescue
|
||||
|
|
|
@ -58,6 +58,26 @@ Authentication is required and the user must be an admin.
|
|||
- `password`
|
||||
- Response: User’s nickname
|
||||
|
||||
## `/api/pleroma/admin/user/follow`
|
||||
### Make a user follow another user
|
||||
|
||||
- Methods: `POST`
|
||||
- Params:
|
||||
- `follower`: The nickname of the follower
|
||||
- `followed`: The nickname of the followed
|
||||
- Response:
|
||||
- "ok"
|
||||
|
||||
## `/api/pleroma/admin/user/unfollow`
|
||||
### Make a user unfollow another user
|
||||
|
||||
- Methods: `POST`
|
||||
- Params:
|
||||
- `follower`: The nickname of the follower
|
||||
- `followed`: The nickname of the followed
|
||||
- Response:
|
||||
- "ok"
|
||||
|
||||
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
||||
|
||||
### Toggle user activation
|
||||
|
|
|
@ -20,6 +20,8 @@ Has these additional fields under the `pleroma` object:
|
|||
|
||||
- `local`: true if the post was made on the local instance.
|
||||
- `conversation_id`: the ID of the conversation the status is associated with (if any)
|
||||
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
||||
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
||||
|
||||
## Attachments
|
||||
|
||||
|
|
|
@ -10,7 +10,29 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
|||
* Authentication: not required
|
||||
* Params: none
|
||||
* Response: JSON
|
||||
* Example response: `{"kalsarikannit_f":"/finmoji/128px/kalsarikannit_f-128.png","perkele":"/finmoji/128px/perkele-128.png","blobdab":"/emoji/blobdab.png","happiness":"/finmoji/128px/happiness-128.png"}`
|
||||
* Example response:
|
||||
```json
|
||||
{
|
||||
"girlpower": {
|
||||
"tags": [
|
||||
"Finmoji"
|
||||
],
|
||||
"image_url": "/finmoji/128px/girlpower-128.png"
|
||||
},
|
||||
"education": {
|
||||
"tags": [
|
||||
"Finmoji"
|
||||
],
|
||||
"image_url": "/finmoji/128px/education-128.png"
|
||||
},
|
||||
"finnishlove": {
|
||||
"tags": [
|
||||
"Finmoji"
|
||||
],
|
||||
"image_url": "/finmoji/128px/finnishlove-128.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
* Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
|
||||
|
||||
## `/api/pleroma/follow_import`
|
||||
|
@ -164,3 +186,14 @@ See [Admin-API](Admin-API.md)
|
|||
"endorsed": false
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/notification_settings`
|
||||
### Updates user notification settings
|
||||
* Method `PUT`
|
||||
* Authentication: required
|
||||
* Params:
|
||||
* `followers`: BOOLEAN field, receives notifications from followers
|
||||
* `follows`: BOOLEAN field, receives notifications from people the user follows
|
||||
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
||||
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
||||
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
||||
|
|
22
docs/api/prometheus.md
Normal file
22
docs/api/prometheus.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Prometheus Metrics
|
||||
|
||||
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
|
||||
|
||||
## `/api/pleroma/app_metrics`
|
||||
### Exports Prometheus application metrics
|
||||
* Method: `GET`
|
||||
* Authentication: not required
|
||||
* Params: none
|
||||
* Response: JSON
|
||||
|
||||
## Grafana
|
||||
### Config example
|
||||
The following is a config example to use with [Grafana](https://grafana.com)
|
||||
|
||||
```
|
||||
- job_name: 'beam'
|
||||
metrics_path: /api/pleroma/app_metrics
|
||||
scheme: https
|
||||
static_configs:
|
||||
- targets: ['pleroma.soykaf.com']
|
||||
```
|
|
@ -314,9 +314,13 @@ curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerando
|
|||
[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
|
||||
|
||||
Pleroma has the following queues:
|
||||
|
||||
* `federator_outgoing` - Outgoing federation
|
||||
* `federator_incoming` - Incoming federation
|
||||
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
||||
* `transmogrifier` - Transmogrifier
|
||||
* `web_push` - Web push notifications
|
||||
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -390,6 +394,17 @@ config :auto_linker,
|
|||
]
|
||||
```
|
||||
|
||||
## Pleroma.ScheduledActivity
|
||||
|
||||
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
|
||||
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
||||
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
||||
|
||||
## Pleroma.Web.Auth.Authenticator
|
||||
|
||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
||||
|
||||
## :ldap
|
||||
|
||||
Use LDAP for user authentication. When a user logs in to the Pleroma
|
||||
|
@ -408,7 +423,61 @@ Pleroma account will be created with the same name as the LDAP user name.
|
|||
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
||||
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
||||
|
||||
## Pleroma.Web.Auth.Authenticator
|
||||
## :auth
|
||||
|
||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
||||
Authentication / authorization settings.
|
||||
|
||||
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
|
||||
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
|
||||
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
|
||||
|
||||
# OAuth consumer mode
|
||||
|
||||
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
||||
Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
|
||||
|
||||
Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,
|
||||
e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.
|
||||
The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
|
||||
|
||||
Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.
|
||||
|
||||
* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback
|
||||
|
||||
* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/
|
||||
|
||||
* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback
|
||||
|
||||
* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback
|
||||
|
||||
Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
|
||||
per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
|
||||
|
||||
```
|
||||
# Twitter
|
||||
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
|
||||
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
|
||||
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
|
||||
|
||||
# Facebook
|
||||
config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
|
||||
client_id: System.get_env("FACEBOOK_APP_ID"),
|
||||
client_secret: System.get_env("FACEBOOK_APP_SECRET"),
|
||||
redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
|
||||
|
||||
# Google
|
||||
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
|
||||
client_id: System.get_env("GOOGLE_CLIENT_ID"),
|
||||
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
|
||||
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
|
||||
|
||||
# Microsoft
|
||||
config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
|
||||
client_id: System.get_env("MICROSOFT_CLIENT_ID"),
|
||||
client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
|
||||
|
||||
config :ueberauth, Ueberauth,
|
||||
providers: [
|
||||
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -11,8 +11,43 @@ image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
|
|||
|
||||
content of `config/custom_emoji.txt`:
|
||||
```
|
||||
happy, /emoji/custom/happy.png
|
||||
sad, /emoji/custom/sad.png
|
||||
happy, /emoji/custom/happy.png, Tag1,Tag2
|
||||
sad, /emoji/custom/sad.png, Tag1
|
||||
foo, /emoji/custom/foo.png
|
||||
```
|
||||
|
||||
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
|
||||
|
||||
## Emoji tags (groups)
|
||||
|
||||
Default tags are set in `config.exs`.
|
||||
```elixir
|
||||
config :pleroma, :emoji,
|
||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||
groups: [
|
||||
Finmoji: "/finmoji/128px/*-128.png",
|
||||
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||
]
|
||||
```
|
||||
|
||||
Order of the `groups` matters, so to override default tags just put your group on top of the list. E.g:
|
||||
```elixir
|
||||
config :pleroma, :emoji,
|
||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||
groups: [
|
||||
"Finmoji special": "/finmoji/128px/a_trusted_friend-128.png", # special file
|
||||
"Cirno": "/emoji/custom/cirno*.png", # png files in /emoji/custom/ which start with `cirno`
|
||||
"Special group": "/emoji/custom/special_folder/*.png", # png files in /emoji/custom/special_folder/
|
||||
"Another group": "/emoji/custom/special_folder/*/.png", # png files in /emoji/custom/special_folder/ subfolders
|
||||
Finmoji: "/finmoji/128px/*-128.png",
|
||||
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||
]
|
||||
```
|
||||
|
||||
Priority of tags assigns in emoji.txt and custom.txt:
|
||||
|
||||
`tag in file > special group setting in config.exs > default setting in config.exs`
|
||||
|
||||
Priority for globs:
|
||||
|
||||
`special group setting in config.exs > default setting in config.exs`
|
||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
|
|||
field(:data, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:actor, :string)
|
||||
field(:recipients, {:array, :string})
|
||||
field(:recipients, {:array, :string}, default: [])
|
||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||
|
||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||
|
|
|
@ -25,6 +25,7 @@ def start(_type, _args) do
|
|||
import Cachex.Spec
|
||||
|
||||
Pleroma.Config.DeprecationWarnings.warn()
|
||||
setup_instrumenters()
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children =
|
||||
|
@ -103,14 +104,15 @@ def start(_type, _args) do
|
|||
],
|
||||
id: :cachex_idem
|
||||
),
|
||||
worker(Pleroma.FlakeId, [])
|
||||
worker(Pleroma.FlakeId, []),
|
||||
worker(Pleroma.ScheduledActivityWorker, [])
|
||||
] ++
|
||||
hackney_pool_children() ++
|
||||
[
|
||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||
worker(Pleroma.Stats, []),
|
||||
worker(Pleroma.Web.Push, []),
|
||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
|
||||
worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
|
||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
|
||||
] ++
|
||||
streamer_child() ++
|
||||
chat_child() ++
|
||||
|
@ -126,6 +128,24 @@ def start(_type, _args) do
|
|||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
defp setup_instrumenters do
|
||||
require Prometheus.Registry
|
||||
|
||||
:ok =
|
||||
:telemetry.attach(
|
||||
"prometheus-ecto",
|
||||
[:pleroma, :repo, :query],
|
||||
&Pleroma.Repo.Instrumenter.handle_event/4,
|
||||
%{}
|
||||
)
|
||||
|
||||
Prometheus.Registry.register_collector(:prometheus_process_collector)
|
||||
Pleroma.Web.Endpoint.MetricsExporter.setup()
|
||||
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
|
||||
Pleroma.Web.Endpoint.Instrumenter.setup()
|
||||
Pleroma.Repo.Instrumenter.setup()
|
||||
end
|
||||
|
||||
def enabled_hackney_pools do
|
||||
[:media] ++
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
||||
|
|
|
@ -57,4 +57,8 @@ def delete([parent_key | keys]) do
|
|||
def delete(key) do
|
||||
Application.delete_env(:pleroma, key)
|
||||
end
|
||||
|
||||
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
||||
|
||||
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
||||
end
|
||||
|
|
|
@ -8,13 +8,19 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
* the built-in Finmojis (if enabled in configuration),
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths
|
||||
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||
|
||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
@type pattern :: Regex.t() | module() | String.t()
|
||||
@type patterns :: pattern() | [pattern()]
|
||||
@type group_patterns :: keyword(patterns())
|
||||
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
||||
@groups Application.get_env(:pleroma, :emoji)[:groups]
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
|
@ -73,13 +79,14 @@ def code_change(_old_vsn, state, _extra) do
|
|||
end
|
||||
|
||||
defp load do
|
||||
finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
|
||||
shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
|
||||
|
||||
emojis =
|
||||
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
|
||||
(load_finmoji(finmoji_enabled) ++
|
||||
load_from_file("config/emoji.txt") ++
|
||||
load_from_file("config/custom_emoji.txt") ++
|
||||
load_from_globs(
|
||||
Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
|
||||
))
|
||||
load_from_globs(shortcode_globs))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
|
@ -151,9 +158,12 @@ defp load do
|
|||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
|
||||
defp load_finmoji(true) do
|
||||
Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
file_name = "/finmoji/128px/#{finmoji}-128.png"
|
||||
group = match_extra(@groups, file_name)
|
||||
{finmoji, file_name, to_string(group)}
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -172,8 +182,14 @@ defp load_from_file_stream(stream) do
|
|||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] -> {name, file}
|
||||
_ -> nil
|
||||
[name, file, tags] ->
|
||||
{name, file, tags}
|
||||
|
||||
[name, file] ->
|
||||
{name, file, to_string(match_extra(@groups, file))}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
|
@ -190,9 +206,40 @@ defp load_from_globs(globs) do
|
|||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
{shortcode, external_path, to_string(tag)}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a matching group for the given emoji filename
|
||||
"""
|
||||
@spec match_extra(group_patterns(), String.t()) :: atom() | nil
|
||||
def match_extra(group_patterns, filename) do
|
||||
match_group_patterns(group_patterns, fn pattern ->
|
||||
case pattern do
|
||||
%Regex{} = regex -> Regex.match?(regex, filename)
|
||||
string when is_binary(string) -> filename == string
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match_group_patterns(group_patterns, matcher) do
|
||||
Enum.find_value(group_patterns, fn {group, patterns} ->
|
||||
patterns =
|
||||
patterns
|
||||
|> List.wrap()
|
||||
|> Enum.map(fn pattern ->
|
||||
if String.contains?(pattern, "*") do
|
||||
~r(#{String.replace(pattern, "*", ".*")})
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.any?(patterns, matcher) && group
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,9 +77,9 @@ def emojify(text) do
|
|||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji, strip \\ false) do
|
||||
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
||||
emoji = HTML.strip_tags(emoji)
|
||||
file = HTML.strip_tags(file)
|
||||
Enum.reduce(emoji, text, fn emoji_data, text ->
|
||||
emoji = HTML.strip_tags(elem(emoji_data, 0))
|
||||
file = HTML.strip_tags(elem(emoji_data, 1))
|
||||
|
||||
html =
|
||||
if not strip do
|
||||
|
@ -101,7 +101,7 @@ def demojify(text) do
|
|||
def demojify(text, nil), do: text
|
||||
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
|
|
@ -28,21 +28,20 @@ def filter_tags(html, scrubber), do: Scrubber.scrub(html, scrubber)
|
|||
def filter_tags(html), do: filter_tags(html, nil)
|
||||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
||||
|
||||
# TODO: rename object to activity because that's what it is really working with
|
||||
def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do
|
||||
key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}"
|
||||
def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
|
||||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
ensure_scrubbed_html(content, scrubbers, object.data["object"]["fake"] || false)
|
||||
ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
|
||||
end)
|
||||
end
|
||||
|
||||
def get_cached_stripped_html_for_object(content, object, module) do
|
||||
get_cached_scrubbed_html_for_object(
|
||||
def get_cached_stripped_html_for_activity(content, activity, key) do
|
||||
get_cached_scrubbed_html_for_activity(
|
||||
content,
|
||||
HtmlSanitizeEx.Scrubber.StripTags,
|
||||
object,
|
||||
module
|
||||
activity,
|
||||
key
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -122,13 +122,7 @@ def create_notifications(_), do: {:ok, []}
|
|||
|
||||
# TODO move to sql, too.
|
||||
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
|
||||
CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
|
||||
(activity.data["type"] == "Follow" and
|
||||
Enum.any?(Notification.for_user(user), fn notif ->
|
||||
notif.activity.data["type"] == "Follow" and
|
||||
notif.activity.data["actor"] == activity.data["actor"]
|
||||
end)) do
|
||||
unless skip?(activity, user) do
|
||||
notification = %Notification{user_id: user.id, activity: activity}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
Pleroma.Web.Streamer.stream("user", notification)
|
||||
|
@ -155,4 +149,59 @@ def get_notified_from_activity(
|
|||
end
|
||||
|
||||
def get_notified_from_activity(_, _local_only), do: []
|
||||
|
||||
def skip?(activity, user) do
|
||||
[:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
|
||||
|> Enum.any?(&skip?(&1, activity, user))
|
||||
end
|
||||
|
||||
def skip?(:self, activity, user) do
|
||||
activity.data["actor"] == user.ap_id
|
||||
end
|
||||
|
||||
def skip?(:blocked, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
User.blocks?(user, %{ap_id: actor})
|
||||
end
|
||||
|
||||
def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
|
||||
do: true
|
||||
|
||||
def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
|
||||
do: true
|
||||
|
||||
def skip?(:muted, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
|
||||
User.mutes?(user, %{ap_id: actor}) or
|
||||
CommonAPI.thread_muted?(user, activity)
|
||||
end
|
||||
|
||||
def skip?(
|
||||
:followers,
|
||||
activity,
|
||||
%{info: %{notification_settings: %{"followers" => false}}} = user
|
||||
) do
|
||||
actor = activity.data["actor"]
|
||||
follower = User.get_cached_by_ap_id(actor)
|
||||
User.following?(follower, user)
|
||||
end
|
||||
|
||||
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
||||
actor = activity.data["actor"]
|
||||
followed = User.get_by_ap_id(actor)
|
||||
User.following?(user, followed)
|
||||
end
|
||||
|
||||
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
|
||||
Notification.for_user(user)
|
||||
|> Enum.any?(fn
|
||||
%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
def skip?(_, _, _), do: false
|
||||
end
|
||||
|
|
57
lib/pleroma/registration.ex
Normal file
57
lib/pleroma/registration.ex
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Registration do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
|
||||
schema "registrations" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
field(:provider, :string)
|
||||
field(:uid, :string)
|
||||
field(:info, :map, default: %{})
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def nickname(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "nickname", default)
|
||||
|
||||
def email(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "email", default)
|
||||
|
||||
def name(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "name", default)
|
||||
|
||||
def description(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "description", default)
|
||||
|
||||
def changeset(registration, params \\ %{}) do
|
||||
registration
|
||||
|> cast(params, [:user_id, :provider, :uid, :info])
|
||||
|> validate_required([:provider, :uid])
|
||||
|> foreign_key_constraint(:user_id)
|
||||
|> unique_constraint(:uid, name: :registrations_provider_uid_index)
|
||||
end
|
||||
|
||||
def bind_to_user(registration, user) do
|
||||
registration
|
||||
|> changeset(%{user_id: (user && user.id) || nil})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def get_by_provider_uid(provider, uid) do
|
||||
Repo.get_by(Registration,
|
||||
provider: to_string(provider),
|
||||
uid: to_string(uid)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -8,6 +8,10 @@ defmodule Pleroma.Repo do
|
|||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.EctoInstrumenter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dynamically loads the repository url from the
|
||||
DATABASE_URL environment variable.
|
||||
|
|
161
lib/pleroma/scheduled_activity.ex
Normal file
161
lib/pleroma/scheduled_activity.ex
Normal file
|
@ -0,0 +1,161 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivity do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
@min_offset :timer.minutes(5)
|
||||
|
||||
schema "scheduled_activities" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
field(:scheduled_at, :naive_datetime)
|
||||
field(:params, :map)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> cast(attrs, [:scheduled_at, :params])
|
||||
|> validate_required([:scheduled_at, :params])
|
||||
|> validate_scheduled_at()
|
||||
|> with_media_attachments()
|
||||
end
|
||||
|
||||
defp with_media_attachments(
|
||||
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
|
||||
)
|
||||
when is_list(media_ids) do
|
||||
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.put("media_attachments", media_attachments)
|
||||
|> Map.put("media_ids", media_ids)
|
||||
|
||||
put_change(changeset, :params, params)
|
||||
end
|
||||
|
||||
defp with_media_attachments(changeset), do: changeset
|
||||
|
||||
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> cast(attrs, [:scheduled_at])
|
||||
|> validate_required([:scheduled_at])
|
||||
|> validate_scheduled_at()
|
||||
end
|
||||
|
||||
def validate_scheduled_at(changeset) do
|
||||
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||
cond do
|
||||
not far_enough?(scheduled_at) ->
|
||||
[scheduled_at: "must be at least 5 minutes from now"]
|
||||
|
||||
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
|
||||
[scheduled_at: "daily limit exceeded"]
|
||||
|
||||
exceeds_total_user_limit?(changeset.data.user_id) ->
|
||||
[scheduled_at: "total limit exceeded"]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def exceeds_daily_user_limit?(user_id, scheduled_at) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user_id)
|
||||
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|
||||
|> select([sa], count(sa.id))
|
||||
|> Repo.one()
|
||||
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
|
||||
end
|
||||
|
||||
def exceeds_total_user_limit?(user_id) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user_id)
|
||||
|> select([sa], count(sa.id))
|
||||
|> Repo.one()
|
||||
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
|
||||
end
|
||||
|
||||
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
|
||||
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
|
||||
far_enough?(scheduled_at)
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def far_enough?(scheduled_at) do
|
||||
now = NaiveDateTime.utc_now()
|
||||
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
|
||||
diff > @min_offset
|
||||
end
|
||||
|
||||
def new(%User{} = user, attrs) do
|
||||
%ScheduledActivity{user_id: user.id}
|
||||
|> changeset(attrs)
|
||||
end
|
||||
|
||||
def create(%User{} = user, attrs) do
|
||||
user
|
||||
|> new(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def get(%User{} = user, scheduled_activity_id) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(id: ^scheduled_activity_id)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> update_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def delete(%ScheduledActivity{} = scheduled_activity) do
|
||||
scheduled_activity
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
def delete(id) when is_binary(id) or is_integer(id) do
|
||||
ScheduledActivity
|
||||
|> where(id: ^id)
|
||||
|> select([sa], sa)
|
||||
|> Repo.delete_all()
|
||||
|> case do
|
||||
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def for_user_query(%User{} = user) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user.id)
|
||||
end
|
||||
|
||||
def due_activities(offset \\ 0) do
|
||||
naive_datetime =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(offset, :millisecond)
|
||||
|
||||
ScheduledActivity
|
||||
|> where([sa], sa.scheduled_at < ^naive_datetime)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivityWorker do
|
||||
@moduledoc """
|
||||
Sends scheduled activities to the job queue.
|
||||
"""
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
@schedule_interval :timer.minutes(1)
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, nil)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
if Config.get([ScheduledActivity, :enabled]) do
|
||||
schedule_next()
|
||||
{:ok, nil}
|
||||
else
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:execute, scheduled_activity_id) do
|
||||
try do
|
||||
{:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
|
||||
%User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
|
||||
{:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
|
||||
rescue
|
||||
error ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:perform, state) do
|
||||
ScheduledActivity.due_activities(@schedule_interval)
|
||||
|> Enum.each(fn scheduled_activity ->
|
||||
PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
|
||||
end)
|
||||
|
||||
schedule_next()
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp schedule_next do
|
||||
Process.send_after(self(), :perform, @schedule_interval)
|
||||
end
|
||||
end
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Formatter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
|
@ -55,6 +56,7 @@ defmodule Pleroma.User do
|
|||
field(:bookmarks, {:array, :string}, default: [])
|
||||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
embeds_one(:info, Pleroma.User.Info)
|
||||
|
||||
timestamps()
|
||||
|
@ -216,7 +218,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
changeset =
|
||||
struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|
@ -227,6 +229,13 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
|> validate_length(:name, min: 1, max: 100)
|
||||
|> put_change(:info, info_change)
|
||||
|
||||
changeset =
|
||||
if opts[:external] do
|
||||
changeset
|
||||
else
|
||||
validate_required(changeset, [:email])
|
||||
end
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
|
@ -505,11 +514,10 @@ def get_by_nickname(nickname) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_by_email(email), do: Repo.get_by(User, email: email)
|
||||
|
||||
def get_by_nickname_or_email(nickname_or_email) do
|
||||
case user = Repo.get_by(User, nickname: nickname_or_email) do
|
||||
%User{} -> user
|
||||
nil -> Repo.get_by(User, email: nickname_or_email)
|
||||
end
|
||||
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
|
||||
end
|
||||
|
||||
def get_cached_user_info(user) do
|
||||
|
@ -977,6 +985,8 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
|
|||
unfollow(blocked, blocker)
|
||||
end
|
||||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
|
||||
info_cng =
|
||||
blocker.info
|
||||
|> User.Info.add_to_block(ap_id)
|
||||
|
@ -1131,6 +1141,14 @@ def deactivate(%User{} = user, status \\ true) do
|
|||
update_and_set_cache(cng)
|
||||
end
|
||||
|
||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||
info_changeset = User.Info.update_notification_settings(user.info, settings)
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def delete(%User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ defmodule Pleroma.User.Info do
|
|||
field(:pinned_activities, {:array, :string}, default: [])
|
||||
field(:flavour, :string, default: nil)
|
||||
|
||||
field(:notification_settings, :map,
|
||||
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
|
||||
)
|
||||
|
||||
# Found in the wild
|
||||
# ap_id -> Where is this used?
|
||||
# bio -> Where is this used?
|
||||
|
@ -58,6 +62,19 @@ def set_activation_status(info, deactivated) do
|
|||
|> validate_required([:deactivated])
|
||||
end
|
||||
|
||||
def update_notification_settings(info, settings) do
|
||||
notification_settings =
|
||||
info.notification_settings
|
||||
|> Map.merge(settings)
|
||||
|> Map.take(["remote", "local", "followers", "follows"])
|
||||
|
||||
params = %{notification_settings: notification_settings}
|
||||
|
||||
info
|
||||
|> cast(params, [:notification_settings])
|
||||
|> validate_required([:notification_settings])
|
||||
end
|
||||
|
||||
def add_to_note_count(info, number) do
|
||||
set_note_count(info, info.note_count + number)
|
||||
end
|
||||
|
|
|
@ -83,6 +83,22 @@ def fix_object(object) do
|
|||
|> fix_content_map
|
||||
|> fix_likes
|
||||
|> fix_addressing
|
||||
|> fix_summary
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => nil} = object) do
|
||||
object
|
||||
|> Map.put("summary", "")
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => _} = object) do
|
||||
# summary is present, nothing to do
|
||||
object
|
||||
end
|
||||
|
||||
def fix_summary(object) do
|
||||
object
|
||||
|> Map.put("summary", "")
|
||||
end
|
||||
|
||||
def fix_addressing_list(map, field) do
|
||||
|
@ -954,7 +970,7 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
|
|||
|
||||
defp strip_internal_tags(object), do: object
|
||||
|
||||
defp user_upgrade_task(user) do
|
||||
def perform(:user_upgrade, user) do
|
||||
# we pass a fake user so that the followers collection is stripped away
|
||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||
|
||||
|
@ -999,28 +1015,18 @@ defp user_upgrade_task(user) do
|
|||
Repo.update_all(q, [])
|
||||
end
|
||||
|
||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
||||
def upgrade_user_from_ap_id(ap_id) do
|
||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
already_ap = User.ap_enabled?(user)
|
||||
|
||||
{:ok, user} =
|
||||
User.upgrade_changeset(user, data)
|
||||
|> Repo.update()
|
||||
|
||||
if !already_ap do
|
||||
# This could potentially take a long time, do it in the background
|
||||
if async do
|
||||
Task.start(fn ->
|
||||
user_upgrade_task(user)
|
||||
end)
|
||||
else
|
||||
user_upgrade_task(user)
|
||||
end
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||
already_ap <- User.ap_enabled?(user),
|
||||
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
|
||||
unless already_ap do
|
||||
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
||||
end
|
||||
|
||||
{:ok, user}
|
||||
else
|
||||
%User{} = user -> {:ok, user}
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
|
|
@ -99,7 +99,10 @@ def make_json_ld_header do
|
|||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld"
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld",
|
||||
%{
|
||||
"@language" => "und"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
|
|
@ -25,6 +25,26 @@ def user_delete(conn, %{"nickname" => nickname}) do
|
|||
|> json(nickname)
|
||||
end
|
||||
|
||||
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||
User.follow(follower, followed)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
end
|
||||
|
||||
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||
User.unfollow(follower, followed)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
end
|
||||
|
||||
def user_create(
|
||||
conn,
|
||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.Authenticator do
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.User
|
||||
|
||||
def implementation do
|
||||
|
@ -12,14 +13,33 @@ def implementation do
|
|||
)
|
||||
end
|
||||
|
||||
@callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
def get_user(plug), do: implementation().get_user(plug)
|
||||
@callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
def get_user(plug, params), do: implementation().get_user(plug, params)
|
||||
|
||||
@callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
|
||||
{:ok, User.t()} | {:error, any()}
|
||||
def create_from_registration(plug, params, registration),
|
||||
do: implementation().create_from_registration(plug, params, registration)
|
||||
|
||||
@callback get_registration(Plug.Conn.t(), Map.t()) ::
|
||||
{:ok, Registration.t()} | {:error, any()}
|
||||
def get_registration(plug, params),
|
||||
do: implementation().get_registration(plug, params)
|
||||
|
||||
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
||||
def handle_error(plug, error), do: implementation().handle_error(plug, error)
|
||||
|
||||
@callback auth_template() :: String.t() | nil
|
||||
def auth_template do
|
||||
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
|
||||
# Note: `config :pleroma, :auth_template, "..."` support is deprecated
|
||||
implementation().auth_template() ||
|
||||
Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
|
||||
"show.html"
|
||||
end
|
||||
|
||||
@callback oauth_consumer_template() :: String.t() | nil
|
||||
def oauth_consumer_template do
|
||||
implementation().oauth_consumer_template() ||
|
||||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,14 +8,19 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
require Logger
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||
|
||||
@connection_timeout 10_000
|
||||
@search_timeout 10_000
|
||||
|
||||
def get_user(%Plug.Conn{} = conn) do
|
||||
defdelegate get_registration(conn, params), to: @base
|
||||
|
||||
defdelegate create_from_registration(conn, params, registration), to: @base
|
||||
|
||||
def get_user(%Plug.Conn{} = conn, params) do
|
||||
if Pleroma.Config.get([:ldap, :enabled]) do
|
||||
{name, password} =
|
||||
case conn.params do
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{name, password}
|
||||
|
||||
|
@ -29,14 +34,14 @@ def get_user(%Plug.Conn{} = conn) do
|
|||
|
||||
{:error, {:ldap_connection_error, _}} ->
|
||||
# When LDAP is unavailable, try default authenticator
|
||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
|
||||
@base.get_user(conn, params)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
else
|
||||
# Fall back to default authenticator
|
||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
|
||||
@base.get_user(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,6 +51,8 @@ def handle_error(%Plug.Conn{} = _conn, error) do
|
|||
|
||||
def auth_template, do: nil
|
||||
|
||||
def oauth_consumer_template, do: nil
|
||||
|
||||
defp ldap_user(name, password) do
|
||||
ldap = Pleroma.Config.get(:ldap, [])
|
||||
host = Keyword.get(ldap, :host, "localhost")
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
|
||||
def get_user(%Plug.Conn{} = conn) do
|
||||
def get_user(%Plug.Conn{} = _conn, params) do
|
||||
{name, password} =
|
||||
case conn.params do
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{name, password}
|
||||
|
||||
|
@ -27,9 +29,69 @@ def get_user(%Plug.Conn{} = conn) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_registration(
|
||||
%Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
|
||||
_params
|
||||
) do
|
||||
registration = Registration.get_by_provider_uid(provider, uid)
|
||||
|
||||
if registration do
|
||||
{:ok, registration}
|
||||
else
|
||||
info = auth.info
|
||||
|
||||
Registration.changeset(%Registration{}, %{
|
||||
provider: to_string(provider),
|
||||
uid: to_string(uid),
|
||||
info: %{
|
||||
"nickname" => info.nickname,
|
||||
"email" => info.email,
|
||||
"name" => info.name,
|
||||
"description" => info.description
|
||||
}
|
||||
})
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
|
||||
def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
|
||||
|
||||
def create_from_registration(_conn, params, registration) do
|
||||
nickname = value([params["nickname"], Registration.nickname(registration)])
|
||||
email = value([params["email"], Registration.email(registration)])
|
||||
name = value([params["name"], Registration.name(registration)]) || nickname
|
||||
bio = value([params["bio"], Registration.description(registration)])
|
||||
|
||||
random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
|
||||
|
||||
with {:ok, new_user} <-
|
||||
User.register_changeset(
|
||||
%User{},
|
||||
%{
|
||||
email: email,
|
||||
nickname: nickname,
|
||||
name: name,
|
||||
bio: bio,
|
||||
password: random_password,
|
||||
password_confirmation: random_password
|
||||
},
|
||||
external: true,
|
||||
confirmed: true
|
||||
)
|
||||
|> Repo.insert(),
|
||||
{:ok, _} <-
|
||||
Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
|
||||
{:ok, new_user}
|
||||
end
|
||||
end
|
||||
|
||||
defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
|
||||
|
||||
def handle_error(%Plug.Conn{} = _conn, error) do
|
||||
error
|
||||
end
|
||||
|
||||
def auth_template, do: nil
|
||||
|
||||
def oauth_consumer_template, do: nil
|
||||
end
|
||||
|
|
|
@ -167,7 +167,7 @@ def post(user, %{"status" => status} = data) do
|
|||
object,
|
||||
"emoji",
|
||||
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
|
||||
|> Enum.reduce(%{}, fn {name, file}, acc ->
|
||||
|> Enum.reduce(%{}, fn {name, file, _}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
) do
|
||||
|
|
|
@ -294,7 +294,7 @@ def confirm_current_password(user, password) do
|
|||
|
||||
def emoji_from_profile(%{info: _info} = user) do
|
||||
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
|
||||
|> Enum.map(fn {shortcode, url} ->
|
||||
|> Enum.map(fn {shortcode, url, _} ->
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
||||
|
|
|
@ -51,11 +51,22 @@ defmodule Pleroma.Web.Endpoint do
|
|||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
||||
secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
|
||||
|
||||
cookie_name =
|
||||
if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
if secure_cookies,
|
||||
do: "__Host-pleroma_key",
|
||||
else: "pleroma_key"
|
||||
|
||||
same_site =
|
||||
if Pleroma.Config.oauth_consumer_enabled?() do
|
||||
# Note: "SameSite=Strict" prevents sign in with external OAuth provider
|
||||
# (there would be no cookies during callback request from OAuth provider)
|
||||
"SameSite=Lax"
|
||||
else
|
||||
"SameSite=Strict"
|
||||
end
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
|
@ -65,11 +76,30 @@ defmodule Pleroma.Web.Endpoint do
|
|||
key: cookie_name,
|
||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||
http_only: true,
|
||||
secure:
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
extra: "SameSite=Strict"
|
||||
secure: secure_cookies,
|
||||
extra: same_site
|
||||
)
|
||||
|
||||
# Note: the plug and its configuration is compile-time this can't be upstreamed yet
|
||||
if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
|
||||
plug(RemoteIp, proxies: proxies)
|
||||
end
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.PhoenixInstrumenter
|
||||
end
|
||||
|
||||
defmodule PipelineInstrumenter do
|
||||
use Prometheus.PlugPipelineInstrumenter
|
||||
end
|
||||
|
||||
defmodule MetricsExporter do
|
||||
use Prometheus.PlugExporter
|
||||
end
|
||||
|
||||
plug(PipelineInstrumenter)
|
||||
plug(MetricsExporter)
|
||||
|
||||
plug(Pleroma.Web.Router)
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
|
||||
def get_followers(user, params \\ %{}) do
|
||||
|
@ -28,6 +29,12 @@ def get_notifications(user, params \\ %{}) do
|
|||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
def get_scheduled_activities(user, params \\ %{}) do
|
||||
user
|
||||
|> ScheduledActivity.for_user_query()
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
defp cast_params(params) do
|
||||
param_types = %{
|
||||
exclude_types: {:array, :string}
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
|
@ -25,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
alias Pleroma.Web.MastodonAPI.ReportView
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
@ -178,14 +181,15 @@ def peers(conn, _params) do
|
|||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Emoji.get_all()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
|> Enum.map(fn {shortcode, relative_url, tags} ->
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
%{
|
||||
"shortcode" => shortcode,
|
||||
"static_url" => url,
|
||||
"visible_in_picker" => true,
|
||||
"url" => url
|
||||
"url" => url,
|
||||
"tags" => String.split(tags, ",")
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
@ -364,6 +368,55 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||
conn
|
||||
|> add_link_headers(:scheduled_statuses, scheduled_activities)
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("index.json", %{scheduled_activities: scheduled_activities})
|
||||
end
|
||||
end
|
||||
|
||||
def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def update_scheduled_status(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{"id" => scheduled_activity_id} = params
|
||||
) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id),
|
||||
{:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id),
|
||||
{:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||
when length(media_ids) > 0 do
|
||||
params =
|
||||
|
@ -384,13 +437,28 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
|||
_ -> Ecto.UUID.generate()
|
||||
end
|
||||
|
||||
scheduled_at = params["scheduled_at"]
|
||||
|
||||
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
||||
with {:ok, scheduled_activity} <-
|
||||
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
end
|
||||
else
|
||||
params = Map.drop(params, ["scheduled_at"])
|
||||
|
||||
{:ok, activity} =
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
|
||||
CommonAPI.post(user, params)
|
||||
end)
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
|
@ -1119,9 +1187,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
|||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
token =
|
||||
conn
|
||||
|> get_session(:oauth_token)
|
||||
token = get_session(conn, :oauth_token)
|
||||
|
||||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
|
@ -1222,6 +1288,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
|||
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
||||
else
|
||||
conn
|
||||
|> put_session(:return_to, conn.request_path)
|
||||
|> redirect(to: "/web/login")
|
||||
end
|
||||
end
|
||||
|
@ -1306,12 +1373,20 @@ def login(conn, _) do
|
|||
scope: Enum.join(app.scopes, " ")
|
||||
)
|
||||
|
||||
conn
|
||||
|> redirect(to: path)
|
||||
redirect(conn, to: path)
|
||||
end
|
||||
end
|
||||
|
||||
defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"])
|
||||
defp local_mastodon_root_path(conn) do
|
||||
case get_session(conn, :return_to) do
|
||||
nil ->
|
||||
mastodon_api_path(conn, :index, ["getting-started"])
|
||||
|
||||
return_to ->
|
||||
delete_session(conn, :return_to)
|
||||
return_to
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_make_app do
|
||||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
||||
|
@ -1427,6 +1502,23 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
|||
|
||||
# fallback action
|
||||
#
|
||||
def errors(conn, {:error, %Changeset{} = changeset}) do
|
||||
error_message =
|
||||
changeset
|
||||
|> Changeset.traverse_errors(fn {message, _opt} -> message end)
|
||||
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
||||
|
||||
conn
|
||||
|> put_status(422)
|
||||
|> json(%{error: error_message})
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Record not found"})
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|
|
|
@ -118,13 +118,15 @@ defp do_render("account.json", %{user: user} = opts) do
|
|||
},
|
||||
|
||||
# Pleroma extension
|
||||
pleroma: %{
|
||||
pleroma:
|
||||
%{
|
||||
confirmation_pending: user_info.confirmation_pending,
|
||||
tags: user.tags,
|
||||
is_moderator: user.info.is_moderator,
|
||||
is_admin: user.info.is_admin,
|
||||
relationship: relationship
|
||||
}
|
||||
|> with_notification_settings(user, opts[:for])
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -133,4 +135,10 @@ defp username_from_nickname(string) when is_binary(string) do
|
|||
end
|
||||
|
||||
defp username_from_nickname(_), do: nil
|
||||
|
||||
defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
Map.put(data, :notification_settings, user.info.notification_settings)
|
||||
end
|
||||
|
||||
defp with_notification_settings(data, _, _), do: data
|
||||
end
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# 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.ScheduledActivityView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
||||
render_many(scheduled_activities, ScheduledActivityView, "show.json")
|
||||
end
|
||||
|
||||
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||
%{
|
||||
id: to_string(scheduled_activity.id),
|
||||
scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
|
||||
params: status_params(scheduled_activity.params)
|
||||
}
|
||||
|> with_media_attachments(scheduled_activity)
|
||||
end
|
||||
|
||||
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
||||
try do
|
||||
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
||||
Map.put(data, :media_attachments, attachments)
|
||||
rescue
|
||||
_ -> data
|
||||
end
|
||||
end
|
||||
|
||||
defp with_media_attachments(data, _), do: data
|
||||
|
||||
defp status_params(params) do
|
||||
data = %{
|
||||
text: params["status"],
|
||||
sensitive: params["sensitive"],
|
||||
spoiler_text: params["spoiler_text"],
|
||||
visibility: params["visibility"],
|
||||
scheduled_at: params["scheduled_at"],
|
||||
poll: params["poll"],
|
||||
in_reply_to_id: params["in_reply_to_id"]
|
||||
}
|
||||
|
||||
data =
|
||||
if media_ids = params["media_ids"] do
|
||||
Map.put(data, :media_ids, media_ids)
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
end
|
|
@ -147,10 +147,37 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
content =
|
||||
object
|
||||
|> render_content()
|
||||
|> HTML.get_cached_scrubbed_html_for_object(
|
||||
|
||||
content_html =
|
||||
content
|
||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||
User.html_filter_policy(opts[:for]),
|
||||
activity,
|
||||
__MODULE__
|
||||
"mastoapi:content"
|
||||
)
|
||||
|
||||
content_plaintext =
|
||||
content
|
||||
|> HTML.get_cached_stripped_html_for_activity(
|
||||
activity,
|
||||
"mastoapi:content"
|
||||
)
|
||||
|
||||
summary = object["summary"] || ""
|
||||
|
||||
summary_html =
|
||||
summary
|
||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||
User.html_filter_policy(opts[:for]),
|
||||
activity,
|
||||
"mastoapi:summary"
|
||||
)
|
||||
|
||||
summary_plaintext =
|
||||
summary
|
||||
|> HTML.get_cached_stripped_html_for_activity(
|
||||
activity,
|
||||
"mastoapi:summary"
|
||||
)
|
||||
|
||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||
|
@ -171,7 +198,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
reblog: nil,
|
||||
card: card,
|
||||
content: content,
|
||||
content: content_html,
|
||||
created_at: created_at,
|
||||
reblogs_count: announcement_count,
|
||||
replies_count: object["repliesCount"] || 0,
|
||||
|
@ -182,7 +209,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
||||
pinned: pinned?(activity, user),
|
||||
sensitive: sensitive,
|
||||
spoiler_text: object["summary"] || "",
|
||||
spoiler_text: summary_html,
|
||||
visibility: get_visibility(object),
|
||||
media_attachments: attachments,
|
||||
mentions: mentions,
|
||||
|
@ -195,7 +222,9 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
emojis: build_emojis(activity.data["object"]["emoji"]),
|
||||
pleroma: %{
|
||||
local: activity.local,
|
||||
conversation_id: get_context_id(activity)
|
||||
conversation_id: get_context_id(activity),
|
||||
content: %{"text/plain" => content_plaintext},
|
||||
spoiler_text: %{"text/plain" => summary_plaintext}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|
|||
# html content comes from DB already encoded, decode first and scrub after
|
||||
|> HtmlEntities.decode()
|
||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|
||||
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|
||||
|> Formatter.demojify()
|
||||
|> Formatter.truncate()
|
||||
end
|
||||
|
|
|
@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
|
|||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
# No user/password
|
||||
def call(conn, _) do
|
||||
def call(conn, {:register, :generic_error}) do
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> put_flash(:error, "Unknown error, please check the details and try again.")
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, {:register, _error}) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, _error) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Auth.Authenticator
|
||||
|
@ -15,6 +16,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
||||
|
||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
|
@ -57,31 +60,27 @@ defp do_authorize(conn, params) do
|
|||
})
|
||||
end
|
||||
|
||||
def create_authorization(conn, %{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_params
|
||||
}) do
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
scopes <- oauth_scopes(auth_params, []),
|
||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||
# Note: `scope` param is intentionally not optional in this context
|
||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
||||
def create_authorization(
|
||||
conn,
|
||||
%{"authorization" => auth_params} = params,
|
||||
opts \\ []
|
||||
) do
|
||||
with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
|
||||
after_create_authorization(conn, auth, auth_params)
|
||||
else
|
||||
error ->
|
||||
handle_create_authorization_error(conn, error, auth_params)
|
||||
end
|
||||
end
|
||||
|
||||
def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
|
||||
cond do
|
||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
|
||||
true ->
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
@ -97,26 +96,29 @@ def create_authorization(conn, %{
|
|||
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
else
|
||||
{scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
|
||||
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||
conn
|
||||
|> put_flash(:error, "This action is outside the authorized scopes")
|
||||
|> put_status(:unauthorized)
|
||||
|> authorize(auth_params)
|
||||
end
|
||||
|
||||
{:auth_active, false} ->
|
||||
defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||
conn
|
||||
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(auth_params)
|
||||
|
||||
error ->
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, error, _auth_params) do
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
|
@ -149,9 +151,10 @@ def token_exchange(
|
|||
conn,
|
||||
%{"grant_type" => "password"} = params
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
|
||||
%App{} = app <- get_app_from_request(conn, params),
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||
scopes <- oauth_scopes(params, app.scopes),
|
||||
[] <- scopes -- app.scopes,
|
||||
true <- Enum.any?(scopes),
|
||||
|
@ -175,6 +178,11 @@ def token_exchange(
|
|||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
||||
|
||||
{:user_active, false} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Your account is currently disabled"})
|
||||
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|> json(%{error: "Invalid credentials"})
|
||||
|
@ -205,6 +213,184 @@ def token_revoke(conn, %{"token" => token} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(conn, %{"provider" => provider} = params) do
|
||||
scope =
|
||||
oauth_scopes(params, [])
|
||||
|> Enum.join(" ")
|
||||
|
||||
state =
|
||||
params
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Poison.encode!()
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.drop(~w(scope scopes client_id redirect_uri))
|
||||
|> Map.put("state", state)
|
||||
|
||||
# Handing the request to Ueberauth
|
||||
redirect(conn, to: o_auth_path(conn, :request, provider, params))
|
||||
end
|
||||
|
||||
def request(conn, params) do
|
||||
message =
|
||||
if params["provider"] do
|
||||
"Unsupported OAuth provider: #{params["provider"]}."
|
||||
else
|
||||
"Bad OAuth request."
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:error, message)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
params = callback_params(params)
|
||||
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||
message = Enum.join(messages, "; ")
|
||||
|
||||
conn
|
||||
|> put_flash(:error, "Failed to authenticate: #{message}.")
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
|
||||
def callback(conn, params) do
|
||||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn, params) do
|
||||
user = Repo.preload(registration, :user).user
|
||||
auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||
|
||||
if user do
|
||||
create_authorization(
|
||||
conn,
|
||||
%{"authorization" => auth_params},
|
||||
user: user
|
||||
)
|
||||
else
|
||||
registration_params =
|
||||
Map.merge(auth_params, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> registration_details(registration_params)
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_flash(:error, "Failed to set up user account.")
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Poison.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(conn, params) do
|
||||
render(conn, "register.html", %{
|
||||
client_id: params["client_id"],
|
||||
redirect_uri: params["redirect_uri"],
|
||||
state: params["state"],
|
||||
scopes: oauth_scopes(params, []),
|
||||
nickname: params["nickname"],
|
||||
email: params["email"]
|
||||
})
|
||||
end
|
||||
|
||||
def register(conn, %{"op" => "connect"} = params) do
|
||||
authorization_params = Map.put(params, "name", params["auth_name"])
|
||||
create_authorization_params = %{"authorization" => authorization_params}
|
||||
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth}} <-
|
||||
{:create_authorization, do_create_authorization(conn, create_authorization_params)},
|
||||
%User{} = user <- Repo.preload(auth, :user).user,
|
||||
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> after_create_authorization(auth, authorization_params)
|
||||
else
|
||||
{:create_authorization, error} ->
|
||||
{:register, handle_create_authorization_error(conn, error, create_authorization_params)}
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
def register(conn, %{"op" => "register"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> create_authorization(
|
||||
%{
|
||||
"authorization" => %{
|
||||
"client_id" => params["client_id"],
|
||||
"redirect_uri" => params["redirect_uri"],
|
||||
"scopes" => oauth_scopes(params, nil)
|
||||
}
|
||||
},
|
||||
user: user
|
||||
)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
message =
|
||||
Enum.map(changeset.errors, fn {field, {error, _}} ->
|
||||
"#{field} #{error}"
|
||||
end)
|
||||
|> Enum.join("; ")
|
||||
|
||||
message =
|
||||
String.replace(
|
||||
message,
|
||||
"ap_id has already been taken",
|
||||
"nickname has already been taken"
|
||||
)
|
||||
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> put_flash(:error, "Error: #{message}.")
|
||||
|> registration_details(params)
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_create_authorization(
|
||||
conn,
|
||||
%{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_params
|
||||
} = params,
|
||||
user \\ nil
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <-
|
||||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
scopes <- oauth_scopes(auth_params, []),
|
||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||
# Note: `scope` param is intentionally not optional in this context
|
||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
||||
Authorization.create_authorization(app, user, scopes)
|
||||
end
|
||||
end
|
||||
|
||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||
# decoding it. Investigate sometime.
|
||||
defp fix_padding(token) do
|
||||
|
@ -242,4 +428,9 @@ defp get_app_from_request(conn, params) do
|
|||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||
|
||||
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
|
||||
|
||||
defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||
|
||||
defp put_session_registration_id(conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
end
|
||||
|
|
|
@ -19,8 +19,8 @@ defmodule Pleroma.Web.Push.Impl do
|
|||
@types ["Create", "Follow", "Announce", "Like"]
|
||||
|
||||
@doc "Performs sending notifications for user subscriptions"
|
||||
@spec perform_send(Notification.t()) :: list(any)
|
||||
def perform_send(
|
||||
@spec perform(Notification.t()) :: list(any) | :error
|
||||
def perform(
|
||||
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
|
||||
notif
|
||||
)
|
||||
|
@ -50,7 +50,7 @@ def perform_send(
|
|||
end
|
||||
end
|
||||
|
||||
def perform_send(_) do
|
||||
def perform(_) do
|
||||
Logger.warn("Unknown notification type")
|
||||
:error
|
||||
end
|
||||
|
|
|
@ -3,18 +3,20 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Push do
|
||||
use GenServer
|
||||
|
||||
alias Pleroma.Web.Push.Impl
|
||||
|
||||
require Logger
|
||||
|
||||
##############
|
||||
# Client API #
|
||||
##############
|
||||
def init do
|
||||
unless enabled() do
|
||||
Logger.warn("""
|
||||
VAPID key pair is not found. If you wish to enabled web push, please run
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||
mix web_push.gen.keypair
|
||||
|
||||
and add the resulting output to your configuration file.
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
def vapid_config do
|
||||
|
@ -30,35 +32,5 @@ def enabled do
|
|||
end
|
||||
|
||||
def send(notification),
|
||||
do: GenServer.cast(__MODULE__, {:send, notification})
|
||||
|
||||
####################
|
||||
# Server Callbacks #
|
||||
####################
|
||||
|
||||
@impl true
|
||||
def init(:ok) do
|
||||
if enabled() do
|
||||
{:ok, nil}
|
||||
else
|
||||
Logger.warn("""
|
||||
VAPID key pair is not found. If you wish to enabled web push, please run
|
||||
|
||||
mix web_push.gen.keypair
|
||||
|
||||
and add the resulting output to your configuration file.
|
||||
""")
|
||||
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:send, notification}, state) do
|
||||
if enabled() do
|
||||
Impl.perform_send(notification)
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
do: PleromaJobQueue.enqueue(:web_push, Impl, [notification])
|
||||
end
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
defmodule Pleroma.Web.Router do
|
||||
use Pleroma.Web, :router
|
||||
|
||||
pipeline :browser do
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
end
|
||||
|
||||
pipeline :oauth do
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
|
@ -140,8 +145,12 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||
pipe_through([:admin_api, :oauth_write])
|
||||
|
||||
post("/user/follow", AdminAPIController, :user_follow)
|
||||
post("/user/unfollow", AdminAPIController, :user_unfollow)
|
||||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
|
||||
delete("/user", AdminAPIController, :user_delete)
|
||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||
post("/user", AdminAPIController, :user_create)
|
||||
|
@ -184,6 +193,7 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
post("/change_password", UtilController, :change_password)
|
||||
post("/delete_account", UtilController, :delete_account)
|
||||
put("/notification_settings", UtilController, :update_notificaton_settings)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
|
@ -209,6 +219,16 @@ defmodule Pleroma.Web.Router do
|
|||
post("/authorize", OAuthController, :create_authorization)
|
||||
post("/token", OAuthController, :token_exchange)
|
||||
post("/revoke", OAuthController, :token_revoke)
|
||||
get("/registration_details", OAuthController, :registration_details)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:browser)
|
||||
|
||||
get("/prepare_request", OAuthController, :prepare_request)
|
||||
get("/:provider", OAuthController, :request)
|
||||
get("/:provider/callback", OAuthController, :callback)
|
||||
post("/register", OAuthController, :register)
|
||||
end
|
||||
end
|
||||
|
||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
|
@ -240,6 +260,9 @@ defmodule Pleroma.Web.Router do
|
|||
get("/notifications", MastodonAPIController, :notifications)
|
||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||
|
||||
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
||||
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
||||
|
||||
get("/lists", MastodonAPIController, :get_lists)
|
||||
get("/lists/:id", MastodonAPIController, :get_list)
|
||||
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||
|
@ -274,6 +297,9 @@ defmodule Pleroma.Web.Router do
|
|||
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
||||
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
||||
|
||||
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
|
||||
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
|
||||
|
||||
post("/media", MastodonAPIController, :upload)
|
||||
put("/media/:id", MastodonAPIController, :update_media)
|
||||
|
||||
|
|
13
lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
Normal file
13
lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div class="scopes-input">
|
||||
<%= label @form, :scope, "Permissions" %>
|
||||
|
||||
<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 @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %>
|
||||
<%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
13
lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
Normal file
13
lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
Normal file
|
@ -0,0 +1,13 @@
|
|||
<h2>Sign in with external provider</h2>
|
||||
|
||||
<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
|
||||
<%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
|
||||
|
||||
<%= hidden_input f, :client_id, value: @client_id %>
|
||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||
<%= hidden_input f, :state, value: @state %>
|
||||
|
||||
<%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
|
||||
<%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
|
||||
<% end %>
|
||||
<% end %>
|
43
lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
Normal file
43
lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
Normal file
|
@ -0,0 +1,43 @@
|
|||
<%= if get_flash(@conn, :info) do %>
|
||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||
<% end %>
|
||||
<%= if get_flash(@conn, :error) do %>
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<% end %>
|
||||
|
||||
<h2>Registration Details</h2>
|
||||
|
||||
<p>If you'd like to register a new account, please provide the details below.</p>
|
||||
|
||||
<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
|
||||
|
||||
<div class="input">
|
||||
<%= label f, :nickname, "Nickname" %>
|
||||
<%= text_input f, :nickname, value: @nickname %>
|
||||
</div>
|
||||
<div class="input">
|
||||
<%= label f, :email, "Email" %>
|
||||
<%= text_input f, :email, value: @email %>
|
||||
</div>
|
||||
|
||||
<%= submit "Proceed as new user", name: "op", value: "register" %>
|
||||
|
||||
<p>Alternatively, sign in to connect to existing account.</p>
|
||||
|
||||
<div class="input">
|
||||
<%= label f, :auth_name, "Name or email" %>
|
||||
<%= text_input f, :auth_name %>
|
||||
</div>
|
||||
<div class="input">
|
||||
<%= label f, :password, "Password" %>
|
||||
<%= password_input f, :password %>
|
||||
</div>
|
||||
|
||||
<%= submit "Proceed as existing user", name: "op", value: "connect" %>
|
||||
|
||||
<%= hidden_input f, :client_id, value: @client_id %>
|
||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||
<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
|
||||
<%= hidden_input f, :state, value: @state %>
|
||||
|
||||
<% end %>
|
|
@ -4,7 +4,9 @@
|
|||
<%= if get_flash(@conn, :error) do %>
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<% end %>
|
||||
|
||||
<h2>OAuth Authorization</h2>
|
||||
|
||||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
|
||||
<div class="input">
|
||||
<%= label f, :name, "Name or email" %>
|
||||
|
@ -14,22 +16,16 @@
|
|||
<%= label f, :password, "Password" %>
|
||||
<%= password_input f, :password %>
|
||||
</div>
|
||||
<div class="scopes-input">
|
||||
<%= label f, :scope, "Permissions" %>
|
||||
<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>
|
||||
|
||||
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f, scope_param: "authorization[scope][]"}) %>
|
||||
|
||||
<%= hidden_input f, :client_id, value: @client_id %>
|
||||
<%= hidden_input f, :response_type, value: @response_type %>
|
||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||
<%= hidden_input f, :state, value: @state%>
|
||||
<%= hidden_input f, :state, value: @state %>
|
||||
<%= submit "Authorize" %>
|
||||
<% end %>
|
||||
|
||||
<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
|
||||
<%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
|
||||
<% end %>
|
||||
|
|
|
@ -283,7 +283,20 @@ def version(conn, _params) do
|
|||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
json(conn, Enum.into(Emoji.get_all(), %{}))
|
||||
emoji =
|
||||
Emoji.get_all()
|
||||
|> Enum.map(fn {short_code, path, tags} ->
|
||||
{short_code, %{image_url: path, tags: String.split(tags, ",")}}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
json(conn, emoji)
|
||||
end
|
||||
|
||||
def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, _} <- User.update_notification_settings(user, params) do
|
||||
json(conn, %{status: "success"})
|
||||
end
|
||||
end
|
||||
|
||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||
|
|
|
@ -254,10 +254,10 @@ def render(
|
|||
|
||||
html =
|
||||
content
|
||||
|> HTML.get_cached_scrubbed_html_for_object(
|
||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||
User.html_filter_policy(opts[:for]),
|
||||
activity,
|
||||
__MODULE__
|
||||
"twitterapi:content"
|
||||
)
|
||||
|> Formatter.emojify(object["emoji"])
|
||||
|
||||
|
@ -265,7 +265,7 @@ def render(
|
|||
if content do
|
||||
content
|
||||
|> String.replace(~r/<br\s?\/?>/, "\n")
|
||||
|> HTML.get_cached_stripped_html_for_object(activity, __MODULE__)
|
||||
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
|
||||
else
|
||||
""
|
||||
end
|
||||
|
|
17
mix.exs
17
mix.exs
|
@ -54,6 +54,12 @@ defp elixirc_paths(_), do: ["lib"]
|
|||
#
|
||||
# Type `mix help deps` for examples and options.
|
||||
defp deps do
|
||||
oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
|
||||
|
||||
oauth_deps =
|
||||
for s <- oauth_strategies,
|
||||
do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
|
||||
|
||||
[
|
||||
{:phoenix, "~> 1.4.1"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
|
@ -71,6 +77,7 @@ defp deps do
|
|||
{:calendar, "~> 0.17.4"},
|
||||
{:cachex, "~> 3.0.2"},
|
||||
{:httpoison, "~> 1.2.0"},
|
||||
{:poison, "~> 3.0", override: true},
|
||||
{:tesla, "~> 1.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:mogrify, "~> 0.6.1"},
|
||||
|
@ -91,12 +98,20 @@ defp deps do
|
|||
{:floki, "~> 0.20.0"},
|
||||
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
|
||||
{:timex, "~> 3.5"},
|
||||
{:ueberauth, "~> 0.4"},
|
||||
{:auto_linker,
|
||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||
ref: "479dd343f4e563ff91215c8275f3b5c67e032850"},
|
||||
{:pleroma_job_queue, "~> 0.2.0"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:prometheus_ex, "~> 3.0"},
|
||||
{:prometheus_plugs, "~> 1.1"},
|
||||
{:prometheus_phoenix, "~> 1.2"},
|
||||
{:prometheus_ecto, "~> 1.4"},
|
||||
{:prometheus_process_collector, "~> 1.4"},
|
||||
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||
{:quack, "~> 0.1.1"}
|
||||
]
|
||||
] ++ oauth_deps
|
||||
end
|
||||
|
||||
# Aliases are shortcuts or tasks specific to the current project.
|
||||
|
|
15
mix.lock
15
mix.lock
|
@ -1,10 +1,11 @@
|
|||
%{
|
||||
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
|
||||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "479dd343f4e563ff91215c8275f3b5c67e032850", [ref: "479dd343f4e563ff91215c8275f3b5c67e032850"]},
|
||||
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
||||
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||
|
@ -27,7 +28,7 @@
|
|||
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
@ -39,7 +40,7 @@
|
|||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||
|
@ -57,8 +58,15 @@
|
|||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
|
||||
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||
|
@ -67,6 +75,7 @@
|
|||
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
|
18
priv/repo/migrations/20190315101315_create_registrations.exs
Normal file
18
priv/repo/migrations/20190315101315_create_registrations.exs
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateRegistrations do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:registrations, primary_key: false) do
|
||||
add :id, :uuid, primary_key: true
|
||||
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
|
||||
add :provider, :string
|
||||
add :uid, :string
|
||||
add :info, :map, default: %{}
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create unique_index(:registrations, [:provider, :uid])
|
||||
create unique_index(:registrations, [:user_id, :provider, :uid])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:scheduled_activities) do
|
||||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
add(:scheduled_at, :naive_datetime, null: false)
|
||||
add(:params, :map, null: false)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create(index(:scheduled_activities, [:scheduled_at]))
|
||||
create(index(:scheduled_activities, [:user_id]))
|
||||
end
|
||||
end
|
106
test/emoji_test.exs
Normal file
106
test/emoji_test.exs
Normal file
|
@ -0,0 +1,106 @@
|
|||
defmodule Pleroma.EmojiTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Pleroma.Emoji
|
||||
|
||||
describe "get_all/0" do
|
||||
setup do
|
||||
emoji_list = Emoji.get_all()
|
||||
{:ok, emoji_list: emoji_list}
|
||||
end
|
||||
|
||||
test "first emoji", %{emoji_list: emoji_list} do
|
||||
[emoji | _others] = emoji_list
|
||||
{code, path, tags} = emoji
|
||||
|
||||
assert tuple_size(emoji) == 3
|
||||
assert is_binary(code)
|
||||
assert is_binary(path)
|
||||
assert is_binary(tags)
|
||||
end
|
||||
|
||||
test "random emoji", %{emoji_list: emoji_list} do
|
||||
emoji = Enum.random(emoji_list)
|
||||
{code, path, tags} = emoji
|
||||
|
||||
assert tuple_size(emoji) == 3
|
||||
assert is_binary(code)
|
||||
assert is_binary(path)
|
||||
assert is_binary(tags)
|
||||
end
|
||||
end
|
||||
|
||||
describe "match_extra/2" do
|
||||
setup do
|
||||
groups = [
|
||||
"list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"],
|
||||
"wildcard folder": "/emoji/custom/*/file.png",
|
||||
"wildcard files": "/emoji/custom/folder/*.png",
|
||||
"special file": "/emoji/custom/special.png"
|
||||
]
|
||||
|
||||
{:ok, groups: groups}
|
||||
end
|
||||
|
||||
test "config for list of files", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/first_file.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "list of files"
|
||||
end
|
||||
|
||||
test "config with wildcard folder", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/some_folder/file.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "wildcard folder"
|
||||
end
|
||||
|
||||
test "config with wildcard folder and subfolders", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "wildcard folder"
|
||||
end
|
||||
|
||||
test "config with wildcard files", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/folder/some_file.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "wildcard files"
|
||||
end
|
||||
|
||||
test "config with wildcard files and subfolders", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "wildcard files"
|
||||
end
|
||||
|
||||
test "config for special file", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/custom/special.png")
|
||||
|> to_string()
|
||||
|
||||
assert group == "special file"
|
||||
end
|
||||
|
||||
test "no mathing returns nil", %{groups: groups} do
|
||||
group =
|
||||
groups
|
||||
|> Emoji.match_extra("/emoji/some_undefined.png")
|
||||
|
||||
refute group
|
||||
end
|
||||
end
|
||||
end
|
|
@ -271,7 +271,9 @@ test "it does not add XSS emoji" do
|
|||
test "it returns the emoji used in the text" do
|
||||
text = "I love :moominmamma:"
|
||||
|
||||
assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}]
|
||||
assert Formatter.get_emoji(text) == [
|
||||
{"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"}
|
||||
]
|
||||
end
|
||||
|
||||
test "it returns a nice empty result when no emojis are present" do
|
||||
|
|
|
@ -53,6 +53,75 @@ test "it doesn't create a notification for user if the user blocks the activity
|
|||
assert nil == Notification.create_notification(activity, user)
|
||||
end
|
||||
|
||||
test "it doesn't create a notificatin for the user if the user mutes the activity author" do
|
||||
muter = insert(:user)
|
||||
muted = insert(:user)
|
||||
{:ok, _} = User.mute(muter, muted)
|
||||
muter = Repo.get(User, muter.id)
|
||||
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
|
||||
|
||||
assert nil == Notification.create_notification(activity, muter)
|
||||
end
|
||||
|
||||
test "it doesn't create a notification for an activity from a muted thread" do
|
||||
muter = insert(:user)
|
||||
other_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
|
||||
CommonAPI.add_mute(muter, activity)
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(other_user, %{
|
||||
"status" => "Hi @#{muter.nickname}",
|
||||
"in_reply_to_status_id" => activity.id
|
||||
})
|
||||
|
||||
assert nil == Notification.create_notification(activity, muter)
|
||||
end
|
||||
|
||||
test "it disables notifications from people on remote instances" do
|
||||
user = insert(:user, info: %{notification_settings: %{"remote" => false}})
|
||||
other_user = insert(:user)
|
||||
|
||||
create_activity = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Create",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"actor" => other_user.ap_id,
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Hi @#{user.nickname}",
|
||||
"attributedTo" => other_user.ap_id
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity)
|
||||
assert nil == Notification.create_notification(activity, user)
|
||||
end
|
||||
|
||||
test "it disables notifications from people on the local instance" do
|
||||
user = insert(:user, info: %{notification_settings: %{"local" => false}})
|
||||
other_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
|
||||
assert nil == Notification.create_notification(activity, user)
|
||||
end
|
||||
|
||||
test "it disables notifications from followers" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
|
||||
User.follow(follower, followed)
|
||||
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
|
||||
assert nil == Notification.create_notification(activity, followed)
|
||||
end
|
||||
|
||||
test "it disables notifications from people the user follows" do
|
||||
follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
|
||||
followed = insert(:user)
|
||||
User.follow(follower, followed)
|
||||
follower = Repo.get(User, follower.id)
|
||||
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
|
||||
assert nil == Notification.create_notification(activity, follower)
|
||||
end
|
||||
|
||||
test "it doesn't create a notification for user if he is the activity author" do
|
||||
activity = insert(:note_activity)
|
||||
author = User.get_by_ap_id(activity.data["actor"])
|
||||
|
|
|
@ -47,16 +47,18 @@ test "it authenticates the auth_user if present and password is correct and rese
|
|||
|> assign(:auth_user, user)
|
||||
|
||||
conn =
|
||||
with_mock User,
|
||||
with_mocks([
|
||||
{:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
|
||||
{User, [],
|
||||
[
|
||||
reset_password: fn user, %{password: password, password_confirmation: password} ->
|
||||
send(self(), :reset_password)
|
||||
{:ok, user}
|
||||
end do
|
||||
conn
|
||||
|> LegacyAuthenticationPlug.call(%{})
|
||||
end
|
||||
]}
|
||||
]) do
|
||||
LegacyAuthenticationPlug.call(conn, %{})
|
||||
end
|
||||
|
||||
assert_received :reset_password
|
||||
assert conn.assigns.user == user
|
||||
end
|
||||
|
||||
|
|
59
test/registration_test.exs
Normal file
59
test/registration_test.exs
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.RegistrationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
|
||||
describe "generic changeset" do
|
||||
test "requires :provider, :uid" do
|
||||
registration = build(:registration, provider: nil, uid: nil)
|
||||
|
||||
cs = Registration.changeset(registration, %{})
|
||||
refute cs.valid?
|
||||
|
||||
assert [
|
||||
provider: {"can't be blank", [validation: :required]},
|
||||
uid: {"can't be blank", [validation: :required]}
|
||||
] == cs.errors
|
||||
end
|
||||
|
||||
test "ensures uniqueness of [:provider, :uid]" do
|
||||
registration = insert(:registration)
|
||||
registration2 = build(:registration, provider: registration.provider, uid: registration.uid)
|
||||
|
||||
cs = Registration.changeset(registration2, %{})
|
||||
assert cs.valid?
|
||||
|
||||
assert {:error,
|
||||
%Ecto.Changeset{
|
||||
errors: [
|
||||
uid:
|
||||
{"has already been taken",
|
||||
[constraint: :unique, constraint_name: "registrations_provider_uid_index"]}
|
||||
]
|
||||
}} = Repo.insert(cs)
|
||||
|
||||
# Note: multiple :uid values per [:user_id, :provider] are intentionally allowed
|
||||
cs2 = Registration.changeset(registration2, %{uid: "available.uid"})
|
||||
assert cs2.valid?
|
||||
assert {:ok, _} = Repo.insert(cs2)
|
||||
|
||||
cs3 = Registration.changeset(registration2, %{provider: "provider2"})
|
||||
assert cs3.valid?
|
||||
assert {:ok, _} = Repo.insert(cs3)
|
||||
end
|
||||
|
||||
test "allows `nil` :user_id (user-unbound registration)" do
|
||||
registration = build(:registration, user_id: nil)
|
||||
cs = Registration.changeset(registration, %{})
|
||||
assert cs.valid?
|
||||
assert {:ok, _} = Repo.insert(cs)
|
||||
end
|
||||
end
|
||||
end
|
64
test/scheduled_activity_test.exs
Normal file
64
test/scheduled_activity_test.exs
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivityTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.DataCase
|
||||
alias Pleroma.ScheduledActivity
|
||||
import Pleroma.Factory
|
||||
|
||||
setup context do
|
||||
DataCase.ensure_local_uploader(context)
|
||||
end
|
||||
|
||||
describe "creation" do
|
||||
test "when daily user limit is exceeded" do
|
||||
user = insert(:user)
|
||||
|
||||
today =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
attrs = %{params: %{}, scheduled_at: today}
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||
assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
|
||||
end
|
||||
|
||||
test "when total user limit is exceeded" do
|
||||
user = insert(:user)
|
||||
|
||||
today =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
tomorrow =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||
{:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||
assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
|
||||
end
|
||||
|
||||
test "when scheduled_at is earlier than 5 minute from now" do
|
||||
user = insert(:user)
|
||||
|
||||
scheduled_at =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(4), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
attrs = %{params: %{}, scheduled_at: scheduled_at}
|
||||
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||
assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
|
||||
end
|
||||
end
|
||||
end
|
19
test/scheduled_activity_worker_test.exs
Normal file
19
test/scheduled_activity_worker_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivityWorkerTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.ScheduledActivity
|
||||
import Pleroma.Factory
|
||||
|
||||
test "creates a status from the scheduled activity" do
|
||||
user = insert(:user)
|
||||
scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"})
|
||||
Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id)
|
||||
|
||||
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
|
||||
assert activity.data["object"]["content"] == "hi"
|
||||
end
|
||||
end
|
|
@ -240,6 +240,16 @@ def oauth_token_factory do
|
|||
}
|
||||
end
|
||||
|
||||
def oauth_authorization_factory do
|
||||
%Pleroma.Web.OAuth.Authorization{
|
||||
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
|
||||
scopes: ["read", "write", "follow", "push"],
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
|
||||
user: build(:user),
|
||||
app: build(:oauth_app)
|
||||
}
|
||||
end
|
||||
|
||||
def push_subscription_factory do
|
||||
%Pleroma.Web.Push.Subscription{
|
||||
user: build(:user),
|
||||
|
@ -257,4 +267,28 @@ def notification_factory do
|
|||
user: build(:user)
|
||||
}
|
||||
end
|
||||
|
||||
def scheduled_activity_factory do
|
||||
%Pleroma.ScheduledActivity{
|
||||
user: build(:user),
|
||||
scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
|
||||
params: build(:note) |> Map.from_struct() |> Map.get(:data)
|
||||
}
|
||||
end
|
||||
|
||||
def registration_factory do
|
||||
user = insert(:user)
|
||||
|
||||
%Pleroma.Registration{
|
||||
user: user,
|
||||
provider: "twitter",
|
||||
uid: "171799000",
|
||||
info: %{
|
||||
"name" => "John Doe",
|
||||
"email" => "john@doe.com",
|
||||
"nickname" => "johndoe",
|
||||
"description" => "My bio"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1150,4 +1150,21 @@ test "bookmarks" do
|
|||
assert {:ok, user_state3} = User.bookmark(user, id2)
|
||||
assert user_state3.bookmarks == [id2]
|
||||
end
|
||||
|
||||
test "follower count is updated when a follower is blocked" do
|
||||
user = insert(:user)
|
||||
follower = insert(:user)
|
||||
follower2 = insert(:user)
|
||||
follower3 = insert(:user)
|
||||
|
||||
{:ok, follower} = Pleroma.User.follow(follower, user)
|
||||
{:ok, _follower2} = Pleroma.User.follow(follower2, user)
|
||||
{:ok, _follower3} = Pleroma.User.follow(follower3, user)
|
||||
|
||||
{:ok, _} = Pleroma.User.block(user, follower)
|
||||
|
||||
user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
|
||||
|
||||
assert Map.get(user_show, "followers_count") == 2
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1028,9 +1028,6 @@ test "it upgrades a user to activitypub" do
|
|||
assert user.info.note_count == 1
|
||||
assert user.follower_address == "https://niu.moe/users/rye/followers"
|
||||
|
||||
# Wait for the background task
|
||||
:timer.sleep(1000)
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
assert user.info.note_count == 1
|
||||
|
||||
|
|
|
@ -193,4 +193,16 @@ test "returns the what was collected if there are less pages than specified" do
|
|||
assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
|
||||
end
|
||||
end
|
||||
|
||||
test "make_json_ld_header/0" do
|
||||
assert Utils.make_json_ld_header() == %{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"http://localhost:4001/schemas/litepub-0.1.jsonld",
|
||||
%{
|
||||
"@language" => "und"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -74,6 +74,50 @@ test "when the user doesn't exist", %{conn: conn} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "/api/pleroma/admin/user/follow" do
|
||||
test "allows to force-follow another user" do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
user = insert(:user)
|
||||
follower = insert(:user)
|
||||
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> post("/api/pleroma/admin/user/follow", %{
|
||||
"follower" => follower.nickname,
|
||||
"followed" => user.nickname
|
||||
})
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
follower = User.get_by_id(follower.id)
|
||||
|
||||
assert User.following?(follower, user)
|
||||
end
|
||||
end
|
||||
|
||||
describe "/api/pleroma/admin/user/unfollow" do
|
||||
test "allows to force-unfollow another user" do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
user = insert(:user)
|
||||
follower = insert(:user)
|
||||
|
||||
User.follow(follower, user)
|
||||
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> post("/api/pleroma/admin/user/unfollow", %{
|
||||
"follower" => follower.nickname,
|
||||
"followed" => user.nickname
|
||||
})
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
follower = User.get_by_id(follower.id)
|
||||
|
||||
refute User.following?(follower, user)
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/pleroma/admin/users/tag" do
|
||||
setup do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
|
|
@ -71,6 +71,20 @@ test "Represent a user account" do
|
|||
assert expected == AccountView.render("account.json", %{user: user})
|
||||
end
|
||||
|
||||
test "Represent the user account for the account owner" do
|
||||
user = insert(:user)
|
||||
|
||||
notification_settings = %{
|
||||
"remote" => true,
|
||||
"local" => true,
|
||||
"followers" => true,
|
||||
"follows" => true
|
||||
}
|
||||
|
||||
assert %{pleroma: %{notification_settings: ^notification_settings}} =
|
||||
AccountView.render("account.json", %{user: user, for: user})
|
||||
end
|
||||
|
||||
test "Represent a Service(bot) account" do
|
||||
user =
|
||||
insert(:user, %{
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
|||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
@ -2359,4 +2360,297 @@ test "accounts fetches correct account for nicknames beginning with numbers", %{
|
|||
refute acc_one == acc_two
|
||||
assert acc_two == acc_three
|
||||
end
|
||||
|
||||
describe "custom emoji" do
|
||||
test "with tags", %{conn: conn} do
|
||||
[emoji | _body] =
|
||||
conn
|
||||
|> get("/api/v1/custom_emojis")
|
||||
|> json_response(200)
|
||||
|
||||
assert Map.has_key?(emoji, "shortcode")
|
||||
assert Map.has_key?(emoji, "static_url")
|
||||
assert Map.has_key?(emoji, "tags")
|
||||
assert is_list(emoji["tags"])
|
||||
assert Map.has_key?(emoji, "url")
|
||||
assert Map.has_key?(emoji, "visible_in_picker")
|
||||
end
|
||||
end
|
||||
|
||||
describe "index/2 redirections" do
|
||||
setup %{conn: conn} do
|
||||
session_opts = [
|
||||
store: :cookie,
|
||||
key: "_test",
|
||||
signing_salt: "cooldude"
|
||||
]
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(session_opts))
|
||||
|> fetch_session()
|
||||
|
||||
test_path = "/web/statuses/test"
|
||||
%{conn: conn, path: test_path}
|
||||
end
|
||||
|
||||
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
|
||||
conn = get(conn, path)
|
||||
|
||||
assert conn.status == 302
|
||||
assert redirected_to(conn) == "/web/login"
|
||||
end
|
||||
|
||||
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
|
||||
token = insert(:oauth_token)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, token.user)
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> get(path)
|
||||
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
test "saves referer path to session", %{conn: conn, path: path} do
|
||||
conn = get(conn, path)
|
||||
return_to = Plug.Conn.get_session(conn, :return_to)
|
||||
|
||||
assert return_to == path
|
||||
end
|
||||
|
||||
test "redirects to the saved path after log in", %{conn: conn, path: path} do
|
||||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||
auth = insert(:oauth_authorization, app: app)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:return_to, path)
|
||||
|> get("/web/login", %{code: auth.token})
|
||||
|
||||
assert conn.status == 302
|
||||
assert redirected_to(conn) == path
|
||||
end
|
||||
|
||||
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
|
||||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||
auth = insert(:oauth_authorization, app: app)
|
||||
|
||||
conn = get(conn, "/web/login", %{code: auth.token})
|
||||
|
||||
assert conn.status == 302
|
||||
assert redirected_to(conn) == "/web/getting-started"
|
||||
end
|
||||
end
|
||||
|
||||
describe "scheduled activities" do
|
||||
test "creates a scheduled activity", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "scheduled",
|
||||
"scheduled_at" => scheduled_at
|
||||
})
|
||||
|
||||
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
|
||||
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
|
||||
assert [] == Repo.all(Activity)
|
||||
end
|
||||
|
||||
test "creates a scheduled activity with a media attachment", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{
|
||||
"media_ids" => [to_string(upload.id)],
|
||||
"status" => "scheduled",
|
||||
"scheduled_at" => scheduled_at
|
||||
})
|
||||
|
||||
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
|
||||
assert %{"type" => "image"} = media_attachment
|
||||
end
|
||||
|
||||
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
|
||||
%{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
scheduled_at =
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "not scheduled",
|
||||
"scheduled_at" => scheduled_at
|
||||
})
|
||||
|
||||
assert %{"content" => "not scheduled"} = json_response(conn, 200)
|
||||
assert [] == Repo.all(ScheduledActivity)
|
||||
end
|
||||
|
||||
test "returns error when daily user limit is exceeded", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
today =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
attrs = %{params: %{}, scheduled_at: today}
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
|
||||
|
||||
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
|
||||
end
|
||||
|
||||
test "returns error when total user limit is exceeded", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
today =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
tomorrow =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
attrs = %{params: %{}, scheduled_at: today}
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
|
||||
|
||||
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
|
||||
end
|
||||
|
||||
test "shows scheduled activities", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|
||||
# min_id
|
||||
conn_res =
|
||||
conn
|
||||
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||
|
||||
result = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||
|
||||
# since_id
|
||||
conn_res =
|
||||
conn
|
||||
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||
|
||||
result = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||
|
||||
# max_id
|
||||
conn_res =
|
||||
conn
|
||||
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||
|
||||
result = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||
end
|
||||
|
||||
test "shows a scheduled activity", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||
|
||||
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/scheduled_statuses/404")
|
||||
|
||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||
end
|
||||
|
||||
test "updates a scheduled activity", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||
|
||||
new_scheduled_at =
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||
scheduled_at: new_scheduled_at
|
||||
})
|
||||
|
||||
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||
|
||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||
end
|
||||
|
||||
test "deletes a scheduled activity", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||
|
||||
assert %{} = json_response(res_conn, 200)
|
||||
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||
|
||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
68
test/web/mastodon_api/scheduled_activity_view_test.exs
Normal file
68
test/web/mastodon_api/scheduled_activity_view_test.exs
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
import Pleroma.Factory
|
||||
|
||||
test "A scheduled activity with a media attachment" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hi"})
|
||||
|
||||
scheduled_at =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(:timer.minutes(10), :millisecond)
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
attrs = %{
|
||||
params: %{
|
||||
"media_ids" => [upload.id],
|
||||
"status" => "hi",
|
||||
"sensitive" => true,
|
||||
"spoiler_text" => "spoiler",
|
||||
"visibility" => "unlisted",
|
||||
"in_reply_to_id" => to_string(activity.id)
|
||||
},
|
||||
scheduled_at: scheduled_at
|
||||
}
|
||||
|
||||
{:ok, scheduled_activity} = ScheduledActivity.create(user, attrs)
|
||||
result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
|
||||
expected = %{
|
||||
id: to_string(scheduled_activity.id),
|
||||
media_attachments:
|
||||
%{"media_ids" => [upload.id]}
|
||||
|> Utils.attachments_from_ids()
|
||||
|> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
|
||||
params: %{
|
||||
in_reply_to_id: to_string(activity.id),
|
||||
media_ids: [upload.id],
|
||||
poll: nil,
|
||||
scheduled_at: nil,
|
||||
sensitive: true,
|
||||
spoiler_text: "spoiler",
|
||||
text: "hi",
|
||||
visibility: "unlisted"
|
||||
},
|
||||
scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at)
|
||||
}
|
||||
|
||||
assert expected == result
|
||||
end
|
||||
end
|
|
@ -101,7 +101,7 @@ test "a note activity" do
|
|||
muted: false,
|
||||
pinned: false,
|
||||
sensitive: false,
|
||||
spoiler_text: note.data["object"]["summary"],
|
||||
spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),
|
||||
visibility: "public",
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
|
@ -126,7 +126,9 @@ test "a note activity" do
|
|||
],
|
||||
pleroma: %{
|
||||
local: true,
|
||||
conversation_id: convo_id
|
||||
conversation_id: convo_id,
|
||||
content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])},
|
||||
spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,24 +5,330 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
import Pleroma.Factory
|
||||
import Mock
|
||||
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
describe "GET /oauth/authorize" do
|
||||
setup do
|
||||
session_opts = [
|
||||
@session_opts [
|
||||
store: :cookie,
|
||||
key: "_test",
|
||||
signing_salt: "cooldude"
|
||||
]
|
||||
|
||||
describe "in OAuth consumer mode, " do
|
||||
setup do
|
||||
oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
|
||||
oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
|
||||
Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
|
||||
end)
|
||||
|
||||
[
|
||||
app: insert(:oauth_app),
|
||||
conn:
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session()
|
||||
]
|
||||
end
|
||||
|
||||
test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/authorize",
|
||||
%{
|
||||
"response_type" => "code",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"scope" => "read"
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 200)
|
||||
assert response =~ "Sign in with Twitter"
|
||||
assert response =~ o_auth_path(conn, :prepare_request)
|
||||
end
|
||||
|
||||
test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/prepare_request",
|
||||
%{
|
||||
"provider" => "twitter",
|
||||
"scope" => "read follow",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state"
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 302)
|
||||
|
||||
redirect_query = URI.parse(redirected_to(conn)).query
|
||||
assert %{"state" => state_param} = URI.decode_query(redirect_query)
|
||||
assert {:ok, state_components} = Poison.decode(state_param)
|
||||
|
||||
expected_client_id = app.client_id
|
||||
expected_redirect_uri = app.redirect_uris
|
||||
|
||||
assert %{
|
||||
"scope" => "read follow",
|
||||
"client_id" => ^expected_client_id,
|
||||
"redirect_uri" => ^expected_redirect_uri,
|
||||
"state" => "a_state"
|
||||
} = state_components
|
||||
end
|
||||
|
||||
test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
|
||||
%{app: app, conn: conn} do
|
||||
registration = insert(:registration)
|
||||
|
||||
state_params = %{
|
||||
"scope" => Enum.join(app.scopes, " "),
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => ""
|
||||
}
|
||||
|
||||
with_mock Pleroma.Web.Auth.Authenticator,
|
||||
get_registration: fn _, _ -> {:ok, registration} end do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Poison.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
|
||||
end
|
||||
end
|
||||
|
||||
test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
|
||||
%{app: app, conn: conn} do
|
||||
registration = insert(:registration, user: nil)
|
||||
|
||||
state_params = %{
|
||||
"scope" => "read write",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state"
|
||||
}
|
||||
|
||||
with_mock Pleroma.Web.Auth.Authenticator,
|
||||
get_registration: fn _, _ -> {:ok, registration} end do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Poison.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 200)
|
||||
assert response =~ ~r/name="op" type="submit" value="register"/
|
||||
assert response =~ ~r/name="op" type="submit" value="connect"/
|
||||
assert response =~ Registration.email(registration)
|
||||
assert response =~ Registration.nickname(registration)
|
||||
end
|
||||
end
|
||||
|
||||
test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
state_params = %{
|
||||
"scope" => Enum.join(app.scopes, " "),
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => ""
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
|
||||
|> get(
|
||||
"/oauth/twitter/callback",
|
||||
%{
|
||||
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
|
||||
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
|
||||
"provider" => "twitter",
|
||||
"state" => Poison.encode!(state_params)
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 302)
|
||||
assert redirected_to(conn) == app.redirect_uris
|
||||
assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
|
||||
end
|
||||
|
||||
test "GET /oauth/registration_details renders registration details form", %{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/oauth/registration_details",
|
||||
%{
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state",
|
||||
"nickname" => nil,
|
||||
"email" => "john@doe.com"
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 200)
|
||||
assert response =~ ~r/name="op" type="submit" value="register"/
|
||||
assert response =~ ~r/name="op" type="submit" value="connect"/
|
||||
end
|
||||
|
||||
test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "register",
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state",
|
||||
"nickname" => "availablenick",
|
||||
"email" => "available@email.com"
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
|
||||
end
|
||||
|
||||
test "with invalid params, POST /oauth/register?op=register renders registration_details page",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
another_user = insert(:user)
|
||||
registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
|
||||
|
||||
params = %{
|
||||
"op" => "register",
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state",
|
||||
"nickname" => "availablenickname",
|
||||
"email" => "available@email.com"
|
||||
}
|
||||
|
||||
for {bad_param, bad_param_value} <-
|
||||
[{"nickname", another_user.nickname}, {"email", another_user.email}] do
|
||||
bad_params = Map.put(params, bad_param, bad_param_value)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post("/oauth/register", bad_params)
|
||||
|
||||
assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
|
||||
assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
|
||||
end
|
||||
end
|
||||
|
||||
test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
|
||||
registration = insert(:registration, user: nil)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post(
|
||||
"/oauth/register",
|
||||
%{
|
||||
"op" => "connect",
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state",
|
||||
"auth_name" => user.nickname,
|
||||
"password" => "testpassword"
|
||||
}
|
||||
)
|
||||
|
||||
assert response = html_response(conn, 302)
|
||||
assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
|
||||
end
|
||||
|
||||
test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
|
||||
%{
|
||||
app: app,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user)
|
||||
registration = insert(:registration, user: nil)
|
||||
|
||||
params = %{
|
||||
"op" => "connect",
|
||||
"scopes" => app.scopes,
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"state" => "a_state",
|
||||
"auth_name" => user.nickname,
|
||||
"password" => "wrong password"
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> post("/oauth/register", params)
|
||||
|
||||
assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
|
||||
assert get_flash(conn, :error) == "Invalid Username/Password"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /oauth/authorize" do
|
||||
setup do
|
||||
[
|
||||
app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
|
||||
conn:
|
||||
build_conn()
|
||||
|> Plug.Session.call(Plug.Session.init(session_opts))
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session()
|
||||
]
|
||||
end
|
||||
|
@ -327,6 +633,32 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|
|||
refute Map.has_key?(resp, "access_token")
|
||||
end
|
||||
|
||||
test "rejects token exchange for valid credentials belonging to deactivated user" do
|
||||
password = "testpassword"
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
|
||||
info: %{deactivated: true}
|
||||
)
|
||||
|
||||
app = insert(:oauth_app)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
"grant_type" => "password",
|
||||
"username" => user.nickname,
|
||||
"password" => password,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret
|
||||
})
|
||||
|
||||
assert resp = json_response(conn, 403)
|
||||
assert %{"error" => _} = resp
|
||||
refute Map.has_key?(resp, "access_token")
|
||||
end
|
||||
|
||||
test "rejects an invalid authorization code" do
|
||||
app = insert(:oauth_app)
|
||||
|
||||
|
|
|
@ -64,17 +64,19 @@ test "performs sending notifications" do
|
|||
}
|
||||
)
|
||||
|
||||
assert Impl.perform_send(notif) == [:ok, :ok]
|
||||
assert Impl.perform(notif) == [:ok, :ok]
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "returns error if notif does not match " do
|
||||
assert Impl.perform_send(%{}) == :error
|
||||
assert Impl.perform(%{}) == :error
|
||||
end
|
||||
|
||||
test "successful message sending" do
|
||||
assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "fail message sending" do
|
||||
assert Impl.push_message(
|
||||
@message,
|
||||
|
|
|
@ -3,6 +3,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
|||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
import Pleroma.Factory
|
||||
|
||||
|
@ -79,6 +80,26 @@ test "it marks a single notification as read", %{conn: conn} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/pleroma/notification_settings" do
|
||||
test "it updates notification settings", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> put("/api/pleroma/notification_settings", %{
|
||||
"remote" => false,
|
||||
"followers" => false,
|
||||
"bar" => 1
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
|
||||
user.info.notification_settings
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/statusnet/config.json" do
|
||||
test "returns the state of safe_dm_mentions flag", %{conn: conn} do
|
||||
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
|
||||
|
@ -170,6 +191,24 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d
|
|||
end
|
||||
end
|
||||
|
||||
describe "/api/pleroma/emoji" do
|
||||
test "returns json with custom emoji with tags", %{conn: conn} do
|
||||
emoji =
|
||||
conn
|
||||
|> get("/api/pleroma/emoji")
|
||||
|> json_response(200)
|
||||
|
||||
assert Enum.all?(emoji, fn
|
||||
{_key,
|
||||
%{
|
||||
"image_url" => url,
|
||||
"tags" => tags
|
||||
}} ->
|
||||
is_binary(url) and is_list(tags)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /ostatus_subscribe?acct=...." do
|
||||
test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
|
||||
conn =
|
||||
|
|
Loading…
Reference in a new issue