Skip to content

Commit c5d050e

Browse files
committed
Update code
1 parent 0db35ff commit c5d050e

File tree

8 files changed

+177
-11
lines changed

8 files changed

+177
-11
lines changed

backend/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ update-data: \
115115
owasp-scrape-committees \
116116
owasp-scrape-projects \
117117
github-update-project-related-repositories \
118-
github-update-external-repositories \
118+
github-update-owasp-related-organizations \
119119
github-update-users \
120120
owasp-aggregate-projects \
121121
owasp-update-events \

backend/apps/github/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ github-update-owasp-organization:
99
@echo "Updating OWASP GitHub organization"
1010
@CMD="python manage.py github_update_owasp_organization" $(MAKE) exec-backend-command
1111

12+
github-update-owasp-related-organizations:
13+
@echo "Updating external OWASP GitHub organizations"
14+
@CMD="python manage.py github_update_external_organizations" $(MAKE) exec-backend-command
15+
1216
github-update-project-related-repositories:
1317
@echo "Updating OWASP project related GitHub repositories"
1418
@CMD="python manage.py github_update_project_related_repositories" $(MAKE) exec-backend-command
1519

16-
github-update-external-repositories:
17-
@echo "Updating external OWASP GitHub repositories"
18-
@CMD="python manage.py github_update_external_repositories" $(MAKE) exec-backend-command
19-
2020
github-update-users:
2121
@echo "Updating GitHub users"
2222
@CMD="python manage.py github_update_users" $(MAKE) exec-backend-command
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""A command to update external OWASP repositories from GitHub data."""
2+
3+
import logging
4+
import os
5+
6+
import github
7+
from django.core.management.base import BaseCommand
8+
from github.GithubException import BadCredentialsException
9+
10+
from apps.core.utils.index import unregister_indexes
11+
from apps.github.common import sync_repository
12+
from apps.github.constants import GITHUB_ITEMS_PER_PAGE
13+
from apps.github.models.organization import Organization
14+
15+
logger: logging.Logger = logging.getLogger(__name__)
16+
17+
18+
class Command(BaseCommand):
19+
"""Fetch external OWASP GitHub repositories and update relevant entities."""
20+
21+
help = "Fetch external OWASP GitHub repositories and update relevant entities."
22+
23+
def add_arguments(self, parser) -> None:
24+
"""Add command-line arguments to the parser.
25+
26+
Args:
27+
parser (argparse.ArgumentParser): The argument parser instance.
28+
29+
"""
30+
parser.add_argument(
31+
"--organization",
32+
required=False,
33+
type=str,
34+
help="The organization name (e.g. juice-shop, DefectDojo')",
35+
)
36+
37+
def handle(self, *_args, **options) -> None:
38+
"""Handle the command execution.
39+
40+
Args:
41+
*_args: Variable length argument list.
42+
**options: Arbitrary keyword arguments containing command options.
43+
44+
"""
45+
unregister_indexes() # Disable automatic indexing
46+
47+
try:
48+
gh = github.Github(os.getenv("GITHUB_TOKEN"), per_page=GITHUB_ITEMS_PER_PAGE)
49+
except BadCredentialsException:
50+
logger.warning(
51+
"Invalid GitHub token. Please create and update .env file with a valid token."
52+
)
53+
return
54+
55+
organizations = Organization.related_organizations
56+
if organization := options["organization"]:
57+
organizations = organizations.filter(login__iexact=organization)
58+
59+
if not organizations.exists():
60+
logger.error("No OWASP related organizations found")
61+
return
62+
63+
organizations_count = organizations.count()
64+
for idx, organization in enumerate(organizations):
65+
prefix = f"{idx + 1} of {organizations_count}"
66+
print(f"{prefix:<10} {organization.url}")
67+
68+
if organization.related_projects.count() != 1:
69+
logger.error(
70+
"Couldn't identify related project for external organization %s. "
71+
"The related projects: %s.",
72+
organization,
73+
organization.related_projects,
74+
)
75+
continue
76+
77+
gh_organization = gh.get_organization(organization.login)
78+
gh_repositories = gh_organization.get_repos(
79+
type="public",
80+
sort="created",
81+
direction="desc",
82+
)
83+
gh_repositories_count = gh_repositories.totalCount
84+
for idx_repository, gh_repository in enumerate(gh_repositories):
85+
prefix = f"{idx_repository + 1} of {gh_repositories_count}"
86+
repository_url = (
87+
f"https://github.com/{organization.login}/{gh_repository.name.lower()}"
88+
)
89+
print(f"{prefix:<12} {repository_url}")
90+
91+
try:
92+
_, repository = sync_repository(gh_repository)
93+
organization.related_projects.first().repositories.add(repository)
94+
except Exception:
95+
logger.exception("Error syncing repository %s", repository_url)
96+
continue
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.2.3 on 2025-06-29 18:01
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("github", "0030_alter_organization_is_owasp_related_organization"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="repository",
15+
name="organization",
16+
field=models.ForeignKey(
17+
blank=True,
18+
null=True,
19+
on_delete=django.db.models.deletion.SET_NULL,
20+
related_name="repositories",
21+
to="github.organization",
22+
verbose_name="Organization",
23+
),
24+
),
25+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""OWASP app organization managers."""
2+
3+
from django.db import models
4+
5+
from apps.owasp.constants import OWASP_ORGANIZATION_NAME
6+
7+
8+
class RelatedOrganizationsManager(models.Manager):
9+
"""OWASP related organizations manager."""
10+
11+
def get_queryset(self):
12+
"""Get open milestones."""
13+
return (
14+
super()
15+
.get_queryset()
16+
.filter(
17+
is_owasp_related_organization=True,
18+
)
19+
.exclude(login=OWASP_ORGANIZATION_NAME)
20+
)

backend/apps/github/models/organization.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
"""Github app organization model."""
22

3+
from __future__ import annotations
4+
35
from functools import lru_cache
6+
from typing import TYPE_CHECKING
47

8+
from django.apps import apps
59
from django.db import models
610

711
from apps.common.models import BulkSaveModel, TimestampedModel
812
from apps.github.models.common import GenericUserModel, NodeModel
13+
from apps.github.models.managers.organization import RelatedOrganizationsManager
914
from apps.github.models.mixins import OrganizationIndexMixin
1015

16+
if TYPE_CHECKING: # pragma: no cover
17+
from django.db.models import QuerySet
18+
1119

1220
class Organization(
1321
BulkSaveModel,
@@ -18,6 +26,9 @@ class Organization(
1826
):
1927
"""Organization model."""
2028

29+
objects = models.Manager()
30+
related_organizations = RelatedOrganizationsManager()
31+
2132
class Meta:
2233
db_table = "github_organizations"
2334
verbose_name_plural = "Organizations"
@@ -38,6 +49,17 @@ def __str__(self) -> str:
3849
"""
3950
return self.name
4051

52+
@property
53+
def related_projects(self) -> QuerySet:
54+
"""Return organization related projects."""
55+
return (
56+
apps.get_model("owasp", "Project") # Dynamic import.
57+
.objects.filter(
58+
repositories__in=self.repositories.all(),
59+
)
60+
.distinct()
61+
)
62+
4163
def from_github(self, gh_organization) -> None:
4264
"""Update the instance based on GitHub organization data.
4365
@@ -69,7 +91,7 @@ def bulk_save(organizations) -> None: # type: ignore[override]
6991
BulkSaveModel.bulk_save(Organization, organizations)
7092

7193
@staticmethod
72-
def update_data(gh_organization, *, save: bool = True) -> "Organization":
94+
def update_data(gh_organization, *, save: bool = True) -> Organization:
7395
"""Update organization data.
7496
7597
Args:

backend/apps/github/models/repository.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class Meta:
8888
organization = models.ForeignKey(
8989
"github.Organization",
9090
verbose_name="Organization",
91+
related_name="repositories",
9192
blank=True,
9293
null=True,
9394
on_delete=models.SET_NULL,

backend/tests/apps/github/management/commands/github_update_external_repositories_test.py renamed to backend/tests/apps/github/management/commands/github_update_owasp_related_repositories_test.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from github.GithubException import BadCredentialsException
66

7-
from apps.github.management.commands.github_update_external_repositories import (
7+
from apps.github.management.commands.github_update_owasp_related_organizations import (
88
Command,
99
Organization,
1010
)
@@ -63,17 +63,17 @@ def setup(self, monkeypatch, command):
6363
self.mock_gh = mock.Mock()
6464
gh_factory = mock.Mock(return_value=self.mock_gh)
6565
monkeypatch.setattr(
66-
"apps.github.management.commands.github_update_external_repositories.github.Github",
66+
"apps.github.management.commands.github_update_owasp_related_organizations.github.Github",
6767
gh_factory,
6868
)
6969
self.mock_sync_repository = mock.Mock()
7070
monkeypatch.setattr(
71-
"apps.github.management.commands.github_update_external_repositories.sync_repository",
71+
"apps.github.management.commands.github_update_owasp_related_organizations.sync_repository",
7272
self.mock_sync_repository,
7373
)
7474
self.mock_org_filter = mock.Mock()
7575
monkeypatch.setattr(
76-
"apps.github.management.commands.github_update_external_repositories.Organization.objects.filter",
76+
"apps.github.management.commands.github_update_owasp_related_organizations.Organization.objects.filter",
7777
self.mock_org_filter,
7878
)
7979
self.command = command
@@ -172,7 +172,9 @@ def test_handle_with_specific_organization(self):
172172
),
173173
],
174174
)
175-
@mock.patch("apps.github.management.commands.github_update_external_repositories.sync_repository")
175+
@mock.patch(
176+
"apps.github.management.commands.github_update_owasp_related_organizations.sync_repository"
177+
)
176178
def test_sync_organization_repositories_error_handling(
177179
mock_sync_repository, command, mock_gh_repository, side_effects
178180
):

0 commit comments

Comments
 (0)