def test_import_with_push_to_jira_add_tags(self):
        import0 = self.import_scan_with_params(self.zap_sample5_filename,
                                               push_to_jira=True)
        test_id = import0['test']
        self.assert_jira_issue_count_in_test(test_id, 2)
        self.assert_jira_group_issue_count_in_test(test_id, 0)

        findings = self.get_test_findings_api(test_id)

        finding = Finding.objects.get(id=findings['results'][0]['id'])

        tags = ['tag1', 'tag2']
        response = self.post_finding_tags_api(finding.id, tags)
        self.patch_finding_api(finding.id, {"push_to_jira": True})

        # Connect to jira to get the new issue
        jira_instance = jira_helper.get_jira_instance(finding)
        jira = jira_helper.get_jira_connection(jira_instance)
        issue = jira.issue(finding.jira_issue.jira_id)

        # Assert that the tags match
        self.assertEqual(issue.fields.labels, tags)

        # by asserting full cassette is played we know all calls to JIRA have been made as expected
        self.assert_cassette_played()
        return test_id
Esempio n. 2
0
 def get_epic_issues(self, engagement):
     instance = jira_helper.get_jira_instance(engagement)
     jira = jira_helper.get_jira_connection(instance)
     epic_id = jira_helper.get_jira_issue_key(engagement)
     response = {}
     if epic_id:
         url = instance.url.strip('/') + '/rest/agile/1.0/epic/' + epic_id + '/issue'
         response = jira._session.get(url).json()
     return response.get('issues', [])
Esempio n. 3
0
 def assert_jira_issue_in_epic(self, finding, engagement, issue_in_epic=True):
     instance = jira_helper.get_jira_instance(engagement)
     jira = jira_helper.get_jira_connection(instance)
     epic_id = jira_helper.get_jira_issue_key(engagement)
     issue_id = jira_helper.get_jira_issue_key(finding)
     epic_link_field = 'customfield_' + str(get_custom_field(jira, 'Epic Link'))
     url = instance.url.strip('/') + '/rest/api/latest/issue/' + issue_id
     response = jira._session.get(url).json().get('fields', {})
     epic_link = response.get(epic_link_field, None)
     if epic_id is None and epic_link is None or issue_in_epic:
         self.assertTrue(epic_id == epic_link)
     else:
         self.assertTrue(epic_id != epic_link)
def jira_status_reconciliation(*args, **kwargs):
    mode = kwargs['mode']
    product = kwargs['product']
    engagement = kwargs['engagement']
    daysback = kwargs['daysback']
    dryrun = kwargs['dryrun']

    logger.debug('mode: %s product:%s engagement: %s dryrun: %s', mode, product, engagement, dryrun)

    if mode and mode not in ('push_status_to_jira', 'import_status_from_jira', 'reconcile'):
        print('mode must be one of reconcile, push_status_to_jira or import_status_from_jira')
        return False

    if not mode:
        mode = 'reconcile'

    findings = Finding.objects.all()
    if product:
        product = Product.objects.filter(name=product).first()
        findings = findings.filter(test__engagement__product=product)

    if engagement:
        engagement = Engagement.objects.filter(name=engagement).first()
        findings = findings.filter(test__engagement=engagement)

    if daysback:
        timestamp = timezone.now() - relativedelta(days=int(daysback))
        findings = findings.filter(created__gte=timestamp)

    findings = findings.exclude(jira_issue__isnull=True)

    # order by product, engagement to increase the cance of being able to reuse jira_instance + jira connection
    findings = findings.order_by('test__engagement__product__id', 'test__engagement__id')

    findings = findings.prefetch_related('jira_issue__jira_project__jira_instance')
    findings = findings.prefetch_related('test__engagement__jira_project__jira_instance')
    findings = findings.prefetch_related('test__engagement__product__jira_project_set__jira_instance')

    logger.debug(findings.query)

    messages = ['jira_key;finding_url;resolution_or_status;find.jira_issue.jira_change;issue_from_jira.fields.updated;find.last_status_update;issue_from_jira.fields.updated;find.last_reviewed;issue_from_jira.fields.updated;flag1;flag2;flag3;action;change_made']
    for find in findings:
        logger.debug('jira status reconciliation for: %i:%s', find.id, find)

        issue_from_jira = jira_helper.get_jira_issue_from_jira(find)

        if not issue_from_jira:
            message = '%s;%s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;unable to retrieve JIRA Issue;%s' % \
                (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), None, None, None, None,
                            find.jira_issue.jira_change, None, find.last_status_update, None, find.last_reviewed, None, 'error')
            messages.append(message)
            logger.info(message)
            continue

        assignee = issue_from_jira.fields.assignee if hasattr(issue_from_jira.fields, 'assignee') else None
        assignee_name = assignee.displayName if assignee else None
        resolution = issue_from_jira.fields.resolution if issue_from_jira.fields.resolution and issue_from_jira.fields.resolution != "None" else None
        resolution_id = resolution.id if resolution else None
        resolution_name = resolution.name if resolution else None

        # convert from str to datetime
        issue_from_jira.fields.updated = parse_datetime(issue_from_jira.fields.updated)

        find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated,

        flag1, flag2, flag3 = None, None, None

        if mode == 'reconcile' and not find.last_status_update:
            message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;skipping finding with no last_status_update;%s' % \
                (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), None, None, None, None,
                find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, 'skipped')
            messages.append(message)
            logger.info(message)
            continue
        elif find.risk_accepted:
            message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%sskipping risk accepted findings;%s' % \
                (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, None, None, None,
                find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, 'skipped')
            messages.append(message)
            logger.info(message)
        elif jira_helper.issue_from_jira_is_active(issue_from_jira) and find.active:
            message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;no action both sides are active/open;%s' % \
                (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, None, None, None,
                    find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, 'equal')
            messages.append(message)
            logger.info(message)
        elif not jira_helper.issue_from_jira_is_active(issue_from_jira) and not find.active:
            message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;no action both sides are inactive/closed;%s' % \
                (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, None, None, None,
                find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, 'equal')
            messages.append(message)
            logger.info(message)

        else:
            # statuses are different
            if mode in ('push_status_to_jira', 'import_status_from_jira'):
                action = mode
            else:
                # reconcile
                # Status is JIRA is newer if:
                # dojo.jira_change < jira.updated, and
                # dojo.last_status_update < jira.updated, and
                # dojo.last_reviewed < jira.update,

                flag1 = (not find.jira_issue.jira_change or (find.jira_issue.jira_change < issue_from_jira.fields.updated))
                flag2 = not find.last_status_update or (find.last_status_update < issue_from_jira.fields.updated)
                flag3 = (not find.last_reviewed or (find.last_reviewed < issue_from_jira.fields.updated))

                logger.debug('%s,%s,%s,%s', resolution_name, flag1, flag2, flag3)

                if flag1 and flag2 and flag3:
                    action = 'import_status_from_jira'

                else:
                    # Status is DOJO is newer if:
                    # dojo.jira_change > jira.updated or # can't happen
                    # dojo.last_status_update > jira.updated or
                    # dojo.last_reviewed > jira.updated
                    # dojo.mitigated > dojo.jira_change

                    flag1 = not find.jira_issue.jira_change or (find.jira_issue.jira_change > issue_from_jira.fields.updated)
                    flag2 = find.last_status_update > issue_from_jira.fields.updated
                    flag3 = find.is_Mitigated and find.mitigated and find.jira_issue.jira_change and find.mitigated > find.jira_issue.jira_change

                    logger.debug('%s,%s,%s,%s', resolution_name, flag1, flag2, flag3)

                    if flag1 or flag2 or flag3:
                        action = 'push_status_to_jira'

            prev_jira_instance, jira = None, None

            if action == 'import_status_from_jira':
                message_action = 'deactivating' if find.active else 'reactivating'

                status_changed = jira_helper.process_resolution_from_jira(find, resolution_id, resolution_name, assignee_name, issue_from_jira.fields.updated) if not dryrun else 'dryrun'
                if status_changed:
                    message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s finding in defectdojo;%s' % \
                        (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, flag1, flag2, flag3,
                        find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, message_action, status_changed)
                    messages.append(message)
                    logger.info(message)
                else:
                    message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;no changes made from jira resolution;%s' % \
                        (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, flag1, flag2, flag3,
                        find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, status_changed)
                    messages.append(message)
                    logger.info(message)

            elif action == 'push_status_to_jira':
                jira_instance = jira_helper.get_jira_instance(find)
                if not prev_jira_instance or (jira_instance.id != prev_jira_instance.id):
                    # only reconnect to jira if the instance if different from the previous finding
                    jira = jira_helper.get_jira_connection(jira_instance)

                message_action = 'reopening' if find.active else 'closing'

                status_changed = jira_helper.push_status_to_jira(find, jira_instance, jira, issue_from_jira, save=True) if not dryrun else 'dryrun'

                if status_changed:
                    message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s jira issue;%s;' % \
                        (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, flag1, flag2, flag3, message_action,
                        find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, status_changed)
                    messages.append(message)
                    logger.info(message)
                else:
                    if status_changed is None:
                        status_changed = 'Error'
                    message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;no changes made while pushing status to jira;%s' % \
                        (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, flag1, flag2, flag3,
                        find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, status_changed)
                    messages.append(message)

                    logger.info(message)
            else:
                message = '%s; %s/finding/%d;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;unable to determine source of truth;%s' % \
                    (find.jira_issue.jira_key, settings.SITE_URL, find.id, find.status(), resolution_name, flag1, flag2, flag3,
                    find.jira_issue.jira_change, issue_from_jira.fields.updated, find.last_status_update, issue_from_jira.fields.updated, find.last_reviewed, issue_from_jira.fields.updated, status_changed)
                messages.append(message)

                logger.info(message)

    logger.info('results (semicolon seperated)')
    for message in messages:
        print(message)