Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

commit #977

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/apps/owasp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class SnapshotAdmin(admin.ModelAdmin):
"new_users",
)
list_display = (
"title",
"start_at",
"end_at",
"status",
Expand All @@ -134,6 +135,8 @@ class SnapshotAdmin(admin.ModelAdmin):
)
ordering = ("-start_at",)
search_fields = (
"title",
"key",
"status",
"error_message",
)
Expand Down
71 changes: 71 additions & 0 deletions backend/apps/owasp/graphql/nodes/snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""OWASP snapshot GraphQL node."""

import graphene

from apps.github.graphql.nodes.issue import IssueNode
from apps.github.graphql.nodes.release import ReleaseNode
from apps.github.graphql.nodes.user import UserNode
from apps.owasp.graphql.nodes.chapter import ChapterNode
from apps.owasp.graphql.nodes.common import GenericEntityNode
from apps.owasp.graphql.nodes.project import ProjectNode
from apps.owasp.models.snapshot import Snapshot

RECENT_ISSUES_LIMIT = 10
RECENT_RELEASES_LIMIT = 10
RECENT_PROJECTS_LIMIT = 10
RECENT_USERS_LIMIT = 10


class SnapshotNode(GenericEntityNode):
"""Snapshot node."""

key = graphene.String()
status = graphene.String()
error_message = graphene.String()
new_chapters = graphene.List(ChapterNode)
new_issues = graphene.List(IssueNode)
new_projects = graphene.List(ProjectNode)
new_releases = graphene.List(ReleaseNode)
new_users = graphene.List(UserNode)

class Meta:
model = Snapshot
fields = (
"title",
"created_at",
"updated_at",
"start_at",
"end_at",
)

def resolve_key(self, info):
"""Resolve key."""
return self.key

def resolve_status(self, info):
"""Resolve status."""
return self.status

def resolve_error_message(self, info):
"""Resolve error message."""
return self.error_message

def resolve_new_chapters(self, info):
"""Resolve new chapters."""
return self.new_chapters.all()

def resolve_new_issues(self, info):
"""Resolve recent new issues."""
return self.new_issues.order_by("-created_at")[:RECENT_ISSUES_LIMIT]
Comment on lines +57 to +59
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Security consideration: consider adding access control checks

The resolvers don't include access control checks. Consider adding authorization logic to ensure users can only access snapshots they have permission to view.


🏁 Script executed:

#!/bin/bash
# Check if there are any permission checks elsewhere in the codebase
# Look for authorization patterns in other resolvers

echo "Looking for authorization patterns in GraphQL resolvers..."
rg -A 5 "def resolve_" --type py backend/apps/owasp/graphql/ | rg -i "permission|access|auth"

echo "Checking if BaseQuery implements access control..."
rg -A 10 "class BaseQuery" --type py backend/apps/common/graphql/

Length of output: 681


Security: Add Authorization Checks in Snapshot Resolver

At backend/apps/owasp/graphql/nodes/snapshot.py (lines 57-59), the resolve_new_issues method currently returns data without verifying if the requesting user has appropriate permissions. There are no existing access control checks in this resolver, and similar patterns elsewhere in the codebase (e.g., in project.py) also lack explicit authorization logic, while the common base (BaseQuery in backend/apps/common/graphql/queries.py) does not seem to enforce such permissions either.

  • Action: Integrate authorization logic—possibly by leveraging a common access control framework or utility—to ensure that only users with the proper permissions can query these snapshots.


def resolve_new_projects(self, info):
"""Resolve recent new projects."""
return self.new_projects.order_by("-created_at")[:RECENT_PROJECTS_LIMIT]

def resolve_new_releases(self, info):
"""Resolve recent new releases."""
return self.new_releases.order_by("-published_at")[:RECENT_RELEASES_LIMIT]
Comment on lines +61 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider adding tests for field resolvers

Implement unit tests for these resolver methods to ensure they correctly handle edge cases like empty collections.


🏁 Script executed:

#!/bin/bash
# Check if there are existing tests for resolver methods

echo "Looking for existing resolver tests..."
rg -A 5 "test.*resolve" --type py backend/tests/

Length of output: 11189


Add Unit Tests for Snapshot Field Resolvers

The implementation of the resolve_new_projects and resolve_new_releases methods in backend/apps/owasp/graphql/nodes/snapshot.py currently lacks dedicated unit tests. While tests for similar GraphQL field resolvers exist elsewhere in the codebase, there are no tests covering these methods. Please add tests that:

  • Verify that the resolvers return values in the correct descending order (by created_at and published_at respectively).
  • Confirm that the slice limits (RECENT_PROJECTS_LIMIT and RECENT_RELEASES_LIMIT) are correctly applied.
  • Handle cases where the underlying collections are empty.

Making these additions will ensure consistent behavior with the resolver patterns implemented and tested in other parts of the project.


def resolve_new_users(self, info):
"""Resolve recent new users."""
return self.new_users.order_by("-created_at")[:RECENT_USERS_LIMIT]
3 changes: 2 additions & 1 deletion backend/apps/owasp/graphql/queries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from .chapter import ChapterQuery
from .committee import CommitteeQuery
from .project import ProjectQuery
from .snapshot import SnapshotQuery
from .stats import StatsQuery


class OwaspQuery(ChapterQuery, CommitteeQuery, ProjectQuery, StatsQuery):
class OwaspQuery(ChapterQuery, CommitteeQuery, ProjectQuery, SnapshotQuery, StatsQuery):
"""OWASP queries."""
32 changes: 32 additions & 0 deletions backend/apps/owasp/graphql/queries/snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""OWASP snapshot GraphQL queries."""

