Skip to content

Commit a8b8653

Browse files
authored
Merge pull request #8 from ziadhany/add-permission
Add missing permissions check on some activities like Update, Delete.
2 parents 5f4fa05 + 627f219 commit a8b8653

File tree

7 files changed

+158
-38
lines changed

7 files changed

+158
-38
lines changed

fedcode/activitypub.py

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from federatedcode.settings import FEDERATED_CODE_DOMAIN
2424
from federatedcode.settings import FEDERATED_CODE_GIT_PATH
2525

26-
from .models import Follow, FederateRequest
26+
from .models import Follow, FederateRequest, SyncRequest
2727
from .models import Note
2828
from .models import Person
2929
from .models import Package
@@ -72,10 +72,10 @@
7272
URL_MAPPER = {
7373
"user-ap-profile": "username",
7474
"purl-ap-profile": "purl_string",
75-
"review-page": "uuid",
76-
"repository-page": "uuid",
77-
"note-page": "uuid",
78-
"vulnerability-page": "str",
75+
"review-page": "review_id",
76+
"repository-page": 'repository_id',
77+
"note-page": "note_id",
78+
"vulnerability-page": "vulnerability_id",
7979
}
8080

8181
logger = logging.getLogger(__name__)
@@ -107,7 +107,7 @@ def add_ap_target(response):
107107

108108
def has_valid_header(view):
109109
"""
110-
check if the request header in the AP_VALID_HEADERS if yes return view else return HttpResponseForbidden
110+
check if the request header in the AP_VALID_HEADERS if yes return view else return
111111
"""
112112

113113
def wrapper(request, *args, **kwargs):
@@ -153,6 +153,43 @@ def federate(cls, targets, body, key_id):
153153
except Exception as e:
154154
logger.error(f"{e}")
155155

156+
@classmethod
157+
def get_actor_permissions(cls, actor, object):
158+
"""get the actor permission to do some activity on the object"""
159+
permissions = {
160+
Person: {
161+
Note: lambda: {
162+
CreateActivity,
163+
UpdateActivity if object.acct == actor.acct else None,
164+
DeleteActivity if object.acct == actor.acct else None
165+
},
166+
167+
Review: lambda: {
168+
CreateActivity,
169+
UpdateActivity if object.author == actor else None,
170+
DeleteActivity if object.author == actor else None
171+
},
172+
},
173+
Service: {
174+
Repository: lambda: {
175+
CreateActivity,
176+
SyncActivity if object.admin == actor else None,
177+
UpdateActivity if object.admin == actor else None,
178+
DeleteActivity if object.admin == actor else None
179+
}
180+
},
181+
Package: {
182+
Note: lambda: {
183+
CreateActivity,
184+
UpdateActivity if object.acct == actor.acct else None,
185+
DeleteActivity if object.acct == actor.acct else None
186+
},
187+
}
188+
}
189+
190+
# Return the permissions for the specific actor and object type
191+
return permissions.get(type(actor), {}).get(type(object), lambda: {})
192+
156193

157194
@dataclass
158195
class ApActor:
@@ -430,13 +467,13 @@ def save(self):
430467
(isinstance(actor, Person) and self.object.type in ["Note", "Review"])
431468
or (isinstance(actor, Service) and self.object.type == "Repository")
432469
or (isinstance(actor, Package) and self.object.type == "Note")
433-
):
470+
) and UpdateActivity in Activity.get_actor_permissions(actor, old_obj)():
434471
for key, value in updated_param[self.object.type].items():
435472
if value:
436473
setattr(old_obj, key, value)
437474
old_obj.save()
438475

439-
Activity.federate(targets=self.to, body=self.to_ap(), key_id=actor.key_id)
476+
Activity.federate(targets=self.to, body=self.to_ap(), key_id=actor.key_id)
440477
return self.succeeded_ap_rs(old_obj.to_ap)
441478

