forked from AkkomaGang/akkoma
Merge branch 'develop' into fix/attachments-cleanup
This commit is contained in:
commit
22e94bb2c7
65 changed files with 624 additions and 181 deletions
|
@ -6,10 +6,6 @@ variables: &global_variables
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
MIX_ENV: test
|
MIX_ENV: test
|
||||||
DOCKER_DRIVER: overlay2
|
|
||||||
DOCKER_HOST: unix:///var/run/docker.sock
|
|
||||||
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:latest
|
|
||||||
DOCKER_IMAGE_SHA: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
|
||||||
|
|
||||||
cache: &global_cache_policy
|
cache: &global_cache_policy
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
key: ${CI_COMMIT_REF_SLUG}
|
||||||
|
@ -274,16 +270,62 @@ arm64-musl:
|
||||||
docker:
|
docker:
|
||||||
stage: docker
|
stage: docker
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
tags:
|
cache: {}
|
||||||
- dind
|
dependencies: []
|
||||||
|
variables: &docker-variables
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
|
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||||
|
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||||
|
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
||||||
|
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
|
||||||
before_script: &before-docker
|
before_script: &before-docker
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
- docker pull $IMAGE_TAG_SLUG || true
|
||||||
- export CI_JOB_TIMESTAMP=$(date --utc -Iseconds)
|
- export CI_JOB_TIMESTAMP=$(date --utc -Iseconds)
|
||||||
- export CI_VCS_REF=$CI_COMMIT_SHORT_SHA
|
- export CI_VCS_REF=$CI_COMMIT_SHORT_SHA
|
||||||
|
allow_failure: true
|
||||||
script:
|
script:
|
||||||
- docker pull $DOCKER_IMAGE || true
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST .
|
||||||
- docker build --cache-from $DOCKER_IMAGE --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $DOCKER_IMAGE_SHA -t $DOCKER_IMAGE .
|
- docker push $IMAGE_TAG
|
||||||
- docker push $DOCKER_IMAGE_SHA
|
- docker push $IMAGE_TAG_SLUG
|
||||||
- docker push $DOCKER_IMAGE
|
- docker push $IMAGE_TAG_LATEST
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
only:
|
only:
|
||||||
- develop
|
- develop@pleroma/pleroma
|
||||||
|
|
||||||
|
docker-stable:
|
||||||
|
stage: docker
|
||||||
|
image: docker:latest
|
||||||
|
cache: {}
|
||||||
|
dependencies: []
|
||||||
|
variables: *docker-variables
|
||||||
|
before_script: *before-docker
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE .
|
||||||
|
- docker push $IMAGE_TAG
|
||||||
|
- docker push $IMAGE_TAG_SLUG
|
||||||
|
- docker push $IMAGE_TAG_LATEST_STABLE
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
|
only:
|
||||||
|
- stable@pleroma/pleroma
|
||||||
|
|
||||||
|
docker-release:
|
||||||
|
stage: docker
|
||||||
|
image: docker:latest
|
||||||
|
cache: {}
|
||||||
|
dependencies: []
|
||||||
|
variables: *docker-variables
|
||||||
|
before_script: *before-docker
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG .
|
||||||
|
- docker push $IMAGE_TAG
|
||||||
|
- docker push $IMAGE_TAG_SLUG
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
|
only:
|
||||||
|
- /^release/.*$/@pleroma/pleroma
|
||||||
|
|
|
@ -10,9 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- **Breaking**: MDII uploader
|
- **Breaking**: MDII uploader
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- **Breaking:** Pleroma won't start if it detects unapplied migrations
|
||||||
- **Breaking:** attachments are removed along with statuses when there are no other references to it
|
- **Breaking:** attachments are removed along with statuses when there are no other references to it
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||||
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
||||||
|
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
|
||||||
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
||||||
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
||||||
- Enabled `:instance, extended_nickname_format` in the default config
|
- Enabled `:instance, extended_nickname_format` in the default config
|
||||||
|
@ -42,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||||
- Admin API: Render whole status in grouped reports
|
- Admin API: Render whole status in grouped reports
|
||||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||||
|
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -90,6 +93,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
|
- Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
|
||||||
- Captcha: Support native provider
|
- Captcha: Support native provider
|
||||||
- Captcha: Enable by default
|
- Captcha: Enable by default
|
||||||
|
- Mastodon API: Add support for `account_id` param to filter notifications by the account
|
||||||
|
- Mastodon API: Add `emoji_reactions` property to Statuses
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -9,7 +9,7 @@ def generate_like_activities(user, posts) do
|
||||||
{time, _} =
|
{time, _} =
|
||||||
:timer.tc(fn ->
|
:timer.tc(fn ->
|
||||||
Task.async_stream(
|
Task.async_stream(
|
||||||
Enum.take_random(posts, count_likes),
|
Enum.take_random(posts, count_likes),
|
||||||
fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end,
|
fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end,
|
||||||
max_concurrency: 10,
|
max_concurrency: 10,
|
||||||
timeout: 30_000
|
timeout: 30_000
|
||||||
|
@ -142,6 +142,48 @@ defp do_generate_activity(users) do
|
||||||
CommonAPI.post(Enum.random(users), post)
|
CommonAPI.post(Enum.random(users), post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_power_intervals(opts \\ []) do
|
||||||
|
count = Keyword.get(opts, :count, 20)
|
||||||
|
power = Keyword.get(opts, :power, 2)
|
||||||
|
IO.puts("Generating #{count} intervals for a power #{power} series...")
|
||||||
|
counts = Enum.map(1..count, fn n -> :math.pow(n, power) end)
|
||||||
|
sum = Enum.sum(counts)
|
||||||
|
|
||||||
|
densities =
|
||||||
|
Enum.map(counts, fn c ->
|
||||||
|
c / sum
|
||||||
|
end)
|
||||||
|
|
||||||
|
densities
|
||||||
|
|> Enum.reduce(0, fn density, acc ->
|
||||||
|
if acc == 0 do
|
||||||
|
[{0, density}]
|
||||||
|
else
|
||||||
|
[{_, lower} | _] = acc
|
||||||
|
[{lower, lower + density} | acc]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_tagged_activities(opts \\ []) do
|
||||||
|
tag_count = Keyword.get(opts, :tag_count, 20)
|
||||||
|
users = Keyword.get(opts, :users, Repo.all(User))
|
||||||
|
activity_count = Keyword.get(opts, :count, 200_000)
|
||||||
|
|
||||||
|
intervals = generate_power_intervals(count: tag_count)
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"Generating #{activity_count} activities using #{tag_count} different tags of format `tag_n`, starting at tag_0"
|
||||||
|
)
|
||||||
|
|
||||||
|
Enum.each(1..activity_count, fn _ ->
|
||||||
|
random = :rand.uniform()
|
||||||
|
i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end)
|
||||||
|
CommonAPI.post(Enum.random(users), %{"status" => "a post with the tag #tag_#{i}"})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_generate_activity_with_mention(user, users) do
|
defp do_generate_activity_with_mention(user, users) do
|
||||||
mentions_cnt = Enum.random([2, 3, 4, 5])
|
mentions_cnt = Enum.random([2, 3, 4, 5])
|
||||||
with_user = Enum.random([true, false])
|
with_user = Enum.random([true, false])
|
||||||
|
|
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||||
|
use Mix.Task
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.LoadTesting.Generator
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def run(_args) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
|
activities_count = Repo.aggregate(from(a in Pleroma.Activity), :count, :id)
|
||||||
|
|
||||||
|
if activities_count == 0 do
|
||||||
|
IO.puts("Did not find any activities, cleaning and generating")
|
||||||
|
clean_tables()
|
||||||
|
Generator.generate_users(users_max: 10)
|
||||||
|
Generator.generate_tagged_activities()
|
||||||
|
else
|
||||||
|
IO.puts("Found #{activities_count} activities, won't generate new ones")
|
||||||
|
end
|
||||||
|
|
||||||
|
tags = Enum.map(0..20, fn i -> {"For #tag_#{i}", "tag_#{i}"} end)
|
||||||
|
|
||||||
|
Enum.each(tags, fn {_, tag} ->
|
||||||
|
query =
|
||||||
|
from(o in Pleroma.Object,
|
||||||
|
where: fragment("(?)->'tag' \\? (?)", o.data, ^tag)
|
||||||
|
)
|
||||||
|
|
||||||
|
count = Repo.aggregate(query, :count, :id)
|
||||||
|
IO.puts("Database contains #{count} posts tagged with #{tag}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
user = Repo.all(Pleroma.User) |> List.first()
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"Hashtag fetching, any" => fn tags ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"any" => tags
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
# Will always return zero results because no overlapping hashtags are generated.
|
||||||
|
"Hashtag fetching, all" => fn tags ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"all" => tags
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs:
|
||||||
|
tags
|
||||||
|
|> Enum.map(fn {_, v} -> v end)
|
||||||
|
|> Enum.chunk_every(2)
|
||||||
|
|> Enum.map(fn tags -> {"For #{inspect(tags)}", tags} end),
|
||||||
|
time: 5
|
||||||
|
)
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"Hashtag fetching" => fn tag ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"tag" => tag
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs: tags,
|
||||||
|
time: 5
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean_tables do
|
||||||
|
IO.puts("Deleting old data...\n")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE users CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE activities CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE objects CASCADE;")
|
||||||
|
end
|
||||||
|
end
|
|
@ -561,7 +561,7 @@
|
||||||
|
|
||||||
config :pleroma,
|
config :pleroma,
|
||||||
:auth,
|
:auth,
|
||||||
enforce_oauth_admin_scope_usage: false,
|
enforce_oauth_admin_scope_usage: true,
|
||||||
oauth_consumer_strategies: oauth_consumer_strategies
|
oauth_consumer_strategies: oauth_consumer_strategies
|
||||||
|
|
||||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
||||||
|
|
|
@ -29,6 +29,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `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`
|
- `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`
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
|
- `emoji_reactions`: An object with all the emoji reactions with count. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
|
||||||
|
|
||||||
## Attachments
|
## Attachments
|
||||||
|
|
||||||
|
|
|
@ -312,9 +312,7 @@ def restrict_deactivated_users(query) do
|
||||||
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
from(activity in query,
|
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||||
where: activity.actor not in ^deactivated_users
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Activity.Queries do
|
||||||
@type query :: Ecto.Queryable.t() | Activity.t()
|
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
@spec by_ap_id(query, String.t()) :: query
|
@spec by_ap_id(query, String.t()) :: query
|
||||||
def by_ap_id(query \\ Activity, ap_id) do
|
def by_ap_id(query \\ Activity, ap_id) do
|
||||||
|
@ -29,6 +30,11 @@ def by_actor(query \\ Activity, actor) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec by_author(query, String.t()) :: query
|
||||||
|
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||||
|
from(a in query, where: a.actor == ^ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
||||||
def by_object_id(query \\ Activity, object_id)
|
def by_object_id(query \\ Activity, object_id)
|
||||||
|
|
||||||
|
@ -72,4 +78,8 @@ def exclude_type(query \\ Activity, activity_type) do
|
||||||
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exclude_authors(query \\ Activity, actors) do
|
||||||
|
from(activity in query, where: activity.actor not in ^actors)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,18 +26,23 @@ def search(user, search_query, options \\ []) do
|
||||||
|> query_with(index_type, search_query)
|
|> query_with(index_type, search_query)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> maybe_restrict_author(author)
|
|> maybe_restrict_author(author)
|
||||||
|
|> maybe_restrict_blocked(user)
|
||||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||||
|> maybe_fetch(user, search_query)
|
|> maybe_fetch(user, search_query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_restrict_author(query, %User{} = author) do
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
from([a, o] in query,
|
Activity.Queries.by_author(query, author)
|
||||||
where: a.actor == ^author.ap_id
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_restrict_author(query, _), do: query
|
def maybe_restrict_author(query, _), do: query
|
||||||
|
|
||||||
|
def maybe_restrict_blocked(query, %User{} = user) do
|
||||||
|
Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
defp restrict_public(q) do
|
defp restrict_public(q) do
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where: fragment("?->>'type' = 'Create'", a.data),
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
|
|
|
@ -33,6 +33,7 @@ def user_agent do
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
Pleroma.HTML.compile_scrubbers()
|
Pleroma.HTML.compile_scrubbers()
|
||||||
Pleroma.Config.DeprecationWarnings.warn()
|
Pleroma.Config.DeprecationWarnings.warn()
|
||||||
|
Pleroma.Repo.check_migrations_applied!()
|
||||||
setup_instrumenters()
|
setup_instrumenters()
|
||||||
load_custom_modules()
|
load_custom_modules()
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do
|
||||||
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
|
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
|
||||||
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
|
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
|
||||||
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
|
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
|
||||||
|
# Admin might opt out of admin scope for some apps to block any admin actions from them.
|
||||||
conn
|
conn
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres,
|
adapter: Ecto.Adapters.Postgres,
|
||||||
migration_timestamps: [type: :naive_datetime_usec]
|
migration_timestamps: [type: :naive_datetime_usec]
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
defmodule Instrumenter do
|
defmodule Instrumenter do
|
||||||
use Prometheus.EctoInstrumenter
|
use Prometheus.EctoInstrumenter
|
||||||
end
|
end
|
||||||
|
@ -47,4 +49,37 @@ def get_assoc(resource, association) do
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_migrations_applied!() do
|
||||||
|
unless Pleroma.Config.get(
|
||||||
|
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||||
|
false
|
||||||
|
) do
|
||||||
|
Ecto.Migrator.with_repo(__MODULE__, fn repo ->
|
||||||
|
down_migrations =
|
||||||
|
Ecto.Migrator.migrations(repo)
|
||||||
|
|> Enum.reject(fn
|
||||||
|
{:up, _, _} -> true
|
||||||
|
{:down, _, _} -> false
|
||||||
|
end)
|
||||||
|
|
||||||
|
if length(down_migrations) > 0 do
|
||||||
|
down_migrations_text =
|
||||||
|
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||||
|
|
||||||
|
Logger.error(
|
||||||
|
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||||
|
)
|
||||||
|
|
||||||
|
raise Pleroma.Repo.UnappliedMigrationsError
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Pleroma.Repo.UnappliedMigrationsError do
|
||||||
|
defexception message: "Unapplied Migrations detected"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1874,22 +1874,13 @@ defp truncate_field(%{"name" => name, "value" => value}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_api_update(user, params) do
|
def admin_api_update(user, params) do
|
||||||
changeset =
|
user
|
||||||
cast(user, params, [
|
|> cast(params, [
|
||||||
:is_moderator,
|
:is_moderator,
|
||||||
:is_admin,
|
:is_admin,
|
||||||
:show_role
|
:show_role
|
||||||
])
|
])
|
||||||
|
|> update_and_set_cache()
|
||||||
with {:ok, updated_user} <- update_and_set_cache(changeset) do
|
|
||||||
if user.is_admin != updated_user.is_admin do
|
|
||||||
# Admin status change results in change of accessible OAuth scopes, and instead of changing
|
|
||||||
# already issued tokens we revoke them, requiring user to sign in again
|
|
||||||
global_sign_out(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, updated_user}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Signs user out of all applications"
|
@doc "Signs user out of all applications"
|
||||||
|
|
|
@ -20,7 +20,7 @@ def filter(%{"type" => message_type} = message) do
|
||||||
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||||
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||||
true <-
|
true <-
|
||||||
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
|
||||||
false <-
|
false <-
|
||||||
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||||
{:ok, _} <- filter(message["object"]) do
|
{:ok, _} <- filter(message["object"]) do
|
||||||
|
|
|
@ -658,24 +658,8 @@ def handle_incoming(
|
||||||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||||
|
|
||||||
locked = new_user_data[:locked] || false
|
|
||||||
attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
|
|
||||||
invisible = new_user_data[:invisible] || false
|
|
||||||
|
|
||||||
fields =
|
|
||||||
attachment
|
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|
||||||
|
|
||||||
update_data =
|
|
||||||
new_user_data
|
|
||||||
|> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
|
|
||||||
|> Map.put(:fields, fields)
|
|
||||||
|> Map.put(:locked, locked)
|
|
||||||
|> Map.put(:invisible, invisible)
|
|
||||||
|
|
||||||
actor
|
actor
|
||||||
|> User.upgrade_changeset(update_data, true)
|
|> User.upgrade_changeset(new_user_data, true)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
ActivityPub.update(%{
|
ActivityPub.update(%{
|
||||||
|
|
|
@ -32,19 +32,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:accounts"], admin: true}
|
%{scopes: ["read:accounts"], admin: true}
|
||||||
when action in [:list_users, :user_show, :right_get, :invites]
|
when action in [:list_users, :user_show, :right_get]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["write:accounts"], admin: true}
|
%{scopes: ["write:accounts"], admin: true}
|
||||||
when action in [
|
when action in [
|
||||||
:get_invite_token,
|
|
||||||
:revoke_invite,
|
|
||||||
:email_invite,
|
|
||||||
:get_password_reset,
|
:get_password_reset,
|
||||||
:user_follow,
|
|
||||||
:user_unfollow,
|
|
||||||
:user_delete,
|
:user_delete,
|
||||||
:users_create,
|
:users_create,
|
||||||
:user_toggle_activation,
|
:user_toggle_activation,
|
||||||
|
@ -57,6 +52,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:invites"], admin: true}
|
||||||
|
when action in [:create_invite_token, :revoke_invite, :email_invite]
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:follows"], admin: true}
|
||||||
|
when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
|
||||||
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:reports"], admin: true}
|
%{scopes: ["read:reports"], admin: true}
|
||||||
|
@ -66,7 +75,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["write:reports"], admin: true}
|
%{scopes: ["write:reports"], admin: true}
|
||||||
when action in [:report_update_state, :report_respond]
|
when action in [:reports_update]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -90,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["write"], admin: true}
|
%{scopes: ["write"], admin: true}
|
||||||
when action in [:relay_follow, :relay_unfollow, :config_update]
|
when action == :config_update
|
||||||
)
|
)
|
||||||
|
|
||||||
@users_page_size 50
|
@users_page_size 50
|
||||||
|
@ -630,7 +639,7 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||||
def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||||
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
|
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
|
||||||
|
|
||||||
Enum.map(users, &User.force_password_reset_async/1)
|
Enum.each(users, &User.force_password_reset_async/1)
|
||||||
|
|
||||||
ModerationLog.insert_log(%{
|
ModerationLog.insert_log(%{
|
||||||
actor: admin,
|
actor: admin,
|
||||||
|
|
|
@ -85,9 +85,13 @@ def delete(activity_id, user) do
|
||||||
def repeat(id_or_ap_id, user, params \\ %{}) do
|
def repeat(id_or_ap_id, user, params \\ %{}) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
object <- Object.normalize(activity),
|
object <- Object.normalize(activity),
|
||||||
nil <- Utils.get_existing_announce(user.ap_id, object),
|
announce_activity <- Utils.get_existing_announce(user.ap_id, object),
|
||||||
public <- public_announce?(object, params) do
|
public <- public_announce?(object, params) do
|
||||||
ActivityPub.announce(user, object, nil, true, public)
|
if announce_activity do
|
||||||
|
{:ok, announce_activity, object}
|
||||||
|
else
|
||||||
|
ActivityPub.announce(user, object, nil, true, public)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
_ -> {:error, dgettext("errors", "Could not repeat")}
|
_ -> {:error, dgettext("errors", "Could not repeat")}
|
||||||
end
|
end
|
||||||
|
@ -105,8 +109,12 @@ def unrepeat(id_or_ap_id, user) do
|
||||||
def favorite(id_or_ap_id, user) do
|
def favorite(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
object <- Object.normalize(activity),
|
object <- Object.normalize(activity),
|
||||||
nil <- Utils.get_existing_like(user.ap_id, object) do
|
like_activity <- Utils.get_existing_like(user.ap_id, object) do
|
||||||
ActivityPub.like(user, object)
|
if like_activity do
|
||||||
|
{:ok, like_activity, object}
|
||||||
|
else
|
||||||
|
ActivityPub.like(user, object)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
_ -> {:error, dgettext("errors", "Could not favorite")}
|
_ -> {:error, dgettext("errors", "Could not favorite")}
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,6 +23,23 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||||
|
|
||||||
# GET /api/v1/notifications
|
# GET /api/v1/notifications
|
||||||
|
def index(conn, %{"account_id" => account_id} = params) do
|
||||||
|
case Pleroma.User.get_cached_by_id(account_id) do
|
||||||
|
%{ap_id: account_ap_id} ->
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.delete("account_id")
|
||||||
|
|> Map.put("account_ap_id", account_ap_id)
|
||||||
|
|
||||||
|
index(conn, params)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{"error" => "Account is not found"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
notifications = MastodonAPI.get_notifications(user, params)
|
notifications = MastodonAPI.get_notifications(user, params)
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para
|
||||||
result =
|
result =
|
||||||
default_values
|
default_values
|
||||||
|> Enum.map(fn {resource, default_value} ->
|
|> Enum.map(fn {resource, default_value} ->
|
||||||
if params["type"] == nil or params["type"] == resource do
|
if params["type"] in [nil, resource] do
|
||||||
{resource, fn -> resource_search(version, resource, query, options) end}
|
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||||
else
|
else
|
||||||
{resource, fn -> default_value end}
|
{resource, fn -> default_value end}
|
||||||
|
|
|
@ -6,9 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
|
||||||
@moduledoc "The module represents functions to manage user subscriptions."
|
@moduledoc "The module represents functions to manage user subscriptions."
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Push.Subscription
|
alias Pleroma.Web.Push.Subscription
|
||||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
|
|
|
@ -77,10 +77,7 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/tag/:tag
|
def hashtag_fetching(params, user, local_only) do
|
||||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
local_only = truthy_param?(params["local"])
|
|
||||||
|
|
||||||
tags =
|
tags =
|
||||||
[params["tag"], params["any"]]
|
[params["tag"], params["any"]]
|
||||||
|> List.flatten()
|
|> List.flatten()
|
||||||
|
@ -98,7 +95,7 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.get("none", [])
|
|> Map.get("none", [])
|
||||||
|> Enum.map(&String.downcase(&1))
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
activities =
|
_activities =
|
||||||
params
|
params
|
||||||
|> Map.put("type", "Create")
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("local_only", local_only)
|
|> Map.put("local_only", local_only)
|
||||||
|
@ -109,6 +106,13 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("tag_all", tag_all)
|
|> Map.put("tag_all", tag_all)
|
||||||
|> Map.put("tag_reject", tag_reject)
|
|> Map.put("tag_reject", tag_reject)
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/tag/:tag
|
||||||
|
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
local_only = truthy_param?(params["local"])
|
||||||
|
|
||||||
|
activities = hashtag_fetching(params, user, local_only)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities, %{"local" => local_only})
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|
|
|
@ -56,6 +56,7 @@ def get_notifications(user, params \\ %{}) do
|
||||||
user
|
user
|
||||||
|> Notification.for_user_query(options)
|
|> Notification.for_user_query(options)
|
||||||
|> restrict(:exclude_types, options)
|
|> restrict(:exclude_types, options)
|
||||||
|
|> restrict(:account_ap_id, options)
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -71,7 +72,8 @@ defp cast_params(params) do
|
||||||
exclude_visibilities: {:array, :string},
|
exclude_visibilities: {:array, :string},
|
||||||
reblogs: :boolean,
|
reblogs: :boolean,
|
||||||
with_muted: :boolean,
|
with_muted: :boolean,
|
||||||
with_move: :boolean
|
with_move: :boolean,
|
||||||
|
account_ap_id: :string
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
@ -88,5 +90,9 @@ defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]})
|
||||||
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do
|
||||||
|
where(query, [n, a], a.actor == ^account_ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
defp restrict(query, _, _), do: query
|
defp restrict(query, _, _), do: query
|
||||||
end
|
end
|
||||||
|
|
|
@ -253,6 +253,16 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
emoji_reactions =
|
||||||
|
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
||||||
|
Enum.map(emoji_reactions, fn {emoji, users} ->
|
||||||
|
{emoji, length(users)}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
else
|
||||||
|
_ -> %{}
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object.data["id"],
|
uri: object.data["id"],
|
||||||
|
@ -293,7 +303,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
spoiler_text: %{"text/plain" => summary_plaintext},
|
spoiler_text: %{"text/plain" => summary_plaintext},
|
||||||
expires_at: expires_at,
|
expires_at: expires_at,
|
||||||
direct_conversation_id: direct_conversation_id,
|
direct_conversation_id: direct_conversation_id,
|
||||||
thread_muted: thread_muted?
|
thread_muted: thread_muted?,
|
||||||
|
emoji_reactions: emoji_reactions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
alias Pleroma.Web.ControllerHelper
|
alias Pleroma.Web.ControllerHelper
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
|
alias Pleroma.Web.OAuth.Scopes
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
||||||
alias Pleroma.Web.OAuth.Scopes
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ def token_exchange(
|
||||||
{:user_active, true} <- {:user_active, !user.deactivated},
|
{:user_active, true} <- {:user_active, !user.deactivated},
|
||||||
{:password_reset_pending, false} <-
|
{:password_reset_pending, false} <-
|
||||||
{:password_reset_pending, user.password_reset_pending},
|
{:password_reset_pending, user.password_reset_pending},
|
||||||
{:ok, scopes} <- validate_scopes(app, params, user),
|
{: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, Token.Response.build(user, token))
|
json(conn, Token.Response.build(user, token))
|
||||||
|
@ -471,7 +471,7 @@ defp do_create_authorization(
|
||||||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
|
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
|
||||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||||
true <- redirect_uri in String.split(app.redirect_uris),
|
true <- redirect_uri in String.split(app.redirect_uris),
|
||||||
{:ok, scopes} <- validate_scopes(app, auth_attrs, user),
|
{:ok, scopes} <- validate_scopes(app, auth_attrs),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
||||||
Authorization.create_authorization(app, user, scopes)
|
Authorization.create_authorization(app, user, scopes)
|
||||||
end
|
end
|
||||||
|
@ -487,12 +487,12 @@ defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :re
|
||||||
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
||||||
do: put_session(conn, :registration_id, registration_id)
|
do: put_session(conn, :registration_id, registration_id)
|
||||||
|
|
||||||
@spec validate_scopes(App.t(), map(), User.t()) ::
|
@spec validate_scopes(App.t(), map()) ::
|
||||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||||
defp validate_scopes(%App{} = app, params, %User{} = user) do
|
defp validate_scopes(%App{} = app, params) do
|
||||||
params
|
params
|
||||||
|> Scopes.fetch_scopes(app.scopes)
|
|> Scopes.fetch_scopes(app.scopes)
|
||||||
|> Scopes.validate(app.scopes, user)
|
|> Scopes.validate(app.scopes)
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_redirect_uri(%App{} = app) do
|
def default_redirect_uri(%App{} = app) do
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.Scopes do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Fetch scopes from request params.
|
Fetch scopes from request params.
|
||||||
|
@ -56,35 +55,18 @@ def to_string(scopes), do: Enum.join(scopes, " ")
|
||||||
@doc """
|
@doc """
|
||||||
Validates scopes.
|
Validates scopes.
|
||||||
"""
|
"""
|
||||||
@spec validate(list() | nil, list(), User.t()) ::
|
@spec validate(list() | nil, list()) ::
|
||||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||||
def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []],
|
def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []],
|
||||||
do: {:error, :missing_scopes}
|
do: {:error, :missing_scopes}
|
||||||
|
|
||||||
def validate(scopes, app_scopes, %User{} = user) do
|
def validate(scopes, app_scopes) do
|
||||||
with {:ok, _} <- ensure_scopes_support(scopes, app_scopes),
|
|
||||||
{:ok, scopes} <- authorize_admin_scopes(scopes, app_scopes, user) do
|
|
||||||
{:ok, scopes}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp ensure_scopes_support(scopes, app_scopes) do
|
|
||||||
case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
|
case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
|
||||||
^scopes -> {:ok, scopes}
|
^scopes -> {:ok, scopes}
|
||||||
_ -> {:error, :unsupported_scopes}
|
_ -> {:error, :unsupported_scopes}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do
|
|
||||||
if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do
|
|
||||||
{:ok, scopes}
|
|
||||||
else
|
|
||||||
# Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising)
|
|
||||||
scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"])
|
|
||||||
validate(scopes, app_scopes, user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def contains_admin_scopes?(scopes) do
|
def contains_admin_scopes?(scopes) do
|
||||||
scopes
|
scopes
|
||||||
|> OAuthScopesPlug.filter_descendants(["admin"])
|
|> OAuthScopesPlug.filter_descendants(["admin"])
|
||||||
|
|
|
@ -23,7 +23,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:statuses"]}
|
%{scopes: ["read:statuses"]}
|
||||||
when action in [:conversation, :conversation_statuses, :emoji_reactions_by]
|
when action in [:conversation, :conversation_statuses]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -124,7 +124,7 @@ defp deps do
|
||||||
{:earmark, "~> 1.3"},
|
{:earmark, "~> 1.3"},
|
||||||
{:bbcode, "~> 0.1.1"},
|
{:bbcode, "~> 0.1.1"},
|
||||||
{:ex_machina, "~> 2.3", only: :test},
|
{:ex_machina, "~> 2.3", only: :test},
|
||||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
|
||||||
{:mock, "~> 0.3.3", only: :test},
|
{:mock, "~> 0.3.3", only: :test},
|
||||||
{:crypt,
|
{:crypt,
|
||||||
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -16,7 +16,7 @@
|
||||||
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"},
|
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"},
|
||||||
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
||||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
|
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||||
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
||||||
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddScopesToPleromaFEOAuthRecords do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
update_scopes_clause = "SET scopes = '{read,write,follow,push,admin}'"
|
||||||
|
apps_where = "WHERE apps.client_name like 'PleromaFE_%' or apps.client_name like 'AdminFE_%'"
|
||||||
|
app_id_subquery_where = "WHERE app_id IN (SELECT apps.id FROM apps #{apps_where})"
|
||||||
|
|
||||||
|
execute("UPDATE apps #{update_scopes_clause} #{apps_where}")
|
||||||
|
|
||||||
|
for table <- ["oauth_authorizations", "oauth_tokens"] do
|
||||||
|
execute("UPDATE #{table} #{update_scopes_clause} #{app_id_subquery_where}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: :noop
|
||||||
|
end
|
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1576166651574.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js></script><script type=text/javascript src=/static/js/app.a9b3f4c3e79baf3fa8b7.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1579102213354.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.86bc6d5e06d2e17976c5.js></script><script type=text/javascript src=/static/js/app.a43640742dacfb13b6b0.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
priv/static/static/font/fontello.1576166651574.svg → priv/static/static/font/fontello.1579102213354.svg
Executable file → Normal file
14
priv/static/static/font/fontello.1576166651574.svg → priv/static/static/font/fontello.1579102213354.svg
Executable file → Normal file
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
<?xml version="1.0" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
<metadata>Copyright (C) 2020 by original authors @ fontello.com</metadata>
|
||||||
<defs>
|
<defs>
|
||||||
<font id="fontello" horiz-adv-x="1000" >
|
<font id="fontello" horiz-adv-x="1000" >
|
||||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
||||||
|
@ -64,6 +64,18 @@
|
||||||
|
|
||||||
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="chat" unicode="" d="M786 428q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="info-circled" unicode="" d="M571 89v89q0 8-5 13t-12 5h-54v286q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h53v-179h-53q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h250q7 0 12 5t5 13z m-71 500v89q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h107q8 0 13 5t5 13z m357-232q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="login" unicode="" d="M661 357q0-14-11-25l-303-304q-11-10-26-10t-25 10-10 25v161h-250q-15 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 10 25t25 10 26-10l303-304q11-10 11-25z m196 196v-392q0-67-47-114t-114-47h-178q-7 0-13 5t-5 13q0 2-1 11t0 15 2 13 5 11 12 3h178q37 0 64 27t26 63v392q0 37-26 64t-64 26h-174t-6 0-6 2-5 3-4 5-1 8q0 2-1 11t0 15 2 13 5 11 12 3h178q67 0 114-47t47-114z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
||||||
|
|
||||||
|
<glyph glyph-name="arrow-curved" unicode="" d="M799 302l0-56 112 0-223-223-224 223 112 0 0 56q0 116-81 197t-197 82-198-82-82-197q0 162 115 276t276 114 276-114 114-276z" horiz-adv-x="928" />
|
||||||
|
|
||||||
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Binary file not shown.
BIN
priv/static/static/font/fontello.1579102213354.woff
Normal file
BIN
priv/static/static/font/fontello.1579102213354.woff
Normal file
Binary file not shown.
BIN
priv/static/static/font/fontello.1579102213354.woff2
Normal file
BIN
priv/static/static/font/fontello.1579102213354.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
@ -303,6 +303,42 @@
|
||||||
"css": "gauge",
|
"css": "gauge",
|
||||||
"code": 61668,
|
"code": 61668,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "31972e4e9d080eaa796290349ae6c1fd",
|
||||||
|
"css": "users",
|
||||||
|
"code": 59421,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
||||||
|
"css": "info-circled",
|
||||||
|
"code": 59423,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
|
||||||
|
"css": "home-2",
|
||||||
|
"code": 59425,
|
||||||
|
"src": "typicons"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
|
||||||
|
"css": "chat",
|
||||||
|
"code": 59422,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "3a00327e61b997b58518bd43ed83c3df",
|
||||||
|
"css": "login",
|
||||||
|
"code": 59424,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "f3ebd6751c15a280af5cc5f4a764187d",
|
||||||
|
"css": "arrow-curved",
|
||||||
|
"code": 59426,
|
||||||
|
"src": "iconic"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
BIN
priv/static/static/js/2.8896ea39a0ea8016391a.js
Normal file
BIN
priv/static/static/js/2.8896ea39a0ea8016391a.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/2.8896ea39a0ea8016391a.js.map
Normal file
BIN
priv/static/static/js/2.8896ea39a0ea8016391a.js.map
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/app.a43640742dacfb13b6b0.js
Normal file
BIN
priv/static/static/js/app.a43640742dacfb13b6b0.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/app.a43640742dacfb13b6b0.js.map
Normal file
BIN
priv/static/static/js/app.a43640742dacfb13b6b0.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js
Normal file
BIN
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map
Normal file
BIN
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map
Normal file
Binary file not shown.
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
|
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
|
||||||
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||||
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -745,7 +745,7 @@ test "it doesn't return notifications from a blocked user when with_muted is set
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
assert length(Notification.for_user(user, %{with_muted: true})) == 0
|
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't return notifications from a domain-blocked user when with_muted is set" do
|
test "it doesn't return notifications from a domain-blocked user when with_muted is set" do
|
||||||
|
@ -755,7 +755,7 @@ test "it doesn't return notifications from a domain-blocked user when with_muted
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
assert length(Notification.for_user(user, %{with_muted: true})) == 0
|
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns notifications from muted threads when with_muted is set" do
|
test "it returns notifications from muted threads when with_muted is set" do
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
defmodule Pleroma.RepoTest do
|
defmodule Pleroma.RepoTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
import ExUnit.CaptureLog
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
import Mock
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
describe "find_resource/1" do
|
describe "find_resource/1" do
|
||||||
|
@ -46,4 +49,44 @@ test "return error if has not assoc " do
|
||||||
assert Repo.get_assoc(token, :user) == {:error, :not_found}
|
assert Repo.get_assoc(token, :user) == {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "check_migrations_applied!" do
|
||||||
|
setup_with_mocks([
|
||||||
|
{Ecto.Migrator, [],
|
||||||
|
[
|
||||||
|
with_repo: fn repo, fun -> passthrough([repo, fun]) end,
|
||||||
|
migrations: fn Pleroma.Repo ->
|
||||||
|
[
|
||||||
|
{:up, 20_191_128_153_944, "fix_missing_following_count"},
|
||||||
|
{:up, 20_191_203_043_610, "create_report_notes"},
|
||||||
|
{:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
]}
|
||||||
|
]) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "raises if it detects unapplied migrations" do
|
||||||
|
assert_raise Pleroma.Repo.UnappliedMigrationsError, fn ->
|
||||||
|
capture_log(&Repo.check_migrations_applied!/0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't do anything if disabled" do
|
||||||
|
disable_migration_check =
|
||||||
|
Pleroma.Config.get([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
|
||||||
|
|
||||||
|
Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Pleroma.Config.put(
|
||||||
|
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||||
|
disable_migration_check
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert :ok == Repo.check_migrations_applied!()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -78,7 +78,7 @@ test "with locked accounts, it does not create a follow or an accept" do
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
assert length(accepts) == 0
|
assert Enum.empty?(accepts)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for follow requests when you are already followed, creating a new accept activity" do
|
test "it works for follow requests when you are already followed, creating a new accept activity" do
|
||||||
|
|
|
@ -1363,6 +1363,30 @@ test "returns 404 when report id is invalid", %{conn: conn} do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
|
||||||
|
read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
|
||||||
|
write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:token, read_token)
|
||||||
|
|> patch("/api/pleroma/admin/reports", %{
|
||||||
|
"reports" => [%{"state" => "resolved", "id" => id}]
|
||||||
|
})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
assert response == %{
|
||||||
|
"error" => "Insufficient permissions: admin:write:reports."
|
||||||
|
}
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> assign(:token, write_token)
|
||||||
|
|> patch("/api/pleroma/admin/reports", %{
|
||||||
|
"reports" => [%{"state" => "resolved", "id" => id}]
|
||||||
|
})
|
||||||
|
|> json_response(:no_content)
|
||||||
|
end
|
||||||
|
|
||||||
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
|
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
|
||||||
conn
|
conn
|
||||||
|> patch("/api/pleroma/admin/reports", %{
|
|> patch("/api/pleroma/admin/reports", %{
|
||||||
|
@ -2840,7 +2864,7 @@ test "GET /instances/:instance/statuses", %{conn: conn} do
|
||||||
|
|
||||||
response = json_response(ret_conn, 200)
|
response = json_response(ret_conn, 200)
|
||||||
|
|
||||||
assert length(response) == 0
|
assert Enum.empty?(response)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -284,22 +284,22 @@ test "favoriting a status" do
|
||||||
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
|
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "retweeting a status twice returns an error" do
|
test "retweeting a status twice returns the status" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||||
{:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
|
{:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
|
||||||
{:error, _} = CommonAPI.repeat(activity.id, user)
|
{:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "favoriting a status twice returns an error" do
|
test "favoriting a status twice returns the status" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||||
{:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
|
{:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user)
|
||||||
{:error, _} = CommonAPI.favorite(activity.id, user)
|
{:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -307,7 +307,7 @@ test "for private posts, not a reply" do
|
||||||
|
|
||||||
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil)
|
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil)
|
||||||
assert length(to) == 2
|
assert length(to) == 2
|
||||||
assert length(cc) == 0
|
assert Enum.empty?(cc)
|
||||||
|
|
||||||
assert mentioned_user.ap_id in to
|
assert mentioned_user.ap_id in to
|
||||||
assert user.follower_address in to
|
assert user.follower_address in to
|
||||||
|
@ -323,7 +323,7 @@ test "for private posts, a reply" do
|
||||||
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil)
|
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil)
|
||||||
|
|
||||||
assert length(to) == 3
|
assert length(to) == 3
|
||||||
assert length(cc) == 0
|
assert Enum.empty?(cc)
|
||||||
|
|
||||||
assert mentioned_user.ap_id in to
|
assert mentioned_user.ap_id in to
|
||||||
assert third_user.ap_id in to
|
assert third_user.ap_id in to
|
||||||
|
@ -338,7 +338,7 @@ test "for direct posts, not a reply" do
|
||||||
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil)
|
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil)
|
||||||
|
|
||||||
assert length(to) == 1
|
assert length(to) == 1
|
||||||
assert length(cc) == 0
|
assert Enum.empty?(cc)
|
||||||
|
|
||||||
assert mentioned_user.ap_id in to
|
assert mentioned_user.ap_id in to
|
||||||
end
|
end
|
||||||
|
@ -353,7 +353,7 @@ test "for direct posts, a reply" do
|
||||||
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil)
|
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil)
|
||||||
|
|
||||||
assert length(to) == 2
|
assert length(to) == 2
|
||||||
assert length(cc) == 0
|
assert Enum.empty?(cc)
|
||||||
|
|
||||||
assert mentioned_user.ap_id in to
|
assert mentioned_user.ap_id in to
|
||||||
assert third_user.ap_id in to
|
assert third_user.ap_id in to
|
||||||
|
|
|
@ -457,6 +457,30 @@ test "preserves parameters in link headers" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "from specified user" do
|
||||||
|
test "account_id" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
|
|
||||||
|
%{id: account_id} = other_user1 = insert(:user)
|
||||||
|
other_user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert [%{"account" => %{"id" => ^account_id}}] =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/notifications", %{account_id: account_id})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert %{"error" => "Account is not found"} =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/notifications", %{account_id: "cofe"})
|
||||||
|
|> json_response(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_notification_id_by_activity(%{id: id}) do
|
defp get_notification_id_by_activity(%{id: id}) do
|
||||||
Notification
|
Notification
|
||||||
|> Repo.get_by(activity_id: id)
|
|> Repo.get_by(activity_id: id)
|
||||||
|
|
|
@ -53,7 +53,8 @@ test "search", %{conn: conn} do
|
||||||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||||
|
|
||||||
results =
|
results =
|
||||||
get(conn, "/api/v2/search", %{"q" => "2hu #private"})
|
conn
|
||||||
|
|> get("/api/v2/search", %{"q" => "2hu #private"})
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
[account | _] = results["accounts"]
|
[account | _] = results["accounts"]
|
||||||
|
@ -73,6 +74,30 @@ test "search", %{conn: conn} do
|
||||||
[status] = results["statuses"]
|
[status] = results["statuses"]
|
||||||
assert status["id"] == to_string(activity.id)
|
assert status["id"] == to_string(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "excludes a blocked users from search results", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})
|
||||||
|
user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"})
|
||||||
|
|
||||||
|
{:ok, act1} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"})
|
||||||
|
{:ok, act2} = CommonAPI.post(user_smith, %{"status" => "Agent Smith"})
|
||||||
|
{:ok, act3} = CommonAPI.post(user_neo, %{"status" => "Agent Smith"})
|
||||||
|
Pleroma.User.block(user, user_smith)
|
||||||
|
|
||||||
|
results =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
||||||
|
|> get("/api/v2/search", %{"q" => "Agent"})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
status_ids = Enum.map(results["statuses"], fn g -> g["id"] end)
|
||||||
|
|
||||||
|
assert act3.id in status_ids
|
||||||
|
refute act2.id in status_ids
|
||||||
|
refute act1.id in status_ids
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".account_search" do
|
describe ".account_search" do
|
||||||
|
@ -146,11 +171,10 @@ test "search", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||||
|
|
||||||
conn =
|
results =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/search", %{"q" => "2hu"})
|
|> get("/api/v1/search", %{"q" => "2hu"})
|
||||||
|
|> json_response(200)
|
||||||
assert results = json_response(conn, 200)
|
|
||||||
|
|
||||||
[account | _] = results["accounts"]
|
[account | _] = results["accounts"]
|
||||||
assert account["id"] == to_string(user_three.id)
|
assert account["id"] == to_string(user_three.id)
|
||||||
|
@ -168,11 +192,10 @@ test "search fetches remote statuses and prefers them over other results", %{con
|
||||||
"status" => "check out https://shitposter.club/notice/2827873"
|
"status" => "check out https://shitposter.club/notice/2827873"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn =
|
results =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||||
|
|> json_response(200)
|
||||||
assert results = json_response(conn, 200)
|
|
||||||
|
|
||||||
[status, %{"id" => ^activity_id}] = results["statuses"]
|
[status, %{"id" => ^activity_id}] = results["statuses"]
|
||||||
|
|
||||||
|
@ -189,11 +212,10 @@ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
||||||
})
|
})
|
||||||
|
|
||||||
capture_log(fn ->
|
capture_log(fn ->
|
||||||
conn =
|
results =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
|
|> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
|
||||||
|
|> json_response(200)
|
||||||
assert results = json_response(conn, 200)
|
|
||||||
|
|
||||||
[] = results["statuses"]
|
[] = results["statuses"]
|
||||||
end)
|
end)
|
||||||
|
@ -202,23 +224,23 @@ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
||||||
test "search fetches remote accounts", %{conn: conn} do
|
test "search fetches remote accounts", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn =
|
results =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
||||||
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"})
|
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
assert results = json_response(conn, 200)
|
|
||||||
[account] = results["accounts"]
|
[account] = results["accounts"]
|
||||||
assert account["acct"] == "mike@osada.macgirvin.com"
|
assert account["acct"] == "mike@osada.macgirvin.com"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
|
test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
|
||||||
conn =
|
results =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"})
|
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
assert results = json_response(conn, 200)
|
|
||||||
assert [] == results["accounts"]
|
assert [] == results["accounts"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -638,6 +638,13 @@ test "favs a status and returns it", %{conn: conn} do
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "favoriting twice will just return 200", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
post(conn, "/api/v1/statuses/#{activity.id}/favourite")
|
||||||
|
assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
|
||||||
|
end
|
||||||
|
|
||||||
test "returns 400 error for a wrong id", %{conn: conn} do
|
test "returns 400 error for a wrong id", %{conn: conn} do
|
||||||
conn = post(conn, "/api/v1/statuses/1/favourite")
|
conn = post(conn, "/api/v1/statuses/1/favourite")
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "has an emoji reaction list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"})
|
||||||
|
|
||||||
|
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
||||||
|
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
||||||
|
activity = Repo.get(Activity, activity.id)
|
||||||
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
|
assert status[:pleroma][:emoji_reactions]["🍵"] == 1
|
||||||
|
assert status[:pleroma][:emoji_reactions]["☕"] == 2
|
||||||
|
end
|
||||||
|
|
||||||
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
|
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -172,7 +188,8 @@ test "a note activity" do
|
||||||
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
||||||
expires_at: nil,
|
expires_at: nil,
|
||||||
direct_conversation_id: nil,
|
direct_conversation_id: nil,
|
||||||
thread_muted: false
|
thread_muted: false,
|
||||||
|
emoji_reactions: %{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -568,29 +568,34 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with
|
||||||
|
|
||||||
describe "POST /oauth/authorize" do
|
describe "POST /oauth/authorize" do
|
||||||
test "redirects with oauth authorization, " <>
|
test "redirects with oauth authorization, " <>
|
||||||
"keeping only non-admin scopes for non-admin user" do
|
"granting requested app-supported scopes to both admin- and non-admin users" do
|
||||||
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
app_scopes = ["read", "write", "admin", "secret_scope"]
|
||||||
|
app = insert(:oauth_app, scopes: app_scopes)
|
||||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||||
|
|
||||||
non_admin = insert(:user, is_admin: false)
|
non_admin = insert(:user, is_admin: false)
|
||||||
admin = insert(:user, is_admin: true)
|
admin = insert(:user, is_admin: true)
|
||||||
|
scopes_subset = ["read:subscope", "write", "admin"]
|
||||||
|
|
||||||
for {user, expected_scopes} <- %{
|
# In case scope param is missing, expecting _all_ app-supported scopes to be granted
|
||||||
non_admin => ["read:subscope", "write"],
|
for user <- [non_admin, admin],
|
||||||
admin => ["read:subscope", "write", "admin"]
|
{requested_scopes, expected_scopes} <-
|
||||||
} do
|
%{scopes_subset => scopes_subset, nil => app_scopes} do
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
post(
|
||||||
|> post("/oauth/authorize", %{
|
build_conn(),
|
||||||
"authorization" => %{
|
"/oauth/authorize",
|
||||||
"name" => user.nickname,
|
%{
|
||||||
"password" => "test",
|
"authorization" => %{
|
||||||
"client_id" => app.client_id,
|
"name" => user.nickname,
|
||||||
"redirect_uri" => redirect_uri,
|
"password" => "test",
|
||||||
"scope" => "read:subscope write admin",
|
"client_id" => app.client_id,
|
||||||
"state" => "statepassed"
|
"redirect_uri" => redirect_uri,
|
||||||
|
"scope" => requested_scopes,
|
||||||
|
"state" => "statepassed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
target = redirected_to(conn)
|
target = redirected_to(conn)
|
||||||
assert target =~ redirect_uri
|
assert target =~ redirect_uri
|
||||||
|
@ -631,34 +636,31 @@ test "returns 401 for wrong credentials", %{conn: conn} do
|
||||||
assert result =~ "Invalid Username/Password"
|
assert result =~ "Invalid Username/Password"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 401 for missing scopes " <>
|
test "returns 401 for missing scopes" do
|
||||||
"(including all admin-only scopes for non-admin user)" do
|
|
||||||
user = insert(:user, is_admin: false)
|
user = insert(:user, is_admin: false)
|
||||||
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
||||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||||
|
|
||||||
for scope_param <- ["", "admin:read admin:write"] do
|
result =
|
||||||
result =
|
build_conn()
|
||||||
build_conn()
|
|> post("/oauth/authorize", %{
|
||||||
|> post("/oauth/authorize", %{
|
"authorization" => %{
|
||||||
"authorization" => %{
|
"name" => user.nickname,
|
||||||
"name" => user.nickname,
|
"password" => "test",
|
||||||
"password" => "test",
|
"client_id" => app.client_id,
|
||||||
"client_id" => app.client_id,
|
"redirect_uri" => redirect_uri,
|
||||||
"redirect_uri" => redirect_uri,
|
"state" => "statepassed",
|
||||||
"state" => "statepassed",
|
"scope" => ""
|
||||||
"scope" => scope_param
|
}
|
||||||
}
|
})
|
||||||
})
|
|> html_response(:unauthorized)
|
||||||
|> html_response(:unauthorized)
|
|
||||||
|
|
||||||
# Keep the details
|
# Keep the details
|
||||||
assert result =~ app.client_id
|
assert result =~ app.client_id
|
||||||
assert result =~ redirect_uri
|
assert result =~ redirect_uri
|
||||||
|
|
||||||
# Error message
|
# Error message
|
||||||
assert result =~ "This action is outside the authorized scopes"
|
assert result =~ "This action is outside the authorized scopes"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
|
test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
|
||||||
|
|
|
@ -14,6 +14,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
|
||||||
"emoji"
|
"emoji"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
clear_config([:auth, :enforce_oauth_admin_scope_usage]) do
|
||||||
|
Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
|
||||||
|
end
|
||||||
|
|
||||||
test "shared & non-shared pack information in list_packs is ok" do
|
test "shared & non-shared pack information in list_packs is ok" do
|
||||||
conn = build_conn()
|
conn = build_conn()
|
||||||
resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
|
resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
|
||||||
|
|
|
@ -57,11 +57,6 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read:statuses"]))
|
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
||||||
|
|
|
@ -55,7 +55,7 @@ test "it returns HTTP 200", %{conn: conn} do
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
|
assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
|
||||||
assert length(Token.get_user_tokens(user)) == 0
|
assert Enum.empty?(Token.get_user_tokens(user))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it sets password_reset_pending to false", %{conn: conn} do
|
test "it sets password_reset_pending to false", %{conn: conn} do
|
||||||
|
|
Loading…
Reference in a new issue