import graphene

from apps.common.graphql.queries import BaseQuery
from apps.owasp.graphql.nodes.snapshot import SnapshotNode
from apps.owasp.models.snapshot import Snapshot


class SnapshotQuery(BaseQuery):
"""Snapshot queries."""

snapshot = graphene.Field(
SnapshotNode,
key=graphene.String(required=True),
)

recent_snapshots = graphene.List(
SnapshotNode,
limit=graphene.Int(default_value=8),
)

def resolve_snapshot(root, info, key):
"""Resolve snapshot by key."""
try:
return Snapshot.objects.get(key=key)
except Snapshot.DoesNotExist:
return None

def resolve_recent_snapshots(root, info, limit):
"""Resolve recent snapshots."""
return Snapshot.objects.order_by("-created_at")[:limit]
10 changes: 6 additions & 4 deletions backend/apps/owasp/migrations/0015_snapshot.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Generated by Django 5.1.6 on 2025-02-22 18:37
# Generated by Django 5.1.6 on 2025-03-02 05:18

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0015_alter_release_author"),
("github", "0016_user_is_bot"),
("owasp", "0014_project_custom_tags"),
]

Expand All @@ -19,6 +19,10 @@ class Migration(migrations.Migration):
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("title", models.CharField(default="", max_length=255)),
("key", models.CharField(max_length=7, unique=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("start_at", models.DateTimeField()),
("end_at", models.DateTimeField()),
(
Expand All @@ -34,8 +38,6 @@ class Migration(migrations.Migration):
max_length=10,
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("error_message", models.TextField(blank=True, default="")),
(
"new_chapters",
Expand Down
10 changes: 10 additions & 0 deletions backend/apps/owasp/models/snapshot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""OWASP app snapshot models."""

from django.db import models
from django.utils.timezone import now


class Snapshot(models.Model):
Expand All @@ -16,6 +17,9 @@ class Status(models.TextChoices):
COMPLETED = "completed", "Completed"
ERROR = "error", "Error"

title = models.CharField(max_length=255, default="")
key = models.CharField(max_length=7, unique=True) # Format: YYYY-mm

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

Expand All @@ -31,6 +35,12 @@ class Status(models.TextChoices):
new_releases = models.ManyToManyField("github.Release", related_name="snapshots", blank=True)
new_users = models.ManyToManyField("github.User", related_name="snapshots", blank=True)

def save(self, *args, **kwargs):
"""Automatically set the key in YYYY-mm format before saving."""
if not self.key:
self.key = now().strftime("%Y-%m")
super().save(*args, **kwargs)

def __str__(self):
"""Return a string representation of the snapshot."""
return f"Snapshot {self.start_at} to {self.end_at} ({self.status})"
7 changes: 7 additions & 0 deletions backend/tests/owasp/models/snapshot_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def setUp(self):
"""Set up a mocked snapshot object."""
self.snapshot = MagicMock(spec=Snapshot) # Mock entire model
self.snapshot.id = 1 # Set an ID to avoid ManyToMany errors
self.snapshot.title = "Mock Snapshot Title"
self.snapshot.key = "2025-02"
self.snapshot.start_at = "2025-02-21"
self.snapshot.end_at = "2025-02-21"
self.snapshot.status = Snapshot.Status.PROCESSING
Expand All @@ -27,3 +29,8 @@ def test_mocked_many_to_many_relations(self):
"""Test ManyToMany relationships using mocks."""
self.snapshot.new_chapters.set(["Mock Chapter"])
self.snapshot.new_chapters.set.assert_called_once_with(["Mock Chapter"])

def test_snapshot_attributes(self):
"""Test that title and key are correctly assigned."""
assert self.snapshot.title == "Mock Snapshot Title"
assert self.snapshot.key == "2025-02"
40 changes: 40 additions & 0 deletions frontend/src/api/queries/snapshotQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { gql } from '@apollo/client'

export const GET_SNAPSHOT_DETAILS = gql`
query GetSnapshotDetails($key: String!) {
snapshot(key: $key) {
title
key
createdAt
updatedAt
startAt
endAt
newReleases {
name
version
releaseDate
}
newProjects {
key
name
summary
starsCount
forksCount
repositoriesCount
topContributors {
name
login
contributionsCount
}
}
newChapters {
key
name
geoLocation {
lat
lng
}
}
}
}
`