From e278d470232f4e8081bbbe358137400074673e75 Mon Sep 17 00:00:00 2001 From: link0ff Date: Fri, 22 Feb 2019 15:03:43 +0200 Subject: [PATCH 01/31] OpenLDAP support --- config/config.exs | 9 +++ docs/config.md | 9 +++ lib/pleroma/ldap.ex | 84 +++++++++++++++++++++++ lib/pleroma/web/oauth/oauth_controller.ex | 27 +++++++- 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/ldap.ex diff --git a/config/config.exs b/config/config.exs index 6119aaea1..28d93e086 100644 --- a/config/config.exs +++ b/config/config.exs @@ -344,6 +344,15 @@ federator_outgoing: [max_jobs: 50], mailer: [max_jobs: 10] +config :pleroma, :ldap, + enabled: System.get_env("LDAP_ENABLED") == "true", + host: System.get_env("LDAP_HOST") || "localhost", + port: String.to_integer(System.get_env("LDAP_PORT") || "389"), + ssl: System.get_env("LDAP_SSL") == "true", + sslopts: [], + base: System.get_env("LDAP_BASE") || "dc=example,dc=com", + uid: System.get_env("LDAP_UID") || "cn" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/docs/config.md b/docs/config.md index 14723b727..a497fb4ca 100644 --- a/docs/config.md +++ b/docs/config.md @@ -301,3 +301,12 @@ For each pool, the options are: * `max_connections` - how much connections a pool can hold * `timeout` - retention duration for connections +## :ldap + +* `enabled`: enables LDAP authentication +* `host`: LDAP server hostname +* `port`: LDAP port, e.g. 389 or 636 +* `ssl`: true to use SSL +* `sslopts`: additional SSL options +* `base`: LDAP base, e.g. "dc=example,dc=com" +* `uid`: attribute type to authenticate the user, e.g. when "cn", the filter will be "cn=username,base" diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex new file mode 100644 index 000000000..282d8e553 --- /dev/null +++ b/lib/pleroma/ldap.ex @@ -0,0 +1,84 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.LDAP do + alias Pleroma.User + + require Logger + + @connection_timeout 10_000 + @search_timeout 10_000 + + def get_user(name, password) do + ldap = Pleroma.Config.get(:ldap, []) + host = Keyword.get(ldap, :host, "localhost") + port = Keyword.get(ldap, :port, 389) + ssl = Keyword.get(ldap, :ssl, false) + sslopts = Keyword.get(ldap, :sslopts, []) + + options = + [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ + if sslopts != [], do: [{:sslopts, sslopts}], else: [] + + case :eldap.open([to_charlist(host)], options) do + {:ok, connection} -> + try do + uid = Keyword.get(ldap, :uid, "cn") + base = Keyword.get(ldap, :base) + + case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do + :ok -> + case User.get_by_nickname_or_email(name) do + %User{} = user -> + user + + _ -> + register_user(connection, base, uid, name, password) + end + + error -> + error + end + after + :eldap.close(connection) + end + + {:error, error} -> + Logger.error("Could not open LDAP connection: #{inspect(error)}") + {:error, {:ldap_connection_error, error}} + end + end + + def register_user(connection, base, uid, name, password) do + case :eldap.search(connection, [ + {:base, to_charlist(base)}, + {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, + {:scope, :eldap.wholeSubtree()}, + {:timeout, @search_timeout} + ]) do + {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> + with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do + params = %{ + email: :erlang.list_to_binary(mail), + name: name, + nickname: name, + password: password, + password_confirmation: password + } + + changeset = User.register_changeset(%User{}, params) + + case User.register(changeset) do + {:ok, user} -> user + error -> error + end + else + _ -> {:error, :ldap_registration_missing_attributes} + end + + error -> + error + end + end +end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 7c1a3adbd..654beb2c4 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -130,8 +130,7 @@ def token_exchange( %{"grant_type" => "password", "username" => name, "password" => password} = params ) do with %App{} = app <- get_app_from_request(conn, params), - %User{} = user <- User.get_by_nickname_or_email(name), - true <- Pbkdf2.checkpw(password, user.password_hash), + %User{} = user <- get_user(name, password), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, scopes <- oauth_scopes(params, app.scopes), [] <- scopes -- app.scopes, @@ -215,4 +214,28 @@ defp get_app_from_request(conn, params) do nil end end + + defp get_user(name, password) do + if Pleroma.Config.get([:ldap, :enabled]) do + case Pleroma.LDAP.get_user(name, password) do + %User{} = user -> + user + + {:error, {:ldap_connection_error, _}} -> + # When LDAP is unavailable, try default login + with %User{} = user <- User.get_by_nickname_or_email(name), + true <- Pbkdf2.checkpw(password, user.password_hash) do + user + end + + error -> + error + end + else + with %User{} = user <- User.get_by_nickname_or_email(name), + true <- Pbkdf2.checkpw(password, user.password_hash) do + user + end + end + end end From 88a672fe88deae53d5459d651859be65555e6af2 Mon Sep 17 00:00:00 2001 From: link0ff Date: Sun, 3 Mar 2019 21:20:36 +0200 Subject: [PATCH 02/31] Move LDAP code to LDAPAuthenticator. Use Authenticator for token_exchange with grant_type as well --- docs/config.md | 5 +++ .../auth/ldap_authenticator.ex} | 42 +++++++++++++++++-- lib/pleroma/web/auth/pleroma_authenticator.ex | 9 +++- lib/pleroma/web/oauth/oauth_controller.ex | 31 ++------------ 4 files changed, 55 insertions(+), 32 deletions(-) rename lib/pleroma/{ldap.ex => web/auth/ldap_authenticator.ex} (68%) diff --git a/docs/config.md b/docs/config.md index d7349d54f..8d6770959 100644 --- a/docs/config.md +++ b/docs/config.md @@ -336,3 +336,8 @@ config :auto_linker, * `sslopts`: additional SSL options * `base`: LDAP base, e.g. "dc=example,dc=com" * `uid`: attribute type to authenticate the user, e.g. when "cn", the filter will be "cn=username,base" + +## Pleroma.Web.Auth.Authenticator + +* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator +* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/web/auth/ldap_authenticator.ex similarity index 68% rename from lib/pleroma/ldap.ex rename to lib/pleroma/web/auth/ldap_authenticator.ex index 282d8e553..56f2f5aed 100644 --- a/lib/pleroma/ldap.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -2,15 +2,51 @@ # Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.LDAP do +defmodule Pleroma.Web.Auth.LDAPAuthenticator do alias Pleroma.User require Logger + @behaviour Pleroma.Web.Auth.Authenticator + @connection_timeout 10_000 @search_timeout 10_000 - def get_user(name, password) do + def get_user(%Plug.Conn{} = conn) do + if Pleroma.Config.get([:ldap, :enabled]) do + {name, password} = + case conn.params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end + + case ldap_user(name, password) do + %User{} = user -> + {:ok, user} + + {:error, {:ldap_connection_error, _}} -> + # When LDAP is unavailable, try default authenticator + Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) + + error -> + error + end + else + # Fall back to default authenticator + Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) + end + end + + def handle_error(%Plug.Conn{} = _conn, error) do + error + end + + def auth_template, do: nil + + defp ldap_user(name, password) do ldap = Pleroma.Config.get(:ldap, []) host = Keyword.get(ldap, :host, "localhost") port = Keyword.get(ldap, :port, 389) @@ -50,7 +86,7 @@ def get_user(name, password) do end end - def register_user(connection, base, uid, name, password) do + defp register_user(connection, base, uid, name, password) do case :eldap.search(connection, [ {:base, to_charlist(base)}, {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 3cc19af01..360772895 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -9,7 +9,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do @behaviour Pleroma.Web.Auth.Authenticator def get_user(%Plug.Conn{} = conn) do - %{"authorization" => %{"name" => name, "password" => password}} = conn.params + {name, password} = + case conn.params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)}, {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index c2b6dd477..7d5a5b9c5 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do alias Pleroma.Web.OAuth.App alias Pleroma.Repo alias Pleroma.User - alias Comeonin.Pbkdf2 import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] @@ -126,10 +125,10 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do def token_exchange( conn, - %{"grant_type" => "password", "username" => name, "password" => password} = params + %{"grant_type" => "password"} = params ) do - with %App{} = app <- get_app_from_request(conn, params), - %User{} = user <- get_user(name, password), + with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)}, + %App{} = app <- get_app_from_request(conn, params), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, scopes <- oauth_scopes(params, app.scopes), [] <- scopes -- app.scopes, @@ -213,28 +212,4 @@ defp get_app_from_request(conn, params) do nil end end - - defp get_user(name, password) do - if Pleroma.Config.get([:ldap, :enabled]) do - case Pleroma.LDAP.get_user(name, password) do - %User{} = user -> - user - - {:error, {:ldap_connection_error, _}} -> - # When LDAP is unavailable, try default login - with %User{} = user <- User.get_by_nickname_or_email(name), - true <- Pbkdf2.checkpw(password, user.password_hash) do - user - end - - error -> - error - end - else - with %User{} = user <- User.get_by_nickname_or_email(name), - true <- Pbkdf2.checkpw(password, user.password_hash) do - user - end - end - end end From 9338f061a303ae3d57a8ea1af524c2ca51929f8d Mon Sep 17 00:00:00 2001 From: link0ff Date: Tue, 12 Mar 2019 18:20:02 +0200 Subject: [PATCH 03/31] Support LDAP method start_tls --- config/config.exs | 2 + docs/config.md | 12 ++++- lib/pleroma/web/auth/ldap_authenticator.ex | 51 ++++++++++++++++------ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/config/config.exs b/config/config.exs index 8544dcc0d..1ed330df2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -361,6 +361,8 @@ port: String.to_integer(System.get_env("LDAP_PORT") || "389"), ssl: System.get_env("LDAP_SSL") == "true", sslopts: [], + tls: System.get_env("LDAP_TLS") == "true", + tlsopts: [], base: System.get_env("LDAP_BASE") || "dc=example,dc=com", uid: System.get_env("LDAP_UID") || "cn" diff --git a/docs/config.md b/docs/config.md index 8d6770959..7052b1eb4 100644 --- a/docs/config.md +++ b/docs/config.md @@ -329,13 +329,21 @@ config :auto_linker, ## :ldap +Use LDAP for user authentication. When a user logs in to the Pleroma +instance, the name and password will be verified by trying to authenticate +(bind) to an LDAP server. If a user exists in the LDAP directory but there +is no account with the same name yet on the Pleroma instance then a new +Pleroma account will be created with the same name as the LDAP user name. + * `enabled`: enables LDAP authentication * `host`: LDAP server hostname * `port`: LDAP port, e.g. 389 or 636 -* `ssl`: true to use SSL +* `ssl`: true to use SSL, usually implies the port 636 * `sslopts`: additional SSL options +* `tls`: true to start TLS, usually implies the port 389 +* `tlsopts`: additional TLS options * `base`: LDAP base, e.g. "dc=example,dc=com" -* `uid`: attribute type to authenticate the user, e.g. when "cn", the filter will be "cn=username,base" +* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base" ## Pleroma.Web.Auth.Authenticator diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 56f2f5aed..88217aab8 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -60,22 +60,23 @@ defp ldap_user(name, password) do case :eldap.open([to_charlist(host)], options) do {:ok, connection} -> try do - uid = Keyword.get(ldap, :uid, "cn") - base = Keyword.get(ldap, :base) + if Keyword.get(ldap, :tls, false) do + :application.ensure_all_started(:ssl) - case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do - :ok -> - case User.get_by_nickname_or_email(name) do - %User{} = user -> - user + case :eldap.start_tls( + connection, + Keyword.get(ldap, :tlsopts, []), + @connection_timeout + ) do + :ok -> + :ok - _ -> - register_user(connection, base, uid, name, password) - end - - error -> - error + error -> + Logger.error("Could not start TLS: #{inspect(error)}") + end end + + bind_user(connection, ldap, name, password) after :eldap.close(connection) end @@ -86,11 +87,31 @@ defp ldap_user(name, password) do end end + defp bind_user(connection, ldap, name, password) do + uid = Keyword.get(ldap, :uid, "cn") + base = Keyword.get(ldap, :base) + + case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do + :ok -> + case User.get_by_nickname_or_email(name) do + %User{} = user -> + user + + _ -> + register_user(connection, base, uid, name, password) + end + + error -> + error + end + end + defp register_user(connection, base, uid, name, password) do case :eldap.search(connection, [ {:base, to_charlist(base)}, {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, {:scope, :eldap.wholeSubtree()}, + {:attributes, ['mail', 'email']}, {:timeout, @search_timeout} ]) do {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> @@ -110,7 +131,9 @@ defp register_user(connection, base, uid, name, password) do error -> error end else - _ -> {:error, :ldap_registration_missing_attributes} + _ -> + Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}") + {:error, :ldap_registration_missing_attributes} end error -> From 414d4b80883a37b0d323522112680d11b49cbb28 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:04:33 +0000 Subject: [PATCH 04/31] test: add a test for accepting inbound Flag activities --- test/web/activity_pub/transmogrifier_test.exs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 8184dbbae..afb931934 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -764,6 +765,30 @@ test "it remaps video URLs as attachments if necessary" do assert object.data["attachment"] == [attachment] end + + test "it accepts Flag activities" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + object = Object.normalize(activity.data["object"]) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "cc" => [user.ap_id], + "object" => [user.ap_id, object.data["id"]], + "type" => "Flag", + "content" => "blocked AND reported!!!", + "actor" => other_user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + assert activity.data["object"] == [user.ap_id, object.data["id"]] + assert activity.data["content"] == "blocked AND reported!!!" + assert activity.data["actor"] == other_user.ap_id + assert activity.data["cc"] == [user.ap_id] + end end describe "prepare outgoing" do From f86f7dbb8f7bd72bd4037b03224b2840de4a4292 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:04:52 +0000 Subject: [PATCH 05/31] activitypub: utils: rework make_flag_data to accept either activity payloads or IRIs --- lib/pleroma/web/activity_pub/utils.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 182f9cacb..9881b7bbb 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -621,7 +621,12 @@ def make_create_data(params, additional) do #### Flag-related helpers def make_flag_data(params, additional) do - status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"]) + status_ap_ids = + Enum.map(params.statuses || [], fn + act when is_map(act) -> act["id"] + act when is_binary(act) -> act + end) + object = [params.account.ap_id] ++ status_ap_ids %{ From 0f3ecb2bfbb42ce16eb3144d62a932bfc32ce656 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:06:02 +0000 Subject: [PATCH 06/31] activitypub: transmogrifier: accept remote Flag activities --- .../web/activity_pub/transmogrifier.ex | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 1247e4b61..8e4bf7b47 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -355,6 +355,40 @@ defp get_follow_activity(follow_object, followed) do end end + # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them + # with nil ID. + def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do + with context <- data["context"] || Utils.generate_context_id(), + content <- data["content"] || "", + %User{} = actor <- User.get_cached_by_ap_id(actor), + + # Reduce the object list to find the reported user. + %User{} = account <- + Enum.reduce_while(objects, nil, fn ap_id, _ -> + with %User{} = user <- User.get_cached_by_ap_id(ap_id) do + {:halt, user} + else + _ -> {:cont, nil} + end + end), + + # Remove the reported user from the object list. + statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do + params = %{ + actor: actor, + context: context, + account: account, + statuses: statuses, + content: content, + additional: %{ + "cc" => [account.ap_id] + } + } + + ActivityPub.flag(params) + end + end + # disallow objects with bogus IDs def handle_incoming(%{"id" => nil}), do: :error def handle_incoming(%{"id" => ""}), do: :error From 379442ad17cab9c27863be2c885d10f8b6eadeca Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:29:04 +0000 Subject: [PATCH 07/31] activitypub: utils: also match Activity objects --- lib/pleroma/web/activity_pub/utils.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9881b7bbb..af317245f 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -623,6 +623,7 @@ def make_create_data(params, additional) do def make_flag_data(params, additional) do status_ap_ids = Enum.map(params.statuses || [], fn + %Activity{} = act -> act.data["id"] act when is_map(act) -> act["id"] act when is_binary(act) -> act end) From 3b48d5f0c27b42a6ea409fffbdc6b831da2ff4ca Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:29:33 +0000 Subject: [PATCH 08/31] common api: add support for forwarding reports --- lib/pleroma/web/common_api/common_api.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index de0759fb0..ead4928b5 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -284,7 +284,8 @@ def report(user, data) do actor: user, account: account, statuses: statuses, - content: content_html + content: content_html, + forward: data["forward"] || false }) do Enum.each(User.all_superusers(), fn superuser -> superuser From 64b0120d678b106f33d5c903749fcac5ed5a1ed7 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:29:47 +0000 Subject: [PATCH 09/31] activitypub: add support for forwarding reports --- lib/pleroma/web/activity_pub/activity_pub.ex | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 70db419ca..7d21fe65f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -370,20 +370,32 @@ def flag( content: content } = params ) do - additional = params[:additional] || %{} - # only accept false as false value local = !(params[:local] == false) + forward = !(params[:forward] == false) - %{ + additional = params[:additional] || %{} + + params = %{ actor: actor, context: context, account: account, statuses: statuses, content: content } - |> make_flag_data(additional) - |> insert(local) + + additional = + if forward do + Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]}) + else + additional + end + + with flag_data <- make_flag_data(params, additional), + {:ok, activity} <- insert(flag_data, local), + :ok <- maybe_federate(activity) do + {:ok, activity} + end end def fetch_activities_for_context(context, opts \\ %{}) do From 5c7b774f09d86414e6a8ef6494ccd2e6a76c1396 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:38:46 +0000 Subject: [PATCH 10/31] reports: unify sending e-mail for both remote and local reports --- lib/pleroma/web/activity_pub/activity_pub.ex | 6 ++++++ lib/pleroma/web/common_api/common_api.ex | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7d21fe65f..32f69e0fa 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -394,6 +394,12 @@ def flag( with flag_data <- make_flag_data(params, additional), {:ok, activity} <- insert(flag_data, local), :ok <- maybe_federate(activity) do + Enum.each(User.all_superusers(), fn superuser -> + superuser + |> Pleroma.AdminEmail.report(actor, account, statuses, content) + |> Pleroma.Mailer.deliver_async() + end) + {:ok, activity} end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index ead4928b5..edbcd3397 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -287,12 +287,6 @@ def report(user, data) do content: content_html, forward: data["forward"] || false }) do - Enum.each(User.all_superusers(), fn superuser -> - superuser - |> Pleroma.AdminEmail.report(user, account, statuses, content_html) - |> Pleroma.Mailer.deliver_async() - end) - {:ok, activity} else {:error, err} -> {:error, err} From 423fd07928d64dd7810ac408265a37ae1274956f Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Thu, 14 Mar 2019 19:52:08 +0000 Subject: [PATCH 11/31] activitypub: inject to/cc fields on non-forwarded reports since Flag activities are now Forwardable --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 32f69e0fa..45a030ca3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -388,7 +388,7 @@ def flag( if forward do Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]}) else - additional + Map.merge(additional, %{"to" => [], "cc" => []}) end with flag_data <- make_flag_data(params, additional), From 958227d5563d76f4f983b7cabb6948897d93bd4b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 15 Mar 2019 01:36:29 +0300 Subject: [PATCH 12/31] MediaProxy: parse filename from content-disposition for non-whitelisted types --- lib/pleroma/reverse_proxy.ex | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index 6298b92f4..39ede8619 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -311,7 +311,25 @@ defp build_resp_content_disposition_header(headers, opts) do end if attachment? do - disposition = "attachment; filename=" <> Keyword.get(opts, :attachment_name, "attachment") + name = + try do + {{"content-disposition", content_disposition_string}, _} = + List.keytake(headers, "content-disposition", 0) + + [name] = + Regex.run( + ~r/filename=\"(.*)\"/u, + content_disposition_string || "", + capture: :all_but_first + ) + + name + rescue + MatchError -> Keyword.get(opts, :attachment_name, "attachment") + end + + disposition = "attachment; filename=" <> name + List.keystore(headers, "content-disposition", 0, {"content-disposition", disposition}) else headers From d02f1120f9fe8e048bac6665e95e51648a50c53b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 15 Mar 2019 08:29:51 +0300 Subject: [PATCH 13/31] Content-Disposition regex improvements --- lib/pleroma/reverse_proxy.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index 39ede8619..a3f177fec 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -316,9 +316,9 @@ defp build_resp_content_disposition_header(headers, opts) do {{"content-disposition", content_disposition_string}, _} = List.keytake(headers, "content-disposition", 0) - [name] = + [name | _] = Regex.run( - ~r/filename=\"(.*)\"/u, + ~r/filename="((?:[^"\\]|\\.)*)"/u, content_disposition_string || "", capture: :all_but_first ) @@ -328,7 +328,7 @@ defp build_resp_content_disposition_header(headers, opts) do MatchError -> Keyword.get(opts, :attachment_name, "attachment") end - disposition = "attachment; filename=" <> name + disposition = "attachment; filename=\"#{name}\"" List.keystore(headers, "content-disposition", 0, {"content-disposition", disposition}) else From 28cfb2c37a0a1ba5a17270740b34f650fdfd19e1 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 15 Mar 2019 12:11:13 +0300 Subject: [PATCH 14/31] Enable dedupe by default --- config/config.exs | 2 +- config/test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index f889e3259..326c4b601 100644 --- a/config/config.exs +++ b/config/config.exs @@ -34,7 +34,7 @@ # Upload configuration config :pleroma, Pleroma.Upload, uploader: Pleroma.Uploaders.Local, - filters: [], + filters: [Pleroma.Upload.Filter.Dedupe], link_name: true, proxy_remote: false, proxy_opts: [ diff --git a/config/test.exs b/config/test.exs index a3f36c9e1..3691e5bd1 100644 --- a/config/test.exs +++ b/config/test.exs @@ -17,7 +17,7 @@ # Print only warnings and errors during test config :logger, level: :warn -config :pleroma, Pleroma.Upload, link_name: false +config :pleroma, Pleroma.Upload, filters: [], link_name: false config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads" From f5b54acc8160dc1ecf4523d98e971eff8bc983de Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 15 Mar 2019 10:58:15 +0100 Subject: [PATCH 15/31] Fix headers and add !929 docs to extras [ci skip] --- docs/Custom-Emoji.md | 2 ++ docs/Message-Rewrite-Facility-configuration.md | 1 + mix.exs | 9 ++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/Custom-Emoji.md b/docs/Custom-Emoji.md index d4af5c97c..9d90e5822 100644 --- a/docs/Custom-Emoji.md +++ b/docs/Custom-Emoji.md @@ -1,3 +1,5 @@ +# Custom emoji + To add custom emoji: * Add the image file(s) to `priv/static/emoji/custom` * In case of conflicts: add the desired shortcode with the path to `config/custom_emoji.txt`, comma-separated and one per line diff --git a/docs/Message-Rewrite-Facility-configuration.md b/docs/Message-Rewrite-Facility-configuration.md index 708098b41..35ce52ea9 100644 --- a/docs/Message-Rewrite-Facility-configuration.md +++ b/docs/Message-Rewrite-Facility-configuration.md @@ -1,3 +1,4 @@ +# Message Rewrite Facility configuration The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages. Possible uses include: diff --git a/mix.exs b/mix.exs index 70b5e4bd6..efdf15d3a 100644 --- a/mix.exs +++ b/mix.exs @@ -23,11 +23,14 @@ def project do logo: "priv/static/static/logo.png", extras: [ "README.md", - "docs/config.md", - "docs/Pleroma-API.md", "docs/Admin-API.md", "docs/Clients.md", - "docs/Differences-in-MastodonAPI-Responses.md" + "docs/config.md", + "docs/Custom-Emoji.md", + "docs/Differences-in-MastodonAPI-Responses.md", + "docs/Message-Rewrite-Facility-configuration.md", + "docs/Pleroma-API.md", + "docs/static_dir.md" ], main: "readme", output: "priv/static/doc" From c8f31e0bc2764c924a4045e007e828052c837ac2 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Sat, 9 Mar 2019 14:08:41 +0100 Subject: [PATCH 16/31] Implement mastodon's reblog hiding feature --- lib/pleroma/user.ex | 4 ++++ lib/pleroma/user/info.ex | 13 +++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 10 +++++++++- lib/pleroma/web/common_api/common_api.ex | 16 ++++++++++++++++ .../web/mastodon_api/mastodon_api_controller.ex | 13 +++++++++++++ .../web/mastodon_api/views/account_view.ex | 2 +- 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1ce9882f6..467cb910b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1385,4 +1385,8 @@ defp paginate(query, page, page_size) do offset: ^((page - 1) * page_size) ) end + + def showing_reblogs?(%User{} = user, %User{} = target) do + target.id not in user.info.muted_reblogs + end end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index e3fd65a6e..35586e212 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -21,6 +21,7 @@ defmodule Pleroma.User.Info do field(:blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: []) field(:mutes, {:array, :string}, default: []) + field(:muted_reblogs, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) @@ -259,4 +260,16 @@ def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do moderator: is_moderator } end + + def add_reblog_mute(info, id) do + params = %{muted_reblogs: info.muted_reblogs ++ [id]} + + cast(info, params, [:muted_reblogs]) + end + + def remove_reblog_mute(info, id) do + params = %{muted_reblogs: List.delete(info.muted_reblogs, id)} + + cast(info, params, [:muted_reblogs]) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 70db419ca..779ee139b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -951,9 +951,17 @@ def contain_broken_threads(%Activity{} = activity, %User{} = user) do entire_thread_visible_for_user?(activity, user) end + # filter out muted threads + def contain_muted_boosts(%Activity{data: %{"type" => "Announce"}} = activity, %User{} = user) do + id = User.get_by_ap_id(activity.actor).id + id not in user.info.muted_reblogs + end + + def contain_muted_boosts(%Activity{} = _activity, %User{} = _user), do: true + # do post-processing on a specific activity def contain_activity(%Activity{} = activity, %User{} = user) do - contain_broken_threads(activity, user) + contain_broken_threads(activity, user) and contain_muted_boosts(activity, user) end # do post-processing on a timeline diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index de0759fb0..035c59387 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -299,4 +299,20 @@ def report(user, data) do {:account, nil} -> {:error, "Account not found"} end end + + def hide_reblogs(user, id) do + if id not in user.info.muted_reblogs do + info_changeset = User.Info.add_reblog_mute(user.info, id) + changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) + User.update_and_set_cache(changeset) + end + end + + def show_reblogs(user, id) do + if id in user.info.muted_reblogs do + info_changeset = User.Info.remove_reblog_mute(user.info, id) + changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) + User.update_and_set_cache(changeset) + end + end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 265bf837e..570bf0c0f 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -723,11 +723,24 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), + false <- User.following?(follower, followed), {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do conn |> put_view(AccountView) |> render("relationship.json", %{user: follower, target: followed}) else + true -> + case conn.params["reblogs"] do + true -> CommonAPI.show_reblogs(follower, id) + false -> CommonAPI.hide_reblogs(follower, id) + end + + followed = Repo.get(User, id) + + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) + {:error, message} -> conn |> put_resp_content_type("application/json") diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index c32f27be2..b5f3bbb9d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -55,7 +55,7 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target muting_notifications: false, requested: requested, domain_blocking: false, - showing_reblogs: false, + showing_reblogs: User.showing_reblogs?(user, target), endorsed: false } end From 13c298d3543b08e656068a0f50acb35f036bdaaa Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Sat, 9 Mar 2019 14:22:55 +0100 Subject: [PATCH 17/31] Fix tests to assume reblogs shown by default --- test/web/mastodon_api/account_view_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index c2ffc21da..6dc60afe9 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -144,7 +144,7 @@ test "represent a relationship" do muting_notifications: false, requested: false, domain_blocking: false, - showing_reblogs: false, + showing_reblogs: true, endorsed: false } @@ -202,7 +202,7 @@ test "represent an embedded relationship" do muting_notifications: false, requested: false, domain_blocking: false, - showing_reblogs: false, + showing_reblogs: true, endorsed: false } } From fe4c1d26fcbe5ce260bde94e75f617ae0fcb338f Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Sat, 9 Mar 2019 16:24:32 +0100 Subject: [PATCH 18/31] Add ActivityPub.contain_activity checks to streamer --- lib/pleroma/web/streamer.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 5850a9579..bd91e1f50 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.MastodonAPI.NotificationView + alias Pleroma.Web.ActivityPub.ActivityPub @keepalive_interval :timer.seconds(30) @@ -203,7 +204,9 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite parent = Object.normalize(item.data["object"]) unless is_nil(parent) or item.actor in blocks or item.actor in mutes or - parent.data["actor"] in blocks or parent.data["actor"] in mutes do + not ActivityPub.contain_activity(item, user) or + parent.data["actor"] in blocks or + parent.data["actor"] in mutes do send(socket.transport_pid, {:text, represent_update(item, user)}) end else @@ -233,7 +236,8 @@ def push_to_socket(topics, topic, item) do blocks = user.info.blocks || [] mutes = user.info.mutes || [] - unless item.actor in blocks or item.actor in mutes do + unless item.actor in blocks or item.actor in mutes or + not ActivityPub.contain_activity(item, user) do send(socket.transport_pid, {:text, represent_update(item, user)}) end else From aa71139e4a9913cc5baf3f5f567bcef4ae59a8c0 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Sat, 9 Mar 2019 16:31:53 +0100 Subject: [PATCH 19/31] Fix elixir 1.8 vs 1.7 format conflict --- lib/pleroma/web/streamer.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index bd91e1f50..a04ee1b6e 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -204,8 +204,7 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite parent = Object.normalize(item.data["object"]) unless is_nil(parent) or item.actor in blocks or item.actor in mutes or - not ActivityPub.contain_activity(item, user) or - parent.data["actor"] in blocks or + not ActivityPub.contain_activity(item, user) or parent.data["actor"] in blocks or parent.data["actor"] in mutes do send(socket.transport_pid, {:text, represent_update(item, user)}) end From 15b21d1983fad08e71f6fd7adffbcfd6f2d8cba1 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Mon, 11 Mar 2019 16:57:54 +0100 Subject: [PATCH 20/31] refactor filtering mechanism --- lib/pleroma/web/activity_pub/activity_pub.ex | 21 ++++++++++++------- .../mastodon_api/mastodon_api_controller.ex | 2 +- lib/pleroma/web/streamer.ex | 2 ++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 779ee139b..5d9ce7fd7 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -679,6 +679,18 @@ defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) defp restrict_pinned(query, _), do: query + defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do + muted_reblogs = info.muted_reblogs || [] + + from( + activity in query, + where: fragment("not ?->>'type' = 'Announce'", activity.data), + where: fragment("not ? = ANY(?)", activity.actor, ^muted_reblogs) + ) + end + + defp restrict_muted_reblogs(query, _), do: query + def fetch_activities_query(recipients, opts \\ %{}) do base_query = from( @@ -706,6 +718,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do |> restrict_replies(opts) |> restrict_reblogs(opts) |> restrict_pinned(opts) + |> restrict_muted_reblogs(opts) end def fetch_activities(recipients, opts \\ %{}) do @@ -951,17 +964,11 @@ def contain_broken_threads(%Activity{} = activity, %User{} = user) do entire_thread_visible_for_user?(activity, user) end - # filter out muted threads - def contain_muted_boosts(%Activity{data: %{"type" => "Announce"}} = activity, %User{} = user) do - id = User.get_by_ap_id(activity.actor).id - id not in user.info.muted_reblogs - end - def contain_muted_boosts(%Activity{} = _activity, %User{} = _user), do: true # do post-processing on a specific activity def contain_activity(%Activity{} = activity, %User{} = user) do - contain_broken_threads(activity, user) and contain_muted_boosts(activity, user) + contain_broken_threads(activity, user) end # do post-processing on a timeline diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 570bf0c0f..68b7d8b49 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -735,7 +735,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do false -> CommonAPI.hide_reblogs(follower, id) end - followed = Repo.get(User, id) + followed = User.get_cached_by_id(id) conn |> put_view(AccountView) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index a04ee1b6e..b87db941b 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -200,10 +200,12 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) blocks = user.info.blocks || [] mutes = user.info.mutes || [] + reblog_mutes = user.info.reblog_mutes || [] parent = Object.normalize(item.data["object"]) unless is_nil(parent) or item.actor in blocks or item.actor in mutes or + item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or parent.data["actor"] in blocks or parent.data["actor"] in mutes do send(socket.transport_pid, {:text, represent_update(item, user)}) From be465c762be19eda6725d4f323798caa23241715 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Mon, 11 Mar 2019 19:59:25 +0100 Subject: [PATCH 21/31] formatting --- lib/pleroma/web/streamer.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index b87db941b..fa0fb2ac8 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -205,9 +205,8 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite parent = Object.normalize(item.data["object"]) unless is_nil(parent) or item.actor in blocks or item.actor in mutes or - item.actor in reblog_mutes or - not ActivityPub.contain_activity(item, user) or parent.data["actor"] in blocks or - parent.data["actor"] in mutes do + item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or + parent.data["actor"] in blocks or parent.data["actor"] in mutes do send(socket.transport_pid, {:text, represent_update(item, user)}) end else From da53c079db91ce5d7ba14809f5e99b10d3ae307a Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Fri, 15 Mar 2019 14:06:58 +0100 Subject: [PATCH 22/31] Refactor to store user ap_id, add tests --- lib/pleroma/user.ex | 2 +- lib/pleroma/user/info.ex | 8 ++--- lib/pleroma/web/common_api/common_api.ex | 16 ++++++---- .../mastodon_api/mastodon_api_controller.ex | 11 +++---- lib/pleroma/web/streamer.ex | 2 +- test/web/activity_pub/activity_pub_test.exs | 13 ++++++++ test/web/common_api/common_api_test.exs | 23 ++++++++++++++ test/web/streamer_test.exs | 30 +++++++++++++++++++ 8 files changed, 88 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 467cb910b..692ae836c 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1387,6 +1387,6 @@ defp paginate(query, page, page_size) do end def showing_reblogs?(%User{} = user, %User{} = target) do - target.id not in user.info.muted_reblogs + target.ap_id not in user.info.muted_reblogs end end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 35586e212..70b72710b 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -261,14 +261,14 @@ def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do } end - def add_reblog_mute(info, id) do - params = %{muted_reblogs: info.muted_reblogs ++ [id]} + def add_reblog_mute(info, ap_id) do + params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]} cast(info, params, [:muted_reblogs]) end - def remove_reblog_mute(info, id) do - params = %{muted_reblogs: List.delete(info.muted_reblogs, id)} + def remove_reblog_mute(info, ap_id) do + params = %{muted_reblogs: List.delete(info.muted_reblogs, ap_id)} cast(info, params, [:muted_reblogs]) end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 035c59387..84d66efc9 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -300,17 +300,21 @@ def report(user, data) do end end - def hide_reblogs(user, id) do - if id not in user.info.muted_reblogs do - info_changeset = User.Info.add_reblog_mute(user.info, id) + def hide_reblogs(user, muted) do + ap_id = muted.ap_id + + if ap_id not in user.info.muted_reblogs do + info_changeset = User.Info.add_reblog_mute(user.info, ap_id) changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) User.update_and_set_cache(changeset) end end - def show_reblogs(user, id) do - if id in user.info.muted_reblogs do - info_changeset = User.Info.remove_reblog_mute(user.info, id) + def show_reblogs(user, muted) do + ap_id = muted.ap_id + + if ap_id in user.info.muted_reblogs do + info_changeset = User.Info.remove_reblog_mute(user.info, ap_id) changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) User.update_and_set_cache(changeset) end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 68b7d8b49..952aa2453 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -730,13 +730,14 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do |> render("relationship.json", %{user: follower, target: followed}) else true -> - case conn.params["reblogs"] do - true -> CommonAPI.show_reblogs(follower, id) - false -> CommonAPI.hide_reblogs(follower, id) - end - followed = User.get_cached_by_id(id) + {:ok, follower} = + case conn.params["reblogs"] do + true -> CommonAPI.show_reblogs(follower, followed) + false -> CommonAPI.hide_reblogs(follower, followed) + end + conn |> put_view(AccountView) |> render("relationship.json", %{user: follower, target: followed}) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index fa0fb2ac8..a0863a062 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -200,7 +200,7 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) blocks = user.info.blocks || [] mutes = user.info.mutes || [] - reblog_mutes = user.info.reblog_mutes || [] + reblog_mutes = user.info.muted_reblogs || [] parent = Object.normalize(item.data["object"]) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 2b83bfb1d..035778218 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -424,6 +424,19 @@ test "retrieves ids up to max_id" do assert length(activities) == 20 assert last == last_expected end + + test "doesn't return reblogs for users for whom reblogs have been muted" do + activity = insert(:note_activity) + user = insert(:user) + booster = insert(:user) + {:ok, user} = CommonAPI.hide_reblogs(user, booster) + + {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + + activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) + + refute Enum.member?(activities, activity) + end end describe "like an object" do diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 181813c76..f83f80b40 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -221,4 +221,27 @@ test "creates a report" do } = flag_activity end end + + describe "reblog muting" do + setup do + muter = insert(:user) + + muted = insert(:user) + + [muter: muter, muted: muted] + end + + test "add a reblog mute", %{muter: muter, muted: muted} do + {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) + + assert Pleroma.User.showing_reblogs?(muter, muted) == false + end + + test "remove a reblog mute", %{muter: muter, muted: muted} do + {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) + {:ok, muter} = CommonAPI.show_reblogs(muter, muted) + + assert Pleroma.User.showing_reblogs?(muter, muted) == true + end + end end diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs index 0a2e91298..f00577a8a 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer_test.exs @@ -202,4 +202,34 @@ test "it send wanted private posts to list" do Task.await(task) end + + test "it doesn't send muted reblogs" do + user1 = insert(:user) + user2 = insert(:user) + user3 = insert(:user) + CommonAPI.hide_reblogs(user1, user2.ap_id) + + task = + Task.async(fn -> + refute_receive {:text, _}, 1_000 + end) + + fake_socket = %{ + transport_pid: task.pid, + assigns: %{ + user: user1 + } + } + + {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) + {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) + + topics = %{ + "public" => [fake_socket] + } + + Streamer.push_to_socket(topics, "public", announce_activity) + + Task.await(task) + end end From ebfc10c834907011c3ac632ea876ce18e9d3c9e4 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Fri, 15 Mar 2019 14:13:42 +0100 Subject: [PATCH 23/31] Fix streamer test --- test/web/streamer_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs index f00577a8a..bfe18cb7f 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer_test.exs @@ -207,7 +207,7 @@ test "it doesn't send muted reblogs" do user1 = insert(:user) user2 = insert(:user) user3 = insert(:user) - CommonAPI.hide_reblogs(user1, user2.ap_id) + CommonAPI.hide_reblogs(user1, user2) task = Task.async(fn -> From 094e1ef048f972fc3ead8d597ac9dbe6f5f62f34 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Fri, 15 Mar 2019 14:28:14 +0100 Subject: [PATCH 24/31] formatting --- lib/pleroma/user/info.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 70b72710b..740a46727 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -260,7 +260,7 @@ def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do moderator: is_moderator } end - + def add_reblog_mute(info, ap_id) do params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]} From dfeb3aec44efb9ffdbe9e6409a79aa50b7e268a8 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Fri, 15 Mar 2019 14:36:07 +0100 Subject: [PATCH 25/31] fix credo warning --- lib/pleroma/web/streamer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index a0863a062..7425bfb54 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -10,9 +10,9 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.MastodonAPI.NotificationView - alias Pleroma.Web.ActivityPub.ActivityPub @keepalive_interval :timer.seconds(30) From d8244c2a1bfc54b9dfa70be3ce49db961205f883 Mon Sep 17 00:00:00 2001 From: Karen Konou Date: Fri, 15 Mar 2019 15:03:03 +0100 Subject: [PATCH 26/31] remove unused function --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5d9ce7fd7..a65d6e020 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -964,8 +964,6 @@ def contain_broken_threads(%Activity{} = activity, %User{} = user) do entire_thread_visible_for_user?(activity, user) end - def contain_muted_boosts(%Activity{} = _activity, %User{} = _user), do: true - # do post-processing on a specific activity def contain_activity(%Activity{} = activity, %User{} = user) do contain_broken_threads(activity, user) From a070dd4a83788dfd76809d8b4ee4111b05cdff47 Mon Sep 17 00:00:00 2001 From: link0ff Date: Fri, 15 Mar 2019 16:28:46 +0200 Subject: [PATCH 27/31] Add tests for LDAP authorization --- test/web/oauth/ldap_authorization_test.exs | 187 +++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 test/web/oauth/ldap_authorization_test.exs diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs new file mode 100644 index 000000000..570e41f3e --- /dev/null +++ b/test/web/oauth/ldap_authorization_test.exs @@ -0,0 +1,187 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do + use Pleroma.Web.ConnCase + alias Pleroma.Repo + alias Pleroma.Web.OAuth.Token + import Pleroma.Factory + import ExUnit.CaptureLog + import Mock + + setup_all do + ldap_authenticator = Pleroma.Config.get([Pleroma.Web.Auth.Authenticator]) + ldap_enabled = Pleroma.Config.get([:ldap, :enabled]) + + on_exit(fn -> + Pleroma.Config.put([Pleroma.Web.Auth.Authenticator], ldap_authenticator) + Pleroma.Config.put([:ldap, :enabled], ldap_enabled) + end) + + Pleroma.Config.put([Pleroma.Web.Auth.Authenticator], Pleroma.Web.Auth.LDAPAuthenticator) + Pleroma.Config.put([:ldap, :enabled], true) + + :ok + end + + test "authorizes the existing user using LDAP credentials" do + password = "testpassword" + user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) + app = insert(:oauth_app, scopes: ["read", "write"]) + + host = Pleroma.Config.get([:ldap, :host]) |> to_charlist + port = Pleroma.Config.get([:ldap, :port]) + + with_mocks [ + {:eldap, [], + [ + open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, + simple_bind: fn _connection, _dn, ^password -> :ok end, + close: fn _connection -> + send(self(), :close_connection) + :ok + end + ]} + ] do + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert %{"access_token" => token} = json_response(conn, 200) + + token = Repo.get_by(Token, token: token) + + assert token.user_id == user.id + assert_received :close_connection + end + end + + test "creates a new user after successful LDAP authorization" do + password = "testpassword" + user = build(:user) + app = insert(:oauth_app, scopes: ["read", "write"]) + + host = Pleroma.Config.get([:ldap, :host]) |> to_charlist + port = Pleroma.Config.get([:ldap, :port]) + + with_mocks [ + {:eldap, [], + [ + open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, + simple_bind: fn _connection, _dn, ^password -> :ok end, + equalityMatch: fn _type, _value -> :ok end, + wholeSubtree: fn -> :ok end, + search: fn _connection, _options -> + {:ok, + {:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}], + []}} + end, + close: fn _connection -> + send(self(), :close_connection) + :ok + end + ]} + ] do + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert %{"access_token" => token} = json_response(conn, 200) + + token = Repo.get_by(Token, token: token) |> Repo.preload(:user) + + assert token.user.nickname == user.nickname + assert_received :close_connection + end + end + + test "falls back to the default authorization when LDAP is unavailable" do + password = "testpassword" + user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) + app = insert(:oauth_app, scopes: ["read", "write"]) + + host = Pleroma.Config.get([:ldap, :host]) |> to_charlist + port = Pleroma.Config.get([:ldap, :port]) + + with_mocks [ + {:eldap, [], + [ + open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end, + simple_bind: fn _connection, _dn, ^password -> :ok end, + close: fn _connection -> + send(self(), :close_connection) + :ok + end + ]} + ] do + log = + capture_log(fn -> + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert %{"access_token" => token} = json_response(conn, 200) + + token = Repo.get_by(Token, token: token) + + assert token.user_id == user.id + end) + + assert log =~ "Could not open LDAP connection: 'connect failed'" + refute_received :close_connection + end + end + + test "disallow authorization for wrong LDAP credentials" do + password = "testpassword" + user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) + app = insert(:oauth_app, scopes: ["read", "write"]) + + host = Pleroma.Config.get([:ldap, :host]) |> to_charlist + port = Pleroma.Config.get([:ldap, :port]) + + with_mocks [ + {:eldap, [], + [ + open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, + simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end, + close: fn _connection -> + send(self(), :close_connection) + :ok + end + ]} + ] do + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert %{"error" => "Invalid credentials"} = json_response(conn, 400) + assert_received :close_connection + end + end +end From 43fb03be5a8968c1df23938ed4f5a93825ab476c Mon Sep 17 00:00:00 2001 From: eugenijm Date: Fri, 15 Mar 2019 20:06:28 +0300 Subject: [PATCH 28/31] Allow to mark a single notification as read --- docs/Pleroma-API.md | 8 +++++++ lib/pleroma/notification.ex | 14 ++++++++++++ lib/pleroma/web/router.ex | 6 +++++ .../controllers/util_controller.ex | 12 ++++++++++ test/web/twitter_api/util_controller_test.exs | 22 +++++++++++++++++++ 5 files changed, 62 insertions(+) diff --git a/docs/Pleroma-API.md b/docs/Pleroma-API.md index 379d3dbed..478c9d874 100644 --- a/docs/Pleroma-API.md +++ b/docs/Pleroma-API.md @@ -108,3 +108,11 @@ See [Admin-API](Admin-API.md) * Response: JSON string. Returns the user flavour or the default one. * Example response: "glitch" * Note: This is intended to be used only by mastofe + +## `/api/pleroma/notifications/read` +### Mark a single notification as read +* Method `POST` +* Authentication: required +* Params: + * `id`: notifications's id +* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index fe8181d8b..765191275 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Notification do alias Pleroma.Web.CommonAPI.Utils import Ecto.Query + import Ecto.Changeset schema "notifications" do field(:seen, :boolean, default: false) @@ -22,6 +23,11 @@ defmodule Pleroma.Notification do timestamps() end + def changeset(%Notification{} = notification, attrs) do + notification + |> cast(attrs, [:seen]) + end + # TODO: Make generic and unify (see activity_pub.ex) defp restrict_max(query, %{"max_id" => max_id}) do from(activity in query, where: activity.id < ^max_id) @@ -68,6 +74,14 @@ def set_read_up_to(%{id: user_id} = _user, id) do Repo.update_all(query, []) end + def read_one(%User{} = user, notification_id) do + with {:ok, %Notification{} = notification} <- get(user, notification_id) do + notification + |> changeset(%{seen: true}) + |> Repo.update() + end + end + def get(%{id: user_id} = _user, id) do query = from( diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c56e4a421..befd382ba 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -190,6 +190,12 @@ defmodule Pleroma.Web.Router do post("/blocks_import", UtilController, :blocks_import) post("/follow_import", UtilController, :follow_import) end + + scope [] do + pipe_through(:oauth_read) + + post("/notifications/read", UtilController, :notifications_read) + end end scope "/oauth", Pleroma.Web.OAuth do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 8ed02a93f..320ec778c 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Comeonin.Pbkdf2 alias Pleroma.Emoji + alias Pleroma.Notification alias Pleroma.PasswordResetToken alias Pleroma.Repo alias Pleroma.User @@ -142,6 +143,17 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id} end end + def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, _} <- Notification.read_one(user, notification_id) do + json(conn, %{status: "success"}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + end + def config(conn, _params) do instance = Pleroma.Config.get(:instance) instance_fe = Pleroma.Config.get(:fe) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index fc762ab18..6e8a25056 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -1,6 +1,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Notification + alias Pleroma.Repo + alias Pleroma.Web.CommonAPI import Pleroma.Factory describe "POST /api/pleroma/follow_import" do @@ -52,6 +55,25 @@ test "it returns HTTP 200", %{conn: conn} do end end + describe "POST /api/pleroma/notifications/read" do + test "it marks a single notification as read", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, [notification1]} = Notification.create_notifications(activity1) + {:ok, [notification2]} = Notification.create_notifications(activity2) + + conn + |> assign(:user, user1) + |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) + |> json_response(:ok) + + assert Repo.get(Notification, notification1.id).seen + refute Repo.get(Notification, notification2.id).seen + end + end + describe "GET /api/statusnet/config.json" do test "it returns the managed config", %{conn: conn} do Pleroma.Config.put([:instance, :managed_config], false) From e0edc706cfe1a625ce5bf35e1935cfdc8b251edc Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 16 Mar 2019 01:12:50 +0000 Subject: [PATCH 29/31] oauth: add me property to token responses --- lib/pleroma/web/oauth/oauth_controller.ex | 7 +++++-- test/web/oauth/oauth_controller_test.exs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index ec70b7ccc..a2c876627 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -105,6 +105,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do fixed_token = fix_padding(params["code"]), %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id), + %User{} = user <- Repo.get(User, auth.user_id), {:ok, token} <- Token.exchange_token(app, auth), {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do response = %{ @@ -113,7 +114,8 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do refresh_token: token.refresh_token, created_at: DateTime.to_unix(inserted_at), expires_in: 60 * 10, - scope: Enum.join(token.scopes, " ") + scope: Enum.join(token.scopes, " "), + me: user.ap_id } json(conn, response) @@ -142,7 +144,8 @@ def token_exchange( access_token: token.token, refresh_token: token.refresh_token, expires_in: 60 * 10, - scope: Enum.join(token.scopes, " ") + scope: Enum.join(token.scopes, " "), + me: user.ap_id } json(conn, response) diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index ed94416ff..ff1e56fe9 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -132,11 +132,12 @@ test "issues a token for an all-body request" do "client_secret" => app.client_secret }) - assert %{"access_token" => token} = json_response(conn, 200) + assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200) token = Repo.get_by(Token, token: token) assert token assert token.scopes == auth.scopes + assert user.ap_id == ap_id end test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do From 40134598a88ec9123696bf42e49332520c9c7b6e Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 16 Mar 2019 04:05:19 +0000 Subject: [PATCH 30/31] test: fix defective ldap setup/teardown --- test/web/oauth/ldap_authorization_test.exs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs index 570e41f3e..5bf7eb93c 100644 --- a/test/web/oauth/ldap_authorization_test.exs +++ b/test/web/oauth/ldap_authorization_test.exs @@ -11,15 +11,17 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do import Mock setup_all do - ldap_authenticator = Pleroma.Config.get([Pleroma.Web.Auth.Authenticator]) + ldap_authenticator = + Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator) + ldap_enabled = Pleroma.Config.get([:ldap, :enabled]) on_exit(fn -> - Pleroma.Config.put([Pleroma.Web.Auth.Authenticator], ldap_authenticator) + Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator) Pleroma.Config.put([:ldap, :enabled], ldap_enabled) end) - Pleroma.Config.put([Pleroma.Web.Auth.Authenticator], Pleroma.Web.Auth.LDAPAuthenticator) + Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) Pleroma.Config.put([:ldap, :enabled], true) :ok From 4ed2618f6c732ba1009510f5698a5d981a151925 Mon Sep 17 00:00:00 2001 From: Fong-Wan Chau Date: Sun, 17 Mar 2019 09:46:46 -0400 Subject: [PATCH 31/31] Allow 'rel' attribute on `` link with specific values (for hashtag recognition). --- lib/pleroma/html.ex | 14 ++++++++++++++ test/html_test.exs | 8 ++++++++ .../mastodon_api/mastodon_api_controller_test.exs | 2 +- test/web/twitter_api/views/activity_view_test.exs | 2 +- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 05253157e..5b152d926 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -95,6 +95,13 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + Meta.allow_tag_with_this_attribute_values("a", "rel", [ + "tag", + "nofollow", + "noopener", + "noreferrer" + ]) + # paragraphs and linebreaks Meta.allow_tag_with_these_attributes("br", []) Meta.allow_tag_with_these_attributes("p", []) @@ -137,6 +144,13 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + Meta.allow_tag_with_this_attribute_values("a", "rel", [ + "tag", + "nofollow", + "noopener", + "noreferrer" + ]) + Meta.allow_tag_with_these_attributes("abbr", ["title"]) Meta.allow_tag_with_these_attributes("b", []) diff --git a/test/html_test.exs b/test/html_test.exs index 29cab17f3..0b5d3d892 100644 --- a/test/html_test.exs +++ b/test/html_test.exs @@ -10,6 +10,8 @@ defmodule Pleroma.HTMLTest do this is in bold

