# -*- coding: utf-8 -*- # Copyright 2017 Lockheed Martin Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import json import logging import time from calendar import timegm from httplib import BAD_REQUEST, NOT_ACCEPTABLE from itertools import chain from django.contrib import messages from django.contrib.auth import get_user_model from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.core.signing import Signer from django.core.urlresolvers import reverse, reverse_lazy, resolve from django.http import HttpResponse from django.http.response import JsonResponse from django.shortcuts import redirect from django.utils.html import conditional_escape from django.utils.timezone import now from django.views.decorators.http import require_http_methods from django.views.generic import TemplateView, ListView, CreateView, UpdateView, View, DeleteView from .extras.helpers.analytics import MissionAnalytics from .extras.helpers.sorters import TestSortingHelper from .extras.utils import ReturnStatus, generate_report_or_attachments from .models import Mission, TestDetail, SupportingData, DARTDynamicSettings, Host, BusinessArea, \ ClassificationLegend, Color logger = logging.getLogger(__name__) class UpdateDynamicSettingsView(UpdateView): success_url = reverse_lazy('missions-list') template_name = 'update_dynamic_settings.html' fields = [ 'system_classification', 'host_output_format' ] def get_object(self): return DARTDynamicSettings.objects.get_as_object() def post(self, request, *args, **kwargs): # Delete cached data for the legends legend_top_key = make_template_fragment_key('legend_partial_top') cache.delete(legend_top_key) legend_bottom_key = make_template_fragment_key('legend_partial_bottom') cache.delete(legend_bottom_key) # Delete cached data for the host format string cache.delete('host_output_format_string') return super(UpdateDynamicSettingsView, self).post(request, *args, **kwargs) class ListMissionView(ListView): model = Mission template_name = 'mission_list.html' def get_context_data(self, **kwargs): logger.debug('GET: ListMissionView') context = super(ListMissionView, self).get_context_data(**kwargs) missions = Mission.objects.all() paginator = Paginator(missions, 10) page = self.request.GET.get('page') try: show_missions = paginator.page(page) except PageNotAnInteger: # If page is not an integer, deliver first page. show_missions = paginator.page(1) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. show_missions = paginator.page(paginator.num_pages) context['missions'] = show_missions return context class CreateMissionView(CreateView): model = Mission template_name = 'edit_mission.html' fields = [ 'mission_name', 'mission_number', 'test_case_identifier', 'business_area', 'introduction', 'scope', 'objectives', 'executive_summary', 'technical_assessment_overview', 'conclusion', 'attack_phase_include_flag', 'attack_type_include_flag', 'assumptions_include_flag', 'test_description_include_flag', 'findings_include_flag', 'mitigation_include_flag', 'tools_used_include_flag', 'command_syntax_include_flag', 'targets_include_flag', 'sources_include_flag', 'attack_time_date_include_flag', 'attack_side_effects_include_flag', 'test_result_observation_include_flag', 'supporting_data_include_flag', 'customer_notes_include_flag', ] def get_success_url(self): logger.debug('Created mission {mission_id}'.format(mission_id=self.object.id)) return reverse('missions-list') def get_context_data(self, **kwargs): logger.debug('GET: CreateMissionView') context = super(CreateMissionView, self).get_context_data(**kwargs) context['action'] = reverse('missions-new') return context class EditMissionView(UpdateView): model = Mission template_name = 'edit_mission.html' fields = [ 'mission_name', 'mission_number', 'test_case_identifier', 'business_area', 'introduction', 'scope', 'objectives', 'executive_summary', 'technical_assessment_overview', 'conclusion', 'attack_phase_include_flag', 'attack_type_include_flag', 'assumptions_include_flag', 'test_description_include_flag', 'findings_include_flag', 'mitigation_include_flag', 'tools_used_include_flag', 'command_syntax_include_flag', 'targets_include_flag', 'sources_include_flag', 'attack_time_date_include_flag', 'attack_side_effects_include_flag', 'test_result_observation_include_flag', 'supporting_data_include_flag', 'customer_notes_include_flag', ] def get_success_url(self): logger.debug('POST: EditMissionView (Saved {mission_id})'.format(mission_id=self.object.id)) return reverse('missions-list') def get_context_data(self, **kwargs): logger.debug('GET: EditMissionView (Edit {mission_id})'.format(mission_id=self.get_object().id)) context = super(EditMissionView, self).get_context_data(**kwargs) context['action'] = reverse('missions-edit', kwargs={'pk': self.get_object().id}) return context class DeleteMissionView(DeleteView): model = Mission template_name = 'delete_mission.html' def get_context_data(self, **kwargs): logger.debug('GET: DeleteMissionView (Confirm delete {mission_id})' .format(mission_id=self.object.id)) return super(DeleteMissionView, self).get_context_data(**kwargs) def get_success_url(self): logger.debug('POST: DeleteMisisonView (Deleted {mission_id})'.format(mission_id=self.object.id)) return reverse('missions-list') class ReportMissionView(View): def get(self, request, *args, **kwargs): mission_id = kwargs.get('mission') logger.debug('GET: ReportMissionView ({mission_id})'.format(mission_id=mission_id)) io_stream = generate_report_or_attachments(mission_id, zip_attachments=False) response = HttpResponse(io_stream.getvalue(), content_type='text/plain') response['Content-Disposition'] = 'attachment; filename={}_mission_report.docx'.format(mission_id) #response['Content-Disposition'] = 'attachment; filename={}_mission_report.zip'.format(mission_id) return response class ReportAttachmentsMissionView(View): def get(self, request, *args, **kwargs): mission_id = kwargs.get('mission') logger.debug('GET: ReportAttachmentsMissionView ({mission_id})'.format(mission_id=mission_id)) io_stream = generate_report_or_attachments(mission_id, zip_attachments=True) response = HttpResponse(io_stream.getvalue(), content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename={}_supporting_data.zip'.format(mission_id) return response class MissionStatsView(TemplateView): template_name = 'mission_stats_partial.html' def get_context_data(self, **kwargs): context = super(MissionStatsView, self).get_context_data(**kwargs) context['analytics'] = MissionAnalytics(self.kwargs['mission']) return context class ListMissionTestsView(ListView): model = TestDetail template_name = 'mission_list_tests.html' def get_queryset(self): return TestSortingHelper.get_ordered_testdetails(self.kwargs['mission']) def get_context_data(self, **kwargs): context = super(ListMissionTestsView, self).get_context_data(**kwargs) tests = self.get_queryset() context['tests'] = tests context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['server_timestamp'] = Signer().sign(time.time()) return context class OrderMissionTestsView(View): def post(self, request, *args, **kwargs): """ Accepts a posted JSON string to specify new test case sort order """ mission_id = self.kwargs['mission'] new_order = [] # Ensure the received value is just ints try: data = json.loads(request.body) for i in data['order']: new_order.append(int(i)) except ValueError: logger.exception('POST: OrderMissionTestsView received non-ints') raise # Maybe do something else here one day # A user with a stale Tests list may override a newer sort order inadvertently # TODO: Add confirmation check if data['server_timestamp'] is older than the last update of the db field try: tests = TestDetail.objects.filter(mission=self.kwargs['mission']) except TestDetail.DoesNotExist: tests = () # Update mission record with the new order mission = Mission.objects.get(pk=mission_id) current_order = json.loads(mission.testdetail_sort_order) # Check to see if new test cases have been created since the page was rendered to this user # if so, just append the missing test cases to the tail end so they don't disappear if len(new_order) < len(current_order): for i in current_order: if i not in new_order: new_order.append(i) mission.testdetail_sort_order = json.dumps(new_order) mission.save(update_fields=['testdetail_sort_order']) rs = ReturnStatus(message="Order Updated") return HttpResponse(rs.to_json()) class CloneMissionTestView(View): def get(self, request, *args, **kwargs): """ Makes a clone within the current mission of a specified test case """ # Verify the test case passed is an int and within the path's mission id_to_clone = int(self.kwargs['pk']) passed_mission_id = int(self.kwargs['mission']) try: test_case = TestDetail.objects.get(pk=id_to_clone) except TestDetail.DoesNotExist: return HttpResponse("Test case not found.", status=404) if test_case.mission.id != passed_mission_id: return HttpResponse("Test case not linked to specified mission.", status=400) test_case.pk = None test_case.test_case_status = 'NEW' test_case.save() return HttpResponse(reverse_lazy('mission-test-edit', kwargs={'mission': test_case.mission.id, 'pk': test_case.pk})) class DeleteMissionTestView(DeleteView): model = TestDetail template_name = 'delete_test.html' def get_context_data(self, **kwargs): context = super(DeleteMissionTestView, self).get_context_data(**kwargs) context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['test_id'] = self.kwargs['pk'] return context def get_success_url(self): return reverse('mission-tests', kwargs={'mission': self.kwargs['mission']}) class CreateMissionTestView(CreateView): model = TestDetail template_name = 'edit_mission_test.html' fields = [ 'test_case_include_flag', 'test_case_status', 'point_of_contact', 'enclave', 'test_objective', 'attack_phase', 'attack_phase_include_flag', 'attack_type', 'attack_type_include_flag', 'assumptions', 'assumptions_include_flag', 're_eval_test_case_number', 'test_description', 'test_description_include_flag', 'sources_include_flag', 'targets_include_flag', 'tools_used', 'tools_used_include_flag', 'command_syntax', 'command_syntax_include_flag', 'attack_time_date', 'attack_time_date_include_flag', 'test_result_observation', 'test_result_observation_include_flag', 'execution_status', 'attack_side_effects', 'attack_side_effects_include_flag', 'findings', 'findings_include_flag', 'mitigation', 'mitigation_include_flag', ] def get_success_url(self): return reverse('mission-tests', kwargs={'mission': self.kwargs['mission']}) def get_context_data(self, **kwargs): context = super(CreateMissionTestView, self).get_context_data(**kwargs) context['action'] = reverse('mission-test-new', kwargs={'mission': self.kwargs['mission']}) context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['display_navbar_save_button'] = True return context def form_valid(self, form): form.instance.mission_id = self.kwargs['mission'] return super(CreateMissionTestView, self).form_valid(form) class EditMissionTestView(UpdateView): model = TestDetail template_name = 'edit_mission_test.html' fields = [ 'test_case_include_flag', 'test_case_status', 'point_of_contact', 'enclave', 'test_objective', 'attack_phase', 'attack_phase_include_flag', 'attack_type', 'attack_type_include_flag', 'assumptions', 'assumptions_include_flag', 're_eval_test_case_number', 'test_description', 'test_description_include_flag', 'sources_include_flag', 'targets_include_flag', 'tools_used', 'tools_used_include_flag', 'command_syntax', 'command_syntax_include_flag', 'attack_time_date', 'attack_time_date_include_flag', 'test_result_observation', 'test_result_observation_include_flag', 'execution_status', 'attack_side_effects', 'attack_side_effects_include_flag', 'findings', 'findings_include_flag', 'mitigation', 'mitigation_include_flag', ] def get_success_url(self): return reverse('mission-tests', kwargs={'mission': self.kwargs['mission']}) def get_context_data(self, **kwargs): context = super(EditMissionTestView, self).get_context_data(**kwargs) context['action'] = reverse('mission-test-edit', kwargs={'pk': self.get_object().id, 'mission': self.kwargs['mission']}) mission_model = Mission.objects.get(id=self.kwargs['mission']) context['this_mission'] = mission_model context['display_navbar_save_button'] = True context['is_read_only'] = resolve(self.request.path_info).url_name == 'mission-test-view' if self.request.GET.get('scrollPos'): try: context['scrollPos'] = int(self.request.GET.get('scrollPos')) except ValueError: logger.exception('URL Parameter scrollPos invalid (not an int); setting to None. ' '(Logged in user: {user})'.format(user=self.request.user.username or "**Anonymous**")) context['scrollPos'] = None return context class ListMissionTestsSupportingDataView(ListView): model = TestDetail template_name = 'test_supporting_data_list.html' def get_queryset(self): return TestSortingHelper.get_ordered_supporting_data(self.kwargs['test_detail']) def get_context_data(self, **kwargs): context = super(ListMissionTestsSupportingDataView, self).get_context_data(**kwargs) testdata = self.get_queryset() paginator = Paginator(testdata, 10) page = self.request.GET.get('page') try: show_data = paginator.page(page) except PageNotAnInteger: # If page is not an integer, deliver first page. show_data = paginator.page(1) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. show_data = paginator.page(paginator.num_pages) context['show_data'] = show_data context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['this_test'] = TestDetail.objects.get(id=self.kwargs['test_detail']) return context class CreateMissionTestsSupportingDataView(CreateView): model = SupportingData template_name = 'edit_test_data.html' fields = ['caption', 'test_file', 'include_flag'] def get_success_url(self): return reverse('test-data-list', kwargs={'mission': self.kwargs['mission'], 'test_detail': self.kwargs['test_detail']}) def get_context_data(self, **kwargs): context = super(CreateMissionTestsSupportingDataView, self).get_context_data(**kwargs) context['action'] = reverse('test-data-new', kwargs={'mission': self.kwargs['mission'], 'test_detail': self.kwargs['test_detail']}) context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['this_test'] = TestDetail.objects.get(id=self.kwargs['test_detail']) return context def form_valid(self, form): form.instance.test_detail_id = self.kwargs['test_detail'] return super(CreateMissionTestsSupportingDataView, self).form_valid(form) class EditMissionTestsSupportingDataView(UpdateView): model = SupportingData template_name = 'edit_test_data.html' fields = ['caption', 'test_file', 'include_flag'] def get_success_url(self): return reverse('test-data-list', kwargs={'mission': self.kwargs['mission'], 'test_detail': self.kwargs['test_detail']}) def get_context_data(self, **kwargs): context = super(EditMissionTestsSupportingDataView, self).get_context_data(**kwargs) context['action'] = reverse('test-data-edit', kwargs={'pk': self.get_object().id, 'mission': self.kwargs['mission'], 'test_detail': self.kwargs['test_detail']}) context['this_mission'] = Mission.objects.get(id=self.kwargs['mission']) context['this_test'] = TestDetail.objects.get(id=self.kwargs['test_detail']) return context class DeleteMissionTestsSupportingDataView(DeleteView): model = SupportingData template_name = 'delete_test_data.html' def get_context_data(self, **kwargs): context = super(DeleteMissionTestsSupportingDataView, self).get_context_data(**kwargs) context['mission_id'] = self.kwargs['mission'] context['test_id'] = self.kwargs['test_detail'] return context def get_success_url(self): return reverse('test-data-list', kwargs={'mission': self.kwargs['mission'], 'test_detail': self.kwargs['test_detail']}) class OrderMissionTestsSupportingDataView(View): def post(self, request, *args, **kwargs): """ Accepts a posted JSON string to specify new test case sort order """ mission_id = self.kwargs['mission'] test_detail_id = self.kwargs['test_detail'] new_order = [] # Ensure the received value is just ints try: data = json.loads(request.body) for i in data['order']: new_order.append(int(i)) except ValueError: logger.exception('POST: OrderMissionTestsSupportingDataView received non-ints') raise # Update testdetail record with the new order testdetail = TestDetail.objects.get(pk=test_detail_id) current_order = json.loads(testdetail.supporting_data_sort_order) # Check to see if new supporting data has been have been created since the page was rendered to this user # if so, just append the missing data to the tail end so they don't disappear if len(new_order) < len(current_order): for i in current_order: if i not in new_order: new_order.append(i) testdetail.supporting_data_sort_order = json.dumps(new_order) testdetail.save(update_fields=['supporting_data_sort_order']) rs = ReturnStatus(message="Supporting Data Order Updated") return HttpResponse(rs.to_json()) class DownloadSupportingDataView(View): model = SupportingData def get(self, request, *args, **kwargs): supporting_data_object = SupportingData.objects.get(id=self.kwargs['supportingdata']) # Backwards compatibility shim for #106 fix if str(supporting_data_object.test_file.name).startswith('supporting_data/'): supporting_data_object.test_file.name = supporting_data_object.test_file.name[16:] supporting_data_object.save() filename = supporting_data_object.filename() response = HttpResponse(supporting_data_object.test_file.file, content_type='text/plain') response['Content-Disposition'] = 'attachment; filename=%s' % filename return response class LoginInterstitialView(TemplateView): template_name = 'login_interstitial.html' def post(self, request): request.session['last_acknowledged_interstitial'] = timegm(now().timetuple()) request.session.save() logger.info('User ({user}) acknowledged and accepted the login interstitial'.format( user=request.user.username )) return redirect('missions-list') class CreateAccountView(TemplateView): template_name = 'create_account.html' @staticmethod def any_accounts_configured(): ''' Determines if there are any accounts currently configured. :return: Boolean ''' User = get_user_model() return User.objects.all().exists() def get_context_data(self, **kwargs): context = super(CreateAccountView, self).get_context_data(**kwargs) context['any_accounts_configured'] = self.any_accounts_configured() return context def post(self, request): # This view can only process posts if there are *no* configured accounts whatsoever if self.any_accounts_configured(): return redirect('logout') else: User = get_user_model() username = request.POST['username'] password = request.POST['password'] User.objects.create_superuser(username, username + '@dart.local', password) return redirect('login') class EditMissionHostsView(TemplateView): template_name = 'edit_mission_hosts.html' def get_context_data(self, **kwargs): context = super(EditMissionHostsView, self).get_context_data(**kwargs) context['mission'] = Mission.objects.prefetch_related('host_set').get(pk=int(kwargs.get('mission_id'))) return context @require_http_methods(["GET", "POST", "DELETE"]) def mission_host_handler(request, host_id): if request.method == "GET": try: host = Host.objects.get(pk=int(host_id)) data = { 'id': host.id, 'is_no_hit': host.is_no_hit, 'mission': host.mission.pk, 'host_name': host.host_name, 'display': conditional_escape(host), 'ip_address': conditional_escape(host.ip_address), } status = ReturnStatus( message='OK', data=data, ) return JsonResponse(status.to_dict()) except Host.DoesNotExist: status = ReturnStatus( success=False, message='Unable to locate mission.', ) return JsonResponse(status.to_dict()) if request.method == "POST": try: if len(request.body) > 0: data = json.loads(request.body) logger.debug('Host update POST data successfully read as json.') try: host = Host.objects.get(pk=int(host_id)) logger.debug('Host update POST pk value is valid.') except Host.DoesNotExist: logger.debug('Host update POST pk value does not correspond to a host in the host_set.') logger.debug('Host update POST is creating a new host.') host = Host() if host.pk is None: # Hosts shouldn't be jumping between missions, so only allow mission assignment upon creation if 'mission_id' in data.keys(): logger.debug('Host update POST contains a mission pk value.') try: host.mission = Mission.objects.get(pk=int(data['mission_id'])) except Mission.DoesNotExist: error_message = 'Unable to find mission.' logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) else: error_message = 'Host requires an associated mission (not provided).' logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) try: host.host_name = data['host_name'] host.ip_address = data['ip_address'] host.is_no_hit = data['is_no_hit'] host.save() logger.debug('Host update POST saved host.') return JsonResponse(ReturnStatus(message='OK', data={'pk': host.pk}).to_dict()) except KeyError: error_message = 'Required field(s) missing from host update / creation.' logger.debug(error_message) return JsonResponse(ReturnStatus(success=False, message=error_message).to_dict()) except ValueError as e: # Probably a json decode error or a non-int value for a pk error_message = 'Unable to process request.' logger.exception(exc_info=e) return JsonResponse(ReturnStatus(success=False, message=error_message).to_dict()) if request.method == "DELETE": try: host = Host.objects.get(pk=int(host_id)) target_set = host.target_set.all() source_set = host.source_set.all() combined_set = list(chain(target_set, source_set)) if len(combined_set) > 0: test_ids = set() for test in combined_set: test_ids.add(str(test.pk)) error_message = "Host {host} is currently listed as a source or target on one or more test cases. " \ "Please remove the host from the following test cases: {ids}".format( host=host.pk, ids=','.join(test_ids), ) logger.debug(error_message) status = ReturnStatus(False, error_message) return JsonResponse(status.to_dict(), status=NOT_ACCEPTABLE) else: host.delete() return JsonResponse(ReturnStatus(message='OK', data={"pk": host_id}).to_dict()) except Host.DoesNotExist: error_message = 'Tried to delete {}, but there is no such host.'.format( host_id ) logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict(), status=BAD_REQUEST) except ValueError: error_message = 'Tried to delete {}, but there was a problem.'.format( host_id ) logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) @require_http_methods(["GET", "POST", "DELETE"]) def test_host_handler(request, mission_id, test_id): if request.method == 'GET': try: testcase = TestDetail.objects.prefetch_related('target_hosts').prefetch_related('source_hosts').get(pk=test_id) except TestDetail.DoesNotExist: error_message = 'test_host_handler GET Test does not exist' logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) data = [] for host in testcase.target_hosts.all(): data.append( { 'id': host.id, 'is_no_hit': host.is_no_hit, 'mission': host.mission.pk, 'host_name': conditional_escape(host.host_name), 'role': 'target', 'display': conditional_escape(host) } ) for host in testcase.source_hosts.all(): data.append( { 'id': host.id, 'is_no_hit': host.is_no_hit, 'mission': host.mission.pk, 'host_name': conditional_escape(host.host_name), 'role': 'source', 'display': conditional_escape(host), } ) unassigned_hosts = Host.objects.filter(mission=testcase.mission) for host in unassigned_hosts.all(): data.append( { 'id': host.id, 'is_no_hit': host.is_no_hit, 'mission': host.mission.pk, 'host_name': conditional_escape(host.host_name), 'role': '', 'display': conditional_escape(host), } ) status = ReturnStatus() status.data = data return JsonResponse(status.to_dict()) if request.method == 'POST': try: if len(request.body) > 0: data = json.loads(request.body) logger.debug('test_host_handler POST data successfully read as json.') try: host_id = data['host_id'] role = data['role'] except KeyError: error_message = 'Missing required key' logger.debug('test_host_handler POST ' + error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) try: host = Host.objects.get(pk=host_id) except Host.DoesNotExist: error_message = 'No such host' logger.debug('test_host_handler POST ' + error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) try: testcase = TestDetail.objects.get(pk=test_id) except TestDetail.DoesNotExist: error_message = 'No such test case' logger.debug('test_host_handler POST ' + error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) if host.mission != testcase.mission: error_message = 'Host not in mission scope' logger.debug('test_host_handler POST ' + error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) if host.is_no_hit: error_message = 'Host is on the no hit list' logger.debug('test_host_handler POST ' + error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) if role == 'target': testcase.target_hosts.add(host) elif role == 'source': testcase.source_hosts.add(host) logger.debug('test_host_handler POST added host {} to testcase {}'.format( host.pk, testcase.pk, )) return JsonResponse(ReturnStatus(True, "OK").to_dict()) except ValueError: logger.debug('test_host_handler POST encountered an error decoding the JSON input') return JsonResponse(ReturnStatus(False, 'Invalid json received').to_dict()) if request.method == 'DELETE': try: if len(request.body) > 0: data = json.loads(request.body) logger.debug('test_host_handler DELETE data successfully read as json.') try: host = Host.objects.get(pk=int(data['host_id'])) logger.debug('test_host_handler DELETE host pk value exists.') except Host.DoesNotExist: error_message = 'test_host_handler DELETE host pk value does not exist.'.format( data['host_id'] ) logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) try: testcase = TestDetail.objects.get(pk=test_id) logger.debug('test_host_handler DELETE test case pk value exists.') except Host.DoesNotExist: error_message = 'test_host_handler DELETE test case pk value does not exist.'.format( data['host_id'] ) logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) if data['role'] == 'target': logger.debug('test_host_handler deleting host from targets') testcase.target_hosts.remove(host) logger.debug('test_host_handler deleting host from targets') return JsonResponse(ReturnStatus(True, 'OK').to_dict()) elif data['role'] == 'source': logger.debug('test_host_handler deleting host from sources') testcase.source_hosts.remove(host) logger.debug('test_host_handler deleting host from sources') return JsonResponse(ReturnStatus(True, 'OK').to_dict()) else: error_message = 'test_host_handler DELETE role unknown.' logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) except KeyError: error_message = 'test_host_handler DELETE missing required element(s).' logger.debug(error_message) return JsonResponse(ReturnStatus(False, error_message).to_dict()) class BusinessAreaListView(ListView): model = BusinessArea template_name = 'business_area_list.html' @require_http_methods(["POST", "DELETE"]) def business_area_handler(request, pk): if request.method == 'POST': data = json.loads(request.body) if pk is None: # New business area BusinessArea.objects.create(name=data['name']) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) else: # existing business area update try: ba = BusinessArea.objects.get(pk=pk) ba.name = data['name'] ba.save() except BusinessArea.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) elif request.method == 'DELETE': try: ba = BusinessArea.objects.get(pk=pk) if ba.mission_set.all().count() != 0: return JsonResponse(ReturnStatus(False, 'Business Areas can not be deleted while missions are still associated with them.').to_dict()) ba.delete() except BusinessArea.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) class ClassificationListView(ListView): model = ClassificationLegend template_name = 'classification_list.html' def get_context_data(self, **kwargs): context = super(ClassificationListView, self).get_context_data() context["available_colors"] = Color.objects.all() return context @require_http_methods(["POST", "DELETE"]) def classification_handler(request, pk): if request.method == 'POST': data = json.loads(request.body) if pk is None: # New classification try: ClassificationLegend.objects.create( verbose_legend=data['verbose_legend'], short_legend=data['short_legend'], text_color=Color.objects.get(pk=data['text_color']), background_color=Color.objects.get(pk=data['background_color']), report_label_color_selection=data['report_label_color_selection'], ) except Color.DoesNotExist: return JsonResponse(ReturnStatus(False, 'One or more of the selected colors does not exist.').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) else: # existing classification update try: classification = ClassificationLegend.objects.get(pk=pk) classification.verbose_legend = data['verbose_legend'] classification.short_legend = data['short_legend'] classification.text_color = Color.objects.get(pk=data['text_color']) classification.background_color = Color.objects.get(pk=data['background_color']) classification.report_label_color_selection = data['report_label_color_selection'] classification.save() # Ensure the legend cache is cleared legend_top_key = make_template_fragment_key('legend_partial_top') cache.delete(legend_top_key) legend_bottom_key = make_template_fragment_key('legend_partial_bottom') cache.delete(legend_bottom_key) except ClassificationLegend.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) elif request.method == 'DELETE': try: classification = ClassificationLegend.objects.get(pk=pk) if classification == DARTDynamicSettings.objects.get_as_object().system_classification: return JsonResponse(ReturnStatus(False, 'Can not delete the classification the system is currently operating at. Change system classification and try again.').to_dict()) classification.delete() except ClassificationLegend.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) class ColorListView(ListView): model = Color template_name = 'color_list.html' @require_http_methods(["POST", "DELETE"]) def color_handler(request, pk): if request.method == 'POST': data = json.loads(request.body) if pk is None: # New color try: Color.objects.create( display_text=data['display_text'], hex_color_code=data['hex_color_code'], ) except Color.DoesNotExist: return JsonResponse(ReturnStatus(False, 'One or more of the selected colors does not exist.').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) else: # existing color update try: color = Color.objects.get(pk=pk) color.display_text = data['display_text'] color.hex_color_code = data['hex_color_code'] color.save() # Ensure the legend cache is cleared just in case this is a color update # to the active class label legend_top_key = make_template_fragment_key('legend_partial_top') cache.delete(legend_top_key) legend_bottom_key = make_template_fragment_key('legend_partial_bottom') cache.delete(legend_bottom_key) except Color.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) elif request.method == 'DELETE': try: color = Color.objects.get(pk=pk) active_text_color = DARTDynamicSettings.objects.get_as_object().system_classification.text_color active_background_color = DARTDynamicSettings.objects.get_as_object().system_classification.background_color if color == active_text_color or color == active_background_color: return JsonResponse(ReturnStatus(False, 'Can not delete a color in use by the classification legend the system is currently operating at. Change system classification or color options and try again.').to_dict()) color.delete() except Color.DoesNotExist: return JsonResponse(ReturnStatus(False, 'Key does not exist').to_dict()) return JsonResponse(ReturnStatus(True, 'OK').to_dict()) class AboutTemplateView(TemplateView): template_name = "about.html"