442479
def succeeded_ap_rs(self, update_obj):
@@ -480,11 +517,12 @@ def save(self):
480517
or (type(actor) is Service and self.object.type == ["Repository", "Package"])
481518
):
482519
instance = self.object.get()
483-
instance.delete()
484-
Activity.federate(targets=self.to, body=self.to_ap(), key_id=actor.key_id)
485-
return self.succeeded_ap_rs()
486-
else:
487-
return self.failed_ap_rs()
520+
if DeleteActivity in Activity.get_actor_permissions(actor, instance)():
521+
instance.delete()
522+
Activity.federate(targets=self.to, body=self.to_ap(), key_id=actor.key_id)
523+
return self.succeeded_ap_rs()
524+
525+
return self.failed_ap_rs()
488526

489527
def ap_rq(self):
490528
"""Request for deleting object in activitypub format"""
@@ -571,9 +609,13 @@ def save(self):
571609
actor = self.actor.get()
572610
if not actor:
573611
return self.failed_ap_rs()
574-
repo = self.object.get().git_repo_obj
575-
repo.remotes.origin.pull()
576-
return self.succeeded_ap_rs()
612+
repo = self.object.get()
613+
614+
if SyncActivity in Activity.get_actor_permissions(actor, repo)():
615+
SyncRequest.objects.create(repo=repo)
616+
return self.succeeded_ap_rs()
617+
618+
return self.failed_ap_rs()
577619

578620
def succeeded_ap_rs(self):
579621
"""Response for successfully deleting the object"""
@@ -607,3 +649,4 @@ def check_remote_actor(key_id):
607649
obj_id, page_name = resolver.kwargs, resolver.url_name
608650
identity = URL_MAPPER[page_name]
609651
return webfinger_actor(parser.netloc, resolver.kwargs[identity])
652+

fedcode/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def __str__(self):
352352
return f"{self.person.user.username} - {self.package.purl}"
353353

354354

355-
class Repository(models.Model): # TODO
355+
class Repository(models.Model):
356356
"""
357357
A git repository used as a backing storage for Package and vulnerability data
358358
"""
@@ -435,7 +435,6 @@ def to_ap(self):
435435

436436

