Details
| ID | SecurityScorecard |
|---|---|
| Provider | SecurityScorecard |
| Category | Vulnerability Management |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Provides commands to access SecurityScorecard’s API.
This integration was integrated and tested with the latest version of SecurityScorecard’s API as of August 2024.
Configure SecurityScorecard in Cortex
| Parameter | Description | Required |
|---|---|---|
| SecurityScorecard API Base URL | True | |
| Username/Email | The SecurityScorecard username/email. | True |
| API Token | True | |
| Fetch incidents | False | |
| Incidents Fetch Interval | SecurityScorecard is updated on a daily basis therefore there’s no need to modify this value. | False |
| Fetch Limit | Maximum number of alerts per fetch. The maximum is 50. | False |
| First fetch | First fetch query (<number> <time unit>, e.g., 12 hours, 7 days. SecurityScorecard provides a maximum of 7 days back. To ensure no alerts are missed, it’s recommended to use a value less than 2 days. | False |
| Incident type | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Portfolio ID | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
securityscorecard-portfolios-list
List all Portfolios.
Base Command
securityscorecard-portfolios-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | Limit the amount of Portfolios to return. Defaults to 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Portfolio.id | String | Portfolio ID. |
| SecurityScorecard.Portfolio.name | String | Portfolio name. |
| SecurityScorecard.Portfolio.description | String | Portfolio description. |
| SecurityScorecard.Portfolio.privacy | String | Portfolio privacy. Can be either private, shared or team. |
| SecurityScorecard.Portfolio.read_only | Boolean | Whether the portfolio is read only. |
securityscorecard-portfolio-list-companies
Lists all companies in Portfolio.
Base Command
securityscorecard-portfolio-list-companies
Input
| Argument Name | Description | Required |
|---|---|---|
| portfolio_id | Portfolio ID. The Portfolio ID can be retrieved using the ‘securityscorecard-portfolios-list’ command. | Required |
| grade | Grade filter. To filter multiple grades, comma-separate them, e.g. A,B. Possible values are: A, B, C, D, E, F. | Optional |
| industry | Industry filter. To filter multiple industries, comma-separate them, e.g. education,financial_services. Possible values are: education, financial_services, food, government, healthcare, information_services, manufacturing, retail, technology. | Optional |
| vulnerability | Vulnerability filter. | Optional |
| issue_type | Comma-separated list of issue types. Possible values are: adware_installation_trail, adware_installation, alleged_breach_incident, chatter, anonymous_proxy, service_cassandra, service_couchdb, attack_detected, attack_feed, new_booter_shell, spa_browser, cdn_hosting, tlscert_expired, tlscert_revoked, tlscert_self_signed, tlscert_excessive_expiration, tlscert_weak_signature, tlscert_no_revocation, service_cloud_provider, csp_no_policy_v2, csp_unsafe_policy_v2, csp_too_broad_v2, marketing_site, cookie_missing_secure_attribute, short_term_lending_site, leaked_credentials, leaked_credentials_info, service_dns, new_defacement, ransomware_victim, domain_uses_hsts_preloading, service_elasticsearch, employee_satisfaction, service_end_of_life, service_end_of_service, exposed_personal_information, exposed_personal_information_info, admin_subdomain_v2, tlscert_extended_validation, service_ftp, patching_cadence_high, web_vuln_host_high, service_vuln_host_high, service_imap, iot_camera, industrial_control_device, insecure_https_redirect_pattern_v2, service_ldap, service_ldap_anonymous, social_network_issues, patching_cadence_low, web_vuln_host_low, service_vuln_host_low, spf_record_malformed, malware_controller, malware_1_day, malware_30_day, malware_365_day, malware_infection, malware_infection_trail, patching_cadence_medium, web_vuln_host_medium, service_vuln_host_medium, service_microsoft_sql, minecraft_server, service_mongodb, no_browser_policy, service_mysql, service_neo4j, service_networking, object_storage_bucket_with_risky_acl, open_resolver, exposed_ports, service_open_vpn, service_oracle_db, outdated_os, outdated_browser, non_malware_events_last_month, service_pop3, service_pptp, phishing, typosquat, service_postgresql, exploited_product, public_text_credit_cards, public_text_database_dump, public_text_hashes, public_text_mention, public_text_password_dump, service_pulse_vpn, service_rdp, ransomware_association, redirect_chain_contains_http_v2, service_redis, remote_access, service_smb, mail_server_unusual_port, service_soap, spf_record_wildcard, spf_record_softfail, spf_record_missing, ssh_weak_protocol, ssh_weak_cipher, ssh_weak_mac, tls_weak_protocol, github_information_leak_disclosure, google_information_leak_disclosure, cookie_missing_http_only, domain_missing_https_v2, suspicious_traffic, tls_ocsp_stapling, tls_weak_cipher, telephony, service_telnet, tor_node_events_last_month, upnp_accessible, unsafe_sri_v2, uce, service_vnc, dnssec_detected, waf_detected_v2, hsts_incorrect_v2, hosted_on_object_storage_v2, references_object_storage_v2, x_content_type_options_incorrect_v2, x_frame_options_incorrect_v2, x_xss_protection_incorrect_v2, service_rsync. | Optional |
| had_breach_within_last_days | Domains with breaches in the last X days. Possible values are numbers, e.g. 1000. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Portfolio.Company.domain | String | Company domain. |
| SecurityScorecard.Portfolio.Company.name | String | Company name. |
| SecurityScorecard.Portfolio.Company.score | Number | Company overall score in numeric form (55-100). |
| SecurityScorecard.Portfolio.Company.grade | String | Company overall score in letter grade. |
| SecurityScorecard.Portfolio.Company.grade_url | String | Company overall score URL to SVG asset. |
| SecurityScorecard.Portfolio.Company.last30days_score_change | Number | Company overall score numeric change (±) in the last month. |
| SecurityScorecard.Portfolio.Company.industry | String | Industry category of the domain. |
| SecurityScorecard.Portfolio.Company.size | String | Company size, e.g. ‘size_more_than_10000’. |
| SecurityScorecard.Portfolio.Company.is_custom_vendor | Boolean | Whether the company is a custom vendor. |
| SecurityScorecard.Portfolio.Company.total | Number | Total number of companies in Portfolio. |
securityscorecard-company-score-get
Retrieve company overall score.
Base Command
securityscorecard-company-score-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain, e.g. google.com. The company must first be added to a Portfolio in order to be able to get its score. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Company.Score.domain | String | Company domain. |
| SecurityScorecard.Company.Score.name | String | Company name. |
| SecurityScorecard.Company.Score.score | Number | Company overall score in numeric form (55-100). |
| SecurityScorecard.Company.Score.grade | String | Company overall score in letter grade form (A-F). |
| SecurityScorecard.Company.Score.last30days_score_change | Number | Company overall score numeric change (±) in the last month. |
| SecurityScorecard.Company.Score.industry | String | ndustry category of the domain. |
| SecurityScorecard.Company.Score.size | String | Company size, e.g. ‘size_more_than_10000’. |
securityscorecard-company-factor-score-get
Retrieve company factor score.
Base Command
securityscorecard-company-factor-score-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain. | Required |
| severity | Issue severity filter. Comma-separated list of the following values: ‘positive’, ‘info’, ‘low’, ‘medium’, ‘high’. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Company.Factor.name | String | Factor name. |
| SecurityScorecard.Company.Factor.score | Number | Factor score in numeric form (55-100). |
| SecurityScorecard.Company.Factor.grade | String | Factor score in letter grade form (A-F). |
| SecurityScorecard.Company.Factor.Issue.type | String | Type of issue found. |
| SecurityScorecard.Company.Factor.Issue.count | Number | How many times the issue was found. |
| SecurityScorecard.Company.Factor.Issue.severity | String | Severity of the issue. |
| SecurityScorecard.Company.Factor.Issue.total_score_impact | Number | Contribution of issue on overall score. |
| SecurityScorecard.Company.Factor.Issue.detail_url | String | URL to the details of the issue. |
| SecurityScorecard.Company.Factor.total | Number | Number of factors returned. |
securityscorecard-company-history-score-get
Retrieve company historical scores.
Base Command
securityscorecard-company-history-score-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain, e.g. google.com. |
Required |
| from | Initial date for historical data. Value should be in format YYYY-MM-DD. |
Optional |
| to | End date for historical data. Value should be in format YYYY-MM-DD. |
Optional |
| timing | Timing granularity. Possible values are: daily, weekly. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Company.ScoreHistory.domain | String | Company domain. |
| SecurityScorecard.Company.ScoreHistory.date | Date | Score date. |
| SecurityScorecard.Company.ScoreHistory.score | Number | Company historical security score in numeric form (55-100). |
securityscorecard-company-history-factor-score-get
Retrieve company historical factor scores.
Base Command
securityscorecard-company-history-factor-score-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain, e.g. google.com. | Required |
| from | Initial date for historical data. Value should be in format ‘YYYY-MM-DD’. | Optional |
| to | End date for historical data. Value should be in format ‘YYYY-MM-DD’. | Optional |
| timing | Timing granularity. or “monthly”. Possible values are: daily, weekly, monthly. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Company.FactorHistory.domain | String | Company domain. |
| SecurityScorecard.Company.FactorHistory.date | Date | Score date. |
| SecurityScorecard.Company.FactorHistory.Factor.name | Number | Factor name. |
| SecurityScorecard.Company.FactorHistory.score | Number | Company historical security score in numeric form (55-100). |
securityscorecard-alert-grade-change-create
Create alert based on grade.
Base Command
securityscorecard-alert-grade-change-create
Input
| Argument Name | Description | Required |
|---|---|---|
| change_direction | Direction of change. Possible values are: rises, drops. | Required |
| score_types | Comma-separated list of risk factors to monitor. Possible values are ‘overall’, ‘any_factor_score’, ‘network_security’, ‘dns_health’, ‘patching_cadence’, ‘endpoint_security’, ‘ip_reputation’, ‘application_security’, ‘cubit_score’, ‘hacker_chatter’, ‘leaked_information’, ‘social_engineering’. | Required |
| target | What do you want to monitor with this alert. This argument is required if the portfolio argument is not specified. Possible values are: my_scorecard, any_followed_company. |
Optional |
| portfolio | A portfolio_id to use as a target for the alert. This argument is required if the target argument is not specified. You can get a list of portfolios by running !securityscorecard-portfolios-list. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Alerts.GradeChangeAlert.id | String | Alert ID. |
securityscorecard-alert-score-threshold-create
Create alert based threshold met.
Base Command
securityscorecard-alert-score-threshold-create
Input
| Argument Name | Description | Required |
|---|---|---|
| change_direction | Direction of change. Possible values are: rises_above, drops_below. | Required |
| threshold | The numeric score used as the threshold to trigger the alert. | Required |
| score_types | Comma separated list of risk factors to monitor. Possible values are ‘overall’, ‘any_factor_score’, ‘network_security’, ‘dns_health’, ‘patching_cadence’, ‘endpoint_security’, ‘ip_reputation’, ‘application_security’, ‘cubit_score’, ‘hacker_chatter’, ‘leaked_information’, ‘social_engineering’. For multiple factors, provide comma-separated list, i.e. leaked_information,social_engineering. | Required |
| target | What do you want to monitor with this alert. This argument is required if the portfolio argument is not specified. Possible values are: my_scorecard, any_followed_company. |
Optional |
| portfolio | A portfolio_id to use as a target for the alert. This argument is required if the target argument is not specified. You can get a list of portfolios by running !securityscorecard-portfolios-list. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Alerts.ScoreThresholdAlert.id | String | Alert ID. |
securityscorecard-alert-delete
Delete an alert.
Base Command
securityscorecard-alert-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | Alert ID. | Required |
Context Output
There is no context output for this command.
securityscorecard-alerts-list
List alerts triggered in the last week.
Base Command
securityscorecard-alerts-list
Input
| Argument Name | Description | Required |
|---|---|---|
| portfolio_id | Portfolio ID. Can be retrieved using !securityscorecard-portfolios-list. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Alerts.Alert.id | String | Alert ID. |
| SecurityScorecard.Alerts.Alert.email | String | Alert email recipient. |
| SecurityScorecard.Alerts.Alert.change_type | String | Alert change type configured (score or threshold). |
| SecurityScorecard.Alerts.Alert.domain | String | Alert domain. |
| SecurityScorecard.Alerts.Alert.company_name | String | Alert company name. |
| SecurityScorecard.Alerts.Alert.Portfolio.id | array | Alert Portfolio ID. |
| SecurityScorecard.Alerts.Alert.my_scorecard | Boolean | Whether the alert was triggered on private scorecard. This depends on whether ‘my_scorecard’ was added to the optional argument ‘target’ when creating alerts using the ‘securityscorecard-alert-score-threshold-create’ and ‘securityscorecard-alert-grade-change-create’ commands. |
| SecurityScorecard.Alerts.Alert.created_at | Date | Timestamp of when the alert was triggered. |
securityscorecard-company-services-get
Retrieve the service providers of a domain.
Base Command
securityscorecard-company-services-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Service.vendor_domain | String | Vendor domain, e.g. Google, Amazon. |
| SecurityScorecard.Service.client_domain | String | Client domain. This value is identical to the input of the domain argument. |
| SecurityScorecard.Service.categories | array | Vendor service provider, e.g. mail_provider, nameserver_provider. |
securityscorecard-company-events-get
Retrieve a company’s historical events.
Base Command
securityscorecard-company-events-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Company domain, e.g. google.com. | Required |
| date_from | Initial date for historical data. Value should be in format 2020-01-30T00:00:00.000Z. |
Optional |
| date_to | End date for historical data. Value should be in format 2020-01-30T00:00:00.000Z. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Events.Event.ssc_event_id | string | event id. |
| SecurityScorecard.Events.Event.date | date | event date. |
| SecurityScorecard.Events.Event.status | unknown | event status. |
| SecurityScorecard.Events.Event.issue_count | number | event issue count. |
| SecurityScorecard.Events.Event.score_impact | number | event score impact. |
| SecurityScorecard.Events.Event.issue_type | string | event issue type. |
| SecurityScorecard.Events.Event.severity | string | event severity. |
| SecurityScorecard.Events.Event.factor | string | event factor. |
| SecurityScorecard.Events.Event.ssc_detail_url | string | event detail url. |
securityscorecard-company-findings-get
Retrieve an issue_type’s historical findings in a scorecard.
Base Command
securityscorecard-company-findings-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Retrieve the service providers of a domain. | Required |
| date | The effective_date for historical data. Value should be in format ‘YYYY-MM-DD’. | Required |
| issue_type | Key representing issue type, e.g. api_key_exposed. | Required |
| status | group_status filter. Comma-separated list of the following values: ‘active’, ‘inactive’, ‘all’. | Optional |
Context Output
There is no context output for this command.
securityscorecard-issue-metadata
Retrieve metadata for an issue type, including description and recommendation.
Base Command
securityscorecard-issue-metadata
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_type | Key representing issue type, e.g. api_key_exposed. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.Metadata.Issues.key | string | Key representing issue type, e.g. api_key_exposed. |
| SecurityScorecard.Metadata.Issues.severity | string | issue severity. |
| SecurityScorecard.Metadata.Issues.factor | string | issue factor. |
| SecurityScorecard.Metadata.Issues.title | string | issue title. |
| SecurityScorecard.Metadata.Issues.short_description | string | issue short description. |
| SecurityScorecard.Metadata.Issues.long_description | string | issue long description. |
| SecurityScorecard.Metadata.Issues.recommendation | string | issue recommendation. |
securityscorecard-alert-rules-list
List alert subscriptions for the user.
Base Command
securityscorecard-alert-rules-list
Input
This command does not require any arguments.
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.AlertRules.Rule.id | String | Alert Rule ID. |
| SecurityScorecard.AlertRules.Rule.name | String | Alert Rule name. |
| SecurityScorecard.AlertRules.Rule.target | String | Target of the Rule. |
| SecurityScorecard.AlertRules.Rule.updated_at | Date | Timestamp when the alert rule was last updated. |
| SecurityScorecard.AlertRules.Rule.paused_at | String | Timestamp when the alert rule was paused. |
securityscorecard-issue-details-get
Retrieve issue details for a specific issue type and domain.
Base Command
securityscorecard-issue-details-get
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | The domain to get the issue details for. | Required |
| issue_type | The issue type to get the details for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SecurityScorecard.IssueDetails.issue_id | String | Unique UUID for this measurement. |
| SecurityScorecard.IssueDetails.parent_domain | String | Parent domain aka vendor. |
| SecurityScorecard.IssueDetails.issue_type | String | issue_type of the findings. |
| SecurityScorecard.IssueDetails.count | Number | The number of findings. |
| SecurityScorecard.IssueDetails.group_status | String | If findings are active or not. |
| SecurityScorecard.IssueDetails.first_seen_time | Date | Epoch of observation in nanoseconds. |
| SecurityScorecard.IssueDetails.last_seen_time | Date | Epoch of observation in nanoseconds. |
| SecurityScorecard.IssueDetails.port | Number | Port number of the observation if applicable. |
| SecurityScorecard.IssueDetails.domain | String | Domain of the observation if applicable. |
| SecurityScorecard.IssueDetails.ip | String | IP address of the observation if applicable. |
| SecurityScorecard.IssueDetails.protocol | String | Protocol of the observation if applicable. |
| SecurityScorecard.IssueDetails.observations | String | Observation data in raw JSON format. |
Configuration parameters
base_url— SecurityScorecard API Base URL (required)username— Username/Email (required)isFetch— Fetch incidentsincidentFetchInterval— Incidents Fetch Intervalmax_fetch— Fetch Limitfirst_fetch— First fetchincidentType— Incident typeinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsportfolio_id— Portfolio ID
Commands (16)
-
securityscorecard-alert-deleteDelete an alert.
-
securityscorecard-alert-grade-change-createCreate alert based on grade.
-
securityscorecard-alert-rules-listList alert subscriptions for the user.
-
securityscorecard-alert-score-threshold-createCreate alert based threshold met.
-
securityscorecard-alerts-listList alerts triggered in the last week.
-
securityscorecard-company-events-getRetrieve a company's historical events.
-
securityscorecard-company-factor-score-getRetrieve company factor score.
-
securityscorecard-company-findings-getRetrieve an issue_type's historical findings in a scorecard.
-
securityscorecard-company-history-factor-score-getRetrieve company historical factor scores.
-
securityscorecard-company-history-score-getRetrieve company historical scores.
-
securityscorecard-company-score-getRetrieve company overall score.
-
securityscorecard-company-services-getRetrieve the service providers of a domain.
-
securityscorecard-issue-details-getRetrieve issue details for a specific issue type and domain.
-
securityscorecard-issue-metadataRetrieve metadata for an issue type, including description and recommendation.
-
securityscorecard-portfolio-list-companiesLists all companies in Portfolio.
-
securityscorecard-portfolios-listList all Portfolios.
import json from unittest.mock import MagicMock import pytest from CommonServerPython import * from SecurityScorecard import ( SecurityScorecardClient, alert_grade_change_create_command, alert_rules_list_command, alert_score_threshold_create_command, company_event_findings_get_command, company_events_get_command, company_factor_score_get_command, company_history_factor_score_get_command, company_history_score_get_command, company_score_get_command, company_services_get_command, get_last_run, incidents_to_import, issue_details_get_command, issue_metadata_get_command, portfolio_list_companies_command, portfolios_list_command, ) """ TEST CONSTANTS """ USERNAME = "user@domain.com" PORTFOLIO_ID = "1" PORTFOLIO_ID_NE = "2" DOMAIN = "domain1.com" DOMAIN_NE = "domain2.com" """ Load test data """ def load_json(path): with open(path, encoding="utf-8") as f: return json.load(f) test_data = load_json("./test_data/data.json") """ Helper Functions Unit Tests""" FROZEN_DATE = "2021-10-09T22:38:02.560Z" @pytest.mark.freeze_time(FROZEN_DATE) @pytest.mark.parametrize( "last_run, first_fetch", [(FROZEN_DATE, None), (None, "3 days"), (None, "7 days"), (FROZEN_DATE, "7 days")] ) def test_get_last_run(last_run, first_fetch): """ Given: - The last run time of the fetch mechanism - The first fetch interval When: - Case A: Last run date is defined and first fetch is undefined. - Case B: Last run date is undefined and first fetch is defined to 3 days. - Case C: Last run date is undefined and first fetch is defined to 7 days. - Case D: Last run date is defined and first fetch is defined to 7 days. Then: - Case A: Last run date Unix timestamp will be returned. - Case B: Last run date will be set to to now and Unix timestamp will be set to most recent alert. - Case C: Last run date will be set to to now and Unix timestamp will be set to most recent alert. - Case D: Last run date Unix timestamp will be returned. """ last_run_dt = get_last_run(last_run=last_run, first_fetch=first_fetch) if last_run: assert last_run_dt.replace(microsecond=0, second=0, minute=0, hour=0) == arg_to_datetime( arg_name="last_run", arg=last_run ).replace(microsecond=0, second=0, minute=0, hour=0, tzinfo=None) else: # resetting microsecond as causes failure: # E +datetime.datetime(2021, 10, 3, 14, 50, 3, 242595) # E -datetime.datetime(2021, 10, 3, 14, 50, 3, 244008) assert last_run_dt.replace(microsecond=0, second=0, minute=0, hour=0, tzinfo=None) == arg_to_datetime( arg_name="first_fetch", arg=first_fetch ).replace(microsecond=0, second=0, minute=0, hour=0, tzinfo=None) alerts_mock = test_data.get("alerts") incidents_mock = test_data.get("incidents") incidents_to_import_test_inputs = [ ([]), (alerts_mock), ] @pytest.mark.parametrize("alerts", incidents_to_import_test_inputs) def test_incidents_to_import(alerts: list): """ Given: - List of alerts When: - Case A: No alerts supplied - Case B: alerts supplied Then: - Case A : No alerts imported - Case B: alerts imported """ # Need to remove tz info to deal with tz awareness with arg_to_datetime incidents = incidents_to_import( alerts=alerts, last_run=arg_to_datetime("2021-07-25T00:00:00.000Z").replace(tzinfo=None), # type: ignore ) if not alerts: assert not incidents else: assert incidents == incidents_mock """ Command Unit Tests """ MOCK_URL = "mock://securityscorecard-mock-url" client = SecurityScorecardClient( base_url=MOCK_URL, verify=False, proxy=False, headers={}, username=USERNAME, api_key="API_KEY", max_fetch=100 ) @pytest.mark.parametrize("args", [({}), ({"limit": "1"}), ({"limit": "60"})]) def test_portfolios_list(mocker, args): """ Given: - A limit - 3 alerts total When: - Case A: limit undefined - Case B: limit defined as 1 (less than the total) - Case C: limit defined as 60 Then: - Case A: All portfolios returned - Case B: 1 portfolio returned - Case C: All portfolios returned """ portfolios_mock = test_data.get("portfolios") portfolio_entries = portfolios_mock.get("entries") mocker.patch.object(client, "get_portfolios", return_value=portfolios_mock) portfolios_cmd_res: CommandResults = portfolios_list_command(client=client, args=args) portfolios_returned = portfolios_cmd_res.outputs if not args: assert portfolio_entries == portfolios_returned companies_list_test_inputs = [ ( { "portfolio_id": PORTFOLIO_ID, "grade": None, "industry": None, "vulnerability": None, "issue_type": None, "had_breach_within_last_days": None, } ), ( { "portfolio_id": PORTFOLIO_ID, "grade": "A", "industry": None, "vulnerability": None, "issue_type": None, "had_breach_within_last_days": None, } ), ( { "portfolio_id": PORTFOLIO_ID, "grade": "A", "industry": "food", "vulnerability": None, "issue_type": None, "had_breach_within_last_days": None, } ), ( { "portfolio_id": PORTFOLIO_ID, "grade": None, "industry": None, "vulnerability": None, "issue_type": None, "had_breach_within_last_days": "7", } ), ] @pytest.mark.parametrize("args", companies_list_test_inputs) def test_portfolio_list_companies(mocker, args: Dict[str, Any]): """ Given: - A portfolio ID - 3 portfolios in test data When: - Case A: no filters supplied - Case B: grade filter supplied - Case C: grade filter and an industry filter supplied - Case D: invalid had breach within last days filter supplied Then: - Case A: All 3 portfolios returned - Case B: 2 portfolios returned - Case C: 1 portfolio returned - Case D: All 3 portfolios returned """ if args.get("grade"): if args.get("industry"): companies_mock: Dict[str, str] = test_data.get("companies_A_grade_food_industry") else: companies_mock = test_data.get("companies_A_grade") else: companies_mock = test_data.get("companies") companies_entries: List[Dict[str, Any]] = companies_mock.get("entries") # type: ignore mocker.patch.object(client, "get_companies_in_portfolio", return_value=companies_mock) companies_cmd_res: CommandResults = portfolio_list_companies_command(client=client, args=args) companies = companies_cmd_res.outputs assert companies == companies_entries def test_portfolio_list_companies_portfolio_not_found(mocker): """ Given: - A portfolio ID When: - Portfolio ID doesn't exist Then: - Throw 404, 'portfolio not found' """ portfolio_not_found = test_data.get("portfolio_not_exist") mocker.patch.object(client, "get_companies_in_portfolio", return_value=portfolio_not_found) with pytest.raises(Exception): portfolio_list_companies_command( client=client, portfolio_id=PORTFOLIO_ID_NE, grade=None, industry_arg=None, vulnerability=None, issue_type=None, had_breach_within_last_days=None, ) company_score_test_input = [ ({"domain": DOMAIN}), ({"domain": "google.com"}), ({"domain": "GOOGLE.COM"}), ({"domain": "nonexistantdomain.com"}), ] @pytest.mark.parametrize("args", company_score_test_input) def test_get_company_score(mocker, args): """ Given: - A domain When: - Case A: domain is domain1.com - Case B: domain is lowercase - Case C: domain is uppercase Then: - Case A: score returned for domain - Case B: score returned for domain - Case C: score returned for domain """ score_mock = test_data.get("score") mocker.patch.object(client, "get_company_score", return_value=score_mock) response_cmd_res: CommandResults = company_score_get_command(client=client, args=args) score = response_cmd_res.outputs assert score == score_mock def test_get_company_score_not_found(mocker): """ Given: - A company domain When: - Company domain doesn't exist Then: - company not found: nonexistentdomain.com (404) """ company_not_found = test_data.get("company_not_found") mocker.patch.object(client, "get_company_score", return_value=company_not_found) cmd_res: CommandResults = company_score_get_command(client=client, args={"domain": DOMAIN_NE}) status = cmd_res.outputs.get("error").get("statusCode") message = cmd_res.outputs.get("error").get("message") assert status == 404 assert message == f"company not found: {DOMAIN_NE}" factor_score_test_inputs = [ ({"domain": DOMAIN, "severity": None}), ({"domain": DOMAIN, "severity": "positive"}), ({"domain": DOMAIN, "severity": "high,low"}), ] @pytest.mark.parametrize("args", factor_score_test_inputs) def test_get_company_factor_score(mocker, args): """ Given: - A domain - A severity filter When: - Case A: Domain is valid, severity unspecified - Case B: Domain is valid, severity is positive - Case C: Domain is valid, severity is low and high Then: - Case A: Results in all severity factor scores for domain - Case B: Results only in positive severity factor scores for domain - Case C: Results in high and low severity factor scores for domain """ if args.get("severity") == "positive": factor_score_mock = test_data.get("factor_score_severity_positive") elif args.get("severity") == "high,low": factor_score_mock = test_data.get("factor_score_severity_low_high") else: factor_score_mock = test_data.get("factor_score") mocker.patch.object(client, "get_company_factor_score", return_value=factor_score_mock) response: CommandResults = company_factor_score_get_command(client=client, args=args) assert response.outputs == factor_score_mock.get("entries") company_historical_scores_test_inputs = [ ({"domain": DOMAIN, "from": None, "to": None, "timing": None}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": "daily"}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": None}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": "weekly"}), ] @pytest.mark.parametrize("args", company_historical_scores_test_inputs) def test_get_company_historical_scores(mocker, args): """ Given: - A domain - Date range - Timing When: - Case A: no range, no resolution - Case B: range 2021-07-01 to 2021-07-04, daily - Case C: range 2021-07-01 to 2021-07-04, no resolution - Case D: range 2021-07-01 to 2021-07-31, weekly Then: - Case A: Return all scores - Case B: Return scores received for 2021-07-01 to 2021-07-04 on daily basis - Case C: Return scores received for 2021-07-01 to 2021-07-04 on daily basis - Case D: Score received for 2021-07-01 to 2021-07-31 on weekly basis """ if args.get("daily"): historical_score_mock = test_data.get("historical_score_daily") elif args.get("weekly"): historical_score_mock = test_data.get("historical_scores_weekly") else: historical_score_mock = test_data.get("historical_score") mocker.patch.object(client, "get_company_historical_scores", return_value=historical_score_mock) response: CommandResults = company_history_score_get_command(client=client, args=args) cmd_output = response.outputs assert cmd_output == historical_score_mock.get("entries") company_historical_factor_scores_test_inputs = [ ({"domain": DOMAIN, "from": None, "to": None, "timing": None}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": "daily"}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": None}), ({"domain": DOMAIN, "from": "2021-07-01", "to": "2021-07-31", "timing": "weekly"}), ] @pytest.mark.parametrize("args", company_historical_factor_scores_test_inputs) def test_get_company_historical_factor_scores(mocker, args): """ Given: - A domain - Date range - Timing When: - Case A: no range, no resolution - Case B: range 2021-07-01 to 2021-07-04, daily - Case C: range 2021-07-01 to 2021-07-04, no resolution - Case D: range 2021-07-01 to 2021-07-31, weekly Then: - Case A: Return all scores - Case B: Return scores received for 2021-07-01 to 2021-07-04 on daily basis - Case C: Return scores received for 2021-07-01 to 2021-07-04 on daily basis - Case D: Score received for 2021-07-01 to 2021-07-31 on weekly basis """ if args.get("daily"): historical_factor_score_mock = test_data.get("historical_factor_score_daily") elif args.get("weekly"): historical_factor_score_mock = test_data.get("historical_factor_scores_weekly") else: historical_factor_score_mock = test_data.get("historical_factor_score") historical_factor_score_mock = test_data.get("historical_factor_score") mocker.patch.object(client, "get_company_historical_factor_scores", return_value=historical_factor_score_mock) cmd_res: CommandResults = company_history_factor_score_get_command(client=client, args=args) factor_scores = cmd_res.outputs assert factor_scores == historical_factor_score_mock.get("entries") alert_creation_inputs = [ ({"change_direction": "drops", "score_types": "overall", "target": "my_scorecard", "portfolio": None}), ({"change_direction": "drops", "score_types": ["application_security"], "target": None, "portfolio": "1"}), ({"change_direction": "rises", "score_types": "application_security", "target": None, "portfolio": "1"}), ({"change_direction": "rises", "score_types": ["overall"], "target": "any_followed_company", "portfolio": None}), ] @pytest.mark.parametrize("args, expected_delivery", zip(alert_creation_inputs, test_data.get("delivery_objects").get("entries"))) def test_create_grade_alert_subscription_delivery_object(mocker, args, expected_delivery): """ Test the creation of a grade alert subscription delivery object. This test mocks the `create_alert_subscription` method of the `client` object to return a predefined alert subscription. It then verifies that the delivery object created by the `alert_grade_change_create_command` function matches the expected delivery structure and that the result outputs the correct alert ID. Args: mocker (MockerFixture): The mocker fixture used to patch methods. args (dict): The arguments for the alert creation. expected_delivery (dict): The expected delivery object. Raises: AssertionError: If the delivery object or result outputs do not match the expected values. """ create_grade_alert_mock = {"id": "test_alert_id"} mocker.patch.object(client, "create_alert_subscription", return_value=create_grade_alert_mock) result = alert_grade_change_create_command(client, args) actual_delivery = client.create_alert_subscription.call_args[1]["delivery"] assert actual_delivery == expected_delivery assert result.outputs == "test_alert_id" # Define the test data for score threshold alert creation tests threshold_alert_creation_inputs = [ { "change_direction": "drops_below", "threshold": 85, "score_types": ["overall"], "target": "any_followed_company", "portfolio": None, }, { "change_direction": "rises_above", "threshold": 70, "score_types": ["application_security", "test_factor"], "target": None, "portfolio": "1", }, ] @pytest.mark.parametrize( "args, expected_delivery", zip(threshold_alert_creation_inputs, test_data.get("threshold_delivery_objects").get("entries")) ) def test_create_score_threshold_alert_subscription_delivery_object(mocker, args, expected_delivery): """ Test the creation of a score threshold alert subscription delivery object. This test mocks the `create_alert_subscription` method of the `client` object to return a predefined alert subscription. It then verifies that the delivery object created by the `alert_score_threshold_create_command` function matches the expected delivery structure and that the result outputs the correct alert ID. Args: mocker (MockerFixture): The mocker fixture used to patch methods. args (dict): The arguments for the alert creation. expected_delivery (dict): The expected delivery object. Raises: AssertionError: If the delivery object or result outputs do not match the expected values. """ create_score_threshold_alert_mock = {"id": "test_alert_id"} mocker.patch.object(client, "create_alert_subscription", return_value=create_score_threshold_alert_mock) result = alert_score_threshold_create_command(client, args) actual_delivery = client.create_alert_subscription.call_args[1]["delivery"] assert actual_delivery == expected_delivery assert result.outputs == "test_alert_id" grade_alert_test_input = [ ({"change_direction": "rises", "score_types": "overall", "target": None, "portfolio": PORTFOLIO_ID}), ({"change_direction": "rises", "score_types": "application_security", "target": "my_scorecard", "portfolio": "1"}), ({"change_direction": "rises", "score_types": "application_security", "target": None, "portfolio": None}), ] @pytest.mark.parametrize("args", grade_alert_test_input) def test_create_grade_change_alert(mocker, args): """ Given: - Direction change - Score type(s) - Target or Portfolio When: - Case A: rising grade, overall score type, to portfolio - Case B: Both portfolio and target are specified - Case C: Neither portfolio and target are specified Then: - Case A: Alert created - Case D: DemistoException thrown - Case E: DemistoException thrown """ create_grade_alert_mock = test_data.get("create_grade_alert") mocker.patch.object(client, "create_alert_subscription", return_value=create_grade_alert_mock) if args.get("target") and args.get("portfolio"): with pytest.raises(DemistoException) as exc: alert_grade_change_create_command(client=client, args=args) assert "Both 'portfolio' and 'target' argument have been set" in str(exc.value) elif not args.get("target") and not args.get("portfolio"): with pytest.raises(DemistoException) as exc: alert_grade_change_create_command(client=client, args=args) assert "Either 'portfolio' or 'target' argument must be given" in str(exc.value) else: cmd_res: CommandResults = alert_grade_change_create_command(client=client, args=args) assert cmd_res.outputs == create_grade_alert_mock.get("id") score_alert_test_input = [ ({"change_direction": "rises", "threshold": 90, "score_types": "overall", "target": None, "portfolio": PORTFOLIO_ID}), ( { "change_direction": "rises", "threshold": 90, "score_types": "application_security", "target": "my_scorecard", "portfolio": "1", } ), ({"change_direction": "rises", "threshold": 90, "score_types": "application_security", "target": None, "portfolio": None}), ({"change_direction": "rises", "threshold": "A", "score_types": "application_security", "target": None, "portfolio": None}), ] @pytest.mark.parametrize("args", score_alert_test_input) def test_create_score_change_alert(mocker, args): """ Given: - A username - Direction change - Score type(s) - A threshold - Target or Portfolio When: - Case A: Username is valid, rising grade, overall score type, to portfolio - Case B: Both portfolio and target are specified - Case C: Neither portfolio and target are specified - Case D: Threshold supplied is not a number Then: - Case A: Alert created - Case B: DemistoException thrown - Case C: DemistoException thrown - Case D: ValueError thrown """ create_score_alert_mock = test_data.get("create_score_alert") mocker.patch.object(client, "create_alert_subscription", return_value=create_score_alert_mock) if not isinstance(args.get("threshold"), int): with pytest.raises(ValueError) as exc: alert_score_threshold_create_command(client=client, args=args) assert "is not a valid number" in str(exc.value) elif args.get("target") and args.get("portfolio"): with pytest.raises(DemistoException) as exc: alert_score_threshold_create_command(client=client, args=args) assert "Both 'portfolio' and 'target' argument have been set" in str(exc.value) elif not args.get("target") and not args.get("portfolio"): with pytest.raises(DemistoException) as exc: alert_score_threshold_create_command(client=client, args=args) assert "Either 'portfolio' or 'target' argument must be given" in str(exc.value) else: cmd_res: CommandResults = alert_score_threshold_create_command(client=client, args=args) assert cmd_res.outputs == create_score_alert_mock.get("id") services_test_input = (({"domain": DOMAIN}), ({"domain": DOMAIN_NE})) @pytest.mark.parametrize("args", services_test_input) def test_get_domain_services(mocker, args): """ Given: - A domain When: - Case A: Domain is valid - Case B: Domain is invalid Then: - Case A: List of services is returned - Case B: Bad request """ if args.get("domain") == DOMAIN_NE: services_mock = test_data.get("company_not_found") mocker.patch.object(client, "get_domain_services", return_value=services_mock) cmd_res: CommandResults = company_services_get_command(client=client, args=args) error = cmd_res.readable_output assert f"Error returning services for domain '{args.get('domain')}'" == error else: services_mock = test_data.get("services") mocker.patch.object(client, "get_domain_services", return_value=services_mock) cmd_res: CommandResults = company_services_get_command(client=client, args=args) services = cmd_res.outputs assert services == services_mock.get("entries") def test_incidents_to_import_empty_alerts_future_last_run(): """ Given: - An empty list of alerts - A last run date in the future When: - incidents_to_import is called Then: - An empty list of incidents is returned """ alerts = [] last_run = arg_to_datetime("2022-01-01T00:00:00.000Z").replace(tzinfo=None) incidents = incidents_to_import(alerts=alerts, last_run=last_run) assert isinstance(incidents, list) assert len(incidents) == 0 @pytest.mark.parametrize("limit", ["0"]) def test_portfolios_list_limit_zero(mocker, limit): """ Given: - A limit of 0 When: - portfolios_list is called Then: - An empty list of portfolios is returned """ mocker.patch.object(client, "get_portfolios", return_value=[]) portfolios = client.get_portfolios(limit=limit) assert isinstance(portfolios, list) assert len(portfolios) == 0 def test_incidents_to_import_alerts_last_run_in_past(): """ Given: - A list of alerts - A last run date in the past When: - incidents_to_import is called Then: - A list of incidents is returned """ mock = [ { "id": "27", "username": "user@domain.com", "change_type": "score_change", "domain": "domain1.co.il", "company_name": "Domain1", "portfolios": [{"id": "a", "name": "test_portfolio"}], "my_scorecard": False, # corrected false to False "created_at": "2021-07-27T00:00:00.000Z", "change_data": [ {"score": 77, "factor": "network_security", "direction": "drops", "grade_letter": "C", "score_impact": -3}, {"score": 69, "factor": "ip_reputation", "direction": "drops", "grade_letter": "D", "score_impact": -3}, ], } ] last_run = arg_to_datetime("2020-01-01T00:00:00.000Z").replace(tzinfo=None) incidents = incidents_to_import(alerts=mock, last_run=last_run) assert isinstance(incidents, list) assert len(incidents) > 0 @pytest.fixture def mocked_security_scorecard_client(): mocked_security_scorecard_client = SecurityScorecardClient( base_url="https://api.securityscorecard.io", verify=True, proxy=None, headers={"Content-Type": "application/json"}, username="your_username", api_key="your_api_key", ) mocked_security_scorecard_client.get_issue_metadata = MagicMock( return_value={ "key": "issue_type", "severity": "high", "factor": "network_security", "title": "Issue Title", "short_description": "Short description", "long_description": "Long description", "recommendation": "Recommendation", } ) mocked_security_scorecard_client.get_company_events = MagicMock( return_value={ "entries": [ { "id": "1", } ] } ) mocked_security_scorecard_client.get_company_event_findings = MagicMock( return_value={ "entries": [ { "id": "1", } ] } ) return mocked_security_scorecard_client def test_issue_metadata_get_command_success(mocked_security_scorecard_client): # Call the issue_metadata_get_command function with a valid issue type args = {"issue_type": "issue_type"} result = issue_metadata_get_command(mocked_security_scorecard_client, args) # Assert that the result is a CommandResults object with the expected attributes assert isinstance(result, CommandResults) assert result.readable_output.startswith("### Issue Type issue_type") assert "key|severity|factor|title|short_description|long_description|recommendation" in result.readable_output assert ( "| issue_type | high | network_security | Issue Title | Short description | Long description | Recommendation" in result.readable_output ) # noqa: E501 assert result.outputs == { "key": "issue_type", "severity": "high", "factor": "network_security", "title": "Issue Title", "short_description": "Short description", "long_description": "Long description", "recommendation": "Recommendation", } assert result.raw_response == { "key": "issue_type", "severity": "high", "factor": "network_security", "title": "Issue Title", "short_description": "Short description", "long_description": "Long description", "recommendation": "Recommendation", } assert result.outputs_key_field == "key" # Assert that the client's get_issue_metadata method was called with the expected arguments mocked_security_scorecard_client.get_issue_metadata.assert_called_with(issue_type="issue_type") def test_company_events_get_command_success(mocked_security_scorecard_client): # Call the company_events_get_command function with valid arguments args = {"domain": "example.com"} result = company_events_get_command(mocked_security_scorecard_client, args) # Assert that the result is a CommandResults object with the expected attributes assert isinstance(result, CommandResults) # assert result.outputs == output.COMPANY_EVENTS_GET_OUTPUTS # assert result.raw_response == output.COMPANY_EVENTS_GET_RAW_RESPONSE assert result.outputs_prefix == "SecurityScorecard.Company.Events" assert result.outputs_key_field == "ssc_event_id" # Assert that the mocked_security_scorecard_client's get_company_events method was called with the expected arguments mocked_security_scorecard_client.get_company_events.assert_called_with(domain="example.com", date_to=None, date_from=None) def test_company_event_findings_get_command_success(mocked_security_scorecard_client): # Call the company_event_findings_get_command function with valid arguments args = {"domain": "example.com", "event_id": "example_event_id"} result = company_event_findings_get_command(mocked_security_scorecard_client, args) # Assert that the result is a CommandResults object with the expected attributes assert isinstance(result, CommandResults) assert result.outputs_prefix == "SecurityScorecard.Company.Findings" assert result.outputs_key_field == "issue_id" # Assert that the mocked_security_scorecard_client's get_company_event_findings method was called with the expected arguments mocked_security_scorecard_client.get_company_event_findings.assert_called() def test_alert_rules_list_command(mocker): """ Given: - No specific arguments When: - Retrieving alert rules Then: - Ensure the alert rules are returned correctly """ alert_rules_mock = test_data.get("alert_rules") mocker.patch.object(client, "http_request_wrapper", return_value=alert_rules_mock) response_cmd_res: CommandResults = alert_rules_list_command(client=client, args={}) alert_rules = response_cmd_res.raw_response.get("entries") assert alert_rules == alert_rules_mock.get("entries") assert response_cmd_res.outputs_prefix == "SecurityScorecard.AlertRules.Rule" assert response_cmd_res.outputs_key_field == "id" def test_issue_details_get_command_success(mocker): """ Given: - A domain and issue type When: - Retrieving issue details for the specified domain and issue type Then: - Ensure the issue details are returned correctly """ mock_response = { "entries": [ { "domain": "example.com", "issue_id": "1", "issue_type": "spf_record_missing", "count": "2", "status": "active", "first_seen_time": "2023-01-01T00:00:00Z", "last_seen_time": "2023-01-02T00:00:00Z", "description": "SPF record is missing", "recommendation": "Add SPF record", } ] } mocker.patch.object(client, "get_company_issue_findings", return_value=mock_response) args = {"domain": "example.com", "issue_type": "spf_record_missing"} result = issue_details_get_command(client, args) assert isinstance(result, CommandResults) assert result.outputs == mock_response["entries"] assert result.outputs_prefix == "SecurityScorecard.IssueDetails" assert result.outputs_key_field == "issue_id" assert result.readable_output.startswith("### Domain example.com -- Findings for spf_record_missing") def test_issue_details_get_command_no_results(mocker): """ Given: - A domain and issue type When: - No issue details are found for the specified domain and issue type Then: - Ensure an empty result is returned """ mock_response = {"entries": []} mocker.patch.object(client, "get_company_issue_findings", return_value=mock_response) args = {"domain": "example.com", "issue_type": "spf_record_missing"} result = issue_details_get_command(client, args) assert isinstance(result, CommandResults) assert result.outputs == [] assert result.outputs_prefix == "SecurityScorecard.IssueDetails" assert result.outputs_key_field == "issue_id" assert "No findings were found" in result.readable_output def test_issue_details_get_command_invalid_domain(mocker): """ Given: - An invalid domain and issue type When: - Retrieving issue details for the specified domain and issue type Then: - Ensure an error is raised """ mocker.patch.object(client, "get_company_issue_findings", side_effect=DemistoException("Invalid domain")) args = {"domain": "invalid.com", "issue_type": "spf_record_missing"} with pytest.raises(DemistoException, match="Invalid domain"): issue_details_get_command(client, args)