Skip to content

Commit fd06538

Browse files
committed
Add test for person inbox and outbox
check activitypub content-type Add importer ( automate creation of Purls and Vulnerabilities by reading the git repo ) Add a small test for the importer Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent aca1288 commit fd06538

13 files changed

Lines changed: 407 additions & 109 deletions

File tree

purl_sync/purl_sync/urls.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
urlpatterns = [
4343
path("admin/", admin.site.urls),
4444
path(".well-known/webfinger", WebfingerView.as_view(), name="web-finger"),
45-
path("home", HomeView.as_view(), name="home-page"),
45+
path("", HomeView.as_view(), name="home-page"),
4646
path("users/@<str:slug>", PersonView.as_view(), name="user-profile"),
4747
path("purls/@<path:slug>/", PurlView.as_view(), name="purl-profile"),
4848
path("purls/@<path:purl_string>/follow", FollowView.as_view(), name="purl-follow"),
@@ -66,15 +66,13 @@
6666
"vulnerability/<uuid:vulnerability_id>/", redirect_vulnerability, name="vulnerability-page"
6767
),
6868
path("notes/<uuid:uuid>", NoteView.as_view(), name="note-page"),
69-
path("/api/v0/users/@<str:username>/outbox/", PersonOutbox.as_view(), name="user-outbox"),
70-
path("/api/v0/users/@<str:username>/outbox/", PersonInbox.as_view(), name="user-inbox"),
71-
path("/api/v0/purls/@<path:purl_string>/inbox/", PurlInbox.as_view(), name="purl-inbox"),
72-
path("/api/v0/purls/@<path:purl_string>/outbox/", PurlOutbox.as_view(), name="purl-outbox"),
69+
path("api/v0/users/@<str:username>/inbox", PersonInbox.as_view(), name="user-inbox"),
70+
path("api/v0/users/@<str:username>/outbox", PersonOutbox.as_view(), name="user-outbox"),
71+
path("api/v0/purls/@<path:purl_string>/inbox", PurlInbox.as_view(), name="purl-inbox"),
72+
path("api/v0/purls/@<path:purl_string>/outbox", PurlOutbox.as_view(), name="purl-outbox"),
73+
path("api/v0/users/@<str:username>/following/", UserFollowing.as_view(), name="user-following"),
7374
path(
74-
"/api/v0/users/@<str:username>/following/", UserFollowing.as_view(), name="user-following"
75-
),
76-
path(
77-
"/api/v0/purls/@<path:purl_string>/followers/",
75+
"api/v0/purls/@<path:purl_string>/followers/",
7876
PurlFollowers.as_view(),
7977
name="purl-followers",
8078
),

purl_sync/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ pytest-django==4.5.2
1111
sqlparse==0.4.4
1212
tomli==2.0.1
1313
typing_extensions==4.6.3
14+
15+
djangorestframework~=3.14.0
16+
requests~=2.31.0
17+
saneyaml~=0.6.0
18+
django-oauth-toolkit~=2.3.0

purl_sync/review/activitypub.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from django.contrib.auth.models import User
1717
from django.http import HttpResponseBadRequest
18+
from django.http import HttpResponseForbidden
1819
from django.http import JsonResponse
1920
from git import Repo
2021

@@ -56,45 +57,45 @@
5657
AP_TARGET = {"cc": "https://www.w3.org/ns/activitystreams#Public"}
5758

5859

59-
def check_ap_context(response):
60+
def check_ap_context(request):
6061
"""
61-
check activitypub context response
62+
check activitypub context request and return request without @context
6263
"""
63-
if not response:
64-
return HttpResponseBadRequest("Invalid context response")
65-
66-
if not response.get("@context"):
67-
response.append(**AP_CONTEXT)
68-
69-
return response
64+
if request.get("@context") == AP_CONTEXT["@context"]:
65+
request.pop("@context")
66+
return request
67+
else:
68+
return None
7069

7170

7271
def add_ap_target(response):
7372
"""
7473
Add target activitypub response
7574
"""
76-
if not response:
77-
raise Exception("Invalid context response")
75+
if response is not dict:
76+
raise Exception("Invalid response")
7877