437437
class Review(models.Model):
438-
# TODO
439438
id = models.UUIDField(
440439
primary_key=True,
441440
editable=False,

federatedcode/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
redirect_vulnerability,
8181
name="vulnerability-page",
8282
),
83-
path("notes/<uuid:uuid>", NoteView.as_view(), name="note-page"),
83+
path("notes/<uuid:note_id>", NoteView.as_view(), name="note-page"),
8484
path("api/v0/users/@<str:username>", UserProfile.as_view(), name="user-ap-profile"),
8585
path(
8686
"api/v0/purls/@<path:purl_string>/",

tests/test_activitypub.py

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,27 @@
1111
import pytest
1212

1313
from fedcode.activitypub import AP_CONTEXT
14+
from fedcode.activitypub import Activity
15+
from fedcode.activitypub import CreateActivity
16+
from fedcode.activitypub import DeleteActivity
17+
from fedcode.activitypub import SyncActivity
18+
from fedcode.activitypub import UpdateActivity
1419
from fedcode.activitypub import create_activity_obj
1520
from fedcode.models import Follow
1621
from fedcode.models import Note
1722
from fedcode.models import Repository
1823
from fedcode.models import Review
24+
from fedcode.models import SyncRequest
1925

26+
from .test_models import fake_service
2027
from .test_models import follow
2128
from .test_models import mute_post_save_signal
2229
from .test_models import note
2330
from .test_models import package
2431
from .test_models import person
32+
from .test_models import pkg_note
2533
from .test_models import repo
34+
from .test_models import review
2635
from .test_models import service
2736
from .test_models import vulnerability
2837

@@ -240,19 +249,55 @@ def test_person_unfollow_package(person, package, follow):
240249
assert Follow.objects.count() == 0
241250

242251

243-
# @pytest.mark.django_db
244-
# def test_person_sync_repo(service, repo):
245-
# payload = json.dumps(
246-
# {
247-
# **AP_CONTEXT,
248-
# "type": "Sync",
249-
# "actor": f"https://127.0.0.1:8000/users/@{service.user.username}",
250-
# "object": {
251-
# "type": "Repository",
252-
# "id": f"https://127.0.0.1:8000/repository/{repo.id}/",
253-
# },
254-
# }
255-
# )
256-
#
257-
# activity = create_activity_obj(payload)
258-
# sync_activity = activity.handler()
252+
@pytest.mark.django_db
253+
def test_get_actor_permissions(
254+
person, package, service, repo, note, review, pkg_note, fake_service
255+
):
256+
assert Activity.get_actor_permissions(person, note)() == {
257+
CreateActivity,
258+
UpdateActivity,
259+
DeleteActivity,
260+
}
261+
assert Activity.get_actor_permissions(person, review)() == {
262+
CreateActivity,
263+
UpdateActivity,
264+
DeleteActivity,
265+
}
266+
assert Activity.get_actor_permissions(service, repo)() == {
267+
CreateActivity,
268+
UpdateActivity,
269+
DeleteActivity,
270+
SyncActivity,
271+
}
272+
assert Activity.get_actor_permissions(package, pkg_note)() == {
273+
CreateActivity,
274+
UpdateActivity,
275+
DeleteActivity,
276+
}
277+
278+
note.acct = "[email protected]"
279+
assert Activity.get_actor_permissions(person, note)() == {CreateActivity, None}
280+
281+
repo.admin = fake_service
282+
assert Activity.get_actor_permissions(service, repo)() == {CreateActivity, None}
283+
284+
assert Activity.get_actor_permissions(package, note)() == {CreateActivity, None}
285+
286+
287+
@pytest.mark.django_db
288+
def test_service_sync_repo(service, repo):
289+
payload = json.dumps(
290+
{
291+
**AP_CONTEXT,
292+
"type": "Sync",
293+
"actor": f"https://127.0.0.1:8000/api/v0/users/@{service.user.username}",
294+
"object": {
295+
"type": "Repository",
296+
"id": f"https://127.0.0.1:8000/repository/{repo.id}/",
297+
},
298+
}
299+
)
300+
301+
activity = create_activity_obj(payload)
302+
sync_activity = activity.handler()
303+
assert SyncRequest.objects.all().count() == 1

tests/test_ap_api.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,18 @@ def test_get_user_outbox(person, vulnerability, review, note):
226226
format="json",
227227
)
228228
assert json.loads(response.content) == {
229-
"notes": {"type": "OrderedCollection", "totalItems": 0, "orderedItems": []},
229+
"notes": {
230+
"type": "OrderedCollection",
231+
"totalItems": 1,
232+
"orderedItems": [
233+
{
234+
"author": note.acct,
235+
"content": note.content,
236+
"id": f"https://127.0.0.1:8000/notes/{note.id}",
237+
"type": "Note",
238+
}
239+
],
240+
},
230241
"reviews": {
231242
"type": "OrderedCollection",
232243
"totalItems": 1,

tests/test_models.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ def service(db):
3535
)
3636

3737

38+
@pytest.fixture
39+
def fake_service(db):
40+
user = User.objects.create(
41+
username="fake_service",
42+
43+
password="complex-password",
44+
)
45+
return Service.objects.create(
46+
user=user,
47+
)
48+
49+
3850
@pytest.fixture
3951
def package(db, service):
4052
return Package.objects.create(
@@ -116,11 +128,21 @@ def review(db, repo, person):
116128
@pytest.fixture
117129
def note(db):
118130
return Note.objects.create(
119-
acct="ziad@vcio",
131+
acct="ziad@127.0.0.1:8000",
120132
content="Comment #1",
121133
)
122134

123135

136+
@pytest.fixture
137+
def pkg_note(db, package):
138+
return Note.objects.create(
139+
acct=package.acct,
140+
content="purl: "
141+
"pkg:maven/[email protected]?arch=aarch64&distroversion=edge&reponame=community\n"
142+
" affected_by_vulnerabilities: ....",
143+
)
144+
145+
124146
@pytest.fixture
125147
def follow(db, package, person):
126148
return Follow.objects.create(package=package, person=person)

tests/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def test_full_reverse():
7676

7777
def test_full_resolve():
7878
assert full_resolve(f"https://127.0.0.1:8000/notes/7e676ad1-995d-405c-a829-cb39813c74e5") == (
79-
{"uuid": uuid.UUID("7e676ad1-995d-405c-a829-cb39813c74e5")},
79+
{"note_id": uuid.UUID("7e676ad1-995d-405c-a829-cb39813c74e5")},
8080
"note-page",
8181
)
8282

0 commit comments

Comments
 (0)