diff --git a/purl_sync/purl_sync/settings.py b/purl_sync/purl_sync/settings.py index 3042c2694..ac33b3107 100644 --- a/purl_sync/purl_sync/settings.py +++ b/purl_sync/purl_sync/settings.py @@ -41,6 +41,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "review", + "oauth2_provider", ] MIDDLEWARE = [ diff --git a/purl_sync/purl_sync/urls.py b/purl_sync/purl_sync/urls.py index 97b0168fa..03d65093f 100644 --- a/purl_sync/purl_sync/urls.py +++ b/purl_sync/purl_sync/urls.py @@ -16,29 +16,44 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.contrib.auth.views import LogoutView +from django.urls import include from django.urls import path from review.views import CreatGitView +from review.views import DatabaseAdminView +from review.views import ReviewListView +from review.views import SecurityTeamSignUp +from review.views import SecurityTeamView +from review.views import UserLogin from review.views import WebfingerView -from review.views import database_admin_profile_view -from review.views import login_view -from review.views import security_team_profile_view -from review.views import security_team_signup_view +from review.views import create_review +from review.views import database_admin_inbox +from review.views import database_admin_outbox +from review.views import review_page_view +from review.views import security_team_inbox +from review.views import security_team_outbox urlpatterns = [ path("admin/", admin.site.urls), path(".well-known/webfinger", WebfingerView.as_view()), - path("security-team/@", security_team_profile_view), - path("database-admin/@", database_admin_profile_view), - path("login/", login_view), - path("signup/", security_team_signup_view), - # path("create-repo/", CreatGitView.as_view()), - # path("review//", review_page_view), + path("security-team/@", SecurityTeamView.as_view()), + path("database-admin/@", DatabaseAdminView.as_view()), + path("sign-up/", SecurityTeamSignUp.as_view(), name="signup"), + path("login/", UserLogin.as_view(), name="login"), + path("logout/", LogoutView.as_view(next_page="login"), name="logout"), + # path("create-repo", CreatGitView.as_view()), + path("review//", review_page_view), + path("create-review", create_review), + path("review-list", ReviewListView.as_view()), + path("security-team//inbox/", security_team_inbox), + path("security-team//outbox/", security_team_outbox), + path("database-admin//outbox/", database_admin_outbox), + path("database-admin//inbox/", database_admin_inbox), # path("security-team/@/edit-followers/", database_admin_profile_view), - # path("/inbox/", ), - # path("/outbox/", ), - # path("/followers/", ), - # path("/following/", ), + # path("database-admin//followers/", ), + # path("security-team//following/", ), + path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")), ] if settings.DEBUG: diff --git a/purl_sync/review/admin.py b/purl_sync/review/admin.py index 48ca2d1ff..7baaf0975 100644 --- a/purl_sync/review/admin.py +++ b/purl_sync/review/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from review.models import DatabaseAdmin +from review.models import Follow from review.models import GitRepo from review.models import Notes from review.models import PackageUrl @@ -21,6 +22,7 @@ admin.site.register(PackageUrl) admin.site.register(Notes) admin.site.register(Review) +admin.site.register(Follow) admin.site.register(RemoteSecurityTeam) admin.site.register(RemoteDatabaseAdmin) diff --git a/purl_sync/review/forms.py b/purl_sync/review/forms.py index 729ff820f..981be48dd 100644 --- a/purl_sync/review/forms.py +++ b/purl_sync/review/forms.py @@ -1,6 +1,21 @@ from django import forms +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.models import User class CreateRepoForm(forms.Form): repo_name = forms.CharField() repo_url = forms.URLField(required=True) + + +class SecurityTeamSignUpForm(UserCreationForm): + email = forms.EmailField(max_length=254) + + class Meta: + model = User + fields = ( + "username", + "email", + "password1", + "password2", + ) diff --git a/purl_sync/review/migrations/0001_initial.py b/purl_sync/review/migrations/0001_initial.py index c7ea45276..f5fcd9888 100644 --- a/purl_sync/review/migrations/0001_initial.py +++ b/purl_sync/review/migrations/0001_initial.py @@ -1,11 +1,9 @@ -# Generated by Django 4.2.2 on 2023-06-29 08:43 +# Generated by Django 4.2.2 on 2023-07-03 13:56 -import uuid - -import django.db.models.deletion from django.conf import settings -from django.db import migrations -from django.db import models +from django.db import migrations, models +import django.db.models.deletion +import uuid class Migration(migrations.Migration): @@ -37,18 +35,8 @@ class Migration(migrations.Migration): ("note", models.CharField(help_text="the profile description", max_length=100)), ("public_key", models.TextField()), ( - "followers", - models.JSONField( - default=dict, - help_text="e.g. {'secrityteam@vcio': ['pkg:npm/foobar@12.3.1']}", - ), - ), - ("followers_count", models.PositiveIntegerField(default=0)), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL - ), + "database_type", + models.IntegerField(choices=[(0, "Local"), (1, "Remote")], default=1), ), ], options={ @@ -112,8 +100,13 @@ class Migration(migrations.Migration): auto_created=True, primary_key=True, serialize=False, verbose_name="ID" ), ), - ("voter_acct", models.CharField(help_text="security@vcio.com", max_length=100)), - ("gainer_acct", models.CharField(help_text="security@nexb.com", max_length=100)), + ("voter", models.CharField(help_text="security@vcio.com", max_length=100)), + ("acceptor", models.CharField(help_text="security@nexb.com", max_length=100)), + ("object_url", models.URLField()), + ( + "object_type", + models.IntegerField(choices=[(0, "Review"), (1, "Comment")], default=0), + ), ("positive", models.BooleanField(default=True)), ], ), @@ -157,16 +150,24 @@ class Migration(migrations.Migration): ("note", models.CharField(help_text="the profile description", max_length=100)), ("public_key", models.TextField()), ( - "following", - models.JSONField( - default=dict, help_text="e.g. {'datebase1@vcio': ['pkg:npm/foobar@12.3.1']}" + "security_team_type", + models.IntegerField(choices=[(0, "Local"), (1, "Remote")], default=1), + ), + ( + "remote_actor", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="review.remotesecurityteam", ), ), - ("following_count", models.PositiveIntegerField(default=0)), ( "user", models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, ), ), ], @@ -188,7 +189,7 @@ class Migration(migrations.Migration): ( "status", models.IntegerField( - choices=[(1, "Open"), (2, "Draft"), (3, "Closed"), (4, "Merged")] + choices=[(0, "Open"), (1, "Draft"), (2, "Closed"), (3, "Merged")] ), ), ( @@ -219,4 +220,53 @@ class Migration(migrations.Migration): ), ], ), + migrations.AddField( + model_name="databaseadmin", + name="remote_actor", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="review.remotedatabaseadmin", + ), + ), + migrations.AddField( + model_name="databaseadmin", + name="user", + field=models.OneToOneField( + null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + migrations.CreateModel( + name="Follow", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "database_admin", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="review.databaseadmin" + ), + ), + ( + "purl", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="review.packageurl" + ), + ), + ( + "security_team", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="review.securityteam" + ), + ), + ], + options={ + "unique_together": {("purl", "database_admin", "security_team")}, + }, + ), ] diff --git a/purl_sync/review/models.py b/purl_sync/review/models.py index 223b96a25..2fbf2b63d 100644 --- a/purl_sync/review/models.py +++ b/purl_sync/review/models.py @@ -29,21 +29,83 @@ class Meta: class Reputation(models.Model): - voter_acct = models.CharField(max_length=100, help_text="security@vcio.com") - gainer_acct = models.CharField(max_length=100, help_text="security@nexb.com") + voter = models.CharField(max_length=100, help_text="security@vcio.com") + acceptor = models.CharField(max_length=100, help_text="security@nexb.com") + object_url = models.URLField() + + class ObjectTypes(models.IntegerChoices): + REVIEW = 0 + COMMENT = 1 + + object_type = models.IntegerField(choices=ObjectTypes.choices, default=0, help_text="") + positive = models.BooleanField(default=True) +class Notes(models.Model): + id = models.AutoField(primary_key=True, help_text="") + acct = models.CharField(max_length=100) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + +class RemoteActor(models.Model): + url = models.URLField(primary_key=True) + username = models.CharField(max_length=100) + profile_data = models.JSONField() + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class RemoteSecurityTeam(RemoteActor): + @property + def get_profile_data(self): + raise NotImplementedError + + @property + def create_activity(self): + raise NotImplementedError + + +class RemoteDatabaseAdmin(RemoteActor): + @property + def get_profile_data(self): + raise NotImplementedError + + @property + def create_activity(self): + raise NotImplementedError + + +class SecurityTeam(Actor): + user = models.OneToOneField(User, null=True, on_delete=models.CASCADE) + remote_actor = models.OneToOneField( + RemoteSecurityTeam, on_delete=models.CASCADE, null=True, blank=True + ) + + class SecurityTeamTypes(models.IntegerChoices): + LOCAL = 0 + REMOTE = 1 + + security_team_type = models.IntegerField( + choices=SecurityTeamTypes.choices, default=1, help_text="" + ) + + class DatabaseAdmin(Actor): - user = models.OneToOneField(User, on_delete=models.CASCADE) - followers = models.JSONField( - default=dict, help_text="e.g. {'secrityteam@vcio': ['pkg:npm/foobar@12.3.1']}" + user = models.OneToOneField(User, on_delete=models.CASCADE, null=True) + remote_actor = models.OneToOneField( + RemoteDatabaseAdmin, on_delete=models.CASCADE, null=True, blank=True ) - followers_count = models.PositiveIntegerField(default=0) - def save(self, *args, **kwargs): - self.followers_count = len(self.followers) - super().save(*args, **kwargs) + class DatabaseTypes(models.IntegerChoices): + LOCAL = 0 + REMOTE = 1 + + database_type = models.IntegerField(choices=DatabaseTypes.choices, default=1, help_text="") class GitRepo(models.Model): @@ -67,24 +129,16 @@ class PackageUrl(models.Model): vulnerability = models.ForeignKey(Vulnerability, on_delete=models.CASCADE) -class SecurityTeam(Actor): - user = models.OneToOneField(User, on_delete=models.CASCADE) - following = models.JSONField( - default=dict, help_text="e.g. {'datebase1@vcio': ['pkg:npm/foobar@12.3.1']}" - ) - following_count = models.PositiveIntegerField(default=0) - - def save(self, *args, **kwargs): - self.following_count = len(self.following) - super().save(*args, **kwargs) +class Follow(models.Model): + purl = models.ForeignKey(PackageUrl, on_delete=models.CASCADE) + database_admin = models.ForeignKey(DatabaseAdmin, on_delete=models.CASCADE) + security_team = models.ForeignKey(SecurityTeam, on_delete=models.CASCADE) + class Meta: + unique_together = [["purl", "database_admin", "security_team"]] -class Notes(models.Model): - id = models.AutoField(primary_key=True, help_text="") - acct = models.CharField(max_length=100) - content = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) + def __str__(self): + return f"{self.security_team} - {self.database_admin}" class Review(models.Model): @@ -97,39 +151,9 @@ class Review(models.Model): notes = models.ManyToManyField(Notes) class ReviewStatus(models.IntegerChoices): - OPEN = 1 - DRAFT = 2 - CLOSED = 3 - MERGED = 4 + OPEN = 0 + DRAFT = 1 + CLOSED = 2 + MERGED = 3 status = models.IntegerField(choices=ReviewStatus.choices, help_text="") - - -class RemoteActor(models.Model): - url = models.URLField(primary_key=True) - username = models.CharField(max_length=100) - profile_data = models.JSONField() - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - abstract = True - - -class RemoteSecurityTeam(RemoteActor): - @property - def get_profile_data(self): - raise NotImplementedError - - @property - def create_activity(self): - raise NotImplementedError - - -class RemoteDatabaseAdmin(RemoteActor): - @property - def get_profile_data(self): - raise NotImplementedError - - @property - def create_activity(self): - raise NotImplementedError diff --git a/purl_sync/review/templates/actor_profile.html b/purl_sync/review/templates/actor_profile.html deleted file mode 100644 index 82042eccd..000000000 --- a/purl_sync/review/templates/actor_profile.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
- -
-

- @{{ actor_details.username }} -

- -

- {{ actor_details.note }} -

-
\ No newline at end of file diff --git a/purl_sync/review/templates/create_review.html b/purl_sync/review/templates/create_review.html index b9a0c7ebe..2b15fd489 100644 --- a/purl_sync/review/templates/create_review.html +++ b/purl_sync/review/templates/create_review.html @@ -3,7 +3,208 @@ Create Review {% endblock %} +{% block extrahead %} +{% endblock %} {% block content %} +
+ +
+
+
+
+ +
+ +

+ Review +

+ + + +
+
+
+ +
+ + + +
+
+
+ +
+
{% endblock %} \ No newline at end of file diff --git a/purl_sync/review/templates/database_admin_profile.html b/purl_sync/review/templates/database_admin_profile.html index c98b3ed73..d81341c1e 100644 --- a/purl_sync/review/templates/database_admin_profile.html +++ b/purl_sync/review/templates/database_admin_profile.html @@ -5,7 +5,35 @@ {% block content %}
- {% include "actor_profile.html" %} +
+
+ +
+

+ @{{ database_admin.user.username }} +

+ +

+ {{ database_admin.note }} +

+
@@ -18,7 +46,7 @@

- {% for repository in repository_list %} + {% for repository in git_list %} {{ repository.name }} @@ -31,6 +59,25 @@
+ +
+ +
+ {% endblock %} diff --git a/purl_sync/review/templates/login.html b/purl_sync/review/templates/login.html index 95f27adae..8fab31ce3 100644 --- a/purl_sync/review/templates/login.html +++ b/purl_sync/review/templates/login.html @@ -8,18 +8,23 @@
-
+ {% if form.errors %} +

Your username and password didn't match. Please try again.

+ {% endif %} + + {% csrf_token %} +
- +
- +
- +
diff --git a/purl_sync/review/templates/navbar.html b/purl_sync/review/templates/navbar.html index 6d06cbf07..2faf64a2e 100644 --- a/purl_sync/review/templates/navbar.html +++ b/purl_sync/review/templates/navbar.html @@ -14,12 +14,12 @@