Skip to content

Commit bb08331

Browse files
committed
Add a server to server follow request
Refactor Remote Actors and add a remote url to objects Add a form for subscribe to remote user Add test for server to server follow request Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent b29c117 commit bb08331

15 files changed

Lines changed: 354 additions & 534 deletions

purl_sync/purl_sync/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
env = environ.Env()
1515
environ.Env.read_env(str(ENV_FILE))
1616

17-
DOMAIN = env.str("DOMAIN", "127.0.0.1")
17+
PURL_SYNC_DOMAIN = env.str("PURL_SYNC_DOMAIN", "127.0.0.1:8000")
1818

1919
# Build paths inside the project like this: BASE_DIR / 'subdir'.
2020
BASE_DIR = Path(__file__).resolve().parent.parent

purl_sync/review/activitypub.py

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,26 @@
1212
from dataclasses import dataclass
1313
from typing import Literal
1414
from typing import Optional
15+
from urllib.parse import urlparse
1516

1617
from django.contrib.auth.models import User
1718
from django.http import HttpResponseBadRequest
1819
from django.http import HttpResponseForbidden
1920
from django.http import JsonResponse
2021
from git import Repo
2122

22-
from purl_sync.settings import GIT_PATH
23-
from review.models import Follow
23+
from purl_sync.settings import GIT_PATH, PURL_SYNC_DOMAIN
24+
from review.models import Follow, RemoteActor
2425
from review.models import Note
2526
from review.models import Person
2627
from review.models import Purl
2728
from review.models import Repository
2829
from review.models import Review
2930
from review.models import Service
3031
from review.models import Vulnerability
31-
from review.utils import full_resolve
32+
from review.utils import full_resolve, fetch_actor, webfinger_actor
3233
from review.utils import full_reverse
34+
from django.urls import resolve
3335

3436
CONTENT_TYPE = "application/activity+json"
3537
ACTOR_TYPES = ["Person", "Purl"]
@@ -63,6 +65,15 @@
6365
"Vulnerability": "vulnerability-page",
6466
}
6567

68+
URL_MAPPER = {
69+
"user-ap-profile": "username",
70+
"purl-ap-profile": "purl_string",
71+
"review-page": "uuid",
72+
"repository-page": "uuid",
73+
"note-page": "uuid",
74+
"vulnerability-page": "uuid",
75+
}
76+
6677