7978
if not response.get("cc"):
8079
response.append(**AP_TARGET)
8180

8281
return response
8382

8483

85-
def is_valid_header(method):
86-
@functools.wraps(method)
84+
def has_valid_header(view):
85+
"""
86+
check if the request header in the VALID_HEADERS if yes return view else return HttpResponseForbidden
87+
"""
88+
8789
def wrapper(request, *args, **kwargs):
88-
if not request.headers.get("Content-Type") in VALID_HEADERS:
89-
return HttpResponseBadRequest("Invalid content-type")
90+
content_type = request.headers.get("Content-Type")
91+
if content_type in VALID_HEADERS:
92+
return view(request, *args, **kwargs)
93+
else:
94+
return HttpResponseForbidden()
9095

9196
return wrapper
9297

9398

94-
def is_valid_context(response):
95-
return True if response in AP_CONTEXT else False
96-
97-
9899
@dataclass
99100
class Activity:
100101
type: Literal["Follow", "Create", "Update", "Delete"]
@@ -361,7 +362,8 @@ def failed_ap_rs(self):
361362
def create_activity_obj(data):
362363
"""Convert json object to activity object"""
363364
payload = json.loads(data)
364-
return Activity(**payload)
365+
payload_without_context = check_ap_context(payload)
366+
return Activity(**payload_without_context)
365367

366368

367369
@dataclass

purl_sync/review/importer.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import json
2+
from dataclasses import dataclass
3+
from pathlib import Path
4+
5+
import saneyaml
6+
7+
from review.models import Note
8+
from review.models import Purl
9+
from review.models import Repository
10+
from review.models import Service
11+
from review.models import Vulnerability
12+
from review.utils import generate_webfinger
13+
14+
15+
@dataclass
16+
class Importer:
17+
repo_obj: Repository
18+
default_service: Service
19+
20+
def run(self):
21+
git_repo = self.repo_obj.git_repo_obj
22+
for commit in git_repo.commit().tree.traverse():
23+
file = Path(commit.abspath)
24+
if file.is_file():
25+
with open(commit.abspath) as f:
26+
yaml_data = saneyaml.load(f.read())
27+
if str(file.name).startswith("VCID"):
28+
self.register_vul(yaml_data)
29+
else:
30+
self.register_pkg(yaml_data)
31+
32+
def register_pkg(self, data):
33+
pacakge = data["pacakge"]
34+
Purl.objects.get_or_create(string=pacakge, service=self.default_service)
35+
for version in data["versions"]:
36+
acct = generate_webfinger(pacakge)
37+
Note.objects.get_or_create(acct=acct, content=json.dumps(version))
38+
39+
def register_vul(self, data):
40+
Vulnerability.objects.get_or_create(repo=self.repo_obj, filename=data["vulnerability_id"])

purl_sync/review/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ class Person(Actor):
204204
RemotePerson, on_delete=models.CASCADE, null=True, blank=True
205205
)
206206

207+
# TODO raise error if the user doesn't have a user or remote actor
208+
207209
@property
208210
def acct(self):
209211
return generate_webfinger(self.user.username)
@@ -251,7 +253,7 @@ def review_count(self):
251253
return Review.objects.filter(vulnerability__repo=self).count()
252254

253255
@property
254-
def git_repo(self):
256+
def git_repo_obj(self):
255257
return Repo(self.path)
256258

257259
class Meta:
@@ -274,7 +276,6 @@ class Vulnerability(models.Model):
274276
help_text="The object's unique global identifier",
275277
)
276278
repo = models.ForeignKey(Repository, on_delete=models.CASCADE)
277-
branch_name = models.CharField(max_length=28, help_text="")
278279
filename = models.CharField(max_length=255, help_text="")
279280
commit_id = models.CharField(max_length=50, help_text="")
280281

