Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
This commit is contained in:
commit
a7a8f3bc2c
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -22,16 +22,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Admin API: Endpoints for listing/revoking invite tokens
|
- Admin API: Endpoints for listing/revoking invite tokens
|
||||||
- Admin API: Endpoints for making users follow/unfollow each other
|
- Admin API: Endpoints for making users follow/unfollow each other
|
||||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
- Admin API: added filters (role, tags, email, name) for users endpoint
|
||||||
|
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
|
||||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||||
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
||||||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
||||||
- Mastodon API: REST API for creating an account
|
- Mastodon API: `POST /api/v1/accounts` (account creation API)
|
||||||
- ActivityPub C2S: OAuth endpoints
|
- ActivityPub C2S: OAuth endpoints
|
||||||
- Metadata RelMe provider
|
- Metadata: RelMe provider
|
||||||
- OAuth: added support for refresh tokens
|
- OAuth: added support for refresh tokens
|
||||||
- Emoji packs and emoji pack manager
|
- Emoji packs and emoji pack manager
|
||||||
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
|
|
||||||
- Addressable lists
|
- Addressable lists
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -45,8 +45,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Federation: Removed `inReplyToStatusId` from objects
|
- Federation: Removed `inReplyToStatusId` from objects
|
||||||
- Configuration: Dedupe enabled by default
|
- Configuration: Dedupe enabled by default
|
||||||
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
||||||
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
|
||||||
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
|
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
|
||||||
|
- Admin API: Move the user related API to `api/pleroma/admin/users`
|
||||||
|
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
||||||
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
||||||
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
||||||
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
||||||
|
@ -64,14 +65,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Deps: Updated Cowboy to 2.6
|
- Deps: Updated Cowboy to 2.6
|
||||||
- Deps: Updated Ecto to 3.0.7
|
- Deps: Updated Ecto to 3.0.7
|
||||||
- Don't ship finmoji by default, they can be installed as an emoji pack
|
- Don't ship finmoji by default, they can be installed as an emoji pack
|
||||||
- Admin API: Move the user related API to `api/pleroma/admin/users`
|
- Hide deactivated users and their statuses
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
|
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
|
||||||
- Followers counter not being updated when a follower is blocked
|
- Followers counter not being updated when a follower is blocked
|
||||||
- Deactivated users being able to request an access token
|
- Deactivated users being able to request an access token
|
||||||
- Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
|
- Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
|
||||||
- proper Twitter Card generation instead of a dummy
|
- Proper Twitter Card generation instead of a dummy
|
||||||
- Deletions failing for users with a large number of posts
|
- Deletions failing for users with a large number of posts
|
||||||
- NodeInfo: Include admins in `staffAccounts`
|
- NodeInfo: Include admins in `staffAccounts`
|
||||||
- ActivityPub: Crashing when requesting empty local user's outbox
|
- ActivityPub: Crashing when requesting empty local user's outbox
|
||||||
|
|
|
@ -239,7 +239,7 @@
|
||||||
safe_dm_mentions: false,
|
safe_dm_mentions: false,
|
||||||
healthcheck: false
|
healthcheck: false
|
||||||
|
|
||||||
config :pleroma, :app_account_creation, enabled: false, max_requests: 5, interval: 1800
|
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
|
@ -424,7 +424,8 @@
|
||||||
mailer: 10,
|
mailer: 10,
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
background: 5
|
background: 5,
|
||||||
|
user: 10
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
total_user_limit: 3,
|
total_user_limit: 3,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :app_account_creation, max_requests: 5
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -61,6 +61,15 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
||||||
* Example response: `{"error": "Invalid password."}`
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
||||||
|
## `/api/pleroma/disable_account`
|
||||||
|
### Disable an account
|
||||||
|
* Method `POST`
|
||||||
|
* Authentication: required
|
||||||
|
* Params:
|
||||||
|
* `password`: user's password
|
||||||
|
* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise
|
||||||
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
||||||
## `/api/account/register`
|
## `/api/account/register`
|
||||||
### Register a new user
|
### Register a new user
|
||||||
* Method `POST`
|
* Method `POST`
|
||||||
|
|
|
@ -132,7 +132,10 @@ def get_by_ap_id_with_object(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id(id) do
|
def get_by_id(id) do
|
||||||
Repo.get(Activity, id)
|
Activity
|
||||||
|
|> where([a], a.id == ^id)
|
||||||
|
|> restrict_deactivated_users()
|
||||||
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_with_object(id) do
|
def get_by_id_with_object(id) do
|
||||||
|
@ -200,6 +203,7 @@ def get_all_create_by_object_ap_id(ap_id) do
|
||||||
|
|
||||||
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||||
create_by_object_ap_id(ap_id)
|
create_by_object_ap_id(ap_id)
|
||||||
|
|> restrict_deactivated_users()
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -314,4 +318,14 @@ def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
|
||||||
def query_by_actor(actor) do
|
def query_by_actor(actor) do
|
||||||
from(a in Activity, where: a.actor == ^actor)
|
from(a in Activity, where: a.actor == ^actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restrict_deactivated_users(query) do
|
||||||
|
from(activity in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
activity.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,12 @@ def get(key), do: get(key, nil)
|
||||||
def get([key], default), do: get(key, default)
|
def get([key], default), do: get(key, default)
|
||||||
|
|
||||||
def get([parent_key | keys], default) do
|
def get([parent_key | keys], default) do
|
||||||
Application.get_env(:pleroma, parent_key)
|
case :pleroma
|
||||||
|> get_in(keys) || default
|
|> Application.get_env(parent_key)
|
||||||
|
|> get_in(keys) do
|
||||||
|
nil -> default
|
||||||
|
any -> any
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(key, default) do
|
def get(key, default) do
|
||||||
|
|
|
@ -77,13 +77,13 @@ def render_activities(activities) do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
like_count = object["like_count"] || 0
|
like_count = object.data["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object.data["announcement_count"] || 0
|
||||||
|
|
||||||
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
||||||
info("#{like_count} likes, #{announcement_count} repeats") <>
|
info("#{like_count} likes, #{announcement_count} repeats") <>
|
||||||
"i\tfake\t(NULL)\t0\r\n" <>
|
"i\tfake\t(NULL)\t0\r\n" <>
|
||||||
info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r")))
|
info(HTML.strip_tags(String.replace(object.data["content"], "<br>", "\r")))
|
||||||
end)
|
end)
|
||||||
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,13 @@ def changeset(%Notification{} = notification, attrs) do
|
||||||
def for_user_query(user) do
|
def for_user_query(user) do
|
||||||
Notification
|
Notification
|
||||||
|> where(user_id: ^user.id)
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
a.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|> join(:left, [n, a], object in Object,
|
|> join(:left, [n, a], object in Object,
|
||||||
on:
|
on:
|
||||||
|
|
|
@ -105,10 +105,8 @@ def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
|
|
||||||
def user_info(%User{} = user) do
|
def user_info(%User{} = user) do
|
||||||
oneself = if user.local, do: 1, else: 0
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
following_count: length(user.following) - oneself,
|
following_count: following_count(user),
|
||||||
note_count: user.info.note_count,
|
note_count: user.info.note_count,
|
||||||
follower_count: user.info.follower_count,
|
follower_count: user.info.follower_count,
|
||||||
locked: user.info.locked,
|
locked: user.info.locked,
|
||||||
|
@ -117,6 +115,20 @@ def user_info(%User{} = user) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restrict_deactivated(query) do
|
||||||
|
from(u in query,
|
||||||
|
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def following_count(%User{following: []}), do: 0
|
||||||
|
|
||||||
|
def following_count(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> get_friends_query()
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
|
||||||
def remote_user_creation(params) do
|
def remote_user_creation(params) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|
@ -255,7 +267,7 @@ defp autofollow_users(user) do
|
||||||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||||
|
|
||||||
autofollowed_users =
|
autofollowed_users =
|
||||||
User.Query.build(%{nickname: candidates, local: true})
|
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
follow_all(user, autofollowed_users)
|
follow_all(user, autofollowed_users)
|
||||||
|
@ -413,24 +425,6 @@ def following?(%User{} = follower, %User{} = followed) do
|
||||||
Enum.member?(follower.following, followed.follower_address)
|
Enum.member?(follower.following, followed.follower_address)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(%User{} = follower, followed_identifiers)
|
|
||||||
when is_list(followed_identifiers) do
|
|
||||||
Enum.map(
|
|
||||||
followed_identifiers,
|
|
||||||
fn followed_identifier ->
|
|
||||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
|
||||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
|
||||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
|
||||||
followed
|
|
||||||
else
|
|
||||||
err ->
|
|
||||||
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def locked?(%User{} = user) do
|
def locked?(%User{} = user) do
|
||||||
user.info.locked || false
|
user.info.locked || false
|
||||||
end
|
end
|
||||||
|
@ -552,8 +546,7 @@ def get_or_fetch_by_nickname(nickname) do
|
||||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
||||||
# TODO turn into job
|
fetch_initial_posts(user)
|
||||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
@ -564,19 +557,12 @@ def get_or_fetch_by_nickname(nickname) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Fetch some posts when the user has just been federated with"
|
@doc "Fetch some posts when the user has just been federated with"
|
||||||
def fetch_initial_posts(user) do
|
def fetch_initial_posts(user),
|
||||||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user])
|
||||||
|
|
||||||
Enum.each(
|
|
||||||
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
|
||||||
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
|
||||||
&Pleroma.Web.Federator.incoming_ap_doc/1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||||
def get_followers_query(%User{} = user, nil) do
|
def get_followers_query(%User{} = user, nil) do
|
||||||
User.Query.build(%{followers: user})
|
User.Query.build(%{followers: user, deactivated: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_followers_query(user, page) do
|
def get_followers_query(user, page) do
|
||||||
|
@ -601,7 +587,7 @@ def get_followers_ids(user, page \\ nil) do
|
||||||
|
|
||||||
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||||
def get_friends_query(%User{} = user, nil) do
|
def get_friends_query(%User{} = user, nil) do
|
||||||
User.Query.build(%{friends: user})
|
User.Query.build(%{friends: user, deactivated: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_friends_query(user, page) do
|
def get_friends_query(user, page) do
|
||||||
|
@ -691,16 +677,16 @@ def update_note_count(%User{} = user) do
|
||||||
|
|
||||||
info_cng = User.Info.set_note_count(user.info, note_count)
|
info_cng = User.Info.set_note_count(user.info, note_count)
|
||||||
|
|
||||||
cng =
|
user
|
||||||
change(user)
|
|> change()
|
||||||
|> put_embed(:info, info_cng)
|
|> put_embed(:info, info_cng)
|
||||||
|
|> update_and_set_cache()
|
||||||
update_and_set_cache(cng)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_follower_count(%User{} = user) do
|
def update_follower_count(%User{} = user) do
|
||||||
follower_count_query =
|
follower_count_query =
|
||||||
User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
|
User.Query.build(%{followers: user, deactivated: false})
|
||||||
|
|> select([u], %{count: count(u.id)})
|
||||||
|
|
||||||
User
|
User
|
||||||
|> where(id: ^user.id)
|
|> where(id: ^user.id)
|
||||||
|
@ -725,7 +711,7 @@ def update_follower_count(%User{} = user) do
|
||||||
|
|
||||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||||
criteria = %{ap_id: ap_ids}
|
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||||
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
||||||
|
|
||||||
User.Query.build(criteria)
|
User.Query.build(criteria)
|
||||||
|
@ -734,7 +720,7 @@ def get_users_from_set(ap_ids, local_only \\ true) do
|
||||||
|
|
||||||
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
||||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||||
User.Query.build(%{recipients_from_activity: to, local: true})
|
User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -832,6 +818,7 @@ defp fts_search_subquery(term, query \\ User) do
|
||||||
^processed_query
|
^processed_query
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|> restrict_deactivated()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp trigram_search_subquery(term) do
|
defp trigram_search_subquery(term) do
|
||||||
|
@ -850,23 +837,7 @@ defp trigram_search_subquery(term) do
|
||||||
},
|
},
|
||||||
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
|
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
|
||||||
)
|
)
|
||||||
end
|
|> restrict_deactivated()
|
||||||
|
|
||||||
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
|
|
||||||
Enum.map(
|
|
||||||
blocked_identifiers,
|
|
||||||
fn blocked_identifier ->
|
|
||||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
|
||||||
{:ok, blocker} <- block(blocker, blocked),
|
|
||||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
blocked
|
|
||||||
else
|
|
||||||
err ->
|
|
||||||
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(muter, %User{ap_id: ap_id}) do
|
def mute(muter, %User{ap_id: ap_id}) do
|
||||||
|
@ -999,19 +970,19 @@ def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||||
|
|
||||||
@spec muted_users(User.t()) :: [User.t()]
|
@spec muted_users(User.t()) :: [User.t()]
|
||||||
def muted_users(user) do
|
def muted_users(user) do
|
||||||
User.Query.build(%{ap_id: user.info.mutes})
|
User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec blocked_users(User.t()) :: [User.t()]
|
@spec blocked_users(User.t()) :: [User.t()]
|
||||||
def blocked_users(user) do
|
def blocked_users(user) do
|
||||||
User.Query.build(%{ap_id: user.info.blocks})
|
User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec subscribers(User.t()) :: [User.t()]
|
@spec subscribers(User.t()) :: [User.t()]
|
||||||
def subscribers(user) do
|
def subscribers(user) do
|
||||||
User.Query.build(%{ap_id: user.info.subscribers})
|
User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1039,14 +1010,25 @@ def unblock_domain(user, domain) do
|
||||||
update_and_set_cache(cng)
|
update_and_set_cache(cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deactivate_async(user, status \\ true) do
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
|
||||||
|
end
|
||||||
|
|
||||||
def deactivate(%User{} = user, status \\ true) do
|
def deactivate(%User{} = user, status \\ true) do
|
||||||
info_cng = User.Info.set_activation_status(user.info, status)
|
info_cng = User.Info.set_activation_status(user.info, status)
|
||||||
|
|
||||||
cng =
|
with {:ok, friends} <- User.get_friends(user),
|
||||||
change(user)
|
{:ok, followers} <- User.get_followers(user),
|
||||||
|> put_embed(:info, info_cng)
|
{:ok, user} <-
|
||||||
|
user
|
||||||
|
|> change()
|
||||||
|
|> put_embed(:info, info_cng)
|
||||||
|
|> update_and_set_cache() do
|
||||||
|
Enum.each(followers, &invalidate_cache(&1))
|
||||||
|
Enum.each(friends, &update_follower_count(&1))
|
||||||
|
|
||||||
update_and_set_cache(cng)
|
{:ok, user}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||||
|
@ -1077,6 +1059,75 @@ def perform(:delete, %User{} = user) do
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||||
|
def perform(:fetch_initial_posts, %User{} = user) do
|
||||||
|
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
||||||
|
|
||||||
|
Enum.each(
|
||||||
|
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
||||||
|
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
||||||
|
&Pleroma.Web.Federator.incoming_ap_doc/1
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(:deactivate_async, user, status), do: deactivate(user, status)
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
|
||||||
|
def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
||||||
|
when is_list(blocked_identifiers) do
|
||||||
|
Enum.map(
|
||||||
|
blocked_identifiers,
|
||||||
|
fn blocked_identifier ->
|
||||||
|
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||||
|
{:ok, blocker} <- block(blocker, blocked),
|
||||||
|
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||||
|
blocked
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
|
||||||
|
def perform(:follow_import, %User{} = follower, followed_identifiers)
|
||||||
|
when is_list(followed_identifiers) do
|
||||||
|
Enum.map(
|
||||||
|
followed_identifiers,
|
||||||
|
fn followed_identifier ->
|
||||||
|
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||||
|
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||||
|
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||||
|
followed
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
|
||||||
|
do:
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [
|
||||||
|
:blocks_import,
|
||||||
|
blocker,
|
||||||
|
blocked_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
|
def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers),
|
||||||
|
do:
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [
|
||||||
|
:follow_import,
|
||||||
|
follower,
|
||||||
|
followed_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
stream =
|
stream =
|
||||||
ap_id
|
ap_id
|
||||||
|
@ -1130,8 +1181,8 @@ def get_or_fetch_by_ap_id(ap_id) do
|
||||||
resp = fetch_by_ap_id(ap_id)
|
resp = fetch_by_ap_id(ap_id)
|
||||||
|
|
||||||
if should_fetch_initial do
|
if should_fetch_initial do
|
||||||
with {:ok, %User{} = user} = resp do
|
with {:ok, %User{} = user} <- resp do
|
||||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
fetch_initial_posts(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1320,7 +1371,7 @@ def error_user(ap_id) do
|
||||||
|
|
||||||
@spec all_superusers() :: [User.t()]
|
@spec all_superusers() :: [User.t()]
|
||||||
def all_superusers do
|
def all_superusers do
|
||||||
User.Query.build(%{super_users: true, local: true})
|
User.Query.build(%{super_users: true, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,11 @@ defp compose_query({:active, _}, query) do
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:deactivated, _}, query) do
|
defp compose_query({:deactivated, false}, query) do
|
||||||
|
User.restrict_deactivated(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp compose_query({:deactivated, true}, query) do
|
||||||
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
|
@ -132,9 +132,7 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
activity
|
activity
|
||||||
end
|
end
|
||||||
|
|
||||||
Task.start(fn ->
|
PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])
|
||||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Notification.create_notifications(activity)
|
Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
@ -851,6 +849,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_reblogs(opts)
|
|> restrict_reblogs(opts)
|
||||||
|> restrict_pinned(opts)
|
|> restrict_pinned(opts)
|
||||||
|> restrict_muted_reblogs(opts)
|
|> restrict_muted_reblogs(opts)
|
||||||
|
|> Activity.restrict_deactivated_users()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activities(recipients, opts \\ %{}) do
|
def fetch_activities(recipients, opts \\ %{}) do
|
||||||
|
|
|
@ -19,8 +19,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
|
|
||||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||||
|
|
||||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
|
||||||
|
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
plug(:fetch_flash)
|
plug(:fetch_flash)
|
||||||
|
|
||||||
|
@ -144,14 +142,14 @@ defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do
|
||||||
@doc "Renew access_token with refresh_token"
|
@doc "Renew access_token with refresh_token"
|
||||||
def token_exchange(
|
def token_exchange(
|
||||||
conn,
|
conn,
|
||||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = params
|
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
|
||||||
) do
|
) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||||
{:ok, token} <- RefreshToken.grant(token) do
|
{:ok, token} <- RefreshToken.grant(token) do
|
||||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||||
|
|
||||||
json(conn, response_token(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|
@ -160,14 +158,14 @@ def token_exchange(
|
||||||
end
|
end
|
||||||
|
|
||||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||||
|
|
||||||
json(conn, response_token(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|
@ -179,14 +177,14 @@ def token_exchange(
|
||||||
conn,
|
conn,
|
||||||
%{"grant_type" => "password"} = params
|
%{"grant_type" => "password"} = params
|
||||||
) do
|
) do
|
||||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||||
%App{} = app <- get_app_from_request(conn, params),
|
{:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||||
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||||
{:ok, scopes} <- validate_scopes(app, params),
|
{:ok, scopes} <- validate_scopes(app, params),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
json(conn, response_token(user, token))
|
json(conn, Token.Response.build(user, token))
|
||||||
else
|
else
|
||||||
{:auth_active, false} ->
|
{:auth_active, false} ->
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
|
@ -218,21 +216,11 @@ def token_exchange(
|
||||||
token_exchange(conn, params)
|
token_exchange(conn, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def token_exchange(conn, %{"grant_type" => "client_credentials"} = params) do
|
def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth),
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
json(conn, Token.Response.build_for_client_credentials(token))
|
||||||
response = %{
|
|
||||||
token_type: "Bearer",
|
|
||||||
access_token: token.token,
|
|
||||||
refresh_token: token.refresh_token,
|
|
||||||
created_at: DateTime.to_unix(inserted_at),
|
|
||||||
expires_in: 60 * 10,
|
|
||||||
scope: Enum.join(token.scopes, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
json(conn, response)
|
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|
@ -244,7 +232,7 @@ def token_exchange(conn, %{"grant_type" => "client_credentials"} = params) do
|
||||||
def token_exchange(conn, params), do: bad_request(conn, params)
|
def token_exchange(conn, params), do: bad_request(conn, params)
|
||||||
|
|
||||||
def token_revoke(conn, %{"token" => _token} = params) do
|
def token_revoke(conn, %{"token" => _token} = params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
else
|
else
|
||||||
|
@ -427,33 +415,6 @@ defp do_create_authorization(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_app_from_request(conn, params) do
|
|
||||||
conn
|
|
||||||
|> fetch_client_credentials(params)
|
|
||||||
|> fetch_client
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
|
|
||||||
Repo.get_by(App, client_id: id, client_secret: secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_client({_id, _secret}), do: nil
|
|
||||||
|
|
||||||
defp fetch_client_credentials(conn, params) do
|
|
||||||
# Per RFC 6749, HTTP Basic is preferred to body params
|
|
||||||
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
|
|
||||||
{:ok, decoded} <- Base.decode64(encoded),
|
|
||||||
[id, secret] <-
|
|
||||||
Enum.map(
|
|
||||||
String.split(decoded, ":"),
|
|
||||||
fn s -> URI.decode_www_form(s) end
|
|
||||||
) do
|
|
||||||
{id, secret}
|
|
||||||
else
|
|
||||||
_ -> {params["client_id"], params["client_secret"]}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Special case: Local MastodonFE
|
# Special case: Local MastodonFE
|
||||||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||||
|
|
||||||
|
@ -464,18 +425,6 @@ defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||||
defp put_session_registration_id(conn, registration_id),
|
defp put_session_registration_id(conn, registration_id),
|
||||||
do: put_session(conn, :registration_id, registration_id)
|
do: put_session(conn, :registration_id, registration_id)
|
||||||
|
|
||||||
defp response_token(%User{} = user, token, opts \\ %{}) do
|
|
||||||
%{
|
|
||||||
token_type: "Bearer",
|
|
||||||
access_token: token.token,
|
|
||||||
refresh_token: token.refresh_token,
|
|
||||||
expires_in: @expires_in,
|
|
||||||
scope: Enum.join(token.scopes, " "),
|
|
||||||
me: user.ap_id
|
|
||||||
}
|
|
||||||
|> Map.merge(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec validate_scopes(App.t(), map()) ::
|
@spec validate_scopes(App.t(), map()) ::
|
||||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||||
defp validate_scopes(app, params) do
|
defp validate_scopes(app, params) do
|
||||||
|
|
32
lib/pleroma/web/oauth/token/response.ex
Normal file
32
lib/pleroma/web/oauth/token/response.ex
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule Pleroma.Web.OAuth.Token.Response do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OAuth.Token.Utils
|
||||||
|
|
||||||
|
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def build(%User{} = user, token, opts \\ %{}) do
|
||||||
|
%{
|
||||||
|
token_type: "Bearer",
|
||||||
|
access_token: token.token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
expires_in: @expires_in,
|
||||||
|
scope: Enum.join(token.scopes, " "),
|
||||||
|
me: user.ap_id
|
||||||
|
}
|
||||||
|
|> Map.merge(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_for_client_credentials(token) do
|
||||||
|
%{
|
||||||
|
token_type: "Bearer",
|
||||||
|
access_token: token.token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
created_at: Utils.format_created_at(token),
|
||||||
|
expires_in: @expires_in,
|
||||||
|
scope: Enum.join(token.scopes, " ")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,44 @@ defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||||
Auxiliary functions for dealing with tokens.
|
Auxiliary functions for dealing with tokens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.OAuth.App
|
||||||
|
|
||||||
|
@doc "Fetch app by client credentials from request"
|
||||||
|
@spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found}
|
||||||
|
def fetch_app(conn) do
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> fetch_client_credentials()
|
||||||
|
|> fetch_client
|
||||||
|
|
||||||
|
case res do
|
||||||
|
%App{} = app -> {:ok, app}
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
|
||||||
|
Repo.get_by(App, client_id: id, client_secret: secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_client({_id, _secret}), do: nil
|
||||||
|
|
||||||
|
defp fetch_client_credentials(conn) do
|
||||||
|
# Per RFC 6749, HTTP Basic is preferred to body params
|
||||||
|
with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"),
|
||||||
|
{:ok, decoded} <- Base.decode64(encoded),
|
||||||
|
[id, secret] <-
|
||||||
|
Enum.map(
|
||||||
|
String.split(decoded, ":"),
|
||||||
|
fn s -> URI.decode_www_form(s) end
|
||||||
|
) do
|
||||||
|
{id, secret}
|
||||||
|
else
|
||||||
|
_ -> {conn.params["client_id"], conn.params["client_secret"]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc "convert token inserted_at to unix timestamp"
|
@doc "convert token inserted_at to unix timestamp"
|
||||||
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
||||||
inserted_at
|
inserted_at
|
||||||
|
|
|
@ -34,4 +34,6 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_data_for_activity(_), do: %{}
|
def fetch_data_for_activity(_), do: %{}
|
||||||
|
|
||||||
|
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
|
||||||
end
|
end
|
||||||
|
|
|
@ -215,6 +215,7 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/change_password", UtilController, :change_password)
|
post("/change_password", UtilController, :change_password)
|
||||||
post("/delete_account", UtilController, :delete_account)
|
post("/delete_account", UtilController, :delete_account)
|
||||||
put("/notification_settings", UtilController, :update_notificaton_settings)
|
put("/notification_settings", UtilController, :update_notificaton_settings)
|
||||||
|
post("/disable_account", UtilController, :disable_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
|
|
|
@ -309,8 +309,13 @@ def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
|
||||||
Enum.map(lines, fn line ->
|
Enum.map(lines, fn line ->
|
||||||
String.split(line, ",") |> List.first()
|
String.split(line, ",") |> List.first()
|
||||||
end)
|
end)
|
||||||
|> List.delete("Account address"),
|
|> List.delete("Account address") do
|
||||||
{:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do
|
PleromaJobQueue.enqueue(:background, User, [
|
||||||
|
:follow_import,
|
||||||
|
follower,
|
||||||
|
followed_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
json(conn, "job started")
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -320,8 +325,13 @@ def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
|
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
|
||||||
with blocked_identifiers <- String.split(list),
|
with blocked_identifiers <- String.split(list) do
|
||||||
{:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do
|
PleromaJobQueue.enqueue(:background, User, [
|
||||||
|
:blocks_import,
|
||||||
|
blocker,
|
||||||
|
blocked_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
json(conn, "job started")
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -360,6 +370,17 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disable_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||||
|
{:ok, user} ->
|
||||||
|
User.deactivate_async(user)
|
||||||
|
json(conn, %{status: "success"})
|
||||||
|
|
||||||
|
{:error, msg} ->
|
||||||
|
json(conn, %{error: msg})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def captcha(conn, _params) do
|
def captcha(conn, _params) do
|
||||||
json(conn, Pleroma.Captcha.new())
|
json(conn, Pleroma.Captcha.new())
|
||||||
end
|
end
|
||||||
|
|
|
@ -236,12 +236,15 @@ def password_reset(nickname_or_email) do
|
||||||
def get_user(user \\ nil, params) do
|
def get_user(user \\ nil, params) do
|
||||||
case params do
|
case params do
|
||||||
%{"user_id" => user_id} ->
|
%{"user_id" => user_id} ->
|
||||||
case target = User.get_cached_by_nickname_or_id(user_id) do
|
case User.get_cached_by_nickname_or_id(user_id) do
|
||||||
nil ->
|
nil ->
|
||||||
{:error, "No user with such user_id"}
|
{:error, "No user with such user_id"}
|
||||||
|
|
||||||
_ ->
|
%User{info: %{deactivated: true}} ->
|
||||||
{:ok, target}
|
{:error, "User has been disabled"}
|
||||||
|
|
||||||
|
user ->
|
||||||
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddIndexOnUserInfoDeactivated do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin))
|
||||||
|
end
|
||||||
|
end
|
|
@ -28,6 +28,15 @@ test "get/1 with a list of keys" do
|
||||||
assert Pleroma.Config.get([:azerty, :uiop], true) == true
|
assert Pleroma.Config.get([:azerty, :uiop], true) == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get/1 when value is false" do
|
||||||
|
Pleroma.Config.put([:instance, :false_test], false)
|
||||||
|
Pleroma.Config.put([:instance, :nested], [])
|
||||||
|
Pleroma.Config.put([:instance, :nested, :false_test], false)
|
||||||
|
|
||||||
|
assert Pleroma.Config.get([:instance, :false_test]) == false
|
||||||
|
assert Pleroma.Config.get([:instance, :nested, :false_test]) == false
|
||||||
|
end
|
||||||
|
|
||||||
test "get!/1" do
|
test "get!/1" do
|
||||||
assert Pleroma.Config.get!(:instance) == Application.get_env(:pleroma, :instance)
|
assert Pleroma.Config.get!(:instance) == Application.get_env(:pleroma, :instance)
|
||||||
|
|
||||||
|
@ -43,6 +52,15 @@ test "get!/1" do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get!/1 when value is false" do
|
||||||
|
Pleroma.Config.put([:instance, :false_test], false)
|
||||||
|
Pleroma.Config.put([:instance, :nested], [])
|
||||||
|
Pleroma.Config.put([:instance, :nested, :false_test], false)
|
||||||
|
|
||||||
|
assert Pleroma.Config.get!([:instance, :false_test]) == false
|
||||||
|
assert Pleroma.Config.get!([:instance, :nested, :false_test]) == false
|
||||||
|
end
|
||||||
|
|
||||||
test "put/2 with a key" do
|
test "put/2 with a key" do
|
||||||
Pleroma.Config.put(:config_test, true)
|
Pleroma.Config.put(:config_test, true)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
@ -213,8 +214,8 @@ test "test if a user is following another user" do
|
||||||
test "fetches correct profile for nickname beginning with number" do
|
test "fetches correct profile for nickname beginning with number" do
|
||||||
# Use old-style integer ID to try to reproduce the problem
|
# Use old-style integer ID to try to reproduce the problem
|
||||||
user = insert(:user, %{id: 1080})
|
user = insert(:user, %{id: 1080})
|
||||||
userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
user_with_numbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
||||||
assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
|
assert user_with_numbers == User.get_cached_by_nickname_or_id(user_with_numbers.nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user registration" do
|
describe "user registration" do
|
||||||
|
@ -816,13 +817,73 @@ test "get recipients from activity" do
|
||||||
assert addressed in recipients
|
assert addressed in recipients
|
||||||
end
|
end
|
||||||
|
|
||||||
test ".deactivate can de-activate then re-activate a user" do
|
describe ".deactivate" do
|
||||||
user = insert(:user)
|
test "can de-activate then re-activate a user" do
|
||||||
assert false == user.info.deactivated
|
user = insert(:user)
|
||||||
{:ok, user} = User.deactivate(user)
|
assert false == user.info.deactivated
|
||||||
assert true == user.info.deactivated
|
{:ok, user} = User.deactivate(user)
|
||||||
{:ok, user} = User.deactivate(user, false)
|
assert true == user.info.deactivated
|
||||||
assert false == user.info.deactivated
|
{:ok, user} = User.deactivate(user, false)
|
||||||
|
assert false == user.info.deactivated
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user from followers " do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, user2)
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
info = User.get_cached_user_info(user2)
|
||||||
|
|
||||||
|
assert info.follower_count == 0
|
||||||
|
assert {:ok, []} = User.get_followers(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user from friends" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user2} = User.follow(user2, user)
|
||||||
|
assert User.following_count(user2) == 1
|
||||||
|
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
info = User.get_cached_user_info(user2)
|
||||||
|
|
||||||
|
assert info.following_count == 0
|
||||||
|
assert User.following_count(user2) == 0
|
||||||
|
assert {:ok, []} = User.get_friends(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user's statuses from timelines and notifications" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user2} = User.follow(user2, user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"})
|
||||||
|
|
||||||
|
activity = Repo.preload(activity, :bookmark)
|
||||||
|
|
||||||
|
[notification] = Pleroma.Notification.for_user(user2)
|
||||||
|
assert notification.activity.id == activity.id
|
||||||
|
|
||||||
|
assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
|
||||||
|
|
||||||
|
assert [activity] ==
|
||||||
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|
|> ActivityPub.contain_timeline(user2)
|
||||||
|
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
assert [] == ActivityPub.fetch_public_activities(%{})
|
||||||
|
assert [] == Pleroma.Notification.for_user(user2)
|
||||||
|
|
||||||
|
assert [] ==
|
||||||
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|
|> ActivityPub.contain_timeline(user2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test ".delete_user_activities deletes all create activities" do
|
test ".delete_user_activities deletes all create activities" do
|
||||||
|
|
53
test/web/oauth/token/utils_test.exs
Normal file
53
test/web/oauth/token/utils_test.exs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.OAuth.Token.UtilsTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Web.OAuth.Token.Utils
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "fetch_app/1" do
|
||||||
|
test "returns error when credentials is invalid" do
|
||||||
|
assert {:error, :not_found} =
|
||||||
|
Utils.fetch_app(%Plug.Conn{params: %{"client_id" => 1, "client_secret" => "x"}})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns App by params credentails" do
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
assert {:ok, load_app} =
|
||||||
|
Utils.fetch_app(%Plug.Conn{
|
||||||
|
params: %{"client_id" => app.client_id, "client_secret" => app.client_secret}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert load_app == app
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns App by header credentails" do
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
header = "Basic " <> Base.encode64("#{app.client_id}:#{app.client_secret}")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
%Plug.Conn{}
|
||||||
|
|> Plug.Conn.put_req_header("authorization", header)
|
||||||
|
|
||||||
|
assert {:ok, load_app} = Utils.fetch_app(conn)
|
||||||
|
assert load_app == app
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "format_created_at/1" do
|
||||||
|
test "returns formatted created at" do
|
||||||
|
token = insert(:oauth_token)
|
||||||
|
date = Utils.format_created_at(token)
|
||||||
|
|
||||||
|
token_date =
|
||||||
|
token.inserted_at
|
||||||
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|
|> DateTime.to_unix()
|
||||||
|
|
||||||
|
assert token_date == date
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -251,4 +251,22 @@ test "GET /api/pleroma/healthcheck", %{conn: conn} do
|
||||||
|
|
||||||
assert conn.status in [200, 503]
|
assert conn.status in [200, 503]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "POST /api/pleroma/disable_account" do
|
||||||
|
test "it returns HTTP 200", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/pleroma/disable_account", %{"password" => "test"})
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert response == %{"status" => "success"}
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
assert user.info.deactivated == true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue