def reject_draft(draft_id: str, reviewer_email: str) -> bool:
    draft_data = get_finding(draft_id)
    history = cast(List[Dict[str, str]], draft_data.get('historicState', [{}]))
    status = history[-1].get('state')
    success = False

    if 'releaseDate' not in draft_data:
        if status == 'SUBMITTED':
            tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
            today = datetime.now(tz=tzn).today()
            rejection_date = str(today.strftime('%Y-%m-%d %H:%M:%S'))
            history.append({
                'date': rejection_date,
                'analyst': reviewer_email,
                'state': 'REJECTED'
            })

            success = finding_dal.update(draft_id, {
                'release_date': None,
                'historic_state': history
            })
            if success:
                finding_utils.send_draft_reject_mail(
                    draft_id, str(draft_data.get('projectName', '')),
                    str(draft_data.get('analyst', '')),
                    str(draft_data.get('finding', '')), reviewer_email)
        else:
            raise NotSubmitted()
    else:
        raise AlreadyApproved()

    return success
def update_description(finding_id: str,
                       updated_values: Dict[str, FindingType]) -> bool:
    for value in updated_values.values():
        validations.validate_field(cast(List[str], str(value)))
    updated_values['finding'] = updated_values.get('title')
    updated_values['vulnerability'] = updated_values.get('description')
    updated_values['effect_solution'] = updated_values.get('recommendation')
    updated_values['records_number'] = str(
        updated_values.get('records_number'))
    updated_values['id'] = finding_id
    del updated_values['title']
    del updated_values['description']
    del updated_values['recommendation']
    updated_values = {
        key: None if not value else value
        for key, value in updated_values.items()
    }
    updated_values = {
        util.camelcase_to_snakecase(k): updated_values.get(k)
        for k in updated_values
    }

    if re.search(r'^[A-Z]+\.(H\.|S\.|SH\.)??[0-9]+\. .+',
                 str(updated_values.get('finding', ''))):
        return finding_dal.update(finding_id, updated_values)

    raise InvalidDraftTitle()
def approve_draft(draft_id: str, reviewer_email: str) -> Tuple[bool, datetime]:
    draft_data = get_finding(draft_id)
    submission_history = cast(List[Dict[str, str]],
                              draft_data.get('historicState'))
    release_date: Union[str, Optional[datetime]] = None
    success = False

    if 'releaseDate' not in draft_data and \
       submission_history[-1].get('state') != 'DELETED':
        has_vulns = vuln_domain.list_vulnerabilities([draft_id])
        if has_vulns:
            if 'reportDate' in draft_data:
                tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
                today = datetime.now(tz=tzn).today()
                release_date = str(today.strftime('%Y-%m-%d %H:%M:%S'))
                history = cast(List[Dict[str, str]],
                               draft_data.get('historicState', [{}]))
                history.append({
                    'date': release_date,
                    'analyst': reviewer_email,
                    'state': 'APPROVED'
                })

                success = finding_dal.update(
                    draft_id, {
                        'lastVulnerability': release_date,
                        'releaseDate': release_date,
                        'treatment': 'NEW',
                        'historic_state': history
                    })
            else:
                raise NotSubmitted()
    else:
        raise AlreadyApproved()
    return success, cast(datetime, release_date)
def submit_draft(finding_id: str, analyst_email: str) -> bool:
    success = False
    finding = get_finding(finding_id)
    submission_history = cast(List[Dict[str, str]],
                              finding.get('historicState'))

    if 'releaseDate' not in finding and \
       submission_history[-1].get('state') != 'DELETED':
        is_submitted = submission_history[-1].get('state') == 'SUBMITTED'
        if not is_submitted:
            finding_evidence = cast(Dict[str, Dict[str, str]],
                                    finding['evidence'])
            evidence_list = \
                cast(List[Dict[str, Dict[str, str]]],
                     [finding_evidence.get(ev_name)
                      for ev_name in finding_evidence])
            has_evidence = any(
                [str(evidence.get('url', '')) for evidence in evidence_list])
            has_severity = float(str(finding['severityCvss'])) > Decimal(0)
            has_vulns = vuln_domain.list_vulnerabilities([finding_id])

            if all([has_evidence, has_severity, has_vulns]):
                today = datetime.now(tz=pytz.timezone(
                    settings.TIME_ZONE)).today()  # type: ignore
                report_date = today.strftime('%Y-%m-%d %H:%M:%S')
                history = cast(List[Dict[str, str]],
                               finding.get('historicState', []))
                history.append({
                    'analyst': analyst_email,
                    'date': report_date,
                    'state': 'SUBMITTED'
                })

                success = finding_dal.update(finding_id, {
                    'report_date': report_date,
                    'historic_state': history
                })
                if success:
                    finding_utils.send_new_draft_mail(
                        analyst_email, finding_id,
                        str(finding.get('finding', '')),
                        str(finding.get('projectName', '')))
            else:
                required_fields = {
                    'evidence': has_evidence,
                    'severity': has_severity,
                    'vulnerabilities': has_vulns
                }
                raise IncompleteDraft([
                    field for field in required_fields
                    if not required_fields[field]
                ])
        else:
            raise AlreadySubmitted()
    else:
        raise AlreadyApproved()

    return success
def mask_finding(finding_id: str) -> bool:
    finding = finding_dal.get_finding(finding_id)
    finding = finding_utils.format_data(finding)

    attrs_to_mask = [
        'affected_systems', 'attack_vector_desc', 'effect_solution',
        'related_findings', 'risk', 'threat', 'treatment', 'treatment_manager',
        'vulnerability'
    ]
    finding_result = finding_dal.update(
        finding_id, {attr: 'Masked'
                     for attr in attrs_to_mask})

    evidence_prefix = '{}/{}'.format(finding['projectName'], finding_id)
    evidence_result = all([
        finding_dal.remove_evidence(file_name)
        for file_name in finding_dal.search_evidence(evidence_prefix)
    ])
    finding_dal.update(
        finding_id, {
            'files': [{
                'file_url': 'Masked',
                'name': 'Masked',
                'description': 'Masked'
            } for _ in cast(List[Dict[str, str]], finding['evidence'])]
        })

    comments = comment_dal.get_comments('comment', int(finding_id))
    comments_result = all([
        comment_dal.delete(comment['finding_id'], comment['user_id'])
        for comment in comments
    ])

    vulns_result = all([
        vuln_domain.mask_vuln(finding_id, str(vuln['UUID']))
        for vuln in vuln_domain.get_vulnerabilities(finding_id)
    ])

    success = all(
        [finding_result, evidence_result, comments_result, vulns_result])
    util.invalidate_cache(finding_id)

    return success
def request_verification(finding_id: str, user_email: str, user_fullname: str,
                         justification: str, vuln_ids: List[str]) -> bool:
    finding = finding_dal.get_finding(finding_id)
    vulnerabilities = get_by_ids(finding_id, vuln_ids)
    vulnerabilities = [
        validate_requested_verification(vuln) for vuln in vulnerabilities
    ]
    vulnerabilities = [validate_closed(vuln) for vuln in vulnerabilities]
    if not vulnerabilities:
        raise VulnNotFound()
    comment_id = int(round(time() * 1000))
    tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
    today = datetime.now(tz=tzn).today().strftime('%Y-%m-%d %H:%M:%S')
    historic_verification = cast(List[Dict[str, Union[str, int, List[str]]]],
                                 finding.get('historic_verification', []))
    historic_verification.append({
        'date': today,
        'user': user_email,
        'status': 'REQUESTED',
        'comment': comment_id,
        'vulns': vuln_ids
    })
    update_finding = finding_dal.update(
        finding_id, {'historic_verification': historic_verification})
    comment_data = {
        'comment_type': 'verification',
        'content': justification,
        'created': today,
        'email': user_email,
        'finding_id': int(finding_id),
        'fullname': user_fullname,
        'modified': today,
        'parent': 0,
    }
    comment_dal.create(comment_id, comment_data)
    update_vulns = [
        vuln_dal.request_verification(vuln) for vuln in vulnerabilities
    ]
    if all(update_vulns) and update_finding:
        finding_utils.send_remediation_email(
            user_email, finding_id, str(finding.get('finding', '')),
            str(finding.get('project_name', '')), justification)
        project_users = project_dal.get_users(
            str(finding.get('project_name', '')))
        notifications.notify_mobile(
            project_users, t('notifications.remediated.title'),
            t('notifications.remediated.content',
              finding=finding.get('finding'),
              project=str(finding.get('project_name', '')).upper()))
    else:
        rollbar.report_message('Error: An error occurred remediating', 'error')

    return all(update_vulns)