6778
def check_and_r_ap_context(request):
6879
"""
@@ -143,17 +154,22 @@ def get_by_type(self):
143154
def get(self):
144155
obj_id, page_name = full_resolve(self.id)
145156
if page_name == "purl-ap-profile":
146-
return Purl.objects.get(string=obj_id["purl_string"])
157+
try:
158+
purl = Purl.objects.get(string=obj_id["purl_string"])
159+
except Purl.DoesNotExist:
160+
purl = None
161+
return purl
162+
147163
elif page_name == "user-ap-profile":
148-
user = User.objects.get(username=obj_id["username"])
149-
if hasattr(user, "person"):
150-
return user.person
151-
elif hasattr(user, "service"):
152-
return user.service
153-
else:
154-
raise AttributeError("Invalid actor type")
155-
else:
156-
raise AttributeError("Invalid actor")
164+
try:
165+
user = User.objects.get(username=obj_id["username"])
166+
if hasattr(user, "person"):
167+
return user.person
168+
elif hasattr(user, "service"):
169+
return user.service
170+
except User.DoesNotExist:
171+
user = None
172+
return None
157173

158174

159175
@dataclass
@@ -172,19 +188,10 @@ class ApObject:
172188
vulnerability: str = None
173189
published: str = None
174190

175-
URL_MAPPER = {
176-
"user-ap-profile": "username",
177-
"purl-ap-profile": "purl_string",
178-
"review-page": "uuid",
179-
"repository-page": "uuid",
180-
"note-page": "uuid",
181-
"vulnerability-page": "uuid",
182-
}
183-
184191
def get_object(self):
185192
if self.id:
186193
obj_id, page_name = full_resolve(self.id)
187-
identifier = self.URL_MAPPER[page_name]
194+
identifier = URL_MAPPER[page_name]
188195
return OBJECT_TYPES[self.type].objects.get(id=obj_id[identifier])
189196
raise ValueError("Invalid object id")
190197

@@ -197,18 +204,52 @@ class FollowActivity:
197204

198205
def save(self):
199206
actor = self.actor.get()
200-
if not actor:
201-
return self.failed_ap_rs()
202-
obj_id, page_name = full_resolve(self.object.id)
203-
if type(actor) is Person:
204-
purl = Purl.objects.get(string=obj_id["purl_string"])
205-
new_obj, created = Follow.objects.get_or_create(person_id=actor.id, purl=purl)
207+
parser = urlparse(self.actor.id)
208+
if not actor and parser.netloc != PURL_SYNC_DOMAIN:
209+
# remote person ( send a remote follow request if created and assume the request was accepted )
210+
resolver = resolve(parser.path)
211+
identity = URL_MAPPER[resolver.url_name]
212+
url = webfinger_actor(parser.netloc, resolver.kwargs[identity])
213+
actor_details = fetch_actor(url)
214+
remote_actor, created = RemoteActor.objects.get_or_create(
215+
username=actor_details["name"], url=actor_details["id"]
216+
)
217+
actor, created = Person.objects.get_or_create(remote_actor=remote_actor)
218+
219+
# --------------------------------------------
220+
parser = urlparse(self.object.id)
221+
resolver = resolve(parser.path)
222+
obj_id, page_name = resolver.kwargs, resolver.url_name
223+
identity = URL_MAPPER[page_name]
224+
if parser.netloc == PURL_SYNC_DOMAIN:
225+
# local purl
226+
try:
227+
purl = Purl.objects.get(string=obj_id["purl_string"])
228+
except Purl.DoesNotExist:
229+
purl = None
206230
else:
207-
return self.failed_ap_rs()
231+
# remote purl
232+
url = webfinger_actor(parser.netloc, resolver.kwargs[identity])
233+
purl_details = fetch_actor(url)
234+
remote_actor, created = RemoteActor.objects.get_or_create(
235+
username=purl_details["name"], url=purl_details["id"]
236+
)
237+
purl, created = Purl.objects.get_or_create(
238+
remote_actor=remote_actor, string=purl_details["string"]
239+
)
240+
# TODO ( send a remote follow request if created and assume the request was accepted )
241+
# if actor or purl is remote
242+
243+
if purl and actor:
244+
Follow.objects.get_or_create(person=actor, purl=purl)
245+
return self.succeeded_ap_rs()
246+
247+
return self.failed_ap_rs()
248+
208249

209250
def succeeded_ap_rs(self):
210251
"""Response for successfully deleting the object"""
211-
return JsonResponse({"Location": {self.object}}, status=201)
252+
return JsonResponse({"Location": "{self.object}"}, status=201)
212253

213254
def failed_ap_rs(self):
214255
"""Response for failure deleting the object"""

purl_sync/review/admin.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
#
99
from django.contrib import admin
1010

11-
from review.models import Follow
11+
from review.models import Follow, RemoteActor
1212
from review.models import Note
1313
from review.models import Person
1414
from review.models import Purl
15-
from review.models import RemotePerson
16-
from review.models import RemoteService
1715
from review.models import Repository
1816
from review.models import Reputation
1917
from review.models import Review
@@ -31,5 +29,4 @@
3129
admin.site.register(Review)
3230
admin.site.register(Reputation)
3331

34-
admin.site.register(RemoteService)
35-
admin.site.register(RemotePerson)
32+
admin.site.register(RemoteActor)

0 commit comments

Comments
 (0)