diff --git a/dojo/db_migrations/0184_remove_child_rule_parent_rule_delete_fieldrule_and_more.py b/dojo/db_migrations/0184_remove_child_rule_parent_rule_delete_fieldrule_and_more.py new file mode 100644 index 00000000000..57c3d651227 --- /dev/null +++ b/dojo/db_migrations/0184_remove_child_rule_parent_rule_delete_fieldrule_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 4.1.7 on 2023-03-27 15:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0183_system_settings_enable_notify_sla_exponential_backoff_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='child_rule', + name='parent_rule', + ), + migrations.DeleteModel( + name='FieldRule', + ), + migrations.RemoveField( + model_name='rule', + name='child_rules', + ), + migrations.RemoveField( + model_name='rule', + name='parent_rule', + ), + migrations.RemoveField( + model_name='system_settings', + name='column_widths', + ), + migrations.RemoveField( + model_name='system_settings', + name='drive_folder_ID', + ), + migrations.RemoveField( + model_name='system_settings', + name='email_address', + ), + migrations.RemoveField( + model_name='system_settings', + name='enable_google_sheets', + ), + migrations.RemoveField( + model_name='system_settings', + name='enable_rules_framework', + ), + migrations.DeleteModel( + name='Child_Rule', + ), + migrations.DeleteModel( + name='Rule', + ), + ] diff --git a/dojo/fixtures/defect_dojo_sample_data.json b/dojo/fixtures/defect_dojo_sample_data.json index e7aae349db6..3db55c5d9d2 100644 --- a/dojo/fixtures/defect_dojo_sample_data.json +++ b/dojo/fixtures/defect_dojo_sample_data.json @@ -7121,10 +7121,6 @@ "allow_anonymous_survey_repsonse": false, "credentials": "", "disclaimer": "", - "column_widths": "", - "drive_folder_ID": "", - "enable_google_sheets": false, - "email_address": "", "risk_acceptance_form_default_days": 180, "risk_acceptance_notify_before_expiration": 10, "enable_credentials": true, diff --git a/dojo/fixtures/questionnaire_testdata.json b/dojo/fixtures/questionnaire_testdata.json index 7158e42894f..c95278c83ac 100644 --- a/dojo/fixtures/questionnaire_testdata.json +++ b/dojo/fixtures/questionnaire_testdata.json @@ -1,4 +1,52 @@ [ + { + "fields": { + "model": "question", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 65 + }, + { + "fields": { + "model": "answer", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 68 + }, + { + "fields": { + "model": "textquestion", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 66 + }, + { + "fields": { + "model": "textanswer", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 69 + }, + { + "fields": { + "model": "choicequestion", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 71 + }, + { + "fields": { + "model": "choiceanswer", + "app_label": "dojo" + }, + "model": "contenttypes.contenttype", + "pk": 70 + }, { "pk": 1, "model": "auth.user", @@ -163,7 +211,7 @@ "model": "dojo.question", "pk": 14, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T19:57:22Z", "modified": "2015-03-30T19:57:22Z", "order": 1, @@ -175,7 +223,7 @@ "model": "dojo.question", "pk": 15, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T19:57:34Z", "modified": "2015-03-30T19:57:34Z", "order": 1, @@ -187,7 +235,7 @@ "model": "dojo.question", "pk": 16, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T19:57:55Z", "modified": "2015-03-30T19:57:55Z", "order": 1, @@ -199,7 +247,7 @@ "model": "dojo.question", "pk": 17, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T19:58:36Z", "modified": "2015-03-30T19:58:36Z", "order": 1, @@ -211,7 +259,7 @@ "model": "dojo.question", "pk": 18, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T20:00:35Z", "modified": "2015-03-30T20:00:35Z", "order": 1, @@ -223,7 +271,7 @@ "model": "dojo.question", "pk": 19, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T20:00:46Z", "modified": "2015-03-30T20:00:46Z", "order": 1, @@ -235,7 +283,7 @@ "model": "dojo.question", "pk": 20, "fields": { - "polymorphic_ctype": 69, + "polymorphic_ctype": 66, "created": "2015-03-30T20:00:58Z", "modified": "2015-03-30T20:00:58Z", "order": 1, @@ -247,7 +295,7 @@ "model": "dojo.question", "pk": 44, "fields": { - "polymorphic_ctype": 74, + "polymorphic_ctype": 71, "created": "2023-03-02T17:58:59.698Z", "modified": "2023-03-02T17:58:59.737Z", "order": 1, @@ -410,7 +458,7 @@ "model": "dojo.answer", "pk": 1, "fields": { - "polymorphic_ctype": 73, + "polymorphic_ctype": 70, "created": "2023-03-02T19:07:55.430Z", "modified": "2023-03-02T19:07:55.447Z", "question": 44, @@ -421,7 +469,7 @@ "model": "dojo.answer", "pk": 2, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.816Z", "modified": "2023-03-02T19:14:07.822Z", "question": 14, @@ -432,7 +480,7 @@ "model": "dojo.answer", "pk": 3, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.829Z", "modified": "2023-03-02T19:14:07.833Z", "question": 15, @@ -443,7 +491,7 @@ "model": "dojo.answer", "pk": 4, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.838Z", "modified": "2023-03-02T19:14:07.841Z", "question": 16, @@ -454,7 +502,7 @@ "model": "dojo.answer", "pk": 5, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.845Z", "modified": "2023-03-02T19:14:07.848Z", "question": 17, @@ -465,7 +513,7 @@ "model": "dojo.answer", "pk": 6, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.853Z", "modified": "2023-03-02T19:14:07.856Z", "question": 19, @@ -476,7 +524,7 @@ "model": "dojo.answer", "pk": 7, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.861Z", "modified": "2023-03-02T19:14:07.864Z", "question": 20, @@ -487,7 +535,7 @@ "model": "dojo.answer", "pk": 8, "fields": { - "polymorphic_ctype": 72, + "polymorphic_ctype": 69, "created": "2023-03-02T19:14:07.868Z", "modified": "2023-03-02T19:14:07.871Z", "question": 18, diff --git a/dojo/forms.py b/dojo/forms.py index 77ad7791d72..d0b7bcf1880 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -28,7 +28,7 @@ JIRA_Issue, JIRA_Project, JIRA_Instance, GITHUB_Issue, GITHUB_PKey, GITHUB_Conf, UserContactInfo, Tool_Type, \ Tool_Configuration, Tool_Product_Settings, Cred_User, Cred_Mapping, System_Settings, Notifications, \ App_Analysis, Objects_Product, Benchmark_Product, Benchmark_Requirement, \ - Benchmark_Product_Summary, Rule, Child_Rule, Engagement_Presets, DojoMeta, \ + Benchmark_Product_Summary, Engagement_Presets, DojoMeta, \ Engagement_Survey, Answered_Survey, TextAnswer, ChoiceAnswer, Choice, Question, TextQuestion, \ ChoiceQuestion, General_Survey, Regulation, FileUpload, SEVERITY_CHOICES, Product_Type_Member, \ Product_Member, Global_Role, Dojo_Group, Product_Group, Product_Type_Group, Dojo_Group_Member, \ @@ -2493,7 +2493,7 @@ def __init__(self, *args, **kwargs): class Meta: model = System_Settings - exclude = ['product_grade', 'credentials', 'column_widths', 'drive_folder_ID'] + exclude = ['product_grade'] class BenchmarkForm(forms.ModelForm): @@ -2539,32 +2539,6 @@ def valid_value(self, value): return True -class RuleForm(forms.ModelForm): - - class Meta: - model = Rule - exclude = ['key_product'] - - -class ChildRuleForm(forms.ModelForm): - - class Meta: - model = Child_Rule - exclude = ['key_product'] - - -RuleFormSet = modelformset_factory(Child_Rule, extra=2, max_num=10, exclude=[''], can_delete=True) - - -class DeleteRuleForm(forms.ModelForm): - id = forms.IntegerField(required=True, - widget=forms.widgets.HiddenInput()) - - class Meta: - model = Rule - fields = ['id'] - - class CredUserForm(forms.ModelForm): # selenium_script = forms.FileField(widget=forms.widgets.FileInput( # attrs={"accept": ".py"}), @@ -2874,43 +2848,6 @@ def __init__(self, *args, **kwargs): epic_priority = forms.CharField(max_length=200, required=False, help_text="EPIC priority. If not specified, the JIRA default priority will be used") -class GoogleSheetFieldsForm(forms.Form): - cred_file = forms.FileField(widget=forms.widgets.FileInput( - attrs={"accept": ".json"}), - label="Google credentials file", - required=True, - allow_empty_file=False, - help_text="Upload the credentials file downloaded from the Google Developer Console") - drive_folder_ID = forms.CharField( - required=True, - label="Google Drive folder ID", - help_text="Extract the Drive folder ID from the URL and provide it here") - email_address = forms.EmailField( - required=True, - label="Email Address", - help_text="Enter the same email Address used to create the Service Account") - enable_service = forms.BooleanField( - initial=False, - required=False, - help_text='Tick this check box to enable Google Sheets Sync feature') - - def __init__(self, *args, **kwargs): - self.credentials_required = kwargs.pop('credentials_required') - options = ((0, 'Hide'), (100, 'Small'), (200, 'Medium'), (400, 'Large')) - protect = ['reporter', 'url', 'numerical_severity', 'endpoint', 'under_review', 'reviewers', - 'review_requested_by', 'is_mitigated', 'jira_creation', 'jira_change', 'sonarqube_issue'] - self.all_fields = kwargs.pop('all_fields') - super(GoogleSheetFieldsForm, self).__init__(*args, **kwargs) - if not self.credentials_required: - self.fields['cred_file'].required = False - for i in self.all_fields: - self.fields[i.name] = forms.ChoiceField(choices=options) - if i.name == 'id' or i.editable is False or i.many_to_one or i.name in protect: - self.fields['Protect ' + i.name] = forms.BooleanField(initial=True, required=True, disabled=True) - else: - self.fields['Protect ' + i.name] = forms.BooleanField(initial=False, required=False) - - class LoginBanner(forms.Form): banner_enable = forms.BooleanField( label="Enable login banner", diff --git a/dojo/google_sheet/__init__.py b/dojo/google_sheet/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/dojo/google_sheet/urls.py b/dojo/google_sheet/urls.py deleted file mode 100644 index 18c6edc2782..00000000000 --- a/dojo/google_sheet/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import re_path - -from dojo.google_sheet import views - -urlpatterns = [ - # google_sheet - re_path(r'^configure_google_sheets$', views.configure_google_sheets, - name='configure_google_sheets'), - re_path(r'^export_to_sheet/(?P\d+)$', views.export_to_sheet, - name='export_to_sheet'), -] diff --git a/dojo/google_sheet/views.py b/dojo/google_sheet/views.py deleted file mode 100644 index 6532ec39734..00000000000 --- a/dojo/google_sheet/views.py +++ /dev/null @@ -1,914 +0,0 @@ -# google sheets - -import logging -import json -import datetime -import httplib2 -import googleapiclient.discovery -from google.oauth2 import service_account - -from django.shortcuts import render, get_object_or_404 -from django.http import HttpResponseRedirect -from django.urls import reverse -from django.utils import timezone -from django.contrib import messages -from django.contrib.auth.models import User -from django.core.exceptions import PermissionDenied -from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters - -from dojo.models import Finding, System_Settings, Test, Dojo_User, Note_Type, NoteHistory, Notes, Sonarqube_Issue -from dojo.forms import GoogleSheetFieldsForm -from dojo.utils import add_breadcrumb, Product_Tab -from dojo.authorization.authorization_decorators import user_is_authorized -from dojo.authorization.roles_permissions import Permissions -from dojo.authorization.authorization_decorators import user_is_configuration_authorized - -logger = logging.getLogger(__name__) - - -@sensitive_post_parameters() -@user_is_configuration_authorized('dojo.change_google_sheet') -def configure_google_sheets(request): - fields = Finding._meta.fields - system_settings = get_object_or_404(System_Settings, id=1) - revoke_access = False - if system_settings.credentials: - revoke_access = True - column_details = json.loads(system_settings.column_widths.replace("'", '"')) - initial = {} - for field in fields: - initial[field.name] = column_details[field.name][0] - if column_details[field.name][1] == 0: - initial['Protect ' + field.name] = False - else: - initial['Protect ' + field.name] = True - initial['drive_folder_ID'] = system_settings.drive_folder_ID - initial['email_address'] = system_settings.email_address - initial['enable_service'] = system_settings.enable_google_sheets - form = GoogleSheetFieldsForm(all_fields=fields, initial=initial, credentials_required=False) - else: - form = GoogleSheetFieldsForm(all_fields=fields, credentials_required=True) - if request.method == 'POST': - if system_settings.credentials: - form = GoogleSheetFieldsForm(request.POST, request.FILES, all_fields=fields, credentials_required=False) - else: - form = GoogleSheetFieldsForm(request.POST, request.FILES, all_fields=fields, credentials_required=True) - - if request.POST.get('revoke'): - system_settings.column_widths = "" - system_settings.credentials = "" - system_settings.drive_folder_ID = "" - system_settings.email_address = "" - system_settings.enable_google_sheets = False - system_settings.save() - messages.add_message( - request, - messages.SUCCESS, - "Access revoked", - extra_tags="alert-success",) - return HttpResponseRedirect(reverse('dashboard')) - - if request.POST.get('update'): - if form.is_valid(): - # Create a dictionary object from the uploaded credentials file - if len(request.FILES) != 0: - cred_file = request.FILES['cred_file'] - cred_byte = cred_file.read() # read data from the temporary uploaded file - cred_str = cred_byte.decode('utf8') # convert bytes object to string - initial = True - else: - cred_str = system_settings.credentials - initial = False - - # Get the drive folder ID - drive_folder_ID = form.cleaned_data['drive_folder_ID'] - validate_inputs = validate_drive_authentication(request, cred_str, drive_folder_ID) - - if validate_inputs: - # Create a dictionary of column names and widths - column_widths = {} - for i in fields: - column_widths[i.name] = [] - column_widths[i.name].append(form.cleaned_data[i.name]) - if form.cleaned_data['Protect ' + i.name]: - column_widths[i.name].append(1) - else: - column_widths[i.name].append(0) - - system_settings.column_widths = column_widths - system_settings.credentials = cred_str - system_settings.drive_folder_ID = drive_folder_ID - system_settings.email_address = form.cleaned_data['email_address'] - system_settings.enable_google_sheets = form.cleaned_data['enable_service'] - system_settings.save() - if initial: - messages.add_message( - request, - messages.SUCCESS, - "Google Drive configuration saved successfully.", - extra_tags="alert-success", - ) - else: - messages.add_message( - request, - messages.SUCCESS, - "Google Drive configuration updated successfully.", - extra_tags="alert-success", - ) - return HttpResponseRedirect(reverse('dashboard')) - else: - system_settings.enable_google_sheets = False - system_settings.save() - add_breadcrumb(title="Google Sheet Sync Configuration", top_level=True, request=request) - return render(request, 'dojo/google_sheet_configuration.html', { - 'name': 'Google Sheet Sync Configuration', - 'metric': False, - 'form': form, - 'revoke_access': revoke_access, - }) - - -@sensitive_variables('cred_str', 'drive_folder_ID', 'service_account_info') -def validate_drive_authentication(request, cred_str, drive_folder_ID): - SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets'] - service_account_info = json.loads(cred_str) - try: - # Validate the uploaded credentials file - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - except ValueError: - messages.add_message( - request, - messages.ERROR, - 'Invalid credentials file.', - extra_tags='alert-danger') - return False - else: - sheets_service = googleapiclient.discovery.build('sheets', 'v4', credentials=credentials, cache_discovery=False) - drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=credentials, cache_discovery=False) - spreadsheet = { - 'properties': { - 'title': 'Test spreadsheet' - } - } - try: - # Check the sheets API is enabled or not - spreadsheet = sheets_service.spreadsheets().create(body=spreadsheet, fields='spreadsheetId').execute() - except googleapiclient.errors.HttpError: - messages.add_message( - request, - messages.ERROR, - 'Enable the Google Sheets API from the Google Developer Console.', - extra_tags='alert-danger') - return False - else: - spreadsheetId = spreadsheet.get('spreadsheetId') - try: - # Check the drive API is enabled or not - file = drive_service.files().get(fileId=spreadsheetId, fields='parents').execute() # Retrieve the existing parents to remove - except googleapiclient.errors.HttpError: - messages.add_message( - request, - messages.ERROR, - 'Enable the Google Drive API from the Google Developer Console.', - extra_tags='alert-danger') - return False - else: - previous_parents = ",".join(file.get('parents')) - folder_id = drive_folder_ID - try: - # Validate the drive folder id and it's permissions - file = drive_service.files().update(fileId=spreadsheetId, # Move the file to the new folder - addParents=folder_id, - removeParents=previous_parents, - fields='id, parents').execute() - except googleapiclient.errors.HttpError as error: - if error.resp.status == 403: - messages.add_message( - request, - messages.ERROR, - 'Unable to write to the given Google Drive folder', - extra_tags='alert-danger') - if error.resp.status == 404: - messages.add_message( - request, - messages.ERROR, - 'Invalid Google Drive folder ID', - extra_tags='alert-danger') - return False - else: - drive_service.files().delete(fileId=spreadsheetId).execute() # Delete 'test spreadsheet' - return True - - -@user_is_authorized(Test, Permissions.Test_View, 'tid') -def export_to_sheet(request, tid): - system_settings = get_object_or_404(System_Settings, id=1) - google_sheets_enabled = system_settings.enable_google_sheets - if google_sheets_enabled is False: - raise PermissionDenied - test = Test.objects.get(id=tid) - spreadsheet_name = test.engagement.product.name + "-" + test.engagement.name + "-" + str(test.id) - service_account_info = json.loads(system_settings.credentials) - SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets'] - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - try: - drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=credentials, cache_discovery=False) - folder_id = system_settings.drive_folder_ID - gs_files = drive_service.files().list(q="mimeType='application/vnd.google-apps.spreadsheet' and parents in '%s' and name='%s'" % (folder_id, spreadsheet_name), - spaces='drive', - pageSize=10, - fields='files(id, name)').execute() - spreadsheets = gs_files.get('files') - if len(spreadsheets) == 1: - spreadsheetId = spreadsheets[0].get('id') - sync = sync_findings(request, tid, spreadsheetId) - errors = sync['errors'] - sheet_title = sync['sheet_title'] - if len(errors) > 0: - product_tab = Product_Tab(test.engagement.product, title="Syncing Errors", tab="engagements") - product_tab.setEngagement(test.engagement) - spreadsheet_url = 'https://docs.google.com/spreadsheets/d/' + spreadsheetId - return render( - request, 'dojo/syncing_errors.html', { - 'test': test, - 'errors': errors, - 'name': 'Google Drive Sync Errors', - 'product_tab': product_tab, - 'sheet_title': sheet_title, - 'spreadsheet_name': spreadsheet_name, - 'spreadsheet_url': spreadsheet_url - }) - else: - messages.add_message( - request, - messages.SUCCESS, - "Synched Google Sheet with database.", - extra_tags="alert-success", - ) - return HttpResponseRedirect(reverse('view_test', args=(tid, ))) - elif len(spreadsheets) == 0: - create_googlesheet(request, tid) - messages.add_message( - request, - messages.SUCCESS, - "Successfully exported finding details to Google Sheet.", - extra_tags="alert-success", - ) - return HttpResponseRedirect(reverse('view_test', args=(tid, ))) - else: - messages.add_message( - request, - messages.ERROR, - "More than one Google Sheet exists for this test. Please contact your system admin to solve the issue.", - extra_tags="alert-danger", - ) - return HttpResponseRedirect(reverse('view_test', args=(tid, ))) - except httplib2.ServerNotFoundError: - error_message = 'Unable to reach the Google Sheet API.' - return render(request, 'google_sheet_error.html', {'error_message': error_message}) - except googleapiclient.errors.HttpError as error: - error_message = 'There is a problem with the Google Sheets Sync Configuration. Contact your system admin to solve the issue.' - return render(request, 'google_sheet_error.html', {'error_message': error_message}) - except Exception as e: - error_message = e - return render(request, 'google_sheet_error.html', {'error_message': error_message}) - - -def create_googlesheet(request, tid): - user_email = request.user.email - if not user_email: - raise Exception('User must have an email address to use this feature.') - test = Test.objects.get(id=tid) - system_settings = get_object_or_404(System_Settings, id=1) - service_account_info = json.loads(system_settings.credentials) - SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets'] - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - sheets_service = googleapiclient.discovery.build('sheets', 'v4', credentials=credentials, cache_discovery=False) - drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=credentials, cache_discovery=False) - # Create a new spreadsheet - spreadsheet_name = test.engagement.product.name + "-" + test.engagement.name + "-" + str(test.id) - spreadsheet = { - 'properties': { - 'title': spreadsheet_name - } - } - spreadsheet = sheets_service.spreadsheets().create(body=spreadsheet, fields='spreadsheetId').execute() - spreadsheetId = spreadsheet.get('spreadsheetId') - folder_id = system_settings.drive_folder_ID - - # Move the spreadsheet inside the drive folder - file = drive_service.files().get(fileId=spreadsheetId, fields='parents').execute() - previous_parents = ",".join(file.get('parents')) - file = drive_service.files().update(fileId=spreadsheetId, - addParents=folder_id, - removeParents=previous_parents, - fields='id, parents').execute() - # Share created Spreadsheet with current user - drive_service.permissions().create(body={'type': 'user', 'role': 'writer', 'emailAddress': user_email}, fileId=spreadsheetId).execute() - populate_sheet(tid, spreadsheetId) - - -def sync_findings(request, tid, spreadsheetId): - test = Test.objects.get(id=tid) - system_settings = get_object_or_404(System_Settings, id=1) - service_account_info = json.loads(system_settings.credentials) - SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - sheets_service = googleapiclient.discovery.build('sheets', 'v4', credentials=credentials, cache_discovery=False) - res = {} - spreadsheet = sheets_service.spreadsheets().get(spreadsheetId=spreadsheetId).execute() - sheet_names = [] - for sheet in spreadsheet['sheets']: - date = (sheet['properties']['title']) - try: - date = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S") - sheet_names.append(date) - except: - pass - try: - sheet_title = str(max(sheet_names)) - except: - raise Exception('Existing Google Spreadsheet has errors. Delete the speadsheet and export again.') - res['sheet_title'] = sheet_title - - result = sheets_service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=sheet_title).execute() - rows = result.get('values', []) - header_raw = rows[0] - findings_sheet = rows[1:] - findings_db = Finding.objects.filter(test=test).order_by('numerical_severity') - column_details = json.loads(system_settings.column_widths.replace("'", '"')) - active_note_types = Note_Type.objects.filter(is_active=True) - note_type_activation = len(active_note_types) - - errors = [] - index_of_active = header_raw.index('active') - index_of_verified = header_raw.index('verified') - index_of_duplicate = header_raw.index('duplicate') - index_of_false_p = header_raw.index('false_p') - index_of_id = header_raw.index('id') - - for finding_sheet in findings_sheet: - finding_id = finding_sheet[index_of_id] - active = finding_sheet[index_of_active] - verified = finding_sheet[index_of_verified] - duplicate = finding_sheet[index_of_duplicate] - false_p = finding_sheet[index_of_false_p] - - if (active == 'TRUE' or verified == 'TRUE') and duplicate == 'TRUE': # Check update finding conditions - error = 'Duplicate findings cannot be verified or active' - errors.append({'finding_id': finding_id, 'column_names': 'active, verified, duplicate', 'error': error}) - elif false_p == 'TRUE' and verified == 'TRUE': - error = 'False positive findings cannot be verified.' - errors.append({'finding_id': finding_id, 'column_names': 'false_p, verified', 'error': error}) - else: - try: - finding_db = findings_db.get(id=finding_id) # Update finding attributes - except: - if finding_id is None: - finding_id = 'Null' - error = 'Finding does not belong to the Test' - errors.append({'finding_id': finding_id, 'column_names': 'id', 'error': error}) - else: - finding_notes = finding_db.notes.all() - for column_name in header_raw: - if column_name in column_details: - if int(column_details[column_name][1]) == 0: - index_of_column = header_raw.index(column_name) - if finding_sheet[index_of_column] == 'TRUE': - setattr(finding_db, column_name, True) - elif finding_sheet[index_of_column] == 'FALSE': - setattr(finding_db, column_name, False) - else: - if finding_sheet[index_of_column] == '': - setattr(finding_db, column_name, None) - else: - setattr(finding_db, column_name, finding_sheet[index_of_column]) - elif column_name[:6] == '[note]' and column_name[-3:] == '_id': # Updating notes - note_column_name = column_name[:-3] - try: - index_of_note_column = header_raw.index(note_column_name) - except ValueError: - pass - else: - index_of_id_column = header_raw.index(column_name) - note_id = finding_sheet[index_of_id_column] - note_entry = finding_sheet[index_of_note_column].rstrip() - if note_entry != '': - if note_id != '': # If the note is an existing one - note_db = finding_notes.get(id=note_id) - if note_entry != note_db.entry.rstrip(): - note_db.entry = note_entry - note_db.edited = True - note_db.editor = request.user - note_db.edit_time = timezone.now() - history = NoteHistory(data=note_db.entry, - time=note_db.edit_time, - current_editor=note_db.editor) - history.save() - note_db.history.add(history) - note_db.save() - else: # If the note is a newly added one - if note_type_activation: - if note_column_name[7:12] == 'Note_': - error = 'Can not add new notes without a note-type. Add your note under the correct note-type column' - errors.append({'finding_id': finding_id, 'column_names': note_column_name, 'error': error}) - else: - note_type_name = note_column_name[7:][:-2] - try: - note_type = active_note_types.get(name=note_type_name) - except: - try: - note_type = Note_Type.objects.get(name=note_type_name) - except: - pass - else: - error = '"' + note_type_name + '" Note-type is disabled. Cannot add new notes of "' + note_type_name + '" type' - errors.append({'finding_id': finding_id, 'column_names': note_column_name, 'error': error}) - else: - new_note = Notes(note_type=note_type, - entry=note_entry, - date=timezone.now(), - author=request.user) - new_note.save() - history = NoteHistory(data=new_note.entry, - time=new_note.date, - current_editor=new_note.author, - note_type=new_note.note_type) - history.save() - new_note.history.add(history) - finding_db.notes.add(new_note) - else: - if note_column_name[7:12] == 'Note_': - new_note = Notes(entry=note_entry, - date=timezone.now(), - author=request.user) - new_note.save() - history = NoteHistory(data=new_note.entry, - time=new_note.date, - current_editor=new_note.author) - history.save() - new_note.history.add(history) - finding_db.notes.add(new_note) - else: - error_location = finding_id + ' ' + note_column_name - error = 'Note-types are not enabled. Notes cannot have a note-type.' - errors.append({'finding_id': finding_id, 'column_names': note_column_name, 'error': error}) - finding_db.save() - res['errors'] = errors - populate_sheet(tid, spreadsheetId) - return res - - -def populate_sheet(tid, spreadsheetId): - system_settings = get_object_or_404(System_Settings, id=1) - service_account_info = json.loads(system_settings.credentials) - service_account_email = service_account_info['client_email'] - email_address = system_settings.email_address - SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - sheets_service = googleapiclient.discovery.build('sheets', 'v4', credentials=credentials, cache_discovery=False) - findings_list = get_findings_list(tid) - row_count = len(findings_list) - column_count = len(findings_list[0]) - - # Create new sheet in the spreadsheet - now = datetime.datetime.now() - sheet_title = now.strftime("%Y-%m-%d %H:%M:%S") - new_sheet = { - "requests": [{ - "addSheet": { - "properties": { - "title": sheet_title, - "gridProperties": { - "rowCount": row_count, - "columnCount": column_count - } - } - } - }] - } - sheets_service.spreadsheets().batchUpdate(spreadsheetId=spreadsheetId, body=new_sheet).execute() - - # Move new sheet to the left most corner - spreadsheet = sheets_service.spreadsheets().get(spreadsheetId=spreadsheetId).execute() - for sheet in spreadsheet['sheets']: - if sheet['properties']['title'] == sheet_title: - sheet_id = sheet['properties']['sheetId'] - break - reqs = { - 'requests': [ - {'updateSheetProperties': { - 'properties': { - 'sheetId': sheet_id, - 'index': 0 - }, - "fields": "index" - }} - ]} - sheets_service.spreadsheets().batchUpdate(spreadsheetId=spreadsheetId, body=reqs).execute() - - # Update created sheet with finding details - result = sheets_service.spreadsheets().values().update(spreadsheetId=spreadsheetId, - range=sheet_title, - valueInputOption='RAW', - body={'values': findings_list}).execute() - - # Format the header row - body = { - "requests": [ - { - "repeatCell": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 0, - "endRowIndex": 1 - }, - "cell": { - "userEnteredFormat": { - "backgroundColor": { - "red": 0.0, - "green": 0.0, - "blue": 0.0 - }, - "horizontalAlignment": "CENTER", - "textFormat": { - "foregroundColor": { - "red": 1.0, - "green": 1.0, - "blue": 1.0 - }, - "fontSize": 12, - "bold": True - } - } - }, - "fields": "userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)" - } - }, - { - "updateSheetProperties": { - "properties": { - "sheetId": sheet_id, - "gridProperties": { - "frozenRowCount": 1 - } - }, - "fields": "gridProperties.frozenRowCount" - } - }, - { - "addProtectedRange": { - "protectedRange": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 0, - "endRowIndex": 1, - "startColumnIndex": 0, - "endColumnIndex": column_count, - }, - "editors": { - "users": [ - service_account_email, - email_address - ] - }, - # "description": "Protecting total row", - "warningOnly": False - } - } - } - ] - } - sheets_service.spreadsheets().batchUpdate(spreadsheetId=spreadsheetId, body=body).execute() - - # Format columns with input field widths and protect columns - range = sheet_title + '!1:1' - result = sheets_service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=range).execute() - rows = result.get('values', []) - header_raw = rows[0] - fields = Finding._meta.fields - column_details = json.loads(system_settings.column_widths.replace("'", '"')) - body = {} - body["requests"] = [] - for column_name in header_raw: - index_of_column = header_raw.index(column_name) - if column_name in column_details: - # If column width is 0 hide column - if int(column_details[column_name][0]) == 0: - body["requests"].append({ - "updateDimensionProperties": { - "range": { - "sheetId": sheet_id, - "dimension": "COLUMNS", - "startIndex": index_of_column, - "endIndex": index_of_column + 1 - }, - "properties": { - "hiddenByUser": True, - }, - "fields": "hiddenByUser" - } - }) - else: - # If column width is not 0 adjust column to given width - body["requests"].append({ - "updateDimensionProperties": { - "range": { - "sheetId": sheet_id, - "dimension": "COLUMNS", - "startIndex": index_of_column, - "endIndex": index_of_column + 1 - }, - "properties": { - "pixelSize": column_details[column_name][0] - }, - "fields": "pixelSize" - } - }) - # If protect column is true, protect in sheet - if column_details[column_name][1] == 1: - body["requests"].append({ - "addProtectedRange": { - "protectedRange": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "editors": { - "users": [ - service_account_email, - email_address - ] - }, - "warningOnly": False - } - } - }) - # Format boolean fields in the google sheet - if (fields[index_of_column].get_internal_type()) == "BooleanField": - body["requests"].append({ - "setDataValidation": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "rule": { - "condition": { - "type": "BOOLEAN", - }, - "inputMessage": "Value must be BOOLEAN", - "strict": True - } - } - }) - # Format integer fields in the google sheet - elif (fields[index_of_column].get_internal_type()) == "IntegerField": - body["requests"].append({ - "setDataValidation": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "rule": { - "condition": { - "type": "NUMBER_GREATER", - "values": [ - { - "userEnteredValue": "-1" - } - ] - }, - "inputMessage": "Value must be an integer", - "strict": True - } - } - }) - # Format date fields in the google sheet - elif (fields[index_of_column].get_internal_type()) == "DateField": - body["requests"].append({ - "setDataValidation": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "rule": { - "condition": { - "type": "DATE_IS_VALID", - }, - "inputMessage": "Value must be a valid date", - "strict": True - } - } - }) - # Make severity column a dropdown - elif column_name == "severity": - body["requests"].append({ - "setDataValidation": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "rule": { - "condition": { - "type": "ONE_OF_LIST", - "values": [ - {"userEnteredValue": "Info"}, - {"userEnteredValue": "Low"}, - {"userEnteredValue": "Medium"}, - {"userEnteredValue": "High"}, - {"userEnteredValue": "Critical"}, - ] - }, - "inputMessage": "Value must be an one of list", - "strict": True - } - } - }) - # Hide and protect note id columns and last column - elif (column_name[:6] == '[note]' and column_name[-3:] == '_id') or column_name == 'Last column': - body["requests"].append({ - "updateDimensionProperties": { - "range": { - "sheetId": sheet_id, - "dimension": "COLUMNS", - "startIndex": index_of_column, - "endIndex": index_of_column + 1 - }, - "properties": { - "hiddenByUser": True, - }, - "fields": "hiddenByUser" - } - }) - body["requests"].append({ - "addProtectedRange": { - "protectedRange": { - "range": { - "sheetId": sheet_id, - "startRowIndex": 1, - "endRowIndex": row_count, - "startColumnIndex": index_of_column, - "endColumnIndex": index_of_column + 1, - }, - "editors": { - "users": [ - service_account_email, - email_address - ] - }, - "warningOnly": False - } - } - }) - elif column_name[:6] == '[note]' or column_name[:11] == '[duplicate]': - body["requests"].append({ - "autoResizeDimensions": { - "dimensions": { - "sheetId": sheet_id, - "dimension": "COLUMNS", - "startIndex": index_of_column, - "endIndex": index_of_column + 1 - } - } - }) - sheets_service.spreadsheets().batchUpdate(spreadsheetId=spreadsheetId, body=body).execute() - - -def get_findings_list(tid): - test = Test.objects.get(id=tid) - system_settings = get_object_or_404(System_Settings, id=1) - findings = Finding.objects.filter(test=test).order_by('numerical_severity') - active_note_types = Note_Type.objects.filter(is_active=True).order_by('id') - note_type_activation = active_note_types.count() - - # Create the header row - fields = Finding._meta.fields - findings_list = [] - headings = [] - for i in fields: - headings.append(i.name) - findings_list.append(headings) - - # Create finding rows - for finding in findings: - finding_details = [] - for field in fields: - value = getattr(finding, field.name) - if type(value) == datetime.date or type(value) == Test or type(value) == datetime.datetime: - var = str(value) - elif type(value) == User or type(value) == Dojo_User: - var = value.username - elif type(value) == Finding: - var = value.id - elif type(value) == Sonarqube_Issue: - var = value.key - else: - var = value - finding_details.append(var) - findings_list.append(finding_details) - - # Add notes into the findings_list - if note_type_activation: - for note_type in active_note_types: - max_note_count = 1 - if note_type.is_single: - findings_list[0].append('[note] ' + note_type.name + '_1_id') - findings_list[0].append('[note] ' + note_type.name + '_1') - else: - for finding in findings: - note_count = finding.notes.filter(note_type=note_type).count() - if max_note_count < note_count: - max_note_count = note_count - for n in range(max_note_count): - findings_list[0].append('[note] ' + note_type.name + '_' + str(n + 1) + '_id') - findings_list[0].append('[note] ' + note_type.name + '_' + str(n + 1)) - for f in range(findings.count()): - finding = findings[f] - notes = finding.notes.filter(note_type=note_type).order_by('id') - for note in notes: - findings_list[f + 1].append(note.id) - findings_list[f + 1].append(note.entry) - missing_notes_count = max_note_count - notes.count() - for i in range(missing_notes_count): - findings_list[f + 1].append('') - findings_list[f + 1].append('') - max_note_count = 0 - for finding in findings: - note_count = finding.notes.exclude(note_type__in=active_note_types).count() - if max_note_count < note_count: - max_note_count = note_count - if max_note_count > 0: - for i in range(max_note_count): - findings_list[0].append('[note] ' + "Note_" + str(i + 1) + '_id') - findings_list[0].append('[note] ' + "Note_" + str(i + 1)) - for f in range(findings.count()): - finding = findings[f] - notes = finding.notes.exclude(note_type__in=active_note_types).order_by('id') - for note in notes: - findings_list[f + 1].append(note.id) - findings_list[f + 1].append(note.entry) - missing_notes_count = max_note_count - notes.count() - for i in range(missing_notes_count): - findings_list[f + 1].append('') - findings_list[f + 1].append('') - else: - max_note_count = 1 - for finding in findings: - note_count = len(finding.notes.all()) - if note_count > max_note_count: - max_note_count = note_count - for i in range(max_note_count): - findings_list[0].append('[note] ' + "Note_" + str(i + 1) + '_id') - findings_list[0].append('[note] ' + "Note_" + str(i + 1)) - for f in range(findings.count()): - finding = findings[f] - notes = finding.notes.all().order_by('id') - for note in notes: - findings_list[f + 1].append(note.id) - findings_list[f + 1].append(note.entry) - missing_notes_count = max_note_count - notes.count() - for i in range(missing_notes_count): - findings_list[f + 1].append('') - findings_list[f + 1].append('') - - if system_settings.enable_deduplication: - if note_type_activation: - for note_type in active_note_types: - findings_list[0].append('[duplicate] ' + note_type.name) - for f in range(findings.count()): - original_finding = findings[f].duplicate_finding - for note_type in active_note_types: - try: - note = original_finding.notes.filter(note_type=note_type).latest('date') - findings_list[f + 1].append(note.entry) - except: - findings_list[f + 1].append('') - else: - findings_list[0].append('[duplicate] note') - for f in range(findings.count()): - original_finding = findings[f].duplicate_finding - try: - note = original_finding.notes.latest('date') - findings_list[f + 1].append(note.entry) - except: - findings_list[f + 1].append('') - - findings_list[0].append('Last column') - for f in range(findings.count()): - findings_list[f + 1].append('-') - return findings_list diff --git a/dojo/locale/en/LC_MESSAGES/django.po b/dojo/locale/en/LC_MESSAGES/django.po index e20b8701105..01ef7f6a472 100644 --- a/dojo/locale/en/LC_MESSAGES/django.po +++ b/dojo/locale/en/LC_MESSAGES/django.po @@ -2715,10 +2715,6 @@ msgstr "" msgid "GitHub" msgstr "" -#: dojo/templates/base.html -msgid "Google Sheets Sync" -msgstr "" - #: dojo/templates/base.html dojo/templates/dojo/simple_search.html #: dojo/templates/dojo/view_product_details.html msgid "JIRA" @@ -4663,13 +4659,6 @@ msgstr "" msgid "Note added successfully." msgstr "" -#: dojo/test/views.py -msgid "" -"There is a problem with the Google Sheets Sync Configuration. Contact your " -"system admin to solve the issue. Until fixed, the Google Sheets Sync feature " -"cannot be used." -msgstr "" - #: dojo/test/views.py msgid "Unable to reach the Google Sheet API." msgstr "" diff --git a/dojo/models.py b/dojo/models.py index e1d684e33f5..ad7709b39d9 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -428,9 +428,6 @@ class System_Settings(models.Model): disclaimer = models.TextField(max_length=3000, default='', blank=True, verbose_name=_('Custom Disclaimer'), help_text=_("Include this custom disclaimer on all notifications and generated reports")) - column_widths = models.TextField(max_length=1500, blank=True) - drive_folder_ID = models.CharField(max_length=100, blank=True) - email_address = models.EmailField(max_length=100, blank=True) risk_acceptance_form_default_days = models.IntegerField(null=True, blank=True, default=180, help_text=_("Default expiry period for risk acceptance form.")) risk_acceptance_notify_before_expiration = models.IntegerField(null=True, blank=True, default=10, verbose_name=_('Risk acceptance expiration heads up days'), help_text=_("Notify X days before risk acceptance expires. Leave empty to disable.")) @@ -454,16 +451,6 @@ class System_Settings(models.Model): blank=False, verbose_name=_('Enable Endpoint Metadata Import'), help_text=_("With this setting turned off, endpoint metadata import will be disabled in the user interface.")) - enable_google_sheets = models.BooleanField( - default=False, - blank=False, - verbose_name=_('Enable Google Sheets Integration'), - help_text=_("With this setting turned off, the Google sheets integration will be disabled in the user interface.")) - enable_rules_framework = models.BooleanField( - default=False, - blank=False, - verbose_name=_('Enable Rules Framework'), - help_text=_("With this setting turned off, the rules framwork will be disabled in the user interface.")) enable_user_profile_editable = models.BooleanField( default=True, blank=False, @@ -2751,10 +2738,6 @@ def save(self, dedupe_option=True, false_history=False, rules_option=True, produ except Exception as ex: logger.error("Can't compute cvssv3 score for finding id %i. Invalid cvssv3 vector found: '%s'. Exception: %s", self.id, self.cvssv3, ex) - if rules_option: - from dojo.utils import do_apply_rules - do_apply_rules(self, *args, **kwargs) - # Finding.save is called once from serializers.py with dedupe_option=False because the finding is not ready yet, for example the endpoints are not built # It is then called a second time with dedupe_option defaulted to true; now we can compute the hash_code and run the deduplication if dedupe_option: @@ -3941,77 +3924,6 @@ class Meta: unique_together = [('product', 'benchmark_type')] -# product_opts = [f.name for f in Product._meta.fields] -# test_opts = [f.name for f in Test._meta.fields] -# test_type_opts = [f.name for f in Test_Type._meta.fields] -finding_opts = [f.name for f in Finding._meta.fields if f.name not in ['last_status_update']] -# endpoint_opts = [f.name for f in Endpoint._meta.fields] -# engagement_opts = [f.name for f in Engagement._meta.fields] -# product_type_opts = [f.name for f in Product_Type._meta.fields] -# single_options = product_opts + test_opts + test_type_opts + finding_opts + \ -# endpoint_opts + engagement_opts + product_type_opts -all_options = [] -for x in finding_opts: - all_options.append((x, x)) -operator_options = (('Matches', 'Matches'), - ('Contains', 'Contains')) -application_options = (('Append', 'Append'), - ('Replace', 'Replace')) -blank_options = (('', ''),) - - -class Rule(models.Model): - # add UI notification to let people know what rules were applied - - name = models.CharField(max_length=200) - enabled = models.BooleanField(default=True) - text = models.TextField() - operator = models.CharField(max_length=30, choices=operator_options) - """ - model_object_options = (('Product', 'Product'), - ('Engagement', 'Engagement'), ('Test', 'Test'), - ('Finding', 'Finding'), ('Endpoint', 'Endpoint'), - ('Product Type', 'Product_Type'), ('Test Type', 'Test_Type')) - """ - model_object_options = (('Finding', 'Finding'),) - model_object = models.CharField(max_length=30, choices=model_object_options) - match_field = models.CharField(max_length=200, choices=all_options) - match_text = models.TextField() - application = models.CharField(max_length=200, choices=application_options) - applies_to = models.CharField(max_length=30, choices=model_object_options) - # TODO: Add or ? - # and_rules = models.ManyToManyField('self') - applied_field = models.CharField(max_length=200, choices=(all_options)) - child_rules = models.ManyToManyField('self', editable=False) - parent_rule = models.ForeignKey('self', editable=False, null=True, on_delete=models.CASCADE) - - -class Child_Rule(models.Model): - # add UI notification to let people know what rules were applied - operator = models.CharField(max_length=30, choices=operator_options) - """ - model_object_options = (('Product', 'Product'), - ('Engagement', 'Engagement'), ('Test', 'Test'), - ('Finding', 'Finding'), ('Endpoint', 'Endpoint'), - ('Product Type', 'Product_Type'), ('Test Type', 'Test_Type')) - """ - model_object_options = (('Finding', 'Finding'),) - model_object = models.CharField(max_length=30, choices=model_object_options) - match_field = models.CharField(max_length=200, choices=all_options) - match_text = models.TextField() - # TODO: Add or ? - # and_rules = models.ManyToManyField('self') - parent_rule = models.ForeignKey(Rule, editable=False, null=True, on_delete=models.CASCADE) - - -class FieldRule(models.Model): - field = models.CharField(max_length=200) - update_options = (('Append', 'Append'), - ('Replace', 'Replace')) - update_type = models.CharField(max_length=30, choices=update_options) - text = models.CharField(max_length=200) - - # ========================== # Defect Dojo Engaegment Surveys # ============================== diff --git a/dojo/rules/__init__.py b/dojo/rules/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/dojo/rules/urls.py b/dojo/rules/urls.py deleted file mode 100644 index aba30ca0cb7..00000000000 --- a/dojo/rules/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import re_path -from dojo.rules import views - -urlpatterns = [ - re_path(r'^rules', views.rules, name='rules'), - re_path(r'^rule/add', views.new_rule, name='Add Rule'), - re_path(r'^rule/(?P\d+)/edit$', views.edit_rule, - name='Edit Rule'), - re_path(r'^rule/(?P\d+)/add_child', views.add_child, - name='Add Child'), - re_path(r'^rule/(?P\d+)/delete$', views.delete_rule, - name='Delete Rule'), ] diff --git a/dojo/rules/views.py b/dojo/rules/views.py deleted file mode 100644 index 325b3ded569..00000000000 --- a/dojo/rules/views.py +++ /dev/null @@ -1,166 +0,0 @@ -# Standard library imports -import json -import logging - -# Third party imports -from django.contrib import messages -from django.urls import reverse -from django.http import HttpResponseRedirect -from django.shortcuts import render, get_object_or_404 -from django.contrib.admin.utils import NestedObjects -from django.db import DEFAULT_DB_ALIAS - -# Local application/library imports -from dojo.models import Rule,\ - System_Settings, Finding, Test, Test_Type, Engagement, \ - Product, Product_Type, Child_Rule -from dojo.forms import RuleFormSet, DeleteRuleForm, RuleForm -from dojo.utils import add_breadcrumb -from dojo.authorization.authorization_decorators import user_is_configuration_authorized - -logger = logging.getLogger(__name__) - -# Fields for each model ruleset - -finding_fields = [f.name for f in Finding._meta.fields] -test_fields = [f.name for f in Test._meta.fields] -test_type_fields = [f.name for f in Test_Type._meta.fields] -engagement_fields = [f.name for f in Engagement._meta.fields] -product_fields = [f.name for f in Product._meta.fields] -product_type_fields = [f.name for f in Product_Type._meta.fields] -field_dictionary = {} -field_dictionary['Finding'] = finding_fields -field_dictionary['Test Type'] = test_type_fields -field_dictionary['Test'] = test_fields -field_dictionary['Engagement'] = engagement_fields -field_dictionary['Product'] = product_fields -field_dictionary['Product Type'] = product_type_fields - - -@user_is_configuration_authorized('dojo.view_rule') -def rules(request): - initial_queryset = Rule.objects.all().order_by('name') - add_breadcrumb(title="Rules", top_level=True, request=request) - return render(request, 'dojo/rules.html', { - 'name': 'Rules List', - 'metric': False, - 'user': request.user, - 'rules': initial_queryset}) - - -@user_is_configuration_authorized('dojo.add_rule') -def new_rule(request): - if request.method == 'POST': - form = RuleForm(request.POST) - if form.is_valid(): - rule = form.save() - messages.add_message(request, - messages.SUCCESS, - 'Rule created successfully.', - extra_tags='alert-success') - if "_Add Child" in request.POST: - return HttpResponseRedirect(reverse('Add Child', args=(rule.id,))) - return HttpResponseRedirect(reverse('rules')) - form = RuleForm() - add_breadcrumb(title="New Dojo Rule", top_level=False, request=request) - return render(request, 'dojo/new_rule2.html', - {'form': form, - 'finding_fields': finding_fields, - 'test_fields': test_fields, - 'engagement_fields': engagement_fields, - 'product_fields': product_fields, - 'product_type_fields': product_type_fields, - 'field_dictionary': json.dumps(field_dictionary)}) - - -@user_is_configuration_authorized('dojo.add_rule') -def add_child(request, pid): - rule = get_object_or_404(Rule, pk=pid) - if request.method == 'POST': - forms = RuleFormSet(request.POST) - for form in forms: - if form.is_valid(): - cr = form.save(commit=False) - cr.parent_rule = rule - cr.save() - messages.add_message(request, - messages.SUCCESS, - 'Rule created successfully.', - extra_tags='alert-success') - return HttpResponseRedirect(reverse('rules')) - form = RuleFormSet(queryset=Child_Rule.objects.filter(parent_rule=rule)) - add_breadcrumb(title="New Dojo Rule", top_level=False, request=request) - return render(request, 'dojo/new_rule.html', - {'form': form, - 'pid': pid, - 'finding_fields': finding_fields, - 'test_fields': test_fields, - 'engagement_fields': engagement_fields, - 'product_fields': product_fields, - 'product_type_fields': product_type_fields, - 'field_dictionary': json.dumps(field_dictionary)}) - - -@user_is_configuration_authorized('dojo.change_rule') -def edit_rule(request, pid): - pt = get_object_or_404(Rule, pk=pid) - children = Rule.objects.filter(parent_rule=pt) - all_rules = children | Rule.objects.filter(pk=pid) - form = RuleForm(instance=pt) - if request.method == 'POST': - form = RuleForm(request.POST, instance=pt) - if form.is_valid(): - pt = form.save() - messages.add_message(request, - messages.SUCCESS, - 'Rule updated successfully.', - extra_tags='alert-success') - if "_Add Child" in request.POST: - return HttpResponseRedirect(reverse('Add Child', args=(pt.id,))) - return HttpResponseRedirect(reverse('rules')) - add_breadcrumb(title="Edit Rule", top_level=False, request=request) - return render(request, 'dojo/edit_rule.html', { - 'name': 'Edit Rule', - 'metric': False, - 'user': request.user, - 'form': form, - 'field_dictionary': json.dumps(field_dictionary), - 'pt': pt, }) - - -@user_is_configuration_authorized('dojo.delete_rule') -def delete_rule(request, tid): - rule = get_object_or_404(Rule, pk=tid) - form = DeleteRuleForm(instance=rule) - - if request.method == 'POST': - # print('id' in request.POST, file=sys.stderr) - # print(str(rule.id) == request.POST['id'], file=sys.stderr) - # print(str(rule.id) == request.POST['id'], file=sys.stderr) - # if 'id' in request.POST and str(rule.id) == request.POST['id']: - form = DeleteRuleForm(request.POST, instance=rule) - # print(form.is_valid(), file=sys.stderr) - # print(form.errors, file=sys.stderr) - # print(form.non_field_errors(), file=sys.stderr) - # print('id' in request.POST, file=sys.stderr) - if form.is_valid(): - rule.delete() - messages.add_message(request, - messages.SUCCESS, - 'Rule deleted.', - extra_tags='alert-success') - return HttpResponseRedirect(reverse('rules')) - - collector = NestedObjects(using=DEFAULT_DB_ALIAS) - collector.collect([rule]) - rels = collector.nested() - - add_breadcrumb(parent=rule, title="Delete", top_level=False, request=request) - system_settings = System_Settings.objects.get() - return render(request, 'dojo/delete_rule.html', - {'rule': rule, - 'form': form, - 'active_tab': 'findings', - 'system_settings': system_settings, - 'rels': rels, - }) diff --git a/dojo/templates/base.html b/dojo/templates/base.html index 9b71e238d9a..5f80b24d0f5 100644 --- a/dojo/templates/base.html +++ b/dojo/templates/base.html @@ -514,13 +514,6 @@ {% endif %} - {% if system_settings.enable_google_sheets and "dojo.change_google_sheet"|has_configuration_permission:request %} -
  • - - {% trans "Google Sheets Sync" %} - -
  • - {% endif %} {% if system_settings.enable_jira and "dojo.view_jira_instance"|has_configuration_permission:request %}
  • @@ -552,13 +545,6 @@ {% trans "Regulations" %}
  • - {% if system_settings.enable_rules_framework and "dojo.view_rule"|has_configuration_permission:request %} -
  • - - {% trans "Rules Framework" %} - -
  • - {% endif %} {% if system_settings.enable_finding_sla and "dojo.view_sla_configuration"|has_configuration_permission:request %}
  • diff --git a/dojo/templates/dojo/delete_rule.html b/dojo/templates/dojo/delete_rule.html deleted file mode 100644 index 949294185cc..00000000000 --- a/dojo/templates/dojo/delete_rule.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} -{% block content %} - {{ block.super }} -

    Delete Rule {{ rule }}

    -

    - Deleting this rule will remove any related objects associated - with it. These relationships are listed below: -

    -
    -
    -

    Danger Zone

    -
    - {% if rels|length > 1 %} -
      {{ rels|unordered_list }}
    - {% else %} -

    No relationships found.

    - {% endif %} -
    - {% csrf_token %} - {{ form }} - -
    - -
    -
    -
    -
    -
    -{% endblock %} diff --git a/dojo/templates/dojo/edit_rule.html b/dojo/templates/dojo/edit_rule.html deleted file mode 100644 index f21df31afba..00000000000 --- a/dojo/templates/dojo/edit_rule.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "base.html" %} -{% block content %} - {{ block.super }} -

    Edit Rule {{ pt.name }}

    -
    {% csrf_token %} - {% include "dojo/form_fields.html" with form=form %} -
    -
    - - -
    -
    -
    -{% endblock %} -{% block postscript %} - {{ block.super }} - -{% endblock %} diff --git a/dojo/templates/dojo/google_sheet_configuration.html b/dojo/templates/dojo/google_sheet_configuration.html deleted file mode 100644 index 25379c3f411..00000000000 --- a/dojo/templates/dojo/google_sheet_configuration.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} -{% block content %} - {{ block.super }} -
    {% csrf_token %} - {% include "dojo/form_fields.html" with form=form %} -
    -
    - - {% if revoke_access %} - - {% endif %} -
    -
    -
    -{% endblock %} diff --git a/dojo/templates/dojo/new_rule.html b/dojo/templates/dojo/new_rule.html deleted file mode 100644 index fb363a69f5d..00000000000 --- a/dojo/templates/dojo/new_rule.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "base.html" %} -{% load get_attribute %} -{% block content %} - {{ block.super }} -

    Add Child Rules

    -
    {% csrf_token %} - {% for f in form %} -
    - {% if forloop.first %} -
    -

    Child Rule

    - {% include "dojo/form_fields.html" with form=f %} - {% else %} -
    -

    Child Rule

    - {% include "dojo/form_fields.html" with form=f %} - {% endif %} -
    - {% endfor %} -
    -
    -
    - {{ form.management_form }} - -
    -
    -
    -
    -{% endblock %} -{% block postscript %} - {{ block.super }} - -{% endblock %} diff --git a/dojo/templates/dojo/new_rule2.html b/dojo/templates/dojo/new_rule2.html deleted file mode 100644 index 07e98d7be83..00000000000 --- a/dojo/templates/dojo/new_rule2.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} -{% block content %} - {{ block.super }} -

    Add New Rule

    -
    {% csrf_token %} - {% include "dojo/form_fields.html" with form=form %} -
    -
    - - -
    -
    -
    -{% endblock %} diff --git a/dojo/templates/dojo/rules.html b/dojo/templates/dojo/rules.html deleted file mode 100644 index e2a8b5a6487..00000000000 --- a/dojo/templates/dojo/rules.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends "base.html" %} -{% load navigation_tags %} -{% load authorization_tags %} -{% block content %} - {{ block.super }} -
    - {% include "dojo/filter_js_snippet.html" %} -{% endblock %} diff --git a/dojo/templates/dojo/syncing_errors.html b/dojo/templates/dojo/syncing_errors.html deleted file mode 100644 index 99e56d68f2c..00000000000 --- a/dojo/templates/dojo/syncing_errors.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends "base.html" %} -{% load navigation_tags %} -{% block content %} - {{ block.super }} - {% load display_tags %} -

    - Synced with "{{ sheet_title }}" sheet of Spreadsheet "{{ spreadsheet_name }}" -

    -
    -
    -
    -
    -

    - {{ name }} -

    -
    -
    - {% if errors %} -
    - {% include "dojo/paging_snippet.html" with page=errors page_size=True %} -
    -
    - - - - - - - {% for e in errors %} - - - - - - {% endfor %} -
    Finding ID Column Names Error
    {{ e.finding_id }}{{ e.column_names }}{{ e.error }}
    -
    -
    - {% include "dojo/paging_snippet.html" with page=errors page_size=True %} -
    - {% else %} -
    No Errors
    - {% endif %} -
    -
    -
    View Google Sheet -{% endblock %} diff --git a/dojo/templates/dojo/view_test.html b/dojo/templates/dojo/view_test.html index 57a0185ed96..2e755690181 100644 --- a/dojo/templates/dojo/view_test.html +++ b/dojo/templates/dojo/view_test.html @@ -68,26 +68,6 @@

    {{ test }} Report

  • - {% if show_export %} - {% if sheet_url is null %} -
  • - - Create Google Sheet - -
  • - {% else %} -
  • - - View Google Sheet - -
  • -
  • - - Sync Google Sheet - -
  • - {% endif %} - {% endif %}
  • Add To Calendar diff --git a/dojo/test/views.py b/dojo/test/views.py index 42dd28f29ea..52525d39be9 100644 --- a/dojo/test/views.py +++ b/dojo/test/views.py @@ -4,12 +4,8 @@ from dojo.importers.utils import construct_imported_message import logging import operator -import json -import httplib2 import base64 from datetime import datetime -import googleapiclient.discovery -from google.oauth2 import service_account from django.conf import settings from django.contrib import messages from django.core.exceptions import ValidationError @@ -129,43 +125,6 @@ def view_test(request, tid): finding_groups = test.finding_group_set.all().prefetch_related('findings', 'jira_issue', 'creator', 'findings__vulnerability_id_set') bulk_edit_form = FindingBulkUpdateForm(request.GET) - - google_sheets_enabled = system_settings.enable_google_sheets - sheet_url = None - if google_sheets_enabled and system_settings.credentials: - spreadsheet_name = test.engagement.product.name + "-" + test.engagement.name + "-" + str(test.id) - system_settings = get_object_or_404(System_Settings, id=1) - service_account_info = json.loads(system_settings.credentials) - SCOPES = ['https://www.googleapis.com/auth/drive'] - credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) - try: - drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=credentials, cache_discovery=False) - folder_id = system_settings.drive_folder_ID - gs_files = drive_service.files().list(q="mimeType='application/vnd.google-apps.spreadsheet' and parents in '%s' and name='%s'" % (folder_id, spreadsheet_name), - spaces='drive', - pageSize=10, - fields='files(id, name)').execute() - - except googleapiclient.errors.HttpError: - messages.add_message( - request, - messages.ERROR, - _("There is a problem with the Google Sheets Sync Configuration. Contact your system admin to solve the issue. Until fixed, the Google Sheets Sync feature cannot be used."), - extra_tags="alert-danger", - ) - google_sheets_enabled = False - except httplib2.ServerNotFoundError: - messages.add_message( - request, - messages.ERROR, - _("Unable to reach the Google Sheet API."), - extra_tags="alert-danger", - ) - else: - spreadsheets = gs_files.get('files') - if len(spreadsheets) == 1: - spreadsheetId = spreadsheets[0].get('id') - sheet_url = 'https://docs.google.com/spreadsheets/d/' + spreadsheetId return render(request, 'dojo/view_test.html', {'test': test, 'prod': prod, @@ -184,8 +143,6 @@ def view_test(request, tid): 'creds': creds, 'cred_test': cred_test, 'jira_project': jira_project, - 'show_export': google_sheets_enabled and system_settings.credentials, - 'sheet_url': sheet_url, 'bulk_edit_form': bulk_edit_form, 'paged_test_imports': paged_test_imports, 'test_import_filter': test_import_filter, diff --git a/dojo/urls.py b/dojo/urls.py index 6f54bca94a9..984b98d75df 100755 --- a/dojo/urls.py +++ b/dojo/urls.py @@ -52,10 +52,8 @@ from dojo.notifications.urls import urlpatterns as notifications_urls from dojo.object.urls import urlpatterns as object_urls from dojo.benchmark.urls import urlpatterns as benchmark_urls -from dojo.rules.urls import urlpatterns as rule_urls from dojo.notes.urls import urlpatterns as notes_urls from dojo.note_type.urls import urlpatterns as note_type_urls -from dojo.google_sheet.urls import urlpatterns as google_sheets_urls from dojo.banner.urls import urlpatterns as banner_urls from dojo.survey.urls import urlpatterns as survey_urls from dojo.components.urls import urlpatterns as component_urls @@ -159,10 +157,8 @@ ur += notifications_urls ur += object_urls ur += benchmark_urls -ur += rule_urls ur += notes_urls ur += note_type_urls -ur += google_sheets_urls ur += banner_urls ur += component_urls ur += regulations diff --git a/dojo/user/utils.py b/dojo/user/utils.py index 07e58eb4684..1c48859dba3 100644 --- a/dojo/user/utils.py +++ b/dojo/user/utils.py @@ -94,13 +94,7 @@ def get_configuration_permissions_fields(): else: questionnaire_permissions = [] - if get_system_setting('enable_rules_framework'): - rules_permissions = [ - Permission_Helper(name='rule', app='auth', view=True, add=True, change=True, delete=True), - ] - else: - rules_permissions = [] - + rules_permissions = [] permission_fields = [ Permission_Helper(name='cred user', app='dojo', view=True, add=True, change=True, delete=True), Permission_Helper(name='development environment', app='dojo', add=True, change=True, delete=True), diff --git a/dojo/utils.py b/dojo/utils.py index 44211ecda14..7c553da0456 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -28,7 +28,7 @@ from dojo.github import add_external_issue_github, update_external_issue_github, close_external_issue_github, reopen_external_issue_github from dojo.models import Finding, Engagement, Finding_Group, Finding_Template, Product, \ Test, User, Dojo_User, System_Settings, Notifications, Endpoint, Benchmark_Type, \ - Language_Type, Languages, Rule, Dojo_Group_Member, NOTIFICATION_CHOICES + Language_Type, Languages, Dojo_Group_Member, NOTIFICATION_CHOICES from asteval import Interpreter from dojo.notifications.helper import create_notification import logging @@ -431,56 +431,6 @@ def set_duplicate_reopen(new_finding, existing_finding): existing_finding.save() -def do_apply_rules(new_finding, *args, **kwargs): - rules = Rule.objects.filter(applies_to='Finding', parent_rule=None) - for rule in rules: - child_val = True - child_list = [val for val in rule.child_rules.all()] - while (len(child_list) != 0): - child_val = child_val and child_rule(child_list.pop(), new_finding) - if child_val: - if rule.operator == 'Matches': - if getattr(new_finding, rule.match_field) == rule.match_text: - if rule.application == 'Append': - set_attribute_rule(new_finding, rule, (getattr( - new_finding, rule.applied_field) + rule.text)) - else: - set_attribute_rule(new_finding, rule, rule.text) - new_finding.save(dedupe_option=False, - rules_option=False) - else: - if rule.match_text in getattr(new_finding, rule.match_field): - if rule.application == 'Append': - set_attribute_rule(new_finding, rule, (getattr( - new_finding, rule.applied_field) + rule.text)) - else: - set_attribute_rule(new_finding, rule, rule.text) - new_finding.save(dedupe_option=False, - rules_option=False) - - -def set_attribute_rule(new_finding, rule, value): - if rule.text == "True": - setattr(new_finding, rule.applied_field, True) - elif rule.text == "False": - setattr(new_finding, rule.applied_field, False) - else: - setattr(new_finding, rule.applied_field, value) - - -def child_rule(rule, new_finding): - if rule.operator == 'Matches': - if getattr(new_finding, rule.match_field) == rule.match_text: - return True - else: - return False - else: - if rule.match_text in getattr(new_finding, rule.match_field): - return True - else: - return False - - def count_findings(findings): product_count = {} finding_count = {'low': 0, 'med': 0, 'high': 0, 'crit': 0} diff --git a/requirements.txt b/requirements.txt index a1666d3e104..0f77ae59690 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,9 +56,6 @@ Python-jose==3.3.0 gitpython==3.1.31 debugpy==1.6.6 python-gitlab==3.13.0 -google-api-python-client==2.83.0 -google-auth==2.16.3 -google-auth-oauthlib==1.0.0 drf_yasg==1.21.5 cpe==1.2.1 packageurl-python==0.11.1 diff --git a/unittests/test_google_sheets_configuration.py b/unittests/test_google_sheets_configuration.py deleted file mode 100644 index ef86c274355..00000000000 --- a/unittests/test_google_sheets_configuration.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -from .dojo_test_case import DojoVCRTestCase -from .dojo_test_case import DojoTestCase -import logging -from vcr import VCR -from django.urls import reverse - - -logger = logging.getLogger(__name__) - - -class GoogleSheetsConfigTestApi(DojoVCRTestCase): - fixtures = ['dojo_testdata.json'] - - def __init__(self, *args, **kwargs): - DojoTestCase.__init__(self, *args, **kwargs) - - def assert_cassette_played(self): - if True: # set to True when committing. set to False when recording new test cassettes - self.assertTrue(self.cassette.all_played) - - def _get_vcr(self, **kwargs): - my_vcr = super(GoogleSheetsConfigTestApi, self)._get_vcr(**kwargs) - my_vcr.record_mode = 'once' - my_vcr.path_transformer = VCR.ensure_suffix('.yaml') - my_vcr.cassette_library_dir = os.path.dirname(os.path.abspath(__file__)) + '/vcr/google_sheets/' - return my_vcr - - def setUp(self): - super().setUp() - self.client.force_login(self.get_test_admin()) - - def test_config_google_sheets(self): - # To regenerate the cassette, use an actual credentials json file - with open('tests/test-dojo-sheets-NONEXISTING.json', 'rb') as f: - data = {} - # fail on purpose to get all the fields dynamically - response = self.client.post(reverse('configure_google_sheets'), data, follow=True) - form = response.context['form'] - self.assertEqual(form.is_valid(), False) - - for field in form: - # do not consider the "protect" checkbox, leave them as is - if 'Protect' in field.html_name: - continue - # Select Hide (0) by default - data.update({field.html_name: 0}) - - data.update({ - # To regenerate the cassette, use non-revoked credentials - # The json file is a real json file, but the account is disabled and the key deleted - # The VCR does not recognize the stream otherwise, and will not match causing tests to fail :sob: :shrug: - # due to the bearer token having to remain in the vcr yaml - 'email_address': 'test-dojo-sheets@test-dojo-sheets.iam.gserviceaccount.com', - # needs to match ID in the cassette - 'drive_folder_ID': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', - 'enable_service': 'on', - 'cred_file': f - }) - # force use of specific submit button - data.update({ - 'update': 'Submit' - }) - - response = self.client.post(reverse('configure_google_sheets'), data, follow=True) - self.assertContains(response, "successfully") - self.assert_cassette_played() diff --git a/unittests/test_system_settings.py b/unittests/test_system_settings.py index 59cdf657ee1..d643831fde8 100644 --- a/unittests/test_system_settings.py +++ b/unittests/test_system_settings.py @@ -25,8 +25,3 @@ def test_system_settings_update(self): system_settings.save() system_settings = System_Settings.objects.get(no_cache=True) self.assertEqual(system_settings.enable_jira, True) - - system_settings.enable_google_sheets = True - system_settings.save() - system_settings = System_Settings.objects.get(no_cache=True) - self.assertEqual(system_settings.enable_google_sheets, True)