def update_evidence(finding_id: str, evidence_type: str, file) -> bool:
    finding = get_finding(finding_id)
    files = cast(List[Dict[str, str]], finding.get('files', []))
    project_name = str(finding.get('projectName', ''))
    success = False

    if evidence_type == 'fileRecords':
        old_file_name: str = next(
            (item['file_url']
             for item in files if item['name'] == 'fileRecords'), '')
        if old_file_name != '':
            old_records = finding_utils.get_records_from_file(
                project_name, finding_id, old_file_name)
            if old_records:
                file = append_records_to_file(
                    cast(List[Dict[str, str]], old_records), file)
                file.open()

    try:
        mime = Magic(mime=True).from_buffer(file.file.getvalue())
        extension = {
            'image/gif': '.gif',
            'image/jpeg': '.jpg',
            'image/png': '.png',
            'application/x-empty': '.exp',
            'text/x-python': '.exp',
            'text/csv': '.csv',
            'text/plain': '.txt'
        }[mime]
    except AttributeError:
        extension = ''
    evidence_id = f'{project_name}-{finding_id}-{evidence_type}{extension}'
    full_name = f'{project_name}/{finding_id}/{evidence_id}'

    if finding_dal.save_evidence(file, full_name):
        evidence: Union[Dict[str, str], list] = \
            next((item
                  for item in files
                  if item['name'] == evidence_type), [])
        if evidence:
            index = files.index(cast(Dict[str, str], evidence))
            success = finding_dal.update(
                finding_id, {f'files[{index}].file_url': evidence_id})
        else:
            success = finding_dal.list_append(finding_id, 'files',
                                              [{
                                                  'name': evidence_type,
                                                  'file_url': evidence_id
                                              }])

    return success
def verify_finding(finding_id: str, user_email: str, justification: str,
                   user_fullname: str) -> bool:
    success = False
    finding = get_finding(finding_id)
    project_name = str(finding.get('projectName', ''))
    finding_name = str(finding.get('finding', ''))
    historic_verification = cast(List[Dict[str, Union[str, int, datetime]]],
                                 finding.get('historicVerification', [{}]))
    if historic_verification[-1].get('status') == 'REQUESTED' and\
       not historic_verification[-1].get('vulns', []):
        tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
        today = datetime.now(tz=tzn).today().strftime('%Y-%m-%d %H:%M:%S')
        comment_id = int(round(time() * 1000))
        new_state: Dict[str, Union[str, int, datetime]] = {
            'date': today,
            'user': user_email,
            'status': 'VERIFIED',
            'comment': comment_id,
        }
        comment_data = {
            'user_id': comment_id,
            'comment_type': 'verification',
            'content': justification,
            'fullname': user_fullname,
            'parent': historic_verification[-1].get('comment', 0),
        }
        historic_verification.append(new_state)
        add_comment(user_email,
                    comment_data,
                    finding_id,
                    is_remediation_comment=True)
        success = finding_dal.update(
            finding_id, {'historic_verification': historic_verification})

        if success:
            vuln_domain.update_vulnerabilities_date(user_email, finding_id)
            finding_utils.send_finding_verified_email(finding_id, finding_name,
                                                      project_name)
            project_users = project_dal.get_users(project_name)
            notifications.notify_mobile(
                project_users, t('notifications.verified.title'),
                t('notifications.verified.content',
                  finding=finding_name,
                  project=project_name.upper()))
        else:
            rollbar.report_message(
                'Error: An error occurred verifying the finding', 'error')
    else:
        raise NotVerificationRequested()

    return success
def update_last_vuln_date(finding_id: str) -> bool:
    inc = 0
    has_new_open_vulns = False
    vulnerabilities = finding_dal.get_vulnerabilities(finding_id)
    today_date = str(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))
    while inc < len(vulnerabilities) and has_new_open_vulns is False:
        vuln_historics = cast(List[Dict[str, str]],
                              vulnerabilities[inc].get('historic_state'))
        current_state = vuln_historics[len(vuln_historics) - 1].get(
            'state', '')
        current_date = vuln_historics[len(vuln_historics) - 1].get('date', '')
        if current_state == 'open' and current_date.split(' ')[0] == today_date.split(' ')[0] and \
           ('approval_status' not in vuln_historics[-1] or
           vuln_historics[-1].get('approval_status') == 'APPROVED'):
            description: Dict[str, FindingType] = {
                'lastVulnerability': today_date
            }
            finding_dal.update(finding_id, description)
            has_new_open_vulns = True
        else:
            inc += 1
    success = has_new_open_vulns
    return success
def update_client_description(finding_id: str, updated_values: Dict[str, str],
                              user_mail: str, update) -> bool:
    for value in updated_values.values():
        validations.validate_field(cast(List[str], str(value)))
    success_treatment, success_external_bts = True, True
    if update.bts_changed:
        success_external_bts = finding_dal.update(
            finding_id, {
                'external_bts':
                updated_values['bts_url']
                if updated_values['bts_url'] else None
            })
    if update.treatment_changed:
        success_treatment = update_treatment(finding_id, updated_values,
                                             user_mail)
    return success_treatment and success_external_bts
def update_evidence_description(finding_id: str, evidence_type: str,
                                description: str) -> bool:
    finding = get_finding(finding_id)
    files = cast(List[Dict[str, str]], finding.get('files', []))
    success = False

    evidence: Union[Dict[str, str], list] = \
        next((item for item in files if item['name'] == evidence_type), [])
    if evidence:
        index = files.index(cast(Dict[str, str], evidence))
        success = finding_dal.update(
            finding_id, {f'files[{index}].description': description})
    else:
        raise EvidenceNotFound()

    return success
def verify_vulnerabilities(finding_id: str, user_email: str,
                           user_fullname: str, info,
                           parameters: Dict[str, FindingType]) -> bool:
    finding = finding_dal.get_finding(finding_id)
    vuln_ids = \
        cast(List[str], parameters.get('open_vulns', [])) + \
        cast(List[str], parameters.get('closed_vulns', []))
    vulnerabilities = get_by_ids(finding_id, vuln_ids)
    vulnerabilities = [validate_verify(vuln) for vuln in vulnerabilities]
    vulnerabilities = [validate_closed(vuln) for vuln in vulnerabilities]
    if not vulnerabilities:
        raise VulnNotFound()
    tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
    today = datetime.now(tz=tzn).today().strftime('%Y-%m-%d %H:%M:%S')
    comment_id = int(round(time() * 1000))
    historic_verification = cast(List[Dict[str, Union[str, int, List[str]]]],
                                 finding.get('historic_verification', []))
    historic_verification.append({
        'date': today,
        'user': user_email,
        'status': 'VERIFIED',
        'comment': comment_id,
        'vulns': vuln_ids
    })
    update_finding = finding_dal.update(
        finding_id, {'historic_verification': historic_verification})
    comment_data: comment_dal.CommentType = {
        'comment_type': 'verification',
        'content': parameters.get('justification', ''),
        'created': today,
        'email': user_email,
        'finding_id': int(finding_id),
        'fullname': user_fullname,
        'modified': today,
        'parent': 0,
    }
    comment_dal.create(comment_id, comment_data)
    success = [vuln_dal.verify_vulnerability(vuln) for vuln in vulnerabilities]
    if all(success) and update_finding:
        success = verify(info, finding_id,
                         cast(List[Dict[str, str]], vulnerabilities),
                         cast(List[str], parameters.get('closed_vulns', [])),
                         today)
    else:
        rollbar.report_message('Error: An error occurred verifying', 'error')
    return all(success)
