Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2020-01-16 16:17:56 +00:00
commit 4e069ce537
200 changed files with 2127 additions and 2686 deletions

View file

@ -12,12 +12,14 @@ cache: &global_cache_policy
paths: paths:
- deps - deps
- _build - _build
stages: stages:
- build - build
- test - test
- benchmark - benchmark
- deploy - deploy
- release - release
- docker
before_script: before_script:
- mix local.hex --force - mix local.hex --force
@ -264,3 +266,66 @@ arm64-musl:
variables: *release-variables variables: *release-variables
before_script: *before-release-musl before_script: *before-release-musl
script: *release script: *release
docker:
stage: docker
image: docker:latest
cache: {}
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
- 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_VCS_REF=$CI_COMMIT_SHORT_SHA
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 .
- docker push $IMAGE_TAG
- docker push $IMAGE_TAG_SLUG
- docker push $IMAGE_TAG_LATEST
tags:
- dind
only:
- 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

View file

@ -7,10 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Removed ### Removed
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
- **Breaking**: OStatus protocol support - **Breaking**: OStatus protocol support
- **Breaking**: MDII uploader
### Changed ### Changed
- **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
@ -88,6 +91,7 @@ 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
</details> </details>
### Fixed ### Fixed
@ -104,6 +108,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname` - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports) - AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports)
- Admin API: Error when trying to update reports in the "old" format - Admin API: Error when trying to update reports in the "old" format
- Mastodon API: Marking a conversation as read (`POST /api/v1/conversations/:id/read`) now no longer brings it to the top in the user's direct conversation list
</details> </details>
## [1.1.6] - 2019-11-19 ## [1.1.6] - 2019-11-19

View file

@ -14,6 +14,20 @@ RUN apk add git gcc g++ musl-dev make &&\
FROM alpine:3.9 FROM alpine:3.9
ARG BUILD_DATE
ARG VCS_REF
LABEL maintainer="ops@pleroma.social" \
org.opencontainers.image.title="pleroma" \
org.opencontainers.image.description="Pleroma for Docker" \
org.opencontainers.image.authors="ops@pleroma.social" \
org.opencontainers.image.vendor="pleroma.social" \
org.opencontainers.image.documentation="https://git.pleroma.social/pleroma/pleroma" \
org.opencontainers.image.licenses="AGPL-3.0" \
org.opencontainers.image.url="https://pleroma.social" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE
ARG HOME=/opt/pleroma ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma ARG DATA=/var/lib/pleroma

View file

@ -82,3 +82,11 @@
IO.puts("RUM enabled: #{rum_enabled}") IO.puts("RUM enabled: #{rum_enabled}")
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
if File.exists?("./config/benchmark.secret.exs") do
import_config "benchmark.secret.exs"
else
IO.puts(
"You may want to create benchmark.secret.exs to declare custom database connection parameters."
)
end

View file

@ -67,7 +67,7 @@
config :pleroma, Pleroma.Captcha, config :pleroma, Pleroma.Captcha,
enabled: true, enabled: true,
seconds_valid: 3000, seconds_valid: 300,
method: Pleroma.Captcha.Native method: Pleroma.Captcha.Native
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch" config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
@ -108,10 +108,6 @@
streaming_enabled: true, streaming_enabled: true,
public_endpoint: "https://s3.amazonaws.com" public_endpoint: "https://s3.amazonaws.com"
config :pleroma, Pleroma.Uploaders.MDII,
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
files: "https://mdii.sakura.ne.jp"
config :pleroma, :emoji, config :pleroma, :emoji,
shortcode_globs: ["/emoji/custom/**/*.png"], shortcode_globs: ["/emoji/custom/**/*.png"],
pack_extensions: [".png", ".gif"], pack_extensions: [".png", ".gif"],
@ -566,7 +562,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

View file

@ -2557,23 +2557,6 @@
} }
] ]
}, },
%{
group: :pleroma,
key: Pleroma.Uploaders.MDII,
type: :group,
children: [
%{
key: :cgi,
type: :string,
suggestions: ["https://mdii.sakura.ne.jp/mdii-post.cgi"]
},
%{
key: :files,
type: :string,
suggestions: ["https://mdii.sakura.ne.jp"]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :http, key: :http,

View file

@ -46,7 +46,7 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
Has these additional fields under the `pleroma` object: Has these additional fields under the `pleroma` object:
- `tags`: Lists an array of tags for the user - `tags`: Lists an array of tags for the user
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship - `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
- `is_moderator`: boolean, nullable, true if user is a moderator - `is_moderator`: boolean, nullable, true if user is a moderator
- `is_admin`: boolean, nullable, true if user is an admin - `is_admin`: boolean, nullable, true if user is an admin
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated - `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated

View file

@ -453,6 +453,7 @@ An example for Sendgrid adapter:
```elixir ```elixir
config :pleroma, Pleroma.Emails.Mailer, config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.Sendgrid, adapter: Swoosh.Adapters.Sendgrid,
api_key: "YOUR_API_KEY" api_key: "YOUR_API_KEY"
``` ```
@ -461,13 +462,13 @@ An example for SMTP adapter:
```elixir ```elixir
config :pleroma, Pleroma.Emails.Mailer, config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.SMTP, adapter: Swoosh.Adapters.SMTP,
relay: "smtp.gmail.com", relay: "smtp.gmail.com",
username: "YOUR_USERNAME@gmail.com", username: "YOUR_USERNAME@gmail.com",
password: "YOUR_SMTP_PASSWORD", password: "YOUR_SMTP_PASSWORD",
port: 465, port: 465,
ssl: true, ssl: true,
tls: :always,
auth: :always auth: :always
``` ```

View file

@ -64,11 +64,13 @@ def mark_as_read(%User{} = user, %Conversation{} = conversation) do
end end
def mark_as_read(participation) do def mark_as_read(participation) do
participation __MODULE__
|> read_cng(%{read: true}) |> where(id: ^participation.id)
|> Repo.update() |> update(set: [read: true])
|> select([p], p)
|> Repo.update_all([])
|> case do |> case do
{:ok, participation} -> {1, [participation]} ->
participation = Repo.preload(participation, :user) participation = Repo.preload(participation, :user)
User.set_unread_conversation_count(participation.user) User.set_unread_conversation_count(participation.user)
{:ok, participation} {:ok, participation}

View file

@ -17,6 +17,8 @@ defmodule Pleroma.Object do
require Logger require Logger
@type t() :: %__MODULE__{}
schema "objects" do schema "objects" do
field(:data, :map) field(:data, :map)
@ -79,6 +81,20 @@ def get_by_ap_id(ap_id) do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end end
@doc """
Get a single attachment by it's name and href
"""
@spec get_attachment_by_name_and_href(String.t(), String.t()) :: Object.t() | nil
def get_attachment_by_name_and_href(name, href) do
query =
from(o in Object,
where: fragment("(?)->>'name' = ?", o.data, ^name),
where: fragment("(?)->>'href' = ?", o.data, ^href)
)
Repo.one(query)
end
defp warn_on_no_object_preloaded(ap_id) do defp warn_on_no_object_preloaded(ap_id) do
"Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object" "Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object"
|> Logger.debug() |> Logger.debug()
@ -164,6 +180,7 @@ def swap_object_with_tombstone(object) do
def delete(%Object{data: %{"id" => id}} = object) do def delete(%Object{data: %{"id" => id}} = object) do
with {:ok, _obj} = swap_object_with_tombstone(object), with {:ok, _obj} = swap_object_with_tombstone(object),
:ok <- delete_attachments(object),
deleted_activity = Activity.delete_all_by_object_ap_id(id), deleted_activity = Activity.delete_all_by_object_ap_id(id),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
@ -171,6 +188,77 @@ def delete(%Object{data: %{"id" => id}} = object) do
end end
end end
defp delete_attachments(%{data: %{"attachment" => [_ | _] = attachments, "actor" => actor}}) do
hrefs =
Enum.flat_map(attachments, fn attachment ->
Enum.map(attachment["url"], & &1["href"])
end)
names = Enum.map(attachments, & &1["name"])
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
# find all objects for copies of the attachments, name and actor doesn't matter here
delete_ids =
from(o in Object,
where:
fragment(
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href'))::jsonb \\?| (?)",
o.data,
^hrefs
)
)
|> Repo.all()
# we should delete 1 object for any given attachment, but don't delete files if
# there are more than 1 object for it
|> Enum.reduce(%{}, fn %{
id: id,
data: %{
"url" => [%{"href" => href}],
"actor" => obj_actor,
"name" => name
}
},
acc ->
Map.update(acc, href, %{id: id, count: 1}, fn val ->
case obj_actor == actor and name in names do
true ->
# set id of the actor's object that will be deleted
%{val | id: id, count: val.count + 1}
false ->
# another actor's object, just increase count to not delete file
%{val | count: val.count + 1}
end
end)
end)
|> Enum.map(fn {href, %{id: id, count: count}} ->
# only delete files that have single instance
with 1 <- count do
prefix =
case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
nil -> "media"
_ -> ""
end
base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
uploader.delete_file(file_path)
end
id
end)
from(o in Object, where: o.id in ^delete_ids)
|> Repo.delete_all()
:ok
end
defp delete_attachments(%{data: _data}), do: :ok
def prune(%Object{data: %{"id" => id}} = object) do def prune(%Object{data: %{"id" => id}} = object) do
with {:ok, object} <- Repo.delete(object), with {:ok, object} <- Repo.delete(object),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),

View file

@ -18,16 +18,13 @@ def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
token = assigns[:token] token = assigns[:token]
scopes = transform_scopes(scopes, options) scopes = transform_scopes(scopes, options)
matched_scopes = token && filter_descendants(scopes, token.scopes) matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
cond do cond do
is_nil(token) -> token && op == :| && Enum.any?(matched_scopes) ->
maybe_perform_instance_privacy_check(conn, options)
op == :| && Enum.any?(matched_scopes) ->
conn conn
op == :& && matched_scopes == scopes -> token && op == :& && matched_scopes == scopes ->
conn conn
options[:fallback] == :proceed_unauthenticated -> options[:fallback] == :proceed_unauthenticated ->

View file

@ -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 ->

View file

@ -5,10 +5,12 @@
defmodule Pleroma.Uploaders.Local do defmodule Pleroma.Uploaders.Local do
@behaviour Pleroma.Uploaders.Uploader @behaviour Pleroma.Uploaders.Uploader
@impl true
def get_file(_) do def get_file(_) do
{:ok, {:static_dir, upload_path()}} {:ok, {:static_dir, upload_path()}}
end end
@impl true
def put_file(upload) do def put_file(upload) do
{local_path, file} = {local_path, file} =
case Enum.reverse(Path.split(upload.path)) do case Enum.reverse(Path.split(upload.path)) do
@ -33,4 +35,15 @@ def put_file(upload) do
def upload_path do def upload_path do
Pleroma.Config.get!([__MODULE__, :uploads]) Pleroma.Config.get!([__MODULE__, :uploads])
end end
@impl true
def delete_file(path) do
upload_path()
|> Path.join(path)
|> File.rm()
|> case do
:ok -> :ok
{:error, posix_error} -> {:error, to_string(posix_error)}
end
end
end end

View file

@ -1,37 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
alias Pleroma.Config
alias Pleroma.HTTP
@behaviour Pleroma.Uploaders.Uploader
# MDII-hosted images are never passed through the MediaPlug; only local media.
# Delegate to Pleroma.Uploaders.Local
def get_file(file) do
Pleroma.Uploaders.Local.get_file(file)
end
def put_file(upload) do
cgi = Config.get([Pleroma.Uploaders.MDII, :cgi])
files = Config.get([Pleroma.Uploaders.MDII, :files])
{:ok, file_data} = File.read(upload.tempfile)
extension = String.split(upload.name, ".") |> List.last()
query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <-
HTTP.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}}
else
_ -> Pleroma.Uploaders.Local.put_file(upload)
end
end
end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Uploaders.S3 do
# The file name is re-encoded with S3's constraints here to comply with previous # The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames # links with less strict filenames
@impl true
def get_file(file) do def get_file(file) do
config = Config.get([__MODULE__]) config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket) bucket = Keyword.fetch!(config, :bucket)
@ -35,6 +36,7 @@ def get_file(file) do
])}} ])}}
end end
@impl true
def put_file(%Pleroma.Upload{} = upload) do def put_file(%Pleroma.Upload{} = upload) do
config = Config.get([__MODULE__]) config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket) bucket = Keyword.get(config, :bucket)
@ -69,6 +71,18 @@ def put_file(%Pleroma.Upload{} = upload) do
end end
end end
@impl true
def delete_file(file) do
[__MODULE__, :bucket]
|> Config.get()
|> ExAws.S3.delete_object(file)
|> ExAws.request()
|> case do
{:ok, %{status_code: 204}} -> :ok
error -> {:error, inspect(error)}
end
end
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]") @regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
def strict_encode(name) do def strict_encode(name) do
String.replace(name, @regex, "-") String.replace(name, @regex, "-")

View file

@ -36,6 +36,8 @@ defmodule Pleroma.Uploaders.Uploader do
@callback put_file(Pleroma.Upload.t()) :: @callback put_file(Pleroma.Upload.t()) ::
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
@callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
@callback http_callback(Plug.Conn.t(), Map.t()) :: @callback http_callback(Plug.Conn.t(), Map.t()) ::
{:ok, Plug.Conn.t()} {:ok, Plug.Conn.t()}
| {:ok, Plug.Conn.t(), file_spec()} | {:ok, Plug.Conn.t(), file_spec()}
@ -43,7 +45,6 @@ defmodule Pleroma.Uploaders.Uploader do
@optional_callbacks http_callback: 2 @optional_callbacks http_callback: 2
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()} @spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
def put_file(uploader, upload) do def put_file(uploader, upload) do
case uploader.put_file(upload) do case uploader.put_file(upload) do
:ok -> {:ok, {:file, upload.path}} :ok -> {:ok, {:file, upload.path}}

View file

@ -1430,9 +1430,36 @@ def get_or_fetch_by_ap_id(ap_id) do
Creates an internal service actor by URI if missing. Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing. Optionally takes nickname for addressing.
""" """
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
with user when is_nil(user) <- get_cached_by_ap_id(uri) do def get_or_create_service_actor_by_ap_id(uri, nickname) do
{:ok, user} = {_, user} =
case get_cached_by_ap_id(uri) do
nil ->
with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
{:error, nil}
end
%User{invisible: false} = user ->
set_invisible(user)
user ->
{:ok, user}
end
user
end
@spec set_invisible(User.t()) :: {:ok, User.t()}
defp set_invisible(user) do
user
|> change(%{invisible: true})
|> update_and_set_cache()
end
@spec create_service_actor(String.t(), String.t()) ::
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
defp create_service_actor(uri, nickname) do
%User{ %User{
invisible: true, invisible: true,
local: true, local: true,
@ -1440,10 +1467,10 @@ def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
nickname: nickname, nickname: nickname,
follower_address: uri <> "/followers" follower_address: uri <> "/followers"
} }
|> change
|> unique_constraint(:nickname)
|> Repo.insert() |> Repo.insert()
|> set_cache()
user
end
end end
# AP style # AP style
@ -1847,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
# Tokens & authorizations containing any admin scopes must be revoked (revoking all).
# This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins).
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"

View file

@ -264,6 +264,10 @@ def gather_webfinger_links(%User{} = user) do
"rel" => "self", "rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id "href" => user.ap_id
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
} }
] ]
end end

View file

@ -9,10 +9,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
require Logger require Logger
@relay_nickname "relay"
def get_actor do def get_actor do
actor = actor =
relay_ap_id() relay_ap_id()
|> User.get_or_create_service_actor_by_ap_id() |> User.get_or_create_service_actor_by_ap_id(@relay_nickname)
actor actor
end end

View file

@ -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}
@ -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

View file

@ -20,19 +20,22 @@ defmodule Pleroma.Web.MastoFEController do
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index) plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
@doc "GET /web/*path" @doc "GET /web/*path"
def index(%{assigns: %{user: user}} = conn, _params) do def index(%{assigns: %{user: user, token: token}} = conn, _params)
token = get_session(conn, :oauth_token) when not is_nil(user) and not is_nil(token) do
if user && token do
conn conn
|> put_layout(false) |> put_layout(false)
|> render("index.html", token: token, user: user, custom_emojis: Pleroma.Emoji.get_all()) |> render("index.html",
else token: token.token,
user: user,
custom_emojis: Pleroma.Emoji.get_all()
)
end
def index(conn, _params) do
conn conn
|> put_session(:return_to, conn.request_path) |> put_session(:return_to, conn.request_path)
|> redirect(to: "/web/login") |> redirect(to: "/web/login")
end end
end
@doc "GET /web/manifest.json" @doc "GET /web/manifest.json"
def manifest(conn, _params) do def manifest(conn, _params) do

View file

@ -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)

View file

@ -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

View file

@ -15,6 +15,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|> String.replace(~r/<br\s?\/?>/, " ") |> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.get_cached_stripped_html_for_activity(object, "metadata") |> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|> Emoji.Formatter.demojify() |> Emoji.Formatter.demojify()
|> HtmlEntities.decode()
|> Formatter.truncate() |> Formatter.truncate()
end end
@ -25,6 +26,7 @@ def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content)
|> String.replace(~r/<br\s?\/?>/, " ") |> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags() |> HTML.strip_tags()
|> Emoji.Formatter.demojify() |> Emoji.Formatter.demojify()
|> HtmlEntities.decode()
|> Formatter.truncate(max_length) |> Formatter.truncate(max_length)
end end

View file

@ -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

View file

@ -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"])

View file

@ -52,7 +52,7 @@ def list_from(conn, %{"instance_address" => address}) do
@doc """ @doc """
Lists the packs available on the instance as JSON. Lists the packs available on the instance as JSON.
The information is public and does not require authentification. The format is The information is public and does not require authentication. The format is
a map of "pack directory name" to pack.json contents. a map of "pack directory name" to pack.json contents.
""" """
def list_packs(conn, _params) do def list_packs(conn, _params) do

View file

@ -22,7 +22,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses] %{scopes: ["read:statuses"]}
when action in [:conversation, :conversation_statuses, :emoji_reactions_by]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
when action in [:react_with_emoji, :unreact_with_emoji]
) )
plug( plug(

View file

@ -229,9 +229,9 @@ defmodule Pleroma.Web.Router do
pipe_through(:pleroma_html) pipe_through(:pleroma_html)
post("/main/ostatus", UtilController, :remote_subscribe) post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow) get("/ostatus_subscribe", RemoteFollowController, :follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow) post("/ostatus_subscribe", RemoteFollowController, :do_follow)
end end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do

View file

@ -0,0 +1,11 @@
<%= if @error == :error do %>
<h2>Error fetching user</h2>
<% else %>
<h2>Remote follow</h2>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<p><%= @followee.nickname %></p>
<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
<%= hidden_input f, :id, value: @followee.id %>
<%= submit "Authorize" %>
<% end %>
<% end %>

View file

@ -0,0 +1,14 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
<h2>Log in to follow</h2>
<p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
<%= text_input f, :name, placeholder: "Username", required: true %>
<br>
<%= password_input f, :password, placeholder: "Password", required: true %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
<%= submit "Authorize" %>
<% end %>

View file

@ -1,11 +0,0 @@
<%= if @error == :error do %>
<h2>Error fetching user</h2>
<% else %>
<h2>Remote follow</h2>
<img width="128" height="128" src="<%= @avatar %>">
<p><%= @name %></p>
<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "user"], fn f -> %>
<%= hidden_input f, :id, value: @id %>
<%= submit "Authorize" %>
<% end %>
<% end %>

View file

@ -1,14 +0,0 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
<h2>Log in to follow</h2>
<p><%= @name %></p>
<img height="128" width="128" src="<%= @avatar %>">
<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "authorization"], fn f -> %>
<%= text_input f, :name, placeholder: "Username" %>
<br>
<%= password_input f, :password, placeholder: "Password" %>
<br>
<%= hidden_input f, :id, value: @id %>
<%= submit "Authorize" %>
<% end %>

View file

@ -0,0 +1,112 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
use Pleroma.Web, :controller
require Logger
alias Pleroma.Activity
alias Pleroma.Object.Fetcher
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.CommonAPI
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action in [:do_follow]
)
# GET /ostatus_subscribe
#
def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
case is_status?(acct) do
true -> follow_status(conn, user, acct)
_ -> follow_account(conn, user, acct)
end
end
defp follow_status(conn, _user, acct) do
with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
%Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
redirect(conn, to: o_status_path(conn, :notice, activity_id))
else
error ->
handle_follow_error(conn, error)
end
end
defp follow_account(conn, user, acct) do
with {:ok, followee} <- User.get_or_fetch(acct) do
render(conn, follow_template(user), %{error: false, followee: followee, acct: acct})
else
{:error, _reason} ->
render(conn, follow_template(user), %{error: :error})
end
end
defp follow_template(%User{} = _user), do: "follow.html"
defp follow_template(_), do: "follow_login.html"
defp is_status?(acct) do
case Fetcher.fetch_and_contain_remote_object_from_id(acct) do
{:ok, %{"type" => type}} when type in @status_types ->
true
_ ->
false
end
end
# POST /ostatus_subscribe
#
def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do
with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
{:ok, _, _, _} <- CommonAPI.follow(user, followee) do
render(conn, "followed.html", %{error: false})
else
error ->
handle_follow_error(conn, error)
end
end
def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do
with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
{_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},
{:ok, _, _, _} <- CommonAPI.follow(user, followee) do
render(conn, "followed.html", %{error: false})
else
error ->
handle_follow_error(conn, error)
end
end
def do_follow(%{assigns: %{user: nil}} = conn, _) do
Logger.debug("Insufficient permissions: follow | write:follows.")
render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."})
end
defp handle_follow_error(conn, {:auth, _, followee} = _) do
render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee})
end
defp handle_follow_error(conn, {:fetch_user, error} = _) do
Logger.debug("Remote follow failed with error #{inspect(error)}")
render(conn, "followed.html", %{error: "Could not find user"})
end
defp handle_follow_error(conn, {:error, "Could not follow user:" <> _} = _) do
render(conn, "followed.html", %{error: "Error following account"})
end
defp handle_follow_error(conn, error) do
Logger.debug("Remote follow failed with error #{inspect(error)}")
render(conn, "followed.html", %{error: "Something went wrong."})
end
end

View file

@ -7,12 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger require Logger
alias Pleroma.Activity
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Healthcheck alias Pleroma.Healthcheck
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
@ -22,7 +20,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} %{scopes: ["follow", "write:follows"]}
when action in [:do_remote_follow, :follow_import] when action == :follow_import
)
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action == :do_remote_follow
) )
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
@ -77,95 +82,6 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
end end
end end
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
if is_status?(acct) do
{:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
%Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
redirect(conn, to: "/notice/#{activity_id}")
else
with {:ok, followee} <- User.get_or_fetch(acct) do
conn
|> render(follow_template(user), %{
error: false,
acct: acct,
avatar: User.avatar_url(followee),
name: followee.nickname,
id: followee.id
})
else
{:error, _reason} ->
render(conn, follow_template(user), %{error: :error})
end
end
end
defp follow_template(%User{} = _user), do: "follow.html"
defp follow_template(_), do: "follow_login.html"
defp is_status?(acct) do
case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
{:ok, %{"type" => type}}
when type in ["Article", "Event", "Note", "Video", "Page", "Question"] ->
true
_ ->
false
end
end
def do_remote_follow(conn, %{
"authorization" => %{"name" => username, "password" => password, "id" => id}
}) do
with %User{} = followee <- User.get_cached_by_id(id),
{_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
{_, true, _} <- {
:auth,
AuthenticationPlug.checkpw(password, user.password_hash),
followee
},
{:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
render(conn, "followed.html", %{error: "Error following account"})
{:auth, _, followee} ->
conn
|> render("follow_login.html", %{
error: "Wrong username or password",
id: id,
name: followee.nickname,
avatar: User.avatar_url(followee)
})
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
render(conn, "followed.html", %{error: "Something went wrong."})
end
end
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
{:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
conn
|> render("followed.html", %{error: false})
else
# Was already following user
{:error, "Could not follow user:" <> _rest} ->
render(conn, "followed.html", %{error: "Error following account"})
{:fetch_user, error} ->
Logger.debug("Remote follow failed with error #{inspect(error)}")
render(conn, "followed.html", %{error: "Could not find user"})
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
render(conn, "followed.html", %{error: "Something went wrong."})
end
end
def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, _} <- Notification.read_one(user, notification_id) do with {:ok, _} <- Notification.read_one(user, notification_id) do
json(conn, %{status: "success"}) json(conn, %{status: "success"})
@ -346,7 +262,9 @@ def change_email(%{assigns: %{user: user}} = conn, params) do
end end
def delete_account(%{assigns: %{user: user}} = conn, params) do def delete_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do password = params["password"] || ""
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} -> {:ok, user} ->
User.delete(user) User.delete(user)
json(conn, %{status: "success"}) json(conn, %{status: "success"})

View file

@ -0,0 +1,10 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
defdelegate avatar_url(user), to: Pleroma.User
end

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.a842fb0a.css rel=stylesheet><link href=chunk-libs.57fe98a3.css rel=stylesheet><link href=app.8589ec81.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.46db235c.js></script><script type=text/javascript src=static/js/chunk-elementUI.fa319e7b.js></script><script type=text/javascript src=static/js/chunk-libs.35c18287.js></script><script type=text/javascript src=static/js/app.9c4316f1.js></script></body></html> <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.a842fb0a.css rel=stylesheet><link href=chunk-libs.57fe98a3.css rel=stylesheet><link href=app.fdd73ce4.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.d6d1aaab.js></script><script type=text/javascript src=static/js/chunk-elementUI.fa319e7b.js></script><script type=text/javascript src=static/js/chunk-libs.35c18287.js></script><script type=text/javascript src=static/js/app.19b7049e.js></script></body></html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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