@@ -284,7 +285,7 @@ def load_file(self):
284285
fetch the url
285286
copied from https://stackoverflow.com/a/54900961/9871531
286287
"""
287-
commit = self.repo.git_repo.commit(self.commit_id)
288+
commit = self.repo.git_repo_obj.commit(self.commit_id)
288289
target_file = commit.tree / self.filename
289290

290291
with io.BytesIO(target_file.data_stream.read()) as f:
@@ -296,7 +297,6 @@ def to_ap(self):
296297
"id": "https://example.com/vulnerability/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
297298
"type": "Vulnerability",
298299
"repository": full_reverse("repository-page", self.id),
299-
"branch": self.branch_name,
300300
"filename": self.filename,
301301
"commit": self.commit_id,
302302
}

purl_sync/review/templates/navbar.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@
3838
More
3939
</a>
4040

41-
<div class="navbar-dropdown">
41+
<div class="navbar-dropdown" href="https://github.com/nexB/vulnerablecode/">
4242
<a class="navbar-item">
4343
About
4444
</a>
4545
<hr class="navbar-divider">
46-
<a class="navbar-item">
46+
<a class="navbar-item" href="https://github.com/nexB/vulnerablecode/issues/new">
4747
Report an issue
4848
</a>
4949
</div>

purl_sync/review/tests/test_activitypub.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import pytest
1212

1313
from purl_sync.settings import DOMAIN
14+
from review.activitypub import AP_CONTEXT
1415
from review.activitypub import Activity
1516
from review.activitypub import create_activity_obj
1617
from review.models import Follow
@@ -34,6 +35,7 @@
3435
def test_person_create_note(person):
3536
payload = json.dumps(
3637
{
38+
**AP_CONTEXT,
3739
"type": "Create",
3840
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
3941
"object": {
@@ -53,6 +55,7 @@ def test_person_create_note(person):
5355
def test_person_create_review(person, vulnerability, repo):
5456
payload = json.dumps(
5557
{
58+
**AP_CONTEXT,
5659
"type": "Create",
5760
"actor": f"https://127.0.0.1:8000/users/@ziad",
5861
"object": {
@@ -81,6 +84,7 @@ def test_person_create_review(person, vulnerability, repo):
8184
def test_purl_create_note(purl, service):
8285
payload = json.dumps(
8386
{
87+
**AP_CONTEXT,
8488
"type": "Create",
8589
"actor": f"https://127.0.0.1:8000/purls/@{purl.string}/",
8690
"object": {
@@ -98,6 +102,7 @@ def test_purl_create_note(purl, service):
98102
def test_service_create_repo(service):
99103
payload = json.dumps(
100104
{
105+
**AP_CONTEXT,
101106
"type": "Create",
102107
"actor": f"https://127.0.0.1:8000/users/@{service.user.username}",
103108
"object": {
@@ -119,6 +124,7 @@ def test_service_create_repo(service):
119124
def test_person_follow_purl(person, purl):
120125
payload = json.dumps(
121126
{
127+
**AP_CONTEXT,
122128
"type": "Follow",
123129
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
124130
"object": {
@@ -138,6 +144,7 @@ def test_person_follow_purl(person, purl):
138144
def test_person_delete_note(person, note):
139145
payload = json.dumps(
140146
{
147+
**AP_CONTEXT,
141148
"type": "Delete",
142149
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
143150
"object": {
@@ -156,6 +163,7 @@ def test_person_delete_note(person, note):
156163
def test_person_delete_note(person, note):
157164
payload = json.dumps(
158165
{
166+
**AP_CONTEXT,
159167
"type": "Delete",
160168
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
161169
"object": {
@@ -174,6 +182,7 @@ def test_person_delete_note(person, note):
174182
def test_person_update_note(person, note):
175183
payload = json.dumps(
176184
{
185+
**AP_CONTEXT,
177186
"type": "Update",
178187
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
179188
"object": {
@@ -194,6 +203,7 @@ def test_person_update_note(person, note):
194203
def test_person_unfollow_purl(person, purl, follow):
195204
payload = json.dumps(
196205
{
206+
**AP_CONTEXT,
197207
"type": "UnFollow",
198208
"actor": f"https://127.0.0.1:8000/users/@{person.user.username}",
199209
"object": {

0 commit comments

Comments
 (0)