def remove_evidence(evidence_name: str, finding_id: str) -> bool:
    finding = get_finding(finding_id)
    project_name = finding['projectName']
    files = cast(List[Dict[str, str]], finding.get('files', []))
    success = False

    evidence: Union[Dict[str, str], str] = \
        next((item for item in files if item['name'] == evidence_name), '')
    evidence_id = str(cast(Dict[str, str], evidence).get('file_url', ''))
    full_name = f'{project_name}/{finding_id}/{evidence_id}'

    if finding_dal.remove_evidence(full_name):
        index = files.index(cast(Dict[str, str], evidence))
        del files[index]
        success = finding_dal.update(finding_id, {'files': files})

    return success
def handle_acceptation(finding_id: str, observations: str, user_mail: str,
                       response: str) -> bool:
    new_state = {
        'acceptance_status': response,
        'treatment': 'ACCEPTED_UNDEFINED',
        'justification': observations,
        'user': user_mail,
    }
    historic_treatment = cast(List[Dict[str, str]],
                              get_finding(finding_id).get('historicTreatment'))
    historic_treatment.append(new_state)
    if response == 'REJECTED':
        tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
        today = datetime.now(tz=tzn).today().strftime('%Y-%m-%d %H:%M:%S')
        historic_treatment.append({'treatment': 'NEW', 'date': today})
    return finding_dal.update(finding_id,
                              {'historic_treatment': historic_treatment})
def update_treatment(finding_id: str, updated_values: Dict[str, str],
                     user_mail: str) -> bool:
    success = False
    tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
    today = datetime.now(tz=tzn).today().strftime('%Y-%m-%d %H:%M:%S')
    finding = get_finding(finding_id)
    historic_treatment = cast(List[Dict[str, str]],
                              finding.get('historicTreatment', []))
    if updated_values['treatment'] == 'ACCEPTED' and updated_values[
            'acceptance_date'] == '-':
        updated_values['acceptance_date'] = \
            (datetime.now() + timedelta(days=180)).strftime('%Y-%m-%d %H:%M:%S')
    updated_values = util.update_treatment_values(updated_values)
    new_treatment = updated_values['treatment']
    new_state = {'date': today, 'treatment': new_treatment}
    if user_mail:
        new_state['user'] = user_mail
    if new_treatment != 'NEW':
        new_state['justification'] = updated_values['justification']
        if new_treatment == 'ACCEPTED':
            new_state['acceptance_date'] = updated_values['acceptance_date']
        if new_treatment == 'ACCEPTED_UNDEFINED':
            new_state['acceptance_status'] = updated_values[
                'acceptance_status']
    if historic_treatment:
        if compare_historic_treatments(historic_treatment[-1], new_state):
            historic_treatment.append(new_state)
    else:
        historic_treatment = [new_state]
    result_update_finding = finding_dal.update(
        finding_id, {'historic_treatment': historic_treatment})
    result_update_vuln = update_treatment_in_vuln(finding_id,
                                                  historic_treatment[-1])
    if result_update_finding and result_update_vuln:
        should_send_mail(finding, updated_values)
        success = True
    return success