this is a paragraph

this is a linebreak
+ this is a link with allowed "rel" attribute:
+ this is a link with not allowed "rel" attribute: example.com this is an image:
""" @@ -24,6 +26,8 @@ test "works as expected" do this is in bold this is a paragraph this is a linebreak + this is a link with allowed "rel" attribute: example.com + this is a link with not allowed "rel" attribute: example.com this is an image: alert('hacked') """ @@ -44,6 +48,8 @@ test "normalizes HTML as expected" do this is in bold

this is a paragraph

this is a linebreak
+ this is a link with allowed "rel" attribute: + this is a link with not allowed "rel" attribute: example.com this is an image:
alert('hacked') """ @@ -66,6 +72,8 @@ test "normalizes HTML as expected" do this is in bold

this is a paragraph

this is a linebreak
+ this is a link with allowed "rel" attribute: + this is a link with not allowed "rel" attribute: example.com this is an image:
alert('hacked') """ diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 059d5237d..74bf05708 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1632,7 +1632,7 @@ test "updates the user's bio", %{conn: conn} do assert user = json_response(conn, 200) assert user["note"] == - ~s(I drink #cofe with with @) <> user2.nickname <> ~s() diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs index 6f0786b1c..d9df01c6e 100644 --- a/test/web/twitter_api/views/activity_view_test.exs +++ b/test/web/twitter_api/views/activity_view_test.exs @@ -82,7 +82,7 @@ test "a create activity with a html status" do result = ActivityView.render("activity.json", activity: activity) assert result["statusnet_html"] == - "#Bike log - Commute Tuesday
https://pla.bike/posts/20181211/
#cycling #CHScycling #commute
MVIMG_20181211_054020.jpg" + "#Bike log - Commute Tuesday
https://pla.bike/posts/20181211/
#cycling #CHScycling #commute
MVIMG_20181211_054020.jpg" assert result["text"] == "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg"