Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor Move for more redundancy #3360

Merged
merged 3 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion bookwyrm/models/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


class Move(ActivityMixin, BookWyrmModel):
"""migrating an activitypub user account"""
"""migrating an activitypub object"""

user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor"
Expand Down
40 changes: 40 additions & 0 deletions bookwyrm/tests/data/ap_user_move.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "https://example.com/user/mouse",
"type": "Person",
"preferredUsername": "mouse",
"name": "MOUSE?? MOUSE!!",
"inbox": "https://example.com/user/mouse/inbox",
"outbox": "https://example.com/user/mouse/outbox",
"followers": "https://example.com/user/mouse/followers",
"following": "https://example.com/user/mouse/following",
"summary": "",
"publicKey": {
"id": "https://example.com/user/mouse/#main-key",
"owner": "https://example.com/user/mouse",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6QisDrjOQvkRo/MqNmSYPwqtt\nCxg/8rCW+9jKbFUKvqjTeKVotEE85122v/DCvobCCdfQuYIFdVMk+dB1xJ0iPGPg\nyU79QHY22NdV9mFKA2qtXVVxb5cxpA4PlwOHM6PM/k8B+H09OUrop2aPUAYwy+vg\n+MXyz8bAXrIS1kq6fQIDAQAB\n-----END PUBLIC KEY-----"
},
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"bookwyrmUser": true,
"manuallyApprovesFollowers": false,
"discoverable": false,
"alsoKnownAs": ["https://your.domain.here/user/rat"],
"devices": "https://friend.camp/users/tripofmice/collections/devices",
"tag": [],
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/images/avatars/AL-2-crop-50.png"
}
}
60 changes: 60 additions & 0 deletions bookwyrm/tests/models/test_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
""" testing move models """
from unittest.mock import patch
from django.core.exceptions import PermissionDenied
from django.test import TestCase

from bookwyrm import models


class MoveUser(TestCase):
"""move your account to another identity"""

@classmethod
def setUpTestData(cls):
"""we need some users for this"""
with patch("bookwyrm.models.user.set_remote_server.delay"):
cls.target_user = models.User.objects.create_user(
"rat",
"rat@rat.com",
"ratword",
local=False,
remote_id="https://example.com/users/rat",
inbox="https://example.com/users/rat/inbox",
outbox="https://example.com/users/rat/outbox",
)

with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
patch("bookwyrm.activitystreams.populate_stream_task.delay"),
patch("bookwyrm.lists_stream.populate_lists_task.delay"),
):
cls.origin_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
)
cls.origin_user.remote_id = "http://local.com/user/mouse"
cls.origin_user.save(broadcast=False, update_fields=["remote_id"])

def test_user_move_unauthorized(self):
"""attempt a user move without alsoKnownAs set"""

with self.assertRaises(PermissionDenied):
models.MoveUser.objects.create(
user=self.origin_user,
object=self.origin_user.remote_id,
target=self.target_user,
)

@patch("bookwyrm.suggested_users.remove_user_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
def test_user_move(self, *_):
"""move user"""

self.target_user.also_known_as.add(self.origin_user.id)
self.target_user.save(broadcast=False)

models.MoveUser.objects.create(
user=self.origin_user,
object=self.origin_user.remote_id,
target=self.target_user,
)
self.assertEqual(self.origin_user.moved_to, self.target_user.remote_id)
116 changes: 116 additions & 0 deletions bookwyrm/tests/views/preferences/test_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
""" test move functionality """
import json
from unittest.mock import patch
import pathlib
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase
from django.test.client import RequestFactory
import responses

from bookwyrm import forms, models, views


@patch("bookwyrm.activitystreams.add_status_task.delay")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
class ViewsHelpers(TestCase):
"""viewing and creating statuses"""

@classmethod
def setUpTestData(cls):
"""we need basic test data and mocks"""

with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
patch("bookwyrm.activitystreams.populate_stream_task.delay"),
patch("bookwyrm.lists_stream.populate_lists_task.delay"),
patch("bookwyrm.suggested_users.rerank_user_task.delay"),
):

