1212from dataclasses import dataclass
1313from typing import Literal
1414from typing import Optional
15+ from urllib .parse import urlparse
1516
1617from django .contrib .auth .models import User
1718from django .http import HttpResponseBadRequest
1819from django .http import HttpResponseForbidden
1920from django .http import JsonResponse
2021from 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
2425from review .models import Note
2526from review .models import Person
2627from review .models import Purl
2728from review .models import Repository
2829from review .models import Review
2930from review .models import Service
3031from review .models import Vulnerability
31- from review .utils import full_resolve
32+ from review .utils import full_resolve , fetch_actor , webfinger_actor
3233from review .utils import full_reverse
34+ from django .urls import resolve
3335
3436CONTENT_TYPE = "application/activity+json"
3537ACTOR_TYPES = ["Person" , "Purl" ]
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
6778def 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"""
0 commit comments