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