diff --git a/dojo/forms.py b/dojo/forms.py index 211314915b3..b9f924754ed 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -32,6 +32,14 @@ from dojo.endpoint.utils import endpoint_filter, endpoint_get_or_create, validate_endpoints_to_add from dojo.engagement.queries import get_authorized_engagements from dojo.finding.queries import get_authorized_findings +from dojo.github.ui.forms import ( # noqa: F401 -- backward compat + DeleteGITHUBConfForm, + ExpressGITHUBForm, + GITHUB_IssueForm, + GITHUB_Product_Form, + GITHUBFindingForm, + GITHUBForm, +) from dojo.group.queries import get_authorized_groups, get_group_member_roles from dojo.jira import services as jira_services from dojo.jira.forms import ( # noqa: F401 backward compat @@ -79,9 +87,6 @@ Finding_Group, Finding_Template, General_Survey, - GITHUB_Conf, - GITHUB_Issue, - GITHUB_PKey, Global_Role, Note_Type, Notes, @@ -2801,42 +2806,6 @@ class Meta: fields = ["id"] -class GITHUB_IssueForm(forms.ModelForm): - - class Meta: - model = GITHUB_Issue - exclude = ["product"] - - -class GITHUBForm(forms.ModelForm): - api_key = forms.CharField(widget=forms.PasswordInput, required=True) - - class Meta: - model = GITHUB_Conf - exclude = ["product"] - - -class DeleteGITHUBConfForm(forms.ModelForm): - id = forms.IntegerField(required=True, - widget=forms.widgets.HiddenInput()) - - class Meta: - model = GITHUB_Conf - fields = ["id"] - - -class ExpressGITHUBForm(forms.ModelForm): - password = forms.CharField(widget=forms.PasswordInput, required=True) - issue_key = forms.CharField(required=True, help_text="A valid issue ID is required to gather the necessary information.") - - class Meta: - model = GITHUB_Conf - exclude = ["product", "epic_name_id", "open_status_key", - "close_status_key", "info_mapping_severity", - "low_mapping_severity", "medium_mapping_severity", - "high_mapping_severity", "critical_mapping_severity", "finding_text"] - - class Benchmark_Product_SummaryForm(forms.ModelForm): class Meta: @@ -3222,25 +3191,6 @@ class Meta: # fields = ['selenium_script'] -class GITHUB_Product_Form(forms.ModelForm): - git_conf = forms.ModelChoiceField(queryset=GITHUB_Conf.objects.all(), label="GITHUB Configuration", required=False) - - class Meta: - model = GITHUB_PKey - exclude = ["product"] - - -class GITHUBFindingForm(forms.Form): - def __init__(self, *args, **kwargs): - self.enabled = kwargs.pop("enabled") - super().__init__(*args, **kwargs) - self.fields["push_to_github"] = forms.BooleanField() - self.fields["push_to_github"].required = False - self.fields["push_to_github"].help_text = "Checking this will overwrite content of your Github issue, or create one." - - push_to_github = forms.BooleanField(required=False) - - class LoginBanner(forms.Form): banner_enable = forms.BooleanField( label="Enable login banner", diff --git a/dojo/github/__init__.py b/dojo/github/__init__.py new file mode 100644 index 00000000000..55e1017a5ad --- /dev/null +++ b/dojo/github/__init__.py @@ -0,0 +1 @@ +import dojo.github.admin # noqa: F401 diff --git a/dojo/github/admin.py b/dojo/github/admin.py new file mode 100644 index 00000000000..e15b3753c8a --- /dev/null +++ b/dojo/github/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +from dojo.github.models import ( + GITHUB_Clone, + GITHUB_Conf, + GITHUB_Details_Cache, + GITHUB_Issue, + GITHUB_PKey, +) + +admin.site.register(GITHUB_Conf) +admin.site.register(GITHUB_Issue) +admin.site.register(GITHUB_Clone) +admin.site.register(GITHUB_Details_Cache) +admin.site.register(GITHUB_PKey) diff --git a/dojo/github/models.py b/dojo/github/models.py new file mode 100644 index 00000000000..65660cac50e --- /dev/null +++ b/dojo/github/models.py @@ -0,0 +1,45 @@ +from django.db import models +from django.utils.translation import gettext as _ + +from dojo.models import Finding, Product + + +class GITHUB_Conf(models.Model): + configuration_name = models.CharField(max_length=2000, help_text=_("Enter a name to give to this configuration"), default="") + api_key = models.CharField(max_length=2000, help_text=_("Enter your Github API Key"), default="") + + def __str__(self): + return self.configuration_name + + +class GITHUB_Issue(models.Model): + issue_id = models.CharField(max_length=200) + issue_url = models.URLField(max_length=2000, verbose_name=_("GitHub issue URL")) + finding = models.OneToOneField(Finding, null=True, blank=True, on_delete=models.CASCADE) + + def __str__(self): + return str(self.issue_id) + "| GitHub Issue URL: " + str(self.issue_url) + + +class GITHUB_Clone(models.Model): + github_id = models.CharField(max_length=200) + github_clone_id = models.CharField(max_length=200) + + +class GITHUB_Details_Cache(models.Model): + github_id = models.CharField(max_length=200) + github_key = models.CharField(max_length=200) + github_status = models.CharField(max_length=200) + github_resolution = models.CharField(max_length=200) + + +class GITHUB_PKey(models.Model): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + + git_project = models.CharField(max_length=200, blank=True, verbose_name=_("Github project"), help_text=_("Specify your project location. (:user/:repo)")) + git_conf = models.ForeignKey(GITHUB_Conf, verbose_name=_("Github Configuration"), + null=True, blank=True, on_delete=models.CASCADE) + git_push_notes = models.BooleanField(default=False, blank=True, help_text=_("Notes added to findings will be automatically added to the corresponding github issue")) + + def __str__(self): + return self.product.name + " | " + self.git_project diff --git a/dojo/github.py b/dojo/github/services.py similarity index 94% rename from dojo/github.py rename to dojo/github/services.py index 6980000fdd4..1de7eaddb53 100644 --- a/dojo/github.py +++ b/dojo/github/services.py @@ -1,19 +1,23 @@ -# python import logging import sys from django.template.loader import render_to_string - -# External libs from github import Auth, Github -# Dojo related imports -from dojo.models import Engagement, GITHUB_Issue, GITHUB_PKey, Product +from dojo.github.models import GITHUB_Issue, GITHUB_PKey +from dojo.models import Engagement, Product -# Create global logger = logging.getLogger(__name__) +def validate_github_credentials(api_key): + """Verify a GitHub API key by fetching the authenticated user. Raises on failure.""" + g = Github(api_key) + user = g.get_user() + logger.debug("Using user " + user.login) + return user.login + + def reopen_external_issue_github(find, note, prod, eng): # Ensure the system setting for GitHub integration is enabled from dojo.utils import get_system_setting # noqa: PLC0415 circular import diff --git a/dojo/templates/dojo/delete_github.html b/dojo/github/templates/dojo/delete_github.html similarity index 100% rename from dojo/templates/dojo/delete_github.html rename to dojo/github/templates/dojo/delete_github.html diff --git a/dojo/templates/dojo/github.html b/dojo/github/templates/dojo/github.html similarity index 100% rename from dojo/templates/dojo/github.html rename to dojo/github/templates/dojo/github.html diff --git a/dojo/templates/dojo/new_github.html b/dojo/github/templates/dojo/new_github.html similarity index 100% rename from dojo/templates/dojo/new_github.html rename to dojo/github/templates/dojo/new_github.html diff --git a/dojo/github_issue_link/__init__.py b/dojo/github/ui/__init__.py similarity index 100% rename from dojo/github_issue_link/__init__.py rename to dojo/github/ui/__init__.py diff --git a/dojo/github/ui/forms.py b/dojo/github/ui/forms.py new file mode 100644 index 00000000000..914fbb28b07 --- /dev/null +++ b/dojo/github/ui/forms.py @@ -0,0 +1,58 @@ +from django import forms + +from dojo.github.models import GITHUB_Conf, GITHUB_Issue, GITHUB_PKey + + +class GITHUB_IssueForm(forms.ModelForm): + + class Meta: + model = GITHUB_Issue + exclude = ["product"] + + +class GITHUBForm(forms.ModelForm): + api_key = forms.CharField(widget=forms.PasswordInput, required=True) + + class Meta: + model = GITHUB_Conf + exclude = ["product"] + + +class DeleteGITHUBConfForm(forms.ModelForm): + id = forms.IntegerField(required=True, + widget=forms.widgets.HiddenInput()) + + class Meta: + model = GITHUB_Conf + fields = ["id"] + + +class ExpressGITHUBForm(forms.ModelForm): + password = forms.CharField(widget=forms.PasswordInput, required=True) + issue_key = forms.CharField(required=True, help_text="A valid issue ID is required to gather the necessary information.") + + class Meta: + model = GITHUB_Conf + exclude = ["product", "epic_name_id", "open_status_key", + "close_status_key", "info_mapping_severity", + "low_mapping_severity", "medium_mapping_severity", + "high_mapping_severity", "critical_mapping_severity", "finding_text"] + + +class GITHUB_Product_Form(forms.ModelForm): + git_conf = forms.ModelChoiceField(queryset=GITHUB_Conf.objects.all(), label="GITHUB Configuration", required=False) + + class Meta: + model = GITHUB_PKey + exclude = ["product"] + + +class GITHUBFindingForm(forms.Form): + def __init__(self, *args, **kwargs): + self.enabled = kwargs.pop("enabled") + super().__init__(*args, **kwargs) + self.fields["push_to_github"] = forms.BooleanField() + self.fields["push_to_github"].required = False + self.fields["push_to_github"].help_text = "Checking this will overwrite content of your Github issue, or create one." + + push_to_github = forms.BooleanField(required=False) diff --git a/dojo/github_issue_link/urls.py b/dojo/github/ui/urls.py similarity index 100% rename from dojo/github_issue_link/urls.py rename to dojo/github/ui/urls.py diff --git a/dojo/github_issue_link/views.py b/dojo/github/ui/views.py similarity index 92% rename from dojo/github_issue_link/views.py rename to dojo/github/ui/views.py index e0ddabd1deb..4107e54a0a8 100644 --- a/dojo/github_issue_link/views.py +++ b/dojo/github/ui/views.py @@ -9,13 +9,11 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views.decorators.csrf import csrf_exempt -from github import Github from dojo.authorization.authorization_decorators import user_is_configuration_authorized - -# Local application/library imports -from dojo.forms import DeleteGITHUBConfForm, GITHUBForm -from dojo.models import GITHUB_Conf +from dojo.github.models import GITHUB_Conf +from dojo.github.services import validate_github_credentials +from dojo.github.ui.forms import DeleteGITHUBConfForm, GITHUBForm from dojo.utils import add_breadcrumb, get_setting logger = logging.getLogger(__name__) @@ -33,9 +31,7 @@ def new_github(request): if gform.is_valid(): try: api_key = gform.cleaned_data.get("api_key") - g = Github(api_key) - user = g.get_user() - logger.debug("Using user " + user.login) + validate_github_credentials(api_key) new_j = gform.save(commit=False) new_j.api_key = api_key diff --git a/dojo/models.py b/dojo/models.py index e80e22aa099..d4dbae62afe 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -4084,47 +4084,13 @@ class BannerConf(models.Model): banner_message = models.CharField(max_length=500, help_text=_("This message will be displayed on the login page. It can contain basic html tags, for example https://example.com"), default="") -class GITHUB_Conf(models.Model): - configuration_name = models.CharField(max_length=2000, help_text=_("Enter a name to give to this configuration"), default="") - api_key = models.CharField(max_length=2000, help_text=_("Enter your Github API Key"), default="") - - def __str__(self): - return self.configuration_name - - -class GITHUB_Issue(models.Model): - issue_id = models.CharField(max_length=200) - issue_url = models.URLField(max_length=2000, verbose_name=_("GitHub issue URL")) - finding = models.OneToOneField(Finding, null=True, blank=True, on_delete=models.CASCADE) - - def __str__(self): - return str(self.issue_id) + "| GitHub Issue URL: " + str(self.issue_url) - - -class GITHUB_Clone(models.Model): - github_id = models.CharField(max_length=200) - github_clone_id = models.CharField(max_length=200) - - -class GITHUB_Details_Cache(models.Model): - github_id = models.CharField(max_length=200) - github_key = models.CharField(max_length=200) - github_status = models.CharField(max_length=200) - github_resolution = models.CharField(max_length=200) - - -class GITHUB_PKey(models.Model): - product = models.ForeignKey(Product, on_delete=models.CASCADE) - - git_project = models.CharField(max_length=200, blank=True, verbose_name=_("Github project"), help_text=_("Specify your project location. (:user/:repo)")) - git_conf = models.ForeignKey(GITHUB_Conf, verbose_name=_("Github Configuration"), - null=True, blank=True, on_delete=models.CASCADE) - git_push_notes = models.BooleanField(default=False, blank=True, help_text=_("Notes added to findings will be automatically added to the corresponding github issue")) - - def __str__(self): - return self.product.name + " | " + self.git_project - - +from dojo.github.models import ( # noqa: E402, F401 -- backward compat + GITHUB_Clone, + GITHUB_Conf, + GITHUB_Details_Cache, + GITHUB_Issue, + GITHUB_PKey, +) from dojo.jira.models import ( # noqa: E402,F401 backward compat JIRA_Instance, JIRA_Instance_Admin, @@ -4776,11 +4742,6 @@ def __str__(self): admin.site.register(Notes) admin.site.register(Note_Type) admin.site.register(Alerts) -admin.site.register(GITHUB_Conf) -admin.site.register(GITHUB_Issue) -admin.site.register(GITHUB_Clone) -admin.site.register(GITHUB_Details_Cache) -admin.site.register(GITHUB_PKey) admin.site.register(Tool_Configuration, Tool_Configuration_Admin) admin.site.register(Notification_Webhooks) admin.site.register(Tool_Product_Settings) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 2c828aa9582..c37b787925e 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -946,6 +946,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [root("dojo/github/templates")], "APP_DIRS": True, "OPTIONS": { "debug": env("DD_DEBUG"), diff --git a/dojo/urls.py b/dojo/urls.py index 87ad7d85c36..cd4349d886b 100644 --- a/dojo/urls.py +++ b/dojo/urls.py @@ -86,7 +86,7 @@ from dojo.engagement.urls import urlpatterns as eng_urls from dojo.finding.urls import urlpatterns as finding_urls from dojo.finding_group.urls import urlpatterns as finding_group_urls -from dojo.github_issue_link.urls import urlpatterns as github_urls +from dojo.github.ui.urls import urlpatterns as github_urls from dojo.group.urls import urlpatterns as group_urls from dojo.home.urls import urlpatterns as home_urls from dojo.jira.urls import urlpatterns as jira_urls diff --git a/dojo/utils.py b/dojo/utils.py index 605fb11b55c..0b3bade4598 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -48,7 +48,7 @@ from dojo.authorization.roles_permissions import Permissions from dojo.celery import app from dojo.finding.queries import get_authorized_findings -from dojo.github import ( +from dojo.github.services import ( add_external_issue_github, close_external_issue_github, reopen_external_issue_github,