def delete_finding(finding_id: str, project_name: str, justification: str,
                   context) -> bool:
    finding_data = get_finding(finding_id)
    submission_history = cast(List[Dict[str, str]],
                              finding_data.get('historicState', [{}]))
    success = False

    if submission_history[-1].get('state') != 'DELETED':
        tzn = pytz.timezone(settings.TIME_ZONE)  # type: ignore
        today = datetime.now(tz=tzn).today()
        delete_date = str(today.strftime('%Y-%m-%d %H:%M:%S'))
        submission_history.append({
            'state':
            'DELETED',
            'date':
            delete_date,
            'justification':
            justification,
            'analyst':
            util.get_jwt_content(context)['user_email'],
        })
        success = finding_dal.update(finding_id,
                                     {'historic_state': submission_history})

        if success:
            justification_dict = {
                'DUPLICATED': 'It is duplicated',
                'FALSE_POSITIVE': 'It is a false positive',
                'NOT_REQUIRED': 'Finding not required',
            }
            finding_utils.send_finding_delete_mail(
                finding_id, str(finding_data.get('finding', '')), project_name,
                str(finding_data.get('analyst', '')),
                justification_dict[justification])

    return success
def save_severity(finding: Dict[str, FindingType]) -> bool:
    """Organize severity metrics to save in dynamo."""
    cvss_version: str = str(finding.get('cvssVersion', ''))
    cvss_parameters = finding_utils.CVSS_PARAMETERS[cvss_version]
    if cvss_version == '3.1':
        severity_fields = [
            'attackVector', 'attackComplexity', 'privilegesRequired',
            'userInteraction', 'severityScope', 'confidentialityImpact',
            'integrityImpact', 'availabilityImpact', 'exploitability',
            'remediationLevel', 'reportConfidence',
            'confidentialityRequirement', 'integrityRequirement',
            'availabilityRequirement', 'modifiedAttackVector',
            'modifiedAttackComplexity', 'modifiedPrivilegesRequired',
            'modifiedUserInteraction', 'modifiedSeverityScope',
            'modifiedConfidentialityImpact', 'modifiedIntegrityImpact',
            'modifiedAvailabilityImpact'
        ]
        severity: Dict[str, FindingType] = \
            {util.camelcase_to_snakecase(k): Decimal(str(finding.get(k)))
             for k in severity_fields}
        unformatted_severity = {
            k: float(str(finding.get(k)))
            for k in severity_fields
        }
        privileges = cvss.calculate_privileges(
            unformatted_severity['privilegesRequired'],
            unformatted_severity['severityScope'])
        unformatted_severity['privilegesRequired'] = privileges
        severity['privileges_required'] = \
            Decimal(privileges).quantize(Decimal('0.01'))
        modified_priviles = cvss.calculate_privileges(
            unformatted_severity['modifiedPrivilegesRequired'],
            unformatted_severity['modifiedSeverityScope'])
        unformatted_severity['modifiedPrivilegesRequired'] = modified_priviles
        severity['modified_privileges_required'] = \
            Decimal(modified_priviles).quantize(Decimal('0.01'))
    else:
        severity_fields = [
            'accessVector', 'accessComplexity', 'authentication',
            'exploitability', 'confidentialityImpact', 'integrityImpact',
            'availabilityImpact', 'resolutionLevel', 'confidenceLevel',
            'collateralDamagePotential', 'findingDistribution',
            'confidentialityRequirement', 'integrityRequirement',
            'availabilityRequirement'
        ]
        severity = {
            util.camelcase_to_snakecase(k): Decimal(str(finding.get(k)))
            for k in severity_fields
        }
        unformatted_severity = {
            k: float(str(finding.get(k)))
            for k in severity_fields
        }
    severity['cvss_basescore'] = cvss.calculate_cvss_basescore(
        unformatted_severity, cvss_parameters, cvss_version)
    severity['cvss_temporal'] = cvss.calculate_cvss_temporal(
        unformatted_severity, float(cast(Decimal, severity['cvss_basescore'])),
        cvss_version)
    severity['cvss_env'] = cvss.calculate_cvss_environment(
        unformatted_severity, cvss_parameters, cvss_version)
    severity['cvss_version'] = cvss_version
    response = finding_dal.update(str(finding.get('id', '')), severity)
    return response