cls.local_user = models.User.objects.create_user(
"rat",
"rat@rat.com",
"ratword",
local=True,
discoverable=True,
localname="rat",
remote_id="https://your.domain.here/user/rat",
)

with (
patch("bookwyrm.models.user.set_remote_server.delay"),
patch("bookwyrm.suggested_users.rerank_user_task.delay"),
):
cls.remote_user = models.User.objects.create_user(
"mouse@example.com",
"mouse@mouse.com",
"mouseword",
local=False,
remote_id="https://example.com/user/mouse",
)

def setUp(self):
"""individual test setup"""
self.factory = RequestFactory()
datafile = pathlib.Path(__file__).parent.joinpath(
"../../data/ap_user_move.json"
)
self.userdata = json.loads(datafile.read_bytes())
del self.userdata["icon"]

@responses.activate
@patch("bookwyrm.models.user.set_remote_server.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
def test_move_user_view(self, *_):
"""move user"""

self.assertEqual(self.remote_user.remote_id, "https://example.com/user/mouse")
self.assertIsNone(self.local_user.moved_to)
self.assertIsNone(self.remote_user.moved_to)
self.assertIsNone(self.local_user.also_known_as.first())
self.assertIsNone(self.remote_user.also_known_as.first())

username = "mouse@example.com"
wellknown = {
"subject": "acct:mouse@example.com",
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://example.com/user/mouse",
}
],
}
responses.add(
responses.GET,
f"https://example.com/.well-known/webfinger?resource=acct:{username}",
json=wellknown,
status=200,
)
responses.add(
responses.GET,
"https://example.com/user/mouse",
json=self.userdata,
status=200,
)

view = views.MoveUser.as_view()
form = forms.MoveUserForm()
form.data["target"] = "mouse@example.com"
form.data["password"] = "ratword"

request = self.factory.post("", form.data)
request.user = self.local_user
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()

with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
view(request)
self.local_user.refresh_from_db()

self.assertEqual(self.local_user.also_known_as.first(), self.remote_user)
self.assertEqual(self.remote_user.also_known_as.first(), self.local_user)
self.assertEqual(self.local_user.moved_to, "https://example.com/user/mouse")
9 changes: 7 additions & 2 deletions bookwyrm/views/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def is_bookwyrm_request(request):
return True


def handle_remote_webfinger(query, unknown_only=False):
def handle_remote_webfinger(query, unknown_only=False, refresh=False):
"""webfingerin' other servers"""
user = None

Expand All @@ -76,6 +76,11 @@ def handle_remote_webfinger(query, unknown_only=False):
return None

try:

if refresh:
# Always fetch the remote info - don't even bother checking the DB
raise models.User.DoesNotExist("remote_only is set to True")

user = models.User.objects.get(username__iexact=query)

if unknown_only:
Expand All @@ -93,7 +98,7 @@ def handle_remote_webfinger(query, unknown_only=False):
if link.get("rel") == "self":
try:
user = activitypub.resolve_remote_id(
link["href"], model=models.User
link["href"], model=models.User, refresh=refresh
)
except (KeyError, activitypub.ActivitySerializerError):
return None
Expand Down
3 changes: 2 additions & 1 deletion bookwyrm/views/preferences/move_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def post(self, request):

if form.is_valid() and user.check_password(form.cleaned_data["password"]):
username = form.cleaned_data["target"]
target = handle_remote_webfinger(username)
target = handle_remote_webfinger(username, refresh=True)

try:
models.MoveUser.objects.create(
Expand Down Expand Up @@ -81,6 +81,7 @@ def post(self, request):
return TemplateResponse(request, "preferences/alias_user.html", data)

user.also_known_as.add(remote_user.id)
user.save(broadcast=True) # broadcast the alias

return redirect("prefs-alias")

Expand Down