...
 
Commits (16)
...@@ -248,7 +248,12 @@ defmodule Pleroma.Formatter do ...@@ -248,7 +248,12 @@ defmodule Pleroma.Formatter do
subs = subs =
subs ++ subs ++
Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} -> Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} ->
ap_id = info["source_data"]["url"] || ap_id ap_id =
if is_binary(info["source_data"]["url"]) do
info["source_data"]["url"]
else
ap_id
end
short_match = String.split(match, "@") |> tl() |> hd() short_match = String.split(match, "@") |> tl() |> hd()
......
...@@ -4,7 +4,7 @@ defmodule Pleroma.User do ...@@ -4,7 +4,7 @@ defmodule Pleroma.User do
import Ecto.{Changeset, Query} import Ecto.{Changeset, Query}
alias Pleroma.{Repo, User, Object, Web, Activity, Notification} alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Web.{OStatus, Websub} alias Pleroma.Web.{OStatus, Websub, OAuth}
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
schema "users" do schema "users" do
...@@ -132,6 +132,9 @@ defmodule Pleroma.User do ...@@ -132,6 +132,9 @@ defmodule Pleroma.User do
|> validate_required([:password, :password_confirmation]) |> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password) |> validate_confirmation(:password)
OAuth.Token.delete_user_tokens(struct)
OAuth.Authorization.delete_user_authorizations(struct)
if changeset.valid? do if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
...@@ -184,7 +187,15 @@ defmodule Pleroma.User do ...@@ -184,7 +187,15 @@ defmodule Pleroma.User do
def needs_update?(_), do: true def needs_update?(_), do: true
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do
{:ok, follower}
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
follow(follower, followed)
end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
if !User.ap_enabled?(followed) do if !User.ap_enabled?(followed) do
follow(follower, followed) follow(follower, followed)
else else
...@@ -728,6 +739,7 @@ defmodule Pleroma.User do ...@@ -728,6 +739,7 @@ defmodule Pleroma.User do
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname) Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
end end
def ap_enabled?(%User{local: true}), do: true
def ap_enabled?(%User{info: info}), do: info["ap_enabled"] def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
def ap_enabled?(_), do: false def ap_enabled?(_), do: false
......
...@@ -83,7 +83,6 @@ defmodule Pleroma.Web.CommonAPI do ...@@ -83,7 +83,6 @@ defmodule Pleroma.Web.CommonAPI do
visibility = get_visibility(data) visibility = get_visibility(data)
with status <- String.trim(status), with status <- String.trim(status),
length when length in 1..@limit <- String.length(status),
attachments <- attachments_from_ids(data["media_ids"]), attachments <- attachments_from_ids(data["media_ids"]),
mentions <- Formatter.parse_mentions(status), mentions <- Formatter.parse_mentions(status),
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
...@@ -100,6 +99,8 @@ defmodule Pleroma.Web.CommonAPI do ...@@ -100,6 +99,8 @@ defmodule Pleroma.Web.CommonAPI do
), ),
context <- make_context(inReplyTo), context <- make_context(inReplyTo),
cw <- data["spoiler_text"], cw <- data["spoiler_text"],
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
length when length in 1..@limit <- String.length(full_payload),
object <- object <-
make_note_data( make_note_data(
user.ap_id, user.ap_id,
......
...@@ -126,6 +126,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do ...@@ -126,6 +126,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> String.replace(~r/\r?\n/, "") |> String.replace(~r/\r?\n/, "")
|> (&{[], &1}).() |> (&{[], &1}).()
|> Formatter.add_user_links(mentions) |> Formatter.add_user_links(mentions)
|> Formatter.add_hashtag_links(tags)
|> Formatter.finalize() |> Formatter.finalize()
end end
......
...@@ -4,7 +4,7 @@ defmodule Pleroma.Web.OAuth.Authorization do ...@@ -4,7 +4,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
alias Pleroma.{User, Repo} alias Pleroma.{User, Repo}
alias Pleroma.Web.OAuth.{Authorization, App} alias Pleroma.Web.OAuth.{Authorization, App}
import Ecto.{Changeset} import Ecto.{Changeset, Query}
schema "oauth_authorizations" do schema "oauth_authorizations" do
field(:token, :string) field(:token, :string)
...@@ -45,4 +45,12 @@ defmodule Pleroma.Web.OAuth.Authorization do ...@@ -45,4 +45,12 @@ defmodule Pleroma.Web.OAuth.Authorization do
end end
def use_token(%Authorization{used: true}), do: {:error, "already used"} def use_token(%Authorization{used: true}), do: {:error, "already used"}
def delete_user_authorizations(%User{id: user_id}) do
from(
a in Pleroma.Web.OAuth.Authorization,
where: a.user_id == ^user_id
)
|> Repo.delete_all()
end
end end
defmodule Pleroma.Web.OAuth.Token do defmodule Pleroma.Web.OAuth.Token do
use Ecto.Schema use Ecto.Schema
import Ecto.Query
alias Pleroma.{User, Repo} alias Pleroma.{User, Repo}
alias Pleroma.Web.OAuth.{Token, App, Authorization} alias Pleroma.Web.OAuth.{Token, App, Authorization}
...@@ -35,4 +37,12 @@ defmodule Pleroma.Web.OAuth.Token do ...@@ -35,4 +37,12 @@ defmodule Pleroma.Web.OAuth.Token do
Repo.insert(token) Repo.insert(token)
end end
def delete_user_tokens(%User{id: user_id}) do
from(
t in Pleroma.Web.OAuth.Token,
where: t.user_id == ^user_id
)
|> Repo.delete_all()
end
end end
...@@ -223,7 +223,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do ...@@ -223,7 +223,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|> Enum.map(fn account -> |> Enum.map(fn account ->
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id), with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id),
%User{} = followed <- User.get_or_fetch(account), %User{} = followed <- User.get_or_fetch(account),
{:ok, follower} <- User.follow(follower, followed) do {:ok, follower} <- User.maybe_direct_follow(follower, followed) do
ActivityPub.follow(follower, followed) ActivityPub.follow(follower, followed)
else else
err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}") err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}")
......
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Person",
"id": "https://osada.macgirvin.com/channel/mike",
"preferredUsername": "mike",
"name": "Mike Macgirvin (Osada)",
"updated": "2018-08-29T03:09:11Z",
"icon": {
"type": "Image",
"mediaType": "image/jpeg",
"updated": "2018-08-29T03:10:13Z",
"url": "https://osada.macgirvin.com/photo/profile/l/2",
"height": 300,
"width": 300
},
"url": [
{
"type": "Link",
"mediaType": "text/html",
"href": "https://osada.macgirvin.com/channel/mike"
},
{
"type": "Link",
"mediaType": "text/x-zot+json",
"href": "https://osada.macgirvin.com/channel/mike"
}
],
"inbox": "https://osada.macgirvin.com/inbox/mike",
"outbox": "https://osada.macgirvin.com/outbox/mike",
"followers": "https://osada.macgirvin.com/followers/mike",
"following": "https://osada.macgirvin.com/following/mike",
"endpoints": {
"sharedInbox": "https://osada.macgirvin.com/inbox"
},
"publicKey": {
"id": "https://osada.macgirvin.com/channel/mike/public_key_pem",
"owner": "https://osada.macgirvin.com/channel/mike",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAskSyK2VwBNKbzZl9XNJk\nvxU5AAilmRArMmmKSzphdHaVBHakeafUfixvqNrQ/oX2srJvJKcghNmEMrJ6MJ7r\npeEndVOo7pcP4PwVjtnC06p3J711q5tBehqM25BfCLCrB2YqWF6c8zk3CPN3Na21\n8k5s4cO95N/rGN+Po0XFAX/HjKjlpgNpKRDrpxmXxTU8NZfAqeQGJ5oiMBZI9vVB\n+eU7t1L6F5/XWuUCeP4OMrG8oZX822AREba8rknS6DpkWGES0Rx2eNOyYTf6ue75\nI6Ek6rlO+da5wMWr+3BvYMq4JMIwTHzAO+ZqqJPFpzKSiVuAWb2DOX/MDFecVWJE\ntF/R60lONxe4e/00MPCoDdqkLKdwROsk1yGL7z4Zk6jOWFEhIcWy/d2Ya5CpPvS3\nu4wNN4jkYAjra+8TiloRELhV4gpcEk8nkyNwLXOhYm7zQ5sIc5rfXoIrFzALB86W\nG05Nnqg+77zZIaTZpD9qekYlaEt+0OVtt9TTIeTiudQ983l6mfKwZYymrzymH1dL\nVgxBRYo+Z53QOSLiSKELfTBZxEoP1pBw6RiOHXydmJ/39hGgc2YAY/5ADwW2F2yb\nJ7+gxG6bPJ3ikDLYcD4CB5iJQdnTcDsFt3jyHAT6wOCzFAYPbHUqtzHfUM30dZBn\nnJhQF8udPLcXLaj6GW75JacCAwEAAQ==\n-----END PUBLIC KEY-----\n"
},
"signature": {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "RsaSignature2017",
"nonce": "bd60167a764a936788d9538531284dfacc258daae0297bc34a83bce136dedb5d",
"creator": "https://osada.macgirvin.com/channel/mike/public_key_pem",
"created": "2018-10-17T07:16:28Z",
"signatureValue": "WbfFVIPImkd3yNu6brz0CvZaeV242rwAbH0vy8DM4vfnXCxLr5Uv/Wj9gwP+tbooTxGaahAKBeqlGkQp8RLEo37LATrKMRLA/0V6DeeV+C5ORWR9B4WxyWiD3s/9Wf+KesFMtktNLAcMZ5PfnOS/xNYerhnpkp/gWPxtkglmLIWJv+w18A5zZ01JCxsO4QljHbhYaEUPHUfQ97abrkLECeam+FThVwdO6BFCtbjoNXHfzjpSZL/oKyBpi5/fpnqMqOLOQPs5WgBBZJvjEYYkQcoPTyxYI5NGpNbzIjGHPQNuACnOelH16A7L+q4swLWDIaEFeXQ2/5bmqVKZDZZ6usNP4QyTVszwd8jqo27qcDTNibXDUTsTdKpNQvM/3UncBuzuzmUV3FczhtGshIU1/pRVZiQycpVqPlGLvXhP/yZCe+1siyqDd+3uMaS2vkHTObSl5r+VYof+c+TcjrZXHSWnQTg8/X3zkoBWosrQ93VZcwjzMxQoARYv6rphbOoTz7RPmGAXYUt3/PDWkqDlmQDwCpLNNkJo1EidyefZBdD9HXQpCBO0ZU0NHb0JmPvg/+zU0krxlv70bm3RHA/maBETVjroIWzt7EwQEg5pL2hVnvSBG+1wF3BtRVe77etkPOHxLnYYIcAMLlVKCcgDd89DPIziQyruvkx1busHI08="
}
}
{
"subject": "acct:mike@osada.macgirvin.com",
"aliases": [
"https://osada.macgirvin.com/channel/mike",
"https://osada.macgirvin.com/~mike",
"https://osada.macgirvin.com/@mike"
],
"properties": {
"http://webfinger.net/ns/name": "Mike Macgirvin (Osada)",
"http://xmlns.com/foaf/0.1/name": "Mike Macgirvin (Osada)",
"https://w3id.org/security/v1#publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAskSyK2VwBNKbzZl9XNJk\nvxU5AAilmRArMmmKSzphdHaVBHakeafUfixvqNrQ/oX2srJvJKcghNmEMrJ6MJ7r\npeEndVOo7pcP4PwVjtnC06p3J711q5tBehqM25BfCLCrB2YqWF6c8zk3CPN3Na21\n8k5s4cO95N/rGN+Po0XFAX/HjKjlpgNpKRDrpxmXxTU8NZfAqeQGJ5oiMBZI9vVB\n+eU7t1L6F5/XWuUCeP4OMrG8oZX822AREba8rknS6DpkWGES0Rx2eNOyYTf6ue75\nI6Ek6rlO+da5wMWr+3BvYMq4JMIwTHzAO+ZqqJPFpzKSiVuAWb2DOX/MDFecVWJE\ntF/R60lONxe4e/00MPCoDdqkLKdwROsk1yGL7z4Zk6jOWFEhIcWy/d2Ya5CpPvS3\nu4wNN4jkYAjra+8TiloRELhV4gpcEk8nkyNwLXOhYm7zQ5sIc5rfXoIrFzALB86W\nG05Nnqg+77zZIaTZpD9qekYlaEt+0OVtt9TTIeTiudQ983l6mfKwZYymrzymH1dL\nVgxBRYo+Z53QOSLiSKELfTBZxEoP1pBw6RiOHXydmJ/39hGgc2YAY/5ADwW2F2yb\nJ7+gxG6bPJ3ikDLYcD4CB5iJQdnTcDsFt3jyHAT6wOCzFAYPbHUqtzHfUM30dZBn\nnJhQF8udPLcXLaj6GW75JacCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"http://purl.org/zot/federation": "zot6,activitypub"
},
"links": [
{
"rel": "http://webfinger.net/rel/avatar",
"type": "image/jpeg",
"href": "https://osada.macgirvin.com/photo/profile/l/2"
},
{
"rel": "http://webfinger.net/rel/blog",
"href": "https://osada.macgirvin.com/channel/mike"
},
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": "https://osada.macgirvin.com"
},
{
"rel": "http://purl.org/zot/protocol/6.0",
"type": "application/x-zot+json",
"href": "https://osada.macgirvin.com/channel/mike"
},
{
"rel": "http://purl.org/openwebauth/v1",
"type": "application/x-zot+json",
"href": "https://osada.macgirvin.com/owa"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://osada.macgirvin.com/follow?url={uri}"
},
{
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href": "https://osada.macgirvin.com/channel/mike"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://osada.macgirvin.com/channel/mike"
}
]
}
defmodule Pleroma.FormatterTest do defmodule Pleroma.FormatterTest do
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.User
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
...@@ -131,6 +132,24 @@ defmodule Pleroma.FormatterTest do ...@@ -131,6 +132,24 @@ defmodule Pleroma.FormatterTest do
assert expected_text == Formatter.finalize({subs, text}) assert expected_text == Formatter.finalize({subs, text})
end end
test "gives a replacement for user links when the user is using Osada" do
mike = User.get_or_fetch("mike@osada.macgirvin.com")
text = "@mike@osada.macgirvin.com test"
mentions = Formatter.parse_mentions(text)
{subs, text} = Formatter.add_user_links({[], text}, mentions)
assert length(subs) == 1
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
expected_text =
"<span><a class='mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
assert expected_text == Formatter.finalize({subs, text})
end
test "gives a replacement for single-character local nicknames" do test "gives a replacement for single-character local nicknames" do
text = "@o hi" text = "@o hi"
o = insert(:user, %{nickname: "o"}) o = insert(:user, %{nickname: "o"})
......
...@@ -3,6 +3,27 @@ defmodule HTTPoisonMock do ...@@ -3,6 +3,27 @@ defmodule HTTPoisonMock do
def get(url, body \\ [], headers \\ []) def get(url, body \\ [], headers \\ [])
def get("https://osada.macgirvin.com/channel/mike", _, _) do
{:ok,
%Response{
status_code: 200,
body:
File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
}}
end
def get(
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
_,
_
) do
{:ok,
%Response{
status_code: 200,
body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
}}
end
def get("https://info.pleroma.site/activity.json", _, _) do def get("https://info.pleroma.site/activity.json", _, _) do
{:ok, {:ok,
%Response{ %Response{
......
...@@ -55,6 +55,15 @@ defmodule Pleroma.UserTest do ...@@ -55,6 +55,15 @@ defmodule Pleroma.UserTest do
{:error, _} = User.follow(blockee, blocker) {:error, _} = User.follow(blockee, blocker)
end end
test "local users do not automatically follow local locked accounts" do
follower = insert(:user, info: %{"locked" => true})
followed = insert(:user, info: %{"locked" => true})
{:ok, follower} = User.maybe_direct_follow(follower, followed)
refute User.following?(follower, followed)
end
# This is a somewhat useless test. # This is a somewhat useless test.
# test "following a remote user will ensure a websub subscription is present" do # test "following a remote user will ensure a websub subscription is present" do
# user = insert(:user) # user = insert(:user)
......
...@@ -55,4 +55,26 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do ...@@ -55,4 +55,26 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do
assert {:error, "token expired"} == Authorization.use_token(expired_auth) assert {:error, "token expired"} == Authorization.use_token(expired_auth)
end end
test "delete authorizations" do
{:ok, app} =
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client",
scopes: "scope",
redirect_uris: "url"
})
)
user = insert(:user)
{:ok, auth} = Authorization.create_authorization(app, user)
{:ok, auth} = Authorization.use_token(auth)
{auths, _} = Authorization.delete_user_authorizations(user)
{_, invalid} = Authorization.use_token(auth)
assert auth != invalid
end
end end
...@@ -29,4 +29,36 @@ defmodule Pleroma.Web.OAuth.TokenTest do ...@@ -29,4 +29,36 @@ defmodule Pleroma.Web.OAuth.TokenTest do
auth = Repo.get(Authorization, auth.id) auth = Repo.get(Authorization, auth.id)
{:error, "already used"} = Token.exchange_token(app, auth) {:error, "already used"} = Token.exchange_token(app, auth)
end end
test "deletes all tokens of a user" do
{:ok, app1} =
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client1",
scopes: "scope",
redirect_uris: "url"
})
)
{:ok, app2} =
Repo.insert(
App.register_changeset(%App{}, %{
client_name: "client2",
scopes: "scope",
redirect_uris: "url"
})
)
user = insert(:user)
{:ok, auth1} = Authorization.create_authorization(app1, user)
{:ok, auth2} = Authorization.create_authorization(app2, user)
{:ok, token1} = Token.exchange_token(app1, auth1)
{:ok, token2} = Token.exchange_token(app2, auth2)
{tokens, _} = Token.delete_user_tokens(user)
assert tokens == 2
end
end end