Panorama

Manage Palo Alto Networks Firewall and Panorama. Use this pack to manage Prisma Access through Panorama. For more information, see the Panorama documentation.

Network Security · PAN-OS by Palo Alto Networks

Details

IDPanorama
ProviderPalo Alto Networks
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/pan-os-python:1.0.0.10133006
Supported ModulesAgentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud

README

This integration supports both Palo Alto Networks Panorama and Palo Alto Networks Firewall. You can create separate instances of each integration, and they are not necessarily related or dependent on one another.

This integration enables you to manage the Palo Alto Networks Firewall and Panorama. For more information see the PAN-OS documentation.
This integration was integrated and tested with versions 8.xx, 9.xx, 10.xx and 11.xx of Palo Alto Firewall and Palo Alto Panorama.

Use Cases

  • Create custom security rules in Palo Alto Networks PAN-OS.
  • Create and update address objects, address-groups, custom URL categories, and URL filtering objects.
  • Use the URL Filtering category information from Palo Alto Networks to enrich URLs by checking the use_url_filtering parameter. A valid license for the Firewall is required.
  • Get URL Filtering category information from Palo Alto. Request Change is a known Palo Alto limitation.
  • Add URL filtering objects including overrides to Palo Alto Panorama and Firewall.
  • Commit a configuration to Palo Alto Firewall and to Panorama, and push a configuration from Panorama to Pre-Defined Device-Groups of Firewalls.
  • Block IP addresses using registered IP tags from PAN-OS without committing the PAN-OS instance. First you have to create a registered IP tag, DAG, and security rule, and commit the instance. You can then register additional IP addresses to the tag without committing the instance.

    1. Create a registered IP tag and add the necessary IP addresses by running the pan-os-register-ip-tag command.

    2. Create a dynamic address group (DAG), by running the pan-os-create-address-group command. Specify values for the following arguments: type=”dynamic”, match={ tagname }.

    3. Create a security rule using the DAG created in the previous step, by running the pan-os-create-rule command.

    4. Commit the PAN-OS instance by running the PanoramaCommitConfiguration playbook.

    5. You can now register IP addresses to, or unregister IP addresses from the IP tag by running the pan-os-register-ip-tag command, or pan-os-unregister-ip-tag command, respectively, without committing the PAN-OS instance.

  • Create a predefined security profiles with the best practices by Palo Alto Networks.
  • Get security profiles best practices as defined by Palo Alto Networks. For more information about Palo Alto Networks best practices, visit Palo Alto Networks best practices.
  • Apply security profiles to specific rule.
  • Set default categories to block in the URL filtering profile.
  • Enforce WildFire best practice.

    1. Set file upload to the maximum size.

    2. Set WildFire Update Schedule to download and install updates every minute.

    3. All file types are forwarded.

Known Limitations

Fetch Incidents

The Panorama integration now supports fetch incidents.
The incidents are fetched according to a number of different optional log type queries. The log types are: Traffic, Threat, URL, Data, Correlation, System, Wildfire, Decryption.

Max incidents per fetch
  • The max incidents per fetch parameter specifies the maximum number of incidents to fetch per Log Type Query.
  • Important note: Cortex XSOAR standard setup is not designed to handle many hundreds of new incidents every minute. Therefore, it is strongly recommended to narrow your query by log type, severity, or other criteria to ensure that each fetch cycle retrieves no more than 200 incidents at a time.
Log Type

The queries that will be included during the fetch are decided according to the “Log Type” parameter (Multiple select dropdown).

  • Selecting “All” will use all the log type queries in the fetch.
  • To choose a specific set of queries, select their log types from the dropdown (make sure “All” option is unselected).
Log Type Query
  • Each log type has its own query field in the instance configuration.
  • Note that the default query values has some example text in it, make sure to enter a valid query.
  • Note: In case of multiple devices, for the sake of speed it is recommended to narrow the query to a specific device.
    For example: “and (device_name eq dummy_device)”.
Log Type Query Examples
Log Type Query Example
Traffic (addr.src in {source}) and (addr.dst in {destination}) and (action eq {action})
Threat (severity geq high)
URL ((action eq block-override) or (action eq block-url)) and (severity geq high)
Data ((action eq alert) or (action eq wildfire-upload-success) or (action eq forward)) and (severity geq high)
Correlation (hostid eq {host_id}) and (match_time in {last_x_time}) and (objectname eq {object_name}) and (severity geq ‘{severity}’) and (src in {source_address})
System (subtype eq {sub_type}) and (severity geq {severity})
Wildfire Submission ((action eq wildfire-upload-fail) or (action eq wildfire-upload-skip) or (action eq sinkhole))
Decryption (app eq {application}) and (policy_name geq {policy_name}) and ((src in {source}) or (dst in {destination}))
Classifiers and Mappers

This integration supports a default Classifier (Panorama Classifier) and Mapper (Panorama Mapper) that handles incidents returned from the API.

Configure Panorama on Cortex XSOAR

  1. Navigate to Settings > Integrations > Instances.
  2. Search for Panorama.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required  
server Server URL (e.g., https://192.168.0.1) True  
port Port (e.g 443) False  
key API Key True  
device_group Device group - Panorama instances only (write shared for Shared location) False  
vsys Vsys - Firewall instances only False  
template Template - Panorama instances only False **
use_url_filtering Use URL Filtering for auto enrichment False  
additional_suspicious URL Filtering Additional suspicious categories. CSV list of categories that will be considered suspicious. False  
additional_malicious URL Filtering Additional malicious categories. CSV list of categories that will be considered malicious. False  
insecure Trust any certificate (not secure) False  
First fetch timestamp First fetch time interval False  
Max incidents per fetch Max incidents per fetch for each selected Log Type Query False  
Log Type Log Types incidents to fetch False **
Log type max number of pull attempts The maximum number of attempts to try and pull results for each log type. Each attempt takes around 1 second. Increasing this value is useful in case there are many logs to pull from a given log type.Note: When increasing this number, in case fetching more than 4 logs types together, it is recommended to split different log types for different integration instance False  
Traffic Log Type Query Traffic Query for fetch incidents False  
Threat Log Type Query Threat Query for fetch incidents False  
URL Log Type Query URL Query for fetch incidents False  
Data Log Type Query Data Query for fetch incidents False  
Correlation Log Type Query Correlation Query for fetch incidents False  
System Log Type Query System Query for fetch incidents False  
Wildfire Submission Log Type Query Wildfire Submission Query for fetch incidents False  
Decryption Log Type Query Decryption Query for fetch incidents False  
Incidents Fetch Interval Time interval between incident fetches False  
  1. Click Test to validate the URLs, token, and connection.

Debugging in Panorama

In order to ease the process of understanding what parameters are required to be used in the !pan-os command, it is highly recommended to use the debugging mode in Panorama to get the correct structure of a request.

Debugging Methods:

Several Examples of !pan-os for a configuration type commands:

1) Create a new address object named test123 for the test device-group.

Given the following debug-log from PAN-OS Web UI Debug after creating an address through the Panorama UI:

`

1.1.1.1

`

The equivalent !pan-os command is:

`
!pan-os action=set xpath=/config/devices/entry[@name=’localhost.localdomain’]/device-group/entry[@name=’test’]/address/entry[@name=’test123’] type=config element=1.1.1.1
`

Argument Description
action Create/add an object. In this case we want to create a new address object, so we will use set - the Panorama debug log shows us its a ‘set’ action.
xpath /config/devices/entry[@name=’localhost.localdomain’]/device-group/entry[@name=’test’]/address/entry[@name=’test123’] - simply the location of the new object.
type This is a configuration type command, therefore use config.
element The object properties (similar to an API body request).

2) Modify an existing address group object named test12345 under the test device group to use a different address object.

Given the following debug-log from PAN-OS Web UI Debug after editing an address group through the Panorama UI to use a different address object:

`

test123

`

The equivalent !pan-os command is:

`
!pan-os action=edit xpath=/config/devices/entry[@name=’localhost.localdomain’]/device-group/entry[@name=’test’]/address-group/entry[@name=’test12345’] type=config element=test123
`

Argument Description
action Edit an object, in this case we want to edit an entry in an existing address group object, so we will use edit - the panorama debug log shows us its an ‘edit’ action.
xpath /config/devices/entry[@name=’localhost.localdomain’]/device-group/entry[@name=’test’]/address-group/entry[@name=’test12345’ - simply the location of the object.
type This is a configuration type command, therefore use config.
element The object properties (similar to an API body request).

3) Get a specific security pre-rule called test1.

Using the API browser, we can easily find the xpath for the security pre-rule object, therefore the pan-os command will be:

`
!pan-os xpath=/config/devices/entry[@name=’localhost.localdomain’]/device-group/entry[@name=’test’]/pre-rulebase/security/rules/entry[@name=’test1’], action=get type=config
`

Argument Description
action Get an object, in this case we want to get an object, so we will use ‘get’ as an action.
xpath By using the API browser, we can find every object’s xpath easily.
type This is a configuration type command, therefore use config.

Several examples of !pan-os for an operational type command:

1) Show system information - Can be viewed by using the API browser to get the structure of the request.

Show System Info Operational command

The equivalent !pan-os command is:

`
!pan-os type=op cmd=
`

2) Show information about all the jobs - Can be viewed by using the API browser to get the structure of the request.

Show all jobs information

The equivalent !pan-os command is:

`
!pan-os type=op cmd=
`

Commands

You can execute these commands from the Cortex XSOAR 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.

NOTE: The “create” commands function as UPSERT (UPDATE and INSERT), overriding existing data with new data if it already exists.

  1. Run any command supported in the Panorama API: pan-os
  2. Get pre-defined threats list from a Firewall or Panorama and stores as a JSON file in the context: panorama-get-predefined-threats-list
  3. Commit a configuration: panorama-commit
  4. Pushes rules from PAN-OS to the configured device group: panorama-push-to-device-group
  5. Returns a list of addresses: panorama-list-addresses
  6. Returns address details for the supplied address name: panorama-get-address
  7. Creates an address object: panorama-create-address
  8. Delete an address object: panorama-delete-address
  9. Returns a list of address groups: panorama-list-address-groups
  10. Get details for the specified address group: panorama-get-address-group
  11. Creates a static or dynamic address group: panorama-create-address-group
  12. Sets a vulnerability signature to block mode: panorama-block-vulnerability
  13. Deletes an address group: panorama-delete-address-group
  14. Edits a static or dynamic address group: panorama-edit-address-group
  15. Returns a list of addresses: panorama-list-services
  16. Returns service details for the supplied service name: panorama-get-service
  17. Creates a service: panorama-create-service
  18. Deletes a service: panorama-delete-service
  19. Returns a list of service groups: panorama-list-service-groups
  20. Returns details for the specified service group: panorama-get-service-group
  21. Creates a service group: panorama-create-service-group
  22. Deletes a service group: panorama-delete-service-group
  23. Edit a service group: panorama-edit-service-group
  24. Returns information for a custom URL category: panorama-get-custom-url-category
  25. Creates a custom URL category: panorama-create-custom-url-category
  26. Deletes a custom URL category: panorama-delete-custom-url-category
  27. Adds or removes sites to and from a custom URL category: panorama-edit-custom-url-category
  28. Gets a URL category from URL Filtering: panorama-get-url-category
  29. Gets a URL information: url
  30. Returns a URL category from URL Filtering in the cloud: panorama-get-url-category-from-cloud
  31. Returns a URL category from URL Filtering on the host: panorama-get-url-category-from-host
  32. Returns information for a URL filtering rule: panorama-get-url-filter
  33. Creates a URL filtering rule: panorama-create-url-filter
  34. Edit a URL filtering rule: panorama-edit-url-filter
  35. Deletes a URL filtering rule: panorama-delete-url-filter
  36. Returns a list of external dynamic lists: panorama-list-edls
  37. Returns information for an external dynamic list: panorama-get-edl
  38. Creates an external dynamic list: panorama-create-edl
  39. Modifies an element of an external dynamic list: panorama-edit-edl
  40. Deletes an external dynamic list: panorama-delete-edl
  41. Refreshes the specified external dynamic list: panorama-refresh-edl
  42. Creates a policy rule: panorama-create-rule
  43. Creates a custom block policy rule: panorama-custom-block-rule
  44. Changes the location of a policy rule: panorama-move-rule
  45. Edits a policy rule: panorama-edit-rule
  46. Deletes a policy rule: panorama-delete-rule
  47. Returns a list of applications: panorama-list-applications
  48. Returns commit status for a configuration: panorama-commit-status
  49. Returns the push status for a configuration: panorama-push-status
  50. Returns information for a Panorama PCAP file: panorama-get-pcap
  51. Returns a list of all PCAP files by PCAP type: panorama-list-pcaps
  52. Registers IP addresses to a tag: panorama-register-ip-tag
  53. Unregisters IP addresses from a tag: panorama-unregister-ip-tag
  54. Registers Users to a tag: panorama-register-user-tag
  55. Unregisters Users from a tag: panorama-unregister-user-tag
  56. Deprecated. Queries traffic logs: panorama-query-traffic-logs
  57. Deprecated. Checks the query status of traffic logs: panorama-check-traffic-logs-status
  58. Deprecated. Retrieves traffic log query data by job id: panorama-get-traffic-logs
  59. Returns a list of predefined Security Rules: panorama-list-rules
  60. Query logs in Panorama: panorama-query-logs
  61. Checks the status of a logs query: panorama-check-logs-status
  62. Retrieves the data of a logs query: panorama-get-logs
  63. Checks whether a session matches the specified security policy: panorama-security-policy-match
  64. Lists the static routes of a virtual router: panorama-list-static-routes
  65. Returns the specified static route of a virtual router: panorama-get-static-route
  66. Adds a static route: panorama-add-static-route
  67. Deletes a static route: panorama-delete-static-route
  68. Show firewall device software version: panorama-show-device-version
  69. Downloads the latest content update: panorama-download-latest-content-update
  70. Checks the download status of a content update: panorama-content-update-download-status
  71. Installs the latest content update: panorama-install-latest-content-update
  72. Gets the installation status of the content update: panorama-content-update-install-status
  73. Checks the PAN-OS software version from the repository: panorama-check-latest-panos-software
  74. Downloads the target PAN-OS software version to install on the target device: panorama-download-panos-version
  75. Gets the download status of the target PAN-OS software: panorama-download-panos-status
  76. Installs the target PAN-OS version on the specified target device: panorama-install-panos-version
  77. Gets the installation status of the PAN-OS software: panorama-install-panos-status
  78. Reboots the Firewall device: panorama-device-reboot
  79. Gets location information for an IP address: panorama-show-location-ip
  80. Gets information about available PAN-OS licenses and their statuses: panorama-get-licenses
  81. Gets information for the specified security profile: panorama-get-security-profiles
  82. Apply a security profile to specific rules or rules with a specific tag: panorama-apply-security-profile
  83. Removes a security profile to specific rules or rules with a specific tag
  84. Get SSL decryption rules: panorama-get-ssl-decryption-rules
  85. Retrieves the Wildfire configuration: panorama-get-wildfire-configuration
  86. Set default categories to block in the URL filtering profile: panorama-url-filtering-block-default-categories
  87. Get anti-spyware best practices: panorama-get-anti-spyware-best-practice
  88. Get file-blocking best practices: panorama-get-file-blocking-best-practice
  89. Get anti-virus best practices: panorama-get-antivirus-best-practice
  90. Get vulnerability-protection best practices: panorama-get-vulnerability-protection-best-practice
  91. View WildFire best practices: panorama-get-wildfire-best-practice
  92. View URL Filtering best practices: panorama-get-url-filtering-best-practice
  93. Enforces wildfire best practices to upload files to the maximum size, forwards all file types, and updates the schedule: panorama-enforce-wildfire-best-practice
  94. Creates an antivirus best practice profile: panorama-create-antivirus-best-practice-profile
  95. Creates an Anti-Spyware best practice profile: panorama-create-anti-spyware-best-practice-profile
  96. Creates a vulnerability protection best practice profile: panorama-create-vulnerability-best-practice-profile
  97. Creates a URL filtering best practice profile: panorama-create-url-filtering-best-practice-profile
  98. Creates a file blocking best practice profile: panorama-create-file-blocking-best-practice-profile
  99. Creates a WildFire analysis best practice profile: panorama-create-wildfire-best-practice-profile
  100. Shows the user ID interface configuration.
  101. Shows the zones configuration.
  102. Retrieves list of user-ID agents configured in the system.
  103. Gets global counter information from all the PAN-OS firewalls in the topology.
  104. Retrieves all BGP peer information from the PAN-OS firewalls in the topology.
  105. Check the devices for software that is available to be installed.
  106. Get the HA state and associated details from the given device and any other details.
  107. Get all the jobs from the devices in the environment, or a single job when ID is specified.
  108. Download The provided software version onto the device.
  109. Download the running configuration
  110. Download the merged configuration
  111. Create Nat-rule
  112. Create PBF-rule

pan-os


Run any command supported in the API.

Base Command

pan-os

Input

Argument Name Description Required
action Action to be taken, such as show, get, set, edit, delete, rename, clone, move, override, multi-move, multi-clone, or complete. Documentation - https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-panorama-api/pan-os-xml-api-request-types/configuration-api Optional
category Category parameter. For example, when exporting a configuration file, use “category=configuration”. Optional
cmd Specifies the XML structure that defines the command. Used for operation commands (op type command). Can be retrieved from the PAN-OS web UI debugger or enabling debugging via the CLI using debug cli on. Optional
command Run a command. For example, command =<show><arp><entry name=’all’/></arp></show> Optional
dst Specifies a destination. Optional
element Used to define a new value for an object. Should be an XML object, for example, test. Optional
to End time (used only when cloning an object). Optional
from Start time (used only when cloning an object). Optional
key Sets a key value. Optional
log-type Retrieves log types. For example, log-type=threat for threat logs. Optional
where Specifies the type of a move operation (for example, where=after, where=before, where=top, where=bottom). Optional
period Time period. For example, period=last-24-hrs Optional
xpath xpath location. xpath defines the location of the object. For example, xpath=/config/predefined/application/entry[@name=’hotmail’]. Documentation - https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-panorama-api/about-the-pan-os-xml-api/structure-of-a-pan-os-xml-api-request/xml-and-xpath. Optional
pcap-id PCAP ID included in the threat log. Optional
serialno Specifies the device serial number. Optional
reporttype Chooses the report type, such as dynamic, predefined or custom. Optional
reportname Report name. Optional
type Request type (e.g. export, import, log, config). Possible values are: keygen, config, commit, op, report, log, import, export, user-id, version. default is config. Optional
search-time The time that the PCAP was received on the firewall. Used for threat PCAPs. Optional
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
job-id Job ID. Optional
query Query string. Optional
vsys The name of the virtual system to be configured. If no vsys is mentioned, this command will not use the vsys parameter. Optional
newname The object’s new name, used when action=rename. If no value is provided, the name defaults to ‘newname’. Optional

Context Output

There is no context output for this command.

Command Example

!pan-os xpath=“/config/devices/entry[@name=‘localhost.localdomain’]/template/entry[@name=‘test’]/config/devices/entry[@name=‘localhost.localdomain’]/network/profiles/zone-protection-profile/entry[@name=‘test’]/scan-white-list/entry[@name=‘test’]/ipv4" type=config action=edit element=“<ipv4>1.1.1.1</ipv4>”

Human Readable Output

Command was executed successfully.

pan-os-get-predefined-threats-list


Gets the pre-defined threats list from a Firewall or Panorama and stores as a JSON file in the context.

Base Command

pan-os-get-predefined-threats-list

Input

Argument Name Description Required
target The firewall managed by Panorama from which to retrieve the predefined threats. Optional

Context Output

Path Type Description
File.Size number File size.
File.Name string File name.
File.Type string File type.
File.Info string File info.
File.Extension string File extension.
File.EntryID string File entryID.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.SHA512 string SHA512 hash of the file.
File.SSDeep string SSDeep hash of the file.

Command Example


### pan-os-commit

***
Commits a configuration to the Palo Alto firewall or Panorama, validates if a commit was successful if using polling="true", otherwise does not validate if the commit was successful. Committing to Panorama does not push the configuration to the firewalls. To push the configuration, run the panorama-push-to-device-group command.

#### Base Command

`pan-os-commit`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| device-group | Panorama only. Limits the commit scope to the specified device group(s), so only pending changes for those device groups are committed. If omitted, all pending changes are committed. | Optional |
| template | Panorama only. Limits the commit scope to the specified template(s), so only pending changes for those templates are committed. If omitted, all pending changes are committed. | Optional |
| description | The commit description. | Optional |
| admin_name | The administrator name. To commit admin-level changes on a firewall, include the administrator name in the request. | Optional |
| force_commit | Forces a commit. Possible values are: true, false. | Optional |
| exclude_device_network_configuration | Performs a partial commit while excluding device and network configuration. Possible values are: true, false. | Optional |
| exclude_shared_objects | Performs a partial commit while excluding shared objects. Possible values are: true, false. | Optional |
| polling | Whether to use polling. Possible values are: true, false. Default is false. | Optional |
| commit_job_id | commit job ID to use in polling commands. (automatically filled by polling). | Optional |
| timeout | The timeout (in seconds) when polling. Default is 120. | Optional |
| interval_in_seconds | The interval (in seconds) when polling. Default is 10. | Optional |
| hide_polling_output | whether to hide the polling result (automatically filled by polling). | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Commit.JobID | Number | The job ID to commit. |
| Panorama.Commit.Status | String | The commit status. |
| Panorama.Commit.Description | String | The commit description from the the command input. |
| Panorama.Commit.Scope | String | Whether the commit is partial. |
| Panorama.Commit.Details | String | The summary of the targeted device group and templates. |

#### Command example with polling

```!pan-os-commit description=test polling=true interval_in_seconds=5 timeout=60```

#### Human Readable Output

>Waiting for commit "test" with job ID 12345 to finish...
>
>### Commit Status
>
>|JobID|Status| Description
>|---|---|---|
>| 12345 | Success | test

#### Context Example

```json
{
    "Panorama": {
        "Commit": {
            "JobID": "12345",
            "Status": "Success",
            "Description": "test",
            "Scope": "Full",
            "Details": "Full commit"
        }
    }
}

Command example without polling

!pan-os-commit description=test

Human Readable Output

Commit Status

JobID Status Description
12345 Pending test

Context Example

{
    "Panorama": {
        "Commit": {
            "JobID": "12345",
            "Status": "Pending",
            "Description": "test",
            "Scope": "Full",
            "Details": "Full commit"
        }
    }
}

pan-os-push-to-device-group


Pushes rules from PAN-OS to the configured device group. In order to push the configuration to Prisma Access managed tenants (single or multi tenancy), use the device group argument with the device group which is associated with the tenant ID. Validates if a push has been successful if polling=”true”.

Base Command

pan-os-push-to-device-group

Input

Argument Name Description Required
device-group The device group to which to push (Panorama instances). Optional
validate-only Pre policy validation. Possible values are: true, false. Default is false. Optional
include-template Whether to include template changes. Possible values are: true, false. Default is true. Optional
description The push description. Optional
serial_number The serial number for a virtual system commit. If provided, the commit will be a virtual system commit. Optional
polling Whether to use polling. Possible values are: true, false. Default is false. Optional
timeout The timeout (in seconds) when polling. Default is 120. Optional
interval_in_seconds The interval (in seconds) when polling. Default is 10. Optional

Context Output

Path Type Description
Panorama.Push.DeviceGroup String The device group in which the policies were pushed.
Panorama.Push.JobID Number The job ID of the policies that were pushed.
Panorama.Push.Status String The push status.
Panorama.Push.Warnings String The push warnings.
Panorama.Push.Errors String The push errors.
Panorama.Push.Details String The job ID details.

Command example with polling=true

!pan-os-push-to-device-group description=test polling=true interval_in_seconds=5 timeout=60

Context Example

{
    "Panorama": {
        "Push": {
            "Details": [
                "commit succeeded with warnings",
                "commit succeeded with warnings"
            ],
            "Errors": ,
            "JobID": "31377",
            "Status": "Completed",
            "Warnings": [
                "Interface loopback.645 has no zone configuration.",
                "External Dynamic List test_pb_domain_edl_DONT_DEL is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - IP EDL-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - URL EDL-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - URL EDL tamarcat3-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - IP EDL tamarcat3-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List minemeld is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-urls-OLD is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-ips is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-domains is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "Warning: No valid Antivirus content package exists",
                "(Module: device)"
            ]
        }
    }
}

Human Readable Output

Waiting for Job-ID 31374 to finish push changes to device-group Lab-Devices..

Push to Device Group status

JobID Status Details Errors Warnings
31377 Completed commit succeeded with warnings,
commit succeeded with warnings
  Interface loopback.645 has no zone configuration.,
External Dynamic List test_pb_domain_edl_DONT_DEL is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - IP EDL-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - URL EDL-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - URL EDL tamarcat3-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - IP EDL tamarcat3-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List minemeld is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-urls-OLD is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-ips is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-domains is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
Warning: No valid Antivirus content package exists,
(Module: device)

Command example with polling=false

!pan-os-push-to-device-group description=test polling=false

Human Readable Output

Push to Device Group status

JobID Status Description
113198 Pending test

Context Example

{
    "Panorama": {
        "Push": {
          "JobID": "113198",
          "Status": "Pending",
          "Description": "test",
          "DeviceGroup": "device group name"
        }
    }
}

pan-os-list-addresses


Returns a list of addresses.

Base Command

pan-os-list-addresses

Input

Argument Name Description Required
device-group The device group for which to return addresses (Panorama instances). Optional
tag Tag for which to filter the list of addresses. Optional

Context Output

Path Type Description
Panorama.Addresses.Name string Address name.
Panorama.Addresses.Description string Address description.
Panorama.Addresses.FQDN string Address FQDN.
Panorama.Addresses.IP_Netmask string Address IP Netmask.
Panorama.Addresses.IP_Range string Address IP range.
Panorama.Addresses.DeviceGroup String Address device group.
Panorama.Addresses.Tags String Address tags.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "Addresses": [
            {
                "IP_Netmask": "10.10.10.1/24",
                "Name": "Demisto address"
            },
            {
                "Description": "a",
                "IP_Netmask": "1.1.1.1",
                "Name": "test1"
            }
        ]
    }
}

Human Readable Output

Addresses

Name IP_Netmask IP_Range FQDN
Demisto address 10.10.10.1/24    
test1 1.1.1.1    

pan-os-get-address


Returns address details for the supplied address name.

Base Command

pan-os-get-address

Input

Argument Name Description Required
name Address name. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.Addresses.Name string Address name.
Panorama.Addresses.Description string Address description.
Panorama.Addresses.FQDN string Address FQDN.
Panorama.Addresses.IP_Netmask string Address IP Netmask.
Panorama.Addresses.IP_Range string Address IP range.
Panorama.Addresses.DeviceGroup String Device group for the address (Panorama instances).
Panorama.Addresses.Tags String Address tags.

Command Example

!pan-os-get-address name="Demisto address"

Context Example

{
    "Panorama": {
        "Addresses": {
            "IP_Netmask": "10.10.10.1/24",
            "Name": "Demisto address"
        }
    }
}

Human Readable Output

Address

Name IP_Netmask
Demisto address 10.10.10.1/24

pan-os-create-address


Creates an address object.

Base Command

pan-os-create-address

Input

Argument Name Description Required
name New address name. Required
description New address description. Optional
fqdn FQDN of the new address. Optional
ip_netmask IP Netmask of the new address. For example, 10.10.10.10/24 Optional
ip_range IP range of the new address IP. For example, 10.10.10.0-10.10.10.255 Optional
ip_wildcard The IP wildcard of the new address. For example, 10.20.1.0/0.0.248.255 Optional
device-group The device group for which to return addresses (Panorama instances). Optional
tag The tag for the new address. Optional
create_tag Whether to create the tag if it does not exist. Optional

Context Output

Path Type Description
Panorama.Addresses.Name string Address name.
Panorama.Addresses.Description string Address description.
Panorama.Addresses.FQDN string Address FQDN.
Panorama.Addresses.IP_Netmask string Address IP Netmask.
Panorama.Addresses.IP_Range string Address IP range.
Panorama.Addresses.DeviceGroup String Device group for the address (Panorama instances).
Panorama.Addresses.Tags String Address tags.

Command Example

!pan-os-create-address name="address_test_pb" description="just a desc" ip_range="10.10.10.9-10.10.10.10"

Context Example

{
    "Panorama": {
        "Addresses": {
            "Description": "just a desc",
            "IP_Range": "10.10.10.9-10.10.10.10",
            "Name": "address_test_pb"
        }
    }
}

Human Readable Output

Address was created successfully.

pan-os-delete-address


Delete an address object

Base Command

pan-os-delete-address

Input

Argument Name Description Required
name Name of the address to delete. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.Addresses.Name string Address name that was deleted.
Panorama.Addresses.DeviceGroup String Device group for the address (Panorama instances).

Command Example

!pan-os-delete-address name="address_test_pb"

Context Example

{
    "Panorama": {
        "Addresses": {
            "Name": "address_test_pb"
        }
    }
}

Human Readable Output

Address was deleted successfully.

pan-os-list-address-groups


Returns a list of address groups.

Base Command

pan-os-list-address-groups

Input

Argument Name Description Required
device-group The device group for which to return addresses (Panorama instances). Optional
tag Tag for which to filter the Address groups. Optional

Context Output

Path Type Description
Panorama.AddressGroups.Name string Address group name.
Panorama.AddressGroups.Type string Address group type.
Panorama.AddressGroups.Match string Dynamic Address group match.
Panorama.AddressGroups.Description string Address group description.
Panorama.AddressGroups.Addresses String Static Address group addresses.
Panorama.AddressGroups.DeviceGroup String Device group for the address group (Panorama instances).
Panorama.AddressGroups.Tags String Address group tags.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "AddressGroups": [
            {
                "Match": "2.2.2.2",
                "Name": "a_g_1",
                "Type": "dynamic"
            },
            {
                "Addresses": [
                    "Demisto address",
                    "test3",
                    "test_demo3"
                ],
                "Name": "Demisto group",
                "Type": "static"
            },
            {
                "Description": "jajja",
                "Match": "4.4.4.4",
                "Name": "dynamic2",
                "Type": "dynamic"
            },
            {
                "Addresses": [
                    "test4",
                    "test2"
                ],
                "Name": "static2",
                "Type": "static"
            }
        ]
    }
}

Human Readable Output

Address groups

Name Type Addresses Match Description Tags
a_g_1 dynamic   2.2.2.2    
Demisto group static Demisto address,
test3,
test_demo3
     
dynamic2 dynamic   4.4.4.4 jajja  
static2 static test4,
test2
     

pan-os-get-address-group


Get details for the specified address group

Base Command

pan-os-get-address-group

Input

Argument Name Description Required
name Address group name. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.AddressGroups.Name string Address group name.
Panorama.AddressGroups.Type string Address group type.
Panorama.AddressGroups.Match string Dynamic Address group match.
Panorama.AddressGroups.Description string Address group description.
Panorama.AddressGroups.Addresses string Static Address group addresses.
Panorama.AddressGroups.DeviceGroup String Device group for the address group (Panorama instances).
Panorama.AddressGroups.Tags String Address group tags.

Command Example

!pan-os-get-address-group name=suspicious_address_group

Human Readable Output

Address groups

Name Type Addresses Match Description
suspicious_address_group dynamic 1.1.1.1 this ip is very bad  

pan-os-create-address-group


Creates a static or dynamic address group.

Base Command

pan-os-create-address-group

Input

Argument Name Description Required
name Address group name. Required
type Address group type. Required
match Dynamic Address group match. e.g: “1.1.1.1 or 2.2.2.2” Optional
addresses Static address group list of addresses. Optional
description Address group description. Optional
device-group The device group for which to return addresses (Panorama instances). Optional
tags The tags for the Address group. Optional

Context Output

Path Type Description
Panorama.AddressGroups.Name string Address group name.
Panorama.AddressGroups.Type string Address group type.
Panorama.AddressGroups.Match string Dynamic Address group match.
Panorama.AddressGroups.Addresses string Static Address group list of addresses.
Panorama.AddressGroups.Description string Address group description.
Panorama.AddressGroups.DeviceGroup String Device group for the address group (Panorama instances).
Panorama.AddressGroups.Tags String Address group tags.

Command Example

!pan-os-create-address-group name=suspicious_address_group type=dynamic match=1.1.1.1 description="this ip is very bad"

Context Example

{
    "Panorama": {
        "AddressGroups": {
            "Description": "this ip is very bad",
            "Match": "1.1.1.1",
            "Name": "suspicious_address_group",
            "Type": "dynamic"
        }
    }
}

Human Readable Output

Address group was created successfully.

pan-os-block-vulnerability


Sets a vulnerability signature to block mode.

Base Command

pan-os-block-vulnerability

Input

Argument Name Description Required
drop_mode Type of session rejection. Possible values are: “drop”, “alert”, “block-ip”, “reset-both”, “reset-client”, and “reset-server”.’ Default is “drop”. Optional
vulnerability_profile Name of vulnerability profile. Required
threat_id Numerical threat ID. Required

Context Output

Path Type Description
Panorama.Vulnerability.ID string ID of vulnerability that has been blocked/overridden.
Panorama.Vulnerability.NewAction string New action for the vulnerability.

Command Example

!pan-os-block-vulnerability threat_id=18250 vulnerability_profile=name

Human Readable Output

Threat with ID 18250 overridden.

pan-os-delete-address-group


Deletes an address group.

Base Command

pan-os-delete-address-group

Input

Argument Name Description Required
name Name of address group to delete. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.AddressGroups.Name string Name of address group that was deleted.
Panorama.AddressGroups.DeviceGroup String Device group for the address group (Panorama instances).

Command Example

!pan-os-delete-address-group name="dynamic_address_group_test_pb3"

Human Readable Output

Address group was deleted successfully

pan-os-edit-address-group


Edits a static or dynamic address group.

Base Command

pan-os-edit-address-group

Input

Argument Name Description Required
name Name of the address group to edit. Required
type Address group type. Required
match Address group new match. For example, ‘1.1.1.1 and 2.2.2.2’. Optional
element_to_add Element to add to the list of the static address group. Only existing Address objects can be added. Optional
element_to_remove Element to remove from the list of the static address group. Only existing Address objects can be removed. Optional
description Address group new description. Optional
tags The tag of the Address group to edit. Optional
device-group The device group in which the address group belongs to. Optional

Context Output

Path Type Description
Panorama.AddressGroups.Name string Address group name.
Panorama.AddressGroups.Type string Address group type.
Panorama.AddressGroups.Filter string Dynamic Address group match.
Panorama.AddressGroups.Description string Address group description.
Panorama.AddressGroups.Addresses string Static Address group addresses.
Panorama.AddressGroups.DeviceGroup String Device group for the address group (Panorama instances).
Panorama.AddressGroups.Tags String Address group tags.

pan-os-list-services


Returns a list of addresses.

Base Command

pan-os-list-services

Input

Argument Name Description Required
device-group The device group for which to return addresses (Panorama instances). Optional
tag Tag for which to filter the Services. Optional

Context Output

Path Type Description
Panorama.Services.Name string Service name.
Panorama.Services.Protocol string Service protocol.
Panorama.Services.Description string Service description.
Panorama.Services.DestinationPort string Service destination port.
Panorama.Services.SourcePort string Service source port.
Panorama.Services.DeviceGroup string Device group in which the service was configured (Panorama instances).
Panorama.Services.Tags String Service tags.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "Services": [
            {
                "Description": "rgfg",
                "DestinationPort": "55",
                "Name": "demisto_service1",
                "Protocol": "tcp",
                "SourcePort": "567-569"
            },
            {
                "Description": "mojo",
                "DestinationPort": "55",
                "Name": "demi_service_test_pb",
                "Protocol": "sctp",
                "SourcePort": "60"
            },
        ]
    }
}

Human Readable Output

Services

Name Protocol SourcePort DestinationPort Description
demisto_service1 tcp 567-569 55 rgfg
demi_service_test_pb sctp 60 55 mojo

pan-os-get-service


Returns service details for the supplied service name.

Base Command

pan-os-get-service

Input

Argument Name Description Required
name Service name. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.Services.Name string Service name.
Panorama.Services.Protocol string Service protocol.
Panorama.Services.Description string Service description.
Panorama.Services.DestinationPort string Service destination port.
Panorama.Services.SourcePort string Service source port.
Panorama.Services.DeviceGroup string Device group for the service (Panorama instances).
Panorama.Service.Tags String Service tags.

Command Example

!pan-os-get-service name=demisto_service1

Human Readable Output

Address

Name Protocol SourcePort DestinationPort Description
demisto_service1 tcp 567-569 55 rgfg

pan-os-create-service


Creates a service.

Base Command

pan-os-create-service

Input

Argument Name Description Required
name The name for the new service. Required
protocol The protocol for the new service. Possible values are: tcp, udp, sctp. Required
destination_port The destination port for the new service. Required
source_port The source port for the new service. Optional
description The description for the new service. Optional
device-group The device group for which to return addresses (Panorama instances). Optional
tags The tags for the new service. Optional

Context Output

Path Type Description
Panorama.Services.Name string The service name.
Panorama.Services.Protocol string The service protocol.
Panorama.Services.Description string The service description.
Panorama.Services.DestinationPort string The service destination port.
Panorama.Services.SourcePort string The service source port.
Panorama.Services.DeviceGroup string The device group for the service (Panorama instances).
Panorama.Services.Tags String The service tags.

Command Example

!pan-os-create-service name=guy_ser3 protocol=udp destination_port=36 description=bfds

Context Example

{
    "Panorama": {
        "Services": {
            "Description": "bfds",
            "DestinationPort": "36",
            "Name": "guy_ser3",
            "Protocol": "udp"
        }
    }
}

Human Readable Output

Service was created successfully.

pan-os-delete-service


Deletes a service.

Base Command

pan-os-delete-service

Input

Argument Name Description Required
name Name of the service to delete. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.Services.Name string Name of the deleted service.
Panorama.Services.DeviceGroup string Device group for the service (Panorama instances).

Command Example

!pan-os-delete-service name=guy_ser3

Context Example

{
    "Panorama": {
        "Services": {
            "Name": "guy_ser3"
        }
    }
}

Human Readable Output

Service was deleted successfully.

pan-os-list-service-groups


Returns a list of service groups.

Base Command

pan-os-list-service-groups

Input

Argument Name Description Required
device-group The device group for which to return addresses (Panorama instances). Optional
tag Tags for which to filter the Service groups. Optional

Context Output

Path Type Description
Panorama.ServiceGroups.Name string Service group name.
Panorama.ServiceGroups.Services string Service group related services.
Panorama.ServiceGroups.DeviceGroup string Device group for the service group (Panorama instances).
Panorama.ServiceGroups.Tags String Service group tags.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "ServiceGroups": [
            {
                "Name": "demisto_default_service_groups",
                "Services": [
                    "service-http",
                    "service-https"
                ]
            },
            {
                "Name": "demisto_test_pb_service_group",
                "Services": "serice_tcp_test_pb"
            }
        ]
    }
}

Human Readable Output

Service groups

Name Services
demisto_default_service_groups service-http,
service-https
demisto_test_pb_service_group service_tcp_test_pb

pan-os-get-service-group


Returns details for the specified service group.

Base Command

pan-os-get-service-group

Input

Argument Name Description Required
name Service group name. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.ServiceGroups.Name string Service group name.
Panorama.ServiceGroups.Services string Service group related services.
Panorama.ServiceGroups.DeviceGroup string Device group for the service group (Panorama instances).
Panorama.ServiceGroups.Tags String Service group tags.

Command Example

!pan-os-get-service-group name=ser_group6

Context Example

{
    "Panorama": {
        "ServiceGroups": {
            "Name": "ser_group6",
            "Services": [
                "serice_tcp_test_pb",
                "demi_service_test_pb"
            ]
        }
    }
}

Human Readable Output

Service group

Name Services
ser_group6 serice_tcp_test_pb,
demi_service_test_pb

pan-os-create-service-group


Creates a service group.

Base Command

pan-os-create-service-group

Input

Argument Name Description Required
name Service group name. Required
services Service group related services. Required
device-group The device group for which to return addresses (Panorama instances). Optional
tags Tags for which to filter Service groups. Optional

Context Output

Path Type Description
Panorama.ServiceGroups.Name string Service group name.
Panorama.ServiceGroups.Services string Service group related services.
Panorama.ServiceGroups.DeviceGroup string Device group for the service group (Panorama instances).
Panorama.ServiceGroups.Tags String Service group tags.

Command Example

!pan-os-create-service-group name=lalush_sg4 services=`["demisto_service1","demi_service_test_pb"]

pan-os-delete-service-group


Deletes a service group.

Base Command

pan-os-delete-service-group

Input

Argument Name Description Required
name Name of the service group to delete. Required
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.ServiceGroups.Name string Name of the deleted service group.
Panorama.ServiceGroups.DeviceGroup string Device group for the service group (Panorama instances).

Command Example

!pan-os-delete-service-group name=lalush_sg4

pan-os-edit-service-group


Edit a service group.

Base Command

pan-os-edit-service-group

Input

Argument Name Description Required
name Name of the service group to edit. Required
services_to_add Services to add to the service group. Only existing Services objects can be added. Optional
services_to_remove Services to remove from the service group. Only existing Services objects can be removed. Optional
tags Tag of the Service group to edit. Optional
device-group The device group in which the service group belongs to. Optional

Context Output

Path Type Description
Panorama.ServiceGroups.Name string Service group name.
Panorama.ServiceGroups.Services string Service group related services.
Panorama.ServiceGroups.DeviceGroup string Device group for the service group (Panorama instances).
Panorama.ServiceGroups.Tags String Service group tags.

Command Example

!pan-os-edit-service-group name=lalush_sg4 services_to_remove=`["serice_udp_test_pb","demisto_service1"]

Human Readable Output

Service group was edited successfully

pan-os-get-custom-url-category


Returns information for a custom URL category.

Base Command

pan-os-get-custom-url-category

Input

Argument Name Description Required
name Custom URL category name. Required
device-group The device group for which to return addresses for the custom URL category (Panorama instances). Optional

Context Output

Path Type Description
Panorama.CustomURLCategory.Name String The category name of the custom URL.
Panorama.CustomURLCategory.Description String The category description of the custom URL.
Panorama.CustomURLCategory.Sites String The list of sites of the custom URL category.
Panorama.CustomURLCategory.DeviceGroup String The device group for the custom URL Category (Panorama instances).
Panorama.CustomURLCategory.Categories String The list of categories of the custom URL category.
Panorama.CustomURLCategory.Type String The category type of the custom URL.

Command Example

!pan-os-get-custom-url-category name=my_personal_url_category

Human Readable Output

Custom URL Category

Name Sites Description
my_personal_url_category thepill.com,
abortion.com
just a desc

pan-os-create-custom-url-category


Creates a custom URL category.

Base Command

pan-os-create-custom-url-category

Input

Argument Name Description Required
name The name of the custom URL category to create. Required
description Description of the custom URL category to create. Optional
sites List of sites for the custom URL category. Optional
device-group The device group for which to return addresses for the custom URL category (Panorama instances). Optional
type The category type of the URL. Relevant from PAN-OS v9.x. Optional
categories The list of categories. Relevant from PAN-OS v9.x. Optional

Context Output

Path Type Description
Panorama.CustomURLCategory.Name String Custom URL category name.
Panorama.CustomURLCategory.Description String Custom URL category description.
Panorama.CustomURLCategory.Sites String Custom URL category list of sites.
Panorama.CustomURLCategory.DeviceGroup String Device group for the Custom URL Category (Panorama instances).
Panorama.CustomURLCategory.Sites String Custom URL category list of categories.
Panorama.CustomURLCategory.Type String Custom URL category type.

Command Example

!pan-os-create-custom-url-category name=suspicious_address_group sites=["thepill.com","abortion.com"] description=momo

Context Example

{
    "Panorama": {
        "CustomURLCategory": {
            "Description": "momo",
            "Name": "suspicious_address_group",
            "Sites": [
                "thepill.com",
                "abortion.com"
            ]
        }
    }
}

Human Readable Output

Created Custom URL Category

Name Sites Description
suspicious_address_group thepill.com,
abortion.com
momo

pan-os-delete-custom-url-category


Deletes a custom URL category.

Base Command

pan-os-delete-custom-url-category

Input

Argument Name Description Required
name Name of the custom URL category to delete. Optional
device-group The device group for which to return addresses (Panorama instances). Optional

Context Output

Path Type Description
Panorama.CustomURLCategory.Name string Name of the custom URL category to delete.
Panorama.CustomURLCategory.DeviceGroup string Device group for the Custom URL Category (Panorama instances).

Command Example

!pan-os-delete-custom-url-category name=suspicious_address_group

Context Example

{
    "Panorama": {
        "CustomURLCategory": {
            "Name": "suspicious_address_group"
        }
    }
}

Human Readable Output

Custom URL category was deleted successfully.

pan-os-edit-custom-url-category


Adds or removes sites to and from a custom URL category.

Base Command

pan-os-edit-custom-url-category

Input

Argument Name Description Required
name Name of the custom URL category to add or remove sites. Required
sites A comma separated list of sites to add to the custom URL category. Optional
action Adds or removes sites or categories. Possible values are: add, remove Required
categories A comma separated list of categories to add to the custom URL category. Optional
device-group The device group in which the URL category belongs to. Optional

Context Output

Path Type Description
Panorama.CustomURLCategory.Name string Custom URL category name.
Panorama.CustomURLCategory.Description string Custom URL category description.
Panorama.CustomURLCategory.Sites string Custom URL category list of sites.
Panorama.CustomURLCategory.DeviceGroup string Device group for the Custom URL Category (Panorama instances).

pan-os-get-url-category


Gets a URL category from URL Filtering. This command is only available on Firewall devices.

Base Command

pan-os-get-url-category

Input

Argument Name Description Required
url URL to check. Optional
target Serial number of the firewall on which to run the command. Use only for a Panorama instance Optional

Context Output

Path Type Description
Panorama.URLFilter.URL string URL.
Panorama.URLFilter.Category string URL category.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Indicator String The indicator that was tested.
URL.Data String The URL address.
URL.Category String The URL Category.

Command Example

!pan-os-get-url-category url="poker.com"

Context Example

{
    "DBotScore": {
        "Indicator": "poker.com",
        "Score": 1,
        "Type": "url",
        "Vendor": "PAN-OS"
    },
    "Panorama": {
        "URLFilter": {
            "Category": "gambling",
            "URL": [
                "poker.com"
            ]
        }
    },
    "URL": {
        "Category": "gambling",
        "Data": "poker.com"
    }
}

Human Readable Output

URL Filtering

URL Category
poker.com gambling

url


Gets a URL category from URL Filtering. This command is only available on Firewall devices.

Base Command

url

Input

Argument Name Description Required
url URL to check. Optional

Context Output

Path Type Description
Panorama.URLFilter.URL string URL.
Panorama.URLFilter.Category string The URL category.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Indicator String The indicator that was tested.
URL.Data String The URL address.
URL.Category String The URL category.

pan-os-get-url-category-from-cloud


Returns a URL category from URL filtering. This command is only available on Firewall devices.

Base Command

pan-os-get-url-category-from-cloud

Input

Argument Name Description Required
url URL to check. Required

Context Output

Path Type Description
Panorama.URLFilter.URL string The URL.
Panorama.URLFilter.Category string URL category.

Command Example

!pan-os-get-url-category-from-cloud url=google.com

Human Readable Output

URL Filtering from cloud

URL Category
google.com search-engines

pan-os-get-url-category-from-host


Returns a URL category from URL Filtering.

Base Command

pan-os-get-url-category-from-host

Input

Argument Name Description Required
url URL to check. Required

Context Output

Path Type Description
Panorama.URLFilter.URL string The URL.
Panorama.URLFilter.Category string The URL category.

Command Example

!pan-os-get-url-category-from-host url=google.com

Human Readable Output

URL Filtering from host

URL Category
google.com search-engines

pan-os-get-url-filter


Returns information for a URL filtering rule.

Base Command

pan-os-get-url-filter

Input

Argument Name Description Required
name URL Filter name. Required
device-group The device group for which to return addresses for the URL Filter (Panorama instances). Optional

Context Output

Path Type Description
Panorama.URLFilter.Name string URL Filter name.
Panorama.URLFilter.Category.Name string URL Filter category name.
Panorama.URLFilter.Category.Action string Action for the URL category.
Panorama.URLFilter.OverrideBlockList string URL Filter override block list.
Panorama.URLFilter.OverrideAllowList string URL Filter override allow list.
Panorama.URLFilter.Description string URL Filter description.
Panorama.URLFilter.DeviceGroup string Device group for the URL Filter (Panorama instances).

Command Example

!pan-os-get-url-filter name=demisto_default_url_filter

Human Readable Output

URL Filter

Name Category OverrideAllowList Description
demisto_default_url_filter {‘Action’: ‘block’, ‘Name’: u’abortion’},
{‘Action’: ‘block’, ‘Name’: u’abuse-drugs’}
888.com,
777.com
gres

pan-os-create-url-filter


Creates a URL filtering rule.

Base Command

pan-os-create-url-filter

Input

Argument Name Description Required
name Name of the URL filter to create. Required
url_category URL categories. Required
action Action for the URL categories. Can be “allow”, “block”, “alert”, “continue”, or “override”. Required
override_allow_list CSV list of URLs to exclude from the allow list. Optional
override_block_list CSV list of URLs to exclude from the blocked list. Optional
description URL Filter description. Optional
device-group The device group for which to return addresses for the URL Filter (Panorama instances). Optional

Context Output

Path Type Description
Panorama.URLFilter.Name string URL Filter name.
Panorama.URLFilter.Category.Name string URL Filter category name.
Panorama.URLFilter.Category.Action string Action for the URL category.
Panorama.URLFilter.OverrideBlockList string URL Filter override allow list.
Panorama.URLFilter.OverrideBlockList string URL Filter override blocked list.
Panorama.URLFilter.Description string URL Filter description.
Panorama.URLFilter.DeviceGroup string Device group for the URL Filter (Panorama instances).

Command Example

!pan-os-create-url-filter action=block name=gambling_url url_category=gambling

Context Example

{
    "Panorama": {
        "URLFilter": {
            "Category": [
                {
                    "Action": "block",
                    "Name": "gambling"
                }
            ],
            "Name": "gambling_url"
        }
    }
}

Human Readable Output

URL Filter was created successfully.

pan-os-edit-url-filter


Edit a URL filtering rule.

Base Command

pan-os-edit-url-filter

Input

Argument Name Description Required
name Name of the URL filter to edit. Required
element_to_change Element to change. Required
element_value Element value. Limited to one value. Required
add_remove_element Add or remove an element from the Allow List or Block List fields. Default is to ‘add’ the element_value to the list. Optional
device-group The device group in which the URL-filter belongs to. Optional

Context Output

Path Type Description
Panorama.URLFilter.Name string URL Filter name.
Panorama.URLFilter.Description string URL Filter description.
Panorama.URLFilter.Category.Name string URL Filter category.
Panorama.URLFilter.Action string Action for the URL category.
Panorama.URLFilter.OverrideAllowList string Allow Overrides for the URL category.
Panorama.URLFilter.OverrideBlockList string Block Overrides for the URL category.
Panorama.URLFilter.DeviceGroup string Device group for the URL Filter (Panorama instances).

Command Example

!pan-os-edit-url-filter name=demisto_default_url_filter element_to_change=override_allow_list element_value="poker.com" add_remove_element=add

Human Readable Output

URL Filter was edited successfully

pan-os-delete-url-filter


Deletes a URL filtering rule.

Base Command

pan-os-delete-url-filter

Input

Argument Name Description Required
name Name of the URL filter rule to delete. Required
device-group The device group for which to return addresses for the URL filter (Panorama instances) Optional

Context Output

Path Type Description
Panorama.URLFilter.Name string URL filter rule name.
Panorama.URLFilter.DeviceGroup string Device group for the URL Filter (Panorama instances).

Command Example

!pan-os-delete-url-filter name=gambling_url

Context Example

{
    "Panorama": {
        "URLFilter": {
            "Name": "gambling_url"
        }
    }
}

Human Readable Output

URL Filter was deleted successfully.

pan-os-list-edls


Returns a list of external dynamic lists.

Base Command

pan-os-list-edls

Input

Argument Name Description Required
device-group The device group for which to return addresses for the EDL (Panorama instances). Optional

Context Output

Path Type Description
Panorama.EDL.Name string Name of the EDL.
Panorama.EDL.Type string The type of EDL.
Panorama.EDL.URL string URL in which the EDL is stored.
Panorama.EDL.Description string Description of the EDL.
Panorama.EDL.CertificateProfile string EDL certificate profile.
Panorama.EDL.Recurring string Time interval that the EDL was pulled and updated.
Panorama.EDL.DeviceGroup string Device group for the EDL (Panorama instances).

Command Example


#### Context Example

```json
{
    "Panorama": {
        "EDL": [
            {
                "Description": "6u4ju7",
                "Name": "blabla3",
                "Recurring": "hourly",
                "Type": "url",
                "URL": "lolo"
            },
            {
                "Description": "ip",
                "Name": "bad_ip_edl_demisot_web_server",
                "Recurring": "five-minute",
                "Type": "ip",
                "URL": "http://192.168.1.15/files/very_bad_ip2.txt"
            }
        ]
    }
}

Human Readable Output

External Dynamic Lists

Name Type URL Recurring Description
blabla3 url lolo hourly 6u4ju7
bad_ip_edl_demisot_web_server ip http://192.168.1.15/files/very_bad_ip2.txt five-minute ip

pan-os-get-edl


Returns information for an external dynamic list

Base Command

pan-os-get-edl

Input

Argument Name Description Required
name Name of the EDL. Required
device-group The device group for which to return addresses for the EDL (Panorama instances). Optional

Context Output

Path Type Description
Panorama.EDL.Name string Name of the EDL.
Panorama.EDL.Type string The type of EDL.
Panorama.EDL.URL string URL in which the EDL is stored.
Panorama.EDL.Description string Description of the EDL.
Panorama.EDL.CertificateProfile string EDL certificate profile.
Panorama.EDL.Recurring string Time interval that the EDL was pulled and updated.
Panorama.EDL.DeviceGroup string Device group for the EDL (Panorama instances).

Command Example

!pan-os-get-edl name=test_pb_domain_edl_DONT_DEL

Context Example

{
    "Panorama": {
        "EDL": {
            "Description": "new description3",
            "Name": "test_pb_domain_edl_DONT_DEL",
            "Recurring": "hourly",
            "Type": "url",
            "URL": "https://test_pb_task.not.real"
        }
    }
}

Human Readable Output

External Dynamic List

Name Type URL Recurring Description
test_pb_domain_edl_DONT_DEL url https://test_pb_task.not.real hourly new description3

pan-os-create-edl


Creates an external dynamic list.

Base Command

pan-os-create-edl

Input

Argument Name Description Required
name Name of the EDL. Required
url URL from which to pull the EDL. Required
type The type of EDL. Required
recurring Time interval for pulling and updating the EDL. Required
certificate_profile Certificate Profile name for the URL that was previously uploaded. to PAN OS. Optional
description Description of the EDL. Optional
device-group The device group for which to return addresses for the EDL (Panorama instances). Optional

Context Output

Path Type Description
Panorama.EDL.Name string Name of theEDL.
Panorama.EDL.Type string Type of the EDL.
Panorama.EDL.URL string URL in which the EDL is stored.
Panorama.EDL.Description string Description of the EDL.
Panorama.EDL.CertificateProfile string EDL certificate profile.
Panorama.EDL.Recurring string Time interval that the EDL was pulled and updated.
Panorama.EDL.DeviceGroup string Device group for the EDL (Panorama instances).

Command Example

!pan-os-create-edl name=new_EDL recurring="five-minute" type=url url="gmail.com"

Context Example

{
    "Panorama": {
        "EDL": {
            "Name": "new_EDL",
            "Recurring": "five-minute",
            "Type": "url",
            "URL": "gmail.com"
        }
    }
}

Human Readable Output

External Dynamic List was created successfully.

pan-os-edit-edl


Modifies an element of an external dynamic list.

Base Command

pan-os-edit-edl

Input

Argument Name Description Required
name Name of the external dynamic list to edit. Required
element_to_change The element to change (“url”, “recurring”, “certificate_profile”, “description”). Required
element_value The element value. Required
device-group The device group in which the EDL belongs to. Optional

Context Output

Path Type Description
Panorama.EDL.Name string Name of the EDL.
Panorama.EDL.URL string URL where the EDL is stored.
Panorama.EDL.Description string Description of the EDL.
Panorama.EDL.CertificateProfile string EDL certificate profile.
Panorama.EDL.Recurring string Time interval that the EDL was pulled and updated.
Panorama.EDL.DeviceGroup string Device group for the EDL (Panorama instances).

Command Example

!pan-os-edit-edl name=test_pb_domain_edl_DONT_DEL element_to_change=description element_value="new description3"

Context Example

{
    "Panorama": {
        "EDL": {
            "Description": "new description3",
            "Name": "test_pb_domain_edl_DONT_DEL"
        }
    }
}

Human Readable Output

External Dynamic List was edited successfully

pan-os-delete-edl


Deletes an external dynamic list.

Base Command

pan-os-delete-edl

Input

Argument Name Description Required
name Name of the EDL to delete. Required
device-group The device group for which to return addresses for the EDL (Panorama instances). Optional

Context Output

Path Type Description
Panorama.EDL.Name string Name of the EDL that was deleted.
Panorama.EDL.DeviceGroup string Device group for the EDL (Panorama instances).

Command Example

!pan-os-delete-edl name=new_EDL

Context Example

{
    "Panorama": {
        "EDL": {
            "Name": "new_EDL"
        }
    }
}

Human Readable Output

External Dynamic List was deleted successfully

pan-os-refresh-edl


Refreshes the specified external dynamic list.

Base Command

pan-os-refresh-edl

Input

Argument Name Description Required
name Name of the EDL Required
device-group The device group for which to return addresses for the EDL (Panorama instances). Optional
edl_type The type of the EDL. Required when refreshing an EDL object which is configured on Panorama. Optional
location The location of the EDL. Required when refreshing an EDL object which is configured on Panorama. Optional
vsys The Vsys of the EDL. Required when refreshing an EDL object which is configured on Panorama. Optional

Context Output

There is no context output for this command.

Command Example

!pan-os-refresh-edl name=test_pb_domain_edl_DONT_DEL

Human Readable Output

Refreshed External Dynamic List successfully

pan-os-create-rule


Creates a policy rule.

Base Command

pan-os-create-rule

Input

Argument Name Description Required
rulename Name of the rule to create. Optional
description Description of the rule to create. Optional
action Action for the rule. Can be “allow”, “deny”, or “drop”. Required
source A comma-separated list of address object names, address group object names, or EDL object names. Optional
destination A comma-separated list of address object names, address group object names, or EDL object names. Optional
source_zone A comma-separated list of source zones. Optional
destination_zone A comma-separated list of destination zones. Optional
negate_source Whether to negate the source (address, address group). Can be “Yes” or “No”. Optional
negate_destination Whether to negate the destination (address, address group). Can be “Yes” or “No”. Optional
service A comma-separated list of service object names for the rule. Optional
disable Whether to disable the rule. Can be “Yes” or “No” (default is “No”). Optional
application A comma-separated list of application object names for the rule. Optional
source_user A comma-separated list of source users for the rule to create. Optional
pre_post Pre rule or Post rule (Panorama instances). Optional
target Specifies a target firewall for the rule (Panorama instances). Optional
log_forwarding Log forwarding profile. Optional
device-group The device group for which to return addresses for the rule (Panorama instances). Optional
tags Rule tags to create. Optional
category A comma-separated list of URL categories. Optional
profile_setting A profile setting group. Optional
where Where to move the rule. Can be “before”, “after”, “top”, or “bottom”. If you specify “before” or “after”, you need to supply the “dst” argument. Optional
dst Destination rule relative to the rule that you are moving. This field is only relevant if you specify “before” or “after” in the “where” argument. Optional
audit_comment An audit comment for the rule. Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string Rule name.
Panorama.SecurityRule.Description string Rule description.
Panorama.SecurityRule.Action string Action for the rule.
Panorama.SecurityRule.Source string Source address.
Panorama.SecurityRule.Destination string Destination address.
Panorama.SecurityRule.NegateSource boolean Whether the source is negated (address, address group).
Panorama.SecurityRule.NegateDestination boolean Whether the destination negated (address, address group).
Panorama.SecurityRule.Service string Service for the rule.
Panorama.SecurityRule.Disabled string Whether the rule is disabled.
Panorama.SecurityRule.Application string Application for the rule.
Panorama.SecurityRule.Target string Target firewall (Panorama instances).
Panorama.SecurityRule.LogForwarding string Log forwarding profile (Panorama instances).
Panorama.SecurityRule.DeviceGroup string Device group for the rule (Panorama instances).
Panorama.SecurityRules.Tags String Rule tags.
Panorama.SecurityRules.ProfileSetting String Profile setting group.

Command Example

!pan-os-create-rule rulename="block_bad_application" description="do not play at work" action="deny" application="fortnite"

Context Example

{
    "Panorama": {
        "SecurityRule": {
            "Action": "deny",
            "Application": "fortnite",
            "Description": "do not play at work",
            "Disabled": "No",
            "Name": "block_bad_application",
            "SourceUser": "any"
        }
    }
}

Human Readable Output

Rule configured successfully.

pan-os-custom-block-rule


Creates a custom block policy rule.

Base Command

pan-os-custom-block-rule

Input

Argument Name Description Required
rulename The name of the custom block policy rule to create. Optional
object_type The object type to block in the policy rule. Possible values are: ip, address-group, application, url-category, edl. Required
object_value A comma-separated list of object values for the object_type argument. Required
direction The direction to block. This argument is not applicable for the “custom-url-category” object_type. Possible values are: to, from, both. Default is both. Optional
pre_post The pre-rule or post-rule (Panorama instances). Possible values are: pre-rulebase, post-rulebase. Optional
target Specifies a target firewall for the rule (Panorama instances). Optional
log_forwarding The log forwarding profile. Optional
device-group The device group for which to return addresses for the rule (Panorama instances). Optional
tags The tags to use for the custom block policy rule. Optional
where Where to move the rule. If you specify “before” or “after”, you need to supply the “dst” argument. Possible values are: before, after, top, bottom. Default is bottom. Optional
dst The destination rule relative to the rule that you are moving. This field is only relevant if you specify “before” or “after” in the “where” argument. Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string The rule name.
Panorama.SecurityRule.Object string The blocked object.
Panorama.SecurityRule.Direction string The direction blocked.
Panorama.SecurityRule.Target string The target firewall (Panorama instances).
Panorama.SecurityRule.LogForwarding string The log forwarding profile (Panorama instances).
Panorama.SecurityRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.SecurityRule.Tags String The rule tags.
Panorama.SecurityRules.ProfileSetting String The profile setting group.

Command Example

!pan-os-custom-block-rule object_type=application object_value=fortnite

Context Example

{
    "Panorama": {
        "SecurityRule": {
            "Application": [
                "fortnite"
            ],
            "Direction": "both",
            "Disabled": false,
            "Name": "demisto-9c9ed15a"
        }
    }
}

Human Readable Output

Object was blocked successfully.

pan-os-move-rule


Changes the location of a policy rule.

Base Command

pan-os-move-rule

Input

Argument Name Description Required
rulename Name of the rule to move. Required
where Where to move the rule. Can be “before”, “after”, “top”, or “bottom”. If you specify “before” or “after”, you need to supply the “dst” argument. Required
dst Destination rule relative to the rule that you are moving. This field is only relevant if you specify “before” or “after” in the “where” argument. Optional
pre_post Rule location. Mandatory for Panorama instances. Optional
device-group The device group for which to return addresses for the rule (Panorama instances). Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string Rule name.
Panorama.SecurityRule.DeviceGroup string Device group for the rule (Panorama instances).

Command Example

!pan-os-move-rule rulename="test_rule3" where="bottom"

Human Readable Output

Rule test_rule3 moved successfully

pan-os-edit-rule


Edits a policy rule.

Base Command

pan-os-edit-rule

Input

Argument Name Description Required
rulename Name of the rule to edit. Required
element_to_change Parameter in the security rule to change. Can be ‘source’, ‘destination’, ‘application’, ‘action’, ‘category’, ‘description’, ‘disabled’, ‘target’, ‘log-forwarding’, ‘tag’, ‘source-user’, ‘service’, ‘profile-setting’ or ‘audit-comment’. Required
element_value The new value for the parameter. Required
pre_post Pre-rule or post-rule (Panorama instances). Optional
behaviour Whether to replace, add, or remove the element_value from the current rule object value. Optional
device-group The device group in which the rule belongs to. Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string Rule name.
Panorama.SecurityRule.Description string Rule description.
Panorama.SecurityRule.Action string Action for the rule.
Panorama.SecurityRule.Source string Source address.
Panorama.SecurityRule.Destination string Destination address.
Panorama.SecurityRule.NegateSource boolean Whether the source is negated (address, address group).
Panorama.SecurityRule.NegateDestination boolean Whether the destination is negated (address, address group).
Panorama.SecurityRule.Service string Service for the rule.
Panorama.SecurityRule.Disabled string Whether the rule is disabled.
Panorama.SecurityRule.Application string Application for the rule.
Panorama.SecurityRule.Target string Target firewall (Panorama instances).
Panorama.SecurityRule.DeviceGroup string Device group for the rule (Panorama instances).
Panorama.SecurityRule.Category string The category of the rule.
Panorama.SecurityRule.LogForwarding string The log forwarding of the rule.
Panorama.SecurityRule.ProfileSetting string The profile setting of the rule.
Panorama.SecurityRule.SourceUser string The source user of the rule.
Panorama.SecurityRule.AuditComment string The audit comment of the rule.
Panorama.SecurityRule.Tags String Tags for the rule.
Panorama.SecurityRules.ProfileSetting String Profile setting group.

Command Example

!pan-os-edit-rule rulename="block_bad_application" element_to_change=action element_value=drop

Context Example

{
    "Panorama": {
        "SecurityRule": {
            "Action": "drop",
            "Name": "block_bad_application"
        }
    }
}

Human Readable Output

Rule edited successfully.

pan-os-delete-rule


Deletes a policy rule.

Base Command

pan-os-delete-rule

Input

Argument Name Description Required
rulename The name of the rule to delete. Required
pre_post The pre-rule or post-rule (Panorama instances). Possible values are: pre-rulebase, post-rulebase. Optional
device-group The device group where the rule is configured (Panorama instances). Optional
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
rulebase The rulebase from which to delete the rule. Possible values are: security, application-override, authentication, decryption, nat, pbf. Default is security. Required
vsys The Firewall VSYS to delete the rule from. Use for deleting local rules from a firewall via Panorama or to specify a different VSYS than set in Integration parameters. Default is vsys1. Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string The rule name.
Panorama.SecurityRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.NAT.Name string The rule name.
Panorama.NAT.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.SSLRule.Name string The rule name.
Panorama.SSLRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.PBF.Name string The rule name.
Panorama.PBF.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.AuthRule.Name string The rule name.
Panorama.AuthRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.AppOverride.Name string The rule name.
Panorama.AppOverride.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.CleanedUpRules.Category string The category of the rule that was cleaned up (Local or Panorama).
Panorama.CleanedUpRules.AppliedAction string The applied cleanup action (Disabled or Deleted).
Panorama.CleanedUpRules.DeviceGroup string The device group where the rule was cleaned up from (Panorama instances).
Panorama.CleanedUpRules.PrePost string The location where the rule was cleaned up from (Panorama instances).
Panorama.CleanedUpRules.RuleName string The name of the rule.
Panorama.CleanedUpRules.Rulebase string The rulebase where the rule was cleaned up from (e.g. ‘security’, ‘nat’).
Panorama.CleanedUpRules.Target string The target firewall serial number, if provided.
Panorama.CleanedUpRules.Vsys string The VSYS where the rule was cleaned up from.

pan-os-list-applications


Returns a list of applications.

Base Command

pan-os-list-applications

Input

Argument Name Description Required
predefined Whether to list predefined applications. Possible values are: true, false. Default is false. Optional
device-group The device group for which to return applications. Optional
name_match When specified, the results returned in the list are limited to applications whose names match the specified string. Optional
name_contain When specified, the results returned in the list are limited to applications whose names contain the specified string. Optional
risk The application risk (1 to 5). Possible values are: 1, 2, 3, 4, 5. Optional
category The application category. Possible values are: collaboration, business-systems, networking, media. Optional
sub_category The application sub-category. Optional
technology The application technology. Possible values are: browser-based, client-server, network-protocol, peer-to-peer. Optional
characteristics A comma-separated list of characteristics. Possible values are: ‘virus-ident’, ‘evasive-behavior’, ‘file-type-ident’, ‘consume-big-bandwidth’, ‘used-by-malware’, ‘able-to-transfer-file’, ‘has-known-vulnerability’, ‘tunnel-other-application’, ‘prone-to-misuse’, ‘pervasive-use’, ‘file-forward’, ‘is-saas’. Optional
limit The maximum number of rules to retrieve. Will be used by default if page argument was not provided. Default is 50. Optional
page_size The page size of the applications to return. Default is 50. Optional
page The page at which to start listing applications. Must be a positive number. Optional

Context Output

Path Type Description
Panorama.Applications.Name string The application name.
Panorama.Applications.Id number The application ID.
Panorama.Applications.Category string The application category.
Panorama.Applications.SubCategory string The application sub-category.
Panorama.Applications.Technology string The application technology.
Panorama.Applications.Risk number The application risk (1 to 5).
Panorama.Applications.Description string The application description.
Panorama.Applications.Characteristics string The application characteristics.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "Applications": {
            "Description": "lala",
            "Id": null,
            "Name": "demisto_fw_app3",
            "Risk": "1",
            "SubCategory": "ip-protocol",
            "Technology": "peer-to-peer"
        }
    }
}

Human Readable Output

Applications

Id Name Risk Category SubCategory Technology Description
  demisto_fw_app3 1   ip-protocol peer-to-peer lala

pan-os-push-status


Returns the push status for a configuration.

Base Command

pan-os-push-status

Input

Argument Name Description Required
job_id The job ID to check. Required

Context Output

Path Type Description
Panorama.Push.DeviceGroup string The device group to which the policies were pushed.
Panorama.Push.JobID number The job ID of the configuration to be pushed.
Panorama.Push.Status string The push status.
Panorama.Push.Details string The job ID details.
Panorama.Push.Warnings String The job ID warnings

Command example

!pan-os-push-status job_id=31377

Context Example

{
    "Panorama": {
        "Push": {
            "Details": [
                "commit succeeded with warnings",
                "commit succeeded with warnings"
            ],
            "Errors": [],
            "JobID": "31377",
            "Status": "Completed",
            "Warnings": [
                "Interface loopback.645 has no zone configuration.",
                "External Dynamic List test_pb_domain_edl_DONT_DEL is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - IP EDL-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - URL EDL-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - URL EDL tamarcat3-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List Cortex XSOAR Remediation - IP EDL tamarcat3-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List minemeld is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-urls-OLD is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-ips is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "External Dynamic List edl-webinar-malicious-domains is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.",
                "Warning: No valid Antivirus content package exists",
                "(Module: device)"
            ]
        }
    }
}

Human Readable Output

Push to Device Group status

JobID Status Details Errors Warnings
31377 Completed commit succeeded with warnings,
commit succeeded with warnings
  Interface loopback.645 has no zone configuration.,
External Dynamic List test_pb_domain_edl_DONT_DEL is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - IP EDL-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - URL EDL-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - URL EDL tamarcat3-url-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List Cortex XSOAR Remediation - IP EDL tamarcat3-ip-edl-object is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List minemeld is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-urls-OLD is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-ips is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
External Dynamic List edl-webinar-malicious-domains is configured with no certificate profile. Please select a certificate profile for performing server certificate validation.,
Warning: No valid Antivirus content package exists,
(Module: device)

pan-os-get-pcap


Returns information for a Panorama PCAP file. The recommended maximum file size is 5 MB. If the limit is exceeded, you might need to SSH the firewall and run the scp export command to export the PCAP file. For more information, see the Palo Alto Networks documentation.

When trying to retrieve threat-PCAPs of a firewall through a panorama instance, be sure to forward the log containing the threat PCAP file from the firewall to the panorama instance.

For more information follow instructions from here.

Base Command

pan-os-get-pcap

PCAPs api docs

You can find information about required/optional arguments for each pcap type here:

filter pcap api

dlp pcap api

application pcap api

threat pcap api

Input

Argument Name Description Required
pcapType Type of Packet Capture. Required
serialNumber The serial number of the firewall to download the PCAP from. Optional
from The file name for the PCAP type (‘dlp-pcap’, ‘filter-pcap’, or ‘application-pcap’). Required for ‘filter-pcap’. Optional
localName The new name for the PCAP file after downloading. If this argument is not specified, the file name is the PCAP file name set in the firewall. Optional
serialNo Serial number for the request. For further information, see the Panorama XML API Documentation. Optional
searchTime The Search time for the request. For example: “2019/12/26 00:00:00”, “2020/01/10”. For more information, see the Panorama XML API documentation. Required for “threat-pcap”. Optional
pcapID The ID of the PCAP for the request. For further information, see the Panorama XML API Documentation. Required for ‘threat-pcap’. Optional
password Password for Panorama, needed for the ‘dlp-pcap’ PCAP type only. Optional
deviceName The Device Name on which the PCAP is stored. For further information, see the Panorama XML API Documentation. Required for ‘threat-pcap’ in pan-os firewalls < 9.0.7 versions. Optional
sessionID The Session ID of the PCAP. For further information, see the Panorama XML API Documentation. Required for ‘threat-pcap’ in pan-os firewalls < 9.0.7 versions. Optional

Context Output

Path Type Description
File.Size number File size.
File.Name string File name.
File.Type string File type.
File.Info string File info.
File.Extension string File extension.
File.EntryID string FIle entryID.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.SHA512 string SHA512 hash of the file.
File.SSDeep string SSDeep hash of the file.

Command Example

!pan-os-get-pcap pcapType="filter-pcap" from=pcap_test

pan-os-list-pcaps


Returns a list of all PCAP files by PCAP type. Not available for threat PCAPs.

Base Command

pan-os-list-pcaps

Input

Argument Name Description Required
pcapType Type of Packet Capture. Required
serialNumber The serial number of the firewall to download the PCAP from. Optional
password Password for Panorama. Relevant for the ‘dlp-pcap’ PCAP type. Optional

Context Output

There is no context output for this command.

Command Example

!pan-os-list-pcaps pcapType=“filter-pcap”

Human Readable Output

List of Pcaps

Pcap name
pcam_name

pan-os-register-ip-tag


Registers IP addresses to a tag.

Base Command

pan-os-register-ip-tag

Input

Argument Name Description Required
tag Tag for which to register IP addresses. Required
IPs IP addresses to register. Required
persistent Whether the IP addresses remain registered to the tag after the device reboots (‘true’:persistent, ‘false’:non-persistent). Default is ‘true’. Optional

Context Output

Path Type Description
Panorama.DynamicTags.Tag string Name of the tag.
Panorama.DynamicTags.IPs string Registered IP addresses.

Command Example

!pan-os-register-ip-tag tag=tag02 IPs=[“10.0.0.13”,“10.0.0.14”]

Human Readable Output

Registered ip-tag successfully

pan-os-unregister-ip-tag


Unregisters IP addresses from a tag.

Base Command

pan-os-unregister-ip-tag

Input

Argument Name Description Required
tag Tag for which to unregister IP addresses. Required
IPs IP addresses to unregister. Required

Context Output

There is no context output for this command.

Command Example

!pan-os-unregister-ip-tag tag=tag02 IPs=["10.0.0.13","10.0.0.14"]

Human Readable Output

Unregistered ip-tag successfully

pan-os-register-user-tag


Registers users to a tag. This command is only available for PAN-OS version 9.x and above.

Base Command

pan-os-register-user-tag

Input

Argument Name Description Required
tag Tag for which to register users. Required
Users A comma-separated list of users to register. Required

Context Output

Path Type Description
Panorama.DynamicTags.Tag string Name of the tag.
Panorama.DynamicTags.Users string List of registered users.

Command Example

!pan-os-register-user-tag tag-tag02 Users=Username

Human Readable Output

Registered user-tag successfully

pan-os-unregister-user-tag


Unregisters users from a tag. This command is only available for PAN-OS version 9.x and above.

Base Command

pan-os-unregister-user-tag

Input

Argument Name Description Required
tag Tag from which to unregister Users. Required
Users A comma-separated list of users to unregister. Required

Context Output

There is no context output for this command.

Command Example

!pan-os-unregister-user-tag tag-tag02 Users=Username

Human Readable Output

Unregistered user-tag successfully

pan-os-query-traffic-logs


Deprecated. Queries traffic logs.

Base Command

pan-os-query-traffic-logs

Input

Argument Name Description Required
query Specifies the match criteria for the logs. This is similar to the query provided in the web interface under the Monitor tab when viewing the logs. Optional
number_of_logs The number of logs to retrieve. Default is 100. Maximum is 5,000. Optional
direction Whether logs are shown oldest first (forward) or newest first (backward). Default is backward. Optional
source Source address for the query. Optional
destination Destination address for the query. Optional
receive_time Date and time after which logs were received, in the format: YYYY/MM/DD HH:MM:SS. Optional
application Application for the query. Optional
to_port Destination port for the query. Optional
action Action for the query. Optional

Context Output

Path Type Description
Panorama.TrafficLogs.JobID number Job ID of the traffic logs query.
Panorama.TrafficLogs.Status string Status of the traffic logs query.

Command Example

!pan-os-query-traffic-logs query="" number_of_logs="100" direction="backward" source="" destination="" receive_time="" application="" to_port="" action="allow"

Human Readable Output

Query Traffic Logs

JobID Status
1858 Pending

pan-os-check-traffic-logs-status


Deprecated. Checks the query status of traffic logs.

Base Command

pan-os-check-traffic-logs-status

Input

Argument Name Description Required
job_id Job ID of the query. Required

Context Output

Path Type Description
Panorama.TrafficLogs.JobID number Job ID of the traffic logs query.
Panorama.TrafficLogs.Status string Status of the traffic logs query.

Command Example

!pan-os-check-traffic-logs-status job_id="1865"

Human Readable Output

Query Traffic Logs status

JobID Status
1858 Pending

pan-os-get-traffic-logs


Deprecated. Retrieves traffic log query data by job id.

Base Command

pan-os-get-traffic-logs

Input

Argument Name Description Required
job_id Job ID of the query. Required

Context Output

Path Type Description
Panorama.TrafficLogs.JobID number Job ID of the traffic logs query.
Panorama.TrafficLogs.Status string Status of the traffic logs query.
Panorama.TrafficLogs.Logs.Action string Action of the traffic log.
Panorama.TrafficLogs.Logs.ActionSource string Action source of the traffic log.
Panorama.TrafficLogs.Logs.Application string Application of the traffic log.
Panorama.TrafficLogs.Logs.Bytes string The total log bytes.
Panorama.TrafficLogs.Logs.BytesReceived string The log bytes received.
Panorama.TrafficLogs.Logs.BytesSent string The log bytes sent.
Panorama.TrafficLogs.Logs.Category string Category of the traffic log.
Panorama.TrafficLogs.Logs.DeviceName string Device name of the traffic log.
Panorama.TrafficLogs.Logs.Destination string Destination of the traffic log.
Panorama.TrafficLogs.Logs.DestinationPort string Destination port of the traffic log.
Panorama.TrafficLogs.Logs.FromZone string From zone of the traffic log.
Panorama.TrafficLogs.Logs.Protocol string Protocol of the traffic log.
Panorama.TrafficLogs.Logs.ReceiveTime string Receive time of the traffic log.
Panorama.TrafficLogs.Logs.Rule string Rule of the traffic log.
Panorama.TrafficLogs.Logs.SessionEndReason string Session end reason of the traffic log.
Panorama.TrafficLogs.Logs.Source string Source of the traffic log.
Panorama.TrafficLogs.Logs.SourcePort string Source port of the traffic log.
Panorama.TrafficLogs.Logs.StartTime string Start time of the traffic log.
Panorama.TrafficLogs.Logs.ToZone string To zone of the traffic log.

Command Example

!pan-os-get-traffic-logs job_id="1865"

pan-os-list-rules


Returns a list of predefined Security Rules. (When passing a query, all other arguments are overridden. Make sure the query includes all the filters you want).

Base Command

pan-os-list-rules

Input

Argument Name Description Required
pre_post The rules location. Mandatory for Panorama instances. Possible values are: pre-rulebase, post-rulebase. Optional
device-group The device group for which to return addresses (Panorama instances). Optional
tag The tag to filter the rules. Optional
tags A comma-separated list of tags by which to filter the rules. Optional
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
rulename The name of the rule to retrieve. If not mentioned, will retrieve all the rules. Optional
disabled Whether to retrieve the disabled rules or not. If not mentioned, will retrieve all the rules. Possible values are: yes, no. Optional
action The action of the rules to retrieve. If not mentioned, will retrieve all the rules. Possible values are: allow, deny, drop. Optional
query Free query to retrieve rules. If not mentioned, will retrieve all the rules. When passing a query, all other arguments are overridden. Optional

Context Output

Path Type Description        
Panorama.SecurityRule.Location String The location of the security rule.        
Panorama.SecurityRule.NegateDestination String Indicates whether the destination is negated in the security rule.        
Panorama.SecurityRule.Disabled String Indicates whether the security rule is disabled.        
Panorama.SecurityRule.ICMPUnreachable String Specifies the behavior for ICMP unreachable messages.        
Panorama.SecurityRule.Description String The description of the security rule.        
Panorama.SecurityRule.GroupTag String The group tag of the security rule.        
Panorama.SecurityRule.LogForwardingProfile String The log forwarding profile applied to the security rule.        
Panorama.SecurityRule.NegateSource String Indicates whether the source is negated in the security rule.        
Panorama.SecurityRule.SecurityProfileGroup String The security profile group assigned to the security rule.        
Panorama.SecurityRule.SecurityProfile Unknown The security profile settings applied to the security rule.        
Panorama.SecurityRule.SecurityProfile.url-filtering String The security profile setting for url-filtering in the security rule.        
Panorama.SecurityRule.SecurityProfile.data-filtering String The security profile setting for data-filtering in the security rule.        
Panorama.SecurityRule.SecurityProfile.file-blocking String The security profile setting for file-blocking in the security rule.        
Panorama.SecurityRule.SecurityProfile.virus String The security profile setting for viruses in the security rule.        
Panorama.SecurityRule.SecurityProfile.spyware String The security profile setting for spyware in the security rule.        
Panorama.SecurityRule.SecurityProfile.vulnerability String The security profile setting for vulnerability in the security rule.        
Panorama.SecurityRule.SecurityProfile.wildfire-analysis String The security profile setting for wildfire-analysis in the security rule.   Panorama.SecurityRule.Target.devices String The devices targeted by the security rule.
Panorama.SecurityRule.Target.negate String Indicates whether the target is negated in the security rule.        
Panorama.SecurityRule.Name String The name of the security rule.        
Panorama.SecurityRule.From String The source zone of the security rule.        
Panorama.SecurityRule.DestinationDevice String The destination device of the security rule.        
Panorama.SecurityRule.Action String The action taken by the security rule.        
Panorama.SecurityRule.SourceDevice String The source device of the security rule.        
Panorama.SecurityRule.Tags String The tags associated with the security rule.        
Panorama.SecurityRule.SourceUser String The source user of the security rule.        
Panorama.SecurityRule.Application String The application used in the security rule.        
Panorama.SecurityRule.Service String The service used in the security rule.        
Panorama.SecurityRule.To String The destination zone of the security rule.        
Panorama.SecurityRule.Source String The source address of the security rule.        
Panorama.SecurityRule.CustomUrlCategory String The custom URL category targeted by the security rule.        
Panorama.SecurityRule.Destination String The destination address of the security rule.        
Panorama.SecurityRule.Options.LogAtSessionStart String Indicates whether the session start is logged.        
Panorama.SecurityRule.Options.LogForwarding String Indicates whether log forwarding is enabled for the security rule.        
Panorama.SecurityRule.Options.Schedule String The schedule applied to the security rule.        
Panorama.SecurityRule.Options.QoSMarking String The QoS marking applied to the security rule.        
Panorama.SecurityRule.Options.DisableServerResponseInspection String Specifies whether to disable server response inspection for the security rule.        
Panorama.SecurityRule.DeviceGroup String The device group of the security rule (Panorama instances only).        
Panorama.SecurityRule.Type String Represents the type of the security rule (e.g., pre-rule, post-rule, intra-zone, inter-zone).        

Command Example

!pan-os-list-rules pre_post=“pre-rulebase”

Context Example

{
    "Panorama": {
        "SecurityRule": [
            {
                "DeviceGroup": "TestDevice",
                "Location": "TestDevice",
                "NegateDestination": "",
                "Disabled": "no",
                "ICMPUnreachable": "",
                "Description": "",
                "GroupTag": "",
                "LogForwardingProfile": "",
                "NegateSource": "",
                "SecurityProfileGroup": "TestGroup",
                "SecurityProfile": {
                    "url-filtering": "",
                    "data-filtering": "",
                    "file-blocking": "",
                    "vulnerability": "",
                    "wildfire-analysis": "",
                    "spyware": "",
                    "virus": ""
                },
                "Target": {
                    "devices": "007051000185487",
                    "negate": "no"
                },
                "Name": "block rule",
                "Type": "",
                "From": [
                    "TestName",
                    "TestName2"
                ],
                "DestinationDevice": "any",
                "Action": "drop",
                "SourceDevice": "any",
                "Tags": [
                    "TestTag1",
                    "TestTag2",
                    "TestTag3"
                ],
                "SourceUser": "any",
                "Application": [
                    "cortex-xdr",
                    "jira",
                    "zoom"
                ],
                "Service": "application-default",
                "To": [
                    "TestName",
                    "TestName2"
                ],
                "Source": [
                    "1.1.1.1",
                    "8.8.4.4",
                    "8.8.8.8"
                ],
                "CustomUrlCategory": [
                    "abortion",
                    "adult",
                    "alcohol-and-tobacco",
                    "hacking"
                ],
                "Destination": [
                    "1.1.1.1",
                    "8.8.8.8"
                ],
                "Options": {
                    "LogAtSessionStart": "",
                    "LogForwarding": "",
                    "Schedule": "",
                    "QoSMarking": "",
                    "DisableServerResponseInspection": ""
                }
            },
            {
                "DeviceGroup": "TestDevice",
                "Location": "TestDevice",
                "NegateDestination": "",
                "Disabled": "",
                "ICMPUnreachable": "yes",
                "Description": "bbbbbbbbbb",
                "GroupTag": "TestGroupTag",
                "LogForwardingProfile": "Log forwarding for SA",
                "NegateSource": "",
                "SecurityProfileGroup": "",
                "SecurityProfile": {
                    "url-filtering": "default",
                    "spyware": "testing",
                    "virus": "default",
                    "data-filtering": "",
                    "file-blocking": "",
                    "vulnerability": "",
                    "wildfire-analysis": ""
                },
                "Target": {
                    "devices": [
                        "007051000185487",
                        "007051000188986"
                    ],
                    "negate": "no"
                },
                "Name": "jl-test-1",
                "Type": "intrazone",
                "From": "internal",
                "DestinationDevice": "bad nam",
                "Action": "reset-server",
                "SourceDevice": "good name",
                "Tags": [
                    "APIiiiiii",
                    "test2shared"
                ],
                "SourceUser": "me",
                "Application": "8x8",
                "Service": [
                    "new group",
                    "service-http",
                    "service-https"
                ],
                "To": "internal",
                "Source": "1.1.1.1",
                "CustomUrlCategory": [
                    "alcohol-and-tobacco",
                    "auctions"
                ],
                "Destination": "my_shared_address_object_test",
                "Options": {
                    "LogAtSessionStart": "yes",
                    "LogForwarding": "Log forwarding for SA",
                    "Schedule": "test-schedule",
                    "QoSMarking": "ip-precedence",
                    "DisableServerResponseInspection": "yes"
                }
            }
        ]
    }
}

Human Readable Output

Security Rules

Name Location Tags Type Source Zone Source Address Source User Source Device Destination Zone Destination Address Destination Device Application Service Url Category Action Profiles Profile Group Options Target
block rule TestDevice TestTag1,
TestTag2,
TestTag3
  TestName,
TestName2
1.1.1.1,
8.8.4.4,
8.8.8.8
any any TestName,
TestName2
1.1.1.1,
8.8.8.8
any cortex-xdr,
jira,
zoom
application-default abortion,
adult,
alcohol-and-tobacco,
hacking
drop url-filtering:
data-filtering:
file-blocking:
virus:
spyware:
vulnerability:
wildfire-analysis:
TestGroup LogAtSessionStart:
LogForwarding:
Schedule:
QoSMarking:
DisableServerResponseInspection:
devices: 007051000185487
negate: no
jl-test-1 TestDevice APIiiiiii,
test2shared
intrazone internal 1.1.1.1 me good name internal my_shared_address_object_test bad nam 8x8 new group,
service-http,
service-https
alcohol-and-tobacco,
auctions
reset-server url-filtering: default
data-filtering:
file-blocking:
virus: default
spyware: testing
vulnerability:
wildfire-analysis:
  LogAtSessionStart: yes
LogForwarding: Log forwarding for SA
Schedule: test-schedule
QoSMarking: ip-precedence
DisableServerResponseInspection: yes
devices: 007051000185487,
007051000188986
negate: no

pan-os-query-logs


The query logs in Panorama.

Base Command

pan-os-query-logs

Input

Argument Name Description Required
log-type The log type. Can be “threat”, “traffic”, “wildfire”, “url”, or “data”. Possible values are: threat, traffic, wildfire, url, data. Required
query The query string by which to match criteria for the logs. This is similar to the query provided in the web interface under the Monitor tab when viewing the logs. Do not use the query arguemnt in combination with the following arugments: time-generated, time-generated-after, addr-src, addr-dst, zone-src, zone-dst, action, port-dst, rule, url, filedigest. Optional
time-generated The time the log was generated from the timestamp and prior to it.
For example “2019/08/11 01:10:44”, 3 days ago.
Optional
time-generated-after The time the log was generated from the timestamp and later then it.
For example “2019/08/11 01:10:44”, 3 days ago.
Optional
addr-src The source address. Optional
addr-dst The destination address. Optional
ip The source or destination IP address. Optional
zone-src The source zone. Optional
zone-dst The destination source. Optional
action The rule action. Optional
port-dst The destination port. Optional
rule The rule name, for example “Allow all outbound”. Optional
url The URL, for example “safebrowsing.googleapis.com”. Optional
filedigest The file hash (for WildFire logs only). Optional
number_of_logs The maximum number of logs to retrieve. If empty, the default is 100. The maximum is 5,000. Default is 100. Optional
polling Whether to use polling. Possible values are: true, false. Default is false. Optional
timeout The timeout (in seconds) when polling. Default is 120. Optional
interval_in_seconds The interval (in seconds) when polling. Default is 10. Optional
show-detail Whether to show only after-change-preview, and before-change-preview, or get full data for it. The full data are under the fields after-change-detail, and before-change-detail. Possible values are: yes, no. Default is no. Optional

Context Output

| Path | Type | Description |
| — | — | — |
| Panorama.Monitor.JobID | String | The job ID of the logs query. |
| Panorama.Monitor.Status | String | The status of the logs query. |
| Panorama.Monitor.Message | String | The message of the logs query. |
| Panorama.Monitor.Logs.Action | String | The action taken for the session. Can be “alert”, “allow”, “deny”, “drop”, “drop-all-packets”, “reset-client”, “reset-server”, “reset-both”, or “block-url”. |
| Panorama.Monitor.Logs.Application | String | The application associated with the session. |
| Panorama.Monitor.Logs.Category | String | The URL category of the URL subtype. For WildFire subtype, it is the verdict on the file, and can be either “malicious”, “phishing”, “grayware”, or “benign”. For other subtypes, the value is “any”. |
| Panorama.Monitor.Logs.DeviceName | String | The hostname of the firewall on which the session was logged. |
| Panorama.Monitor.Logs.DestinationAddress | String | The original session destination IP address. |
| Panorama.Monitor.Logs.DestinationUser | String | The username of the user to which the session was destined. |
| Panorama.Monitor.Logs.DestinationCountry | String | The destination country or internal region for private addresses. Maximum length is 32 bytes. |
| Panorama.Monitor.Logs.DestinationPort | String | The destination port utilized by the session. |
| Panorama.Monitor.Logs.FileDigest | String | Only for the WildFire subtype, all other types do not use this field. The filedigest string shows the binary hash of the file sent to be analyzed by the WildFire service. |
| Panorama.Monitor.Logs.FileName | String | File name or file type when the subtype is file.
File name when the subtype is virus.
File name when the subtype is wildfire-virus.
File name when the subtype is wildfire. |
| Panorama.Monitor.Logs.FileType | String | Only for the WildFire subtype, all other types do not use this field.
Specifies the type of file that the firewall forwarded for WildFire analysis. |
| Panorama.Monitor.Logs.FromZone | String | The zone from which the session was sourced. |
| Panorama.Monitor.Logs.URLOrFilename | String | The actual URL when the subtype is url.
The file name or file type when the subtype is file.
The file name when the subtype is virus.
The file name when the subtype is wildfire-virus.
The file name when the subtype is wildfire.
The URL or file name when the subtype is vulnerability (if applicable). |
| Panorama.Monitor.Logs.NATDestinationIP | String | The post-NAT destination IP address if destination NAT was performed. |
| Panorama.Monitor.Logs.NATDestinationPort | String | The post-NAT destination port. |
| Panorama.Monitor.Logs.NATSourceIP | String | The post-NAT source IP address if source NAT was performed. |
| Panorama.Monitor.Logs.NATSourcePort | String | The post-NAT source port. |
| Panorama.Monitor.Logs.PCAPid | String | The packet capture (pcap) ID is a 64 bit unsigned integral denoting
an ID to correlate threat pcap files with extended pcaps taken as a part of
that flow. All threat logs will contain either a pcap_id of 0 (no associated
pcap), or an ID referencing the extended pcap file. |
| Panorama.Monitor.Logs.IPProtocol | String | The IP protocol associated with the session. |
| Panorama.Monitor.Logs.Recipient | String | Only for the WildFire subtype, all other types do not use this field.
Specifies the name of the receiver of an email that WildFire determined to be malicious when analyzing an email link forwarded by the firewall. |
| Panorama.Monitor.Logs.Rule | String | The name of the rule that the session matched. |
| Panorama.Monitor.Logs.RuleID | String | The ID of the rule that the session matched. |
| Panorama.Monitor.Logs.ReceiveTime | String | The time the log was received at the management plane. |
| Panorama.Monitor.Logs.Sender | String | Only for the WildFire subtype; all other types do not use this field.
Specifies the name of the sender of an email that WildFire determined to be malicious when analyzing an email link forwarded by the firewall. |
| Panorama.Monitor.Logs.SessionID | String | An internal numerical identifier applied to each session. |
| Panorama.Monitor.Logs.DeviceSN | String | The serial number of the firewall on which the session was logged. |
| Panorama.Monitor.Logs.Severity | String | The severity associated with the threat. Can be “informational”, “low”,
“medium”, “high”, or “critical”. |
| Panorama.Monitor.Logs.SourceAddress | String | The original session source IP address. |
| Panorama.Monitor.Logs.SourceCountry | String | The source country or internal region for private addresses. Maximum
length is 32 bytes. |
| Panorama.Monitor.Logs.SourceUser | String | The username of the user who initiated the session. |
| Panorama.Monitor.Logs.SourcePort | String | The source port utilized by the session. |
| Panorama.Monitor.Logs.ThreatCategory | String | The threat categories used to classify different types of
threat signatures. |
| Panorama.Monitor.Logs.Name | String | The Palo Alto Networks identifier for the threat. A description
string followed by a 64-bit numerical identifier. |
| Panorama.Monitor.Logs.ID | String | The Palo Alto Networks ID for the threat. |
| Panorama.Monitor.Logs.ToZone | String | The zone to which the session was destined. |
| Panorama.Monitor.Logs.TimeGenerated | String | The time the log was generated on the data plane. |
| Panorama.Monitor.Logs.URLCategoryList | String | A list of the URL filtering categories the firewall used to
enforce the policy. |
| Panorama.Monitor.Logs.Bytes | String | The total log bytes. |
| Panorama.Monitor.Logs.BytesReceived | String | The log bytes received. |
| Panorama.Monitor.Logs.BytesSent | String | The log bytes sent. |
| Panorama.Monitor.Logs.Vsys | String | The VSYS on the firewall that generated the log. |

Command example with polling

!pan-os-query-logs log-type=traffic number_of_logs=1 polling=true show-detail=yes

Context example

{
    "Panorama": {
        "Monitor": {
            "JobID": "1291",
            "LogType": "traffic",
            "Logs": {
              "TimeGenerated": "2019/07/24 08:50:24",
              "SourceAddress": "1.1.1.1",
              "DestinationAddress": "2.3.4.5",
              "Application": "web-browsing",
              "Action": "deny",
              "Rule": "any - any accept"
            },
            "Status": "Completed"
        }
    }
}

Human Readable Output

Fetching traffic logs for job ID 1291…

Query traffic Logs

TimeGenerated SourceAddress DestinationAddress Application Action Rule
2019/07/24 08:50:24 1.1.1.1 2.3.4.5 web-browsing deny any - any accept

Command example without polling

!pan-os-query-logs log-type=traffic number_of_logs=1

Context Example

{
    "Panorama": {
        "Monitor": {
            "JobID": "1283",
            "LogType": "traffic",
            "Message": "query job enqueued with jobid 1283",
            "Status": "Pending"
        }
    }
}

Human Readable Output

Query Logs

JobID Status
1283 Pending

pan-os-check-logs-status


Checks the status of a logs query.

Base Command

pan-os-check-logs-status

Input

Argument Name Description Required
job_id Job ID of the query. Required

Context Output

Path Type Description
Panorama.Monitor.JobID String Job ID of the logs query.
Panorama.Monitor.Status String Status of the logs query.

Command Example

!pan-os-check-logs-status job_id=657

Human Readable Output

Query Logs Status

JobID Status
657 Completed

pan-os-get-logs


Retrieves the data of a logs query.

Base Command

pan-os-get-logs

Input

Argument Name Description Required
job_id Job ID of the query. Required
ignore_auto_extract Whether to auto-enrich the War Room entry. If “true”, entry is not auto-enriched. If “false”, entry is auto-extracted. Default is “true”. Optional

Context Output

Path Type Description
Panorama.Monitor.Logs.Action String Action taken for the session. Can be “alert”, “allow”, “deny”, “drop”, “drop-all-packets”, “reset-client”, “reset-server”, “reset-both”, or “block-url”.
Panorama.Monitor.Logs.Application String Application associated with the session.
Panorama.Monitor.Logs.Category String The URL category of the URL subtype. For WildFire subtype, it is the verdict on the file, and can be either “malicious”, “phishing”, “grayware”’, or “benign”. For other subtypes, the value is “any”.
Panorama.Monitor.Logs.DeviceName String The hostname of the firewall on which the session was logged.
Panorama.Monitor.Logs.DestinationAddress String Original session destination IP address.
Panorama.Monitor.Logs.DestinationUser String Username of the user to which the session was destined.
Panorama.Monitor.Logs.DestinationCountry String Destination country or internal region for private addresses. Maximum length is 32 bytes.
Panorama.Monitor.Logs.DestinationPort String Destination port utilized by the session.
Panorama.Monitor.Logs.FileDigest String Only for the WildFire subtype, all other types do not use this field. The file digest string shows the binary hash of the file sent to be analyzed by the WildFire service.
Panorama.Monitor.Logs.FileName String File name or file type when the subtype is file.
File name when the subtype is virus.
File name when the subtype is wildfire-virus.
File name when the subtype is wildfire.
Panorama.Monitor.Logs.FileType String Only for the WildFire subtype, all other types do not use this field.
Specifies the type of file that the firewall forwarded for WildFire analysis.
Panorama.Monitor.Logs.FromZone String The zone from which the session was sourced.
Panorama.Monitor.Logs.URLOrFilename String The actual URL when the subtype is url.
File name or file type when the subtype is file.
File name when the subtype is virus.
File name when the subtype is wildfire-virus.
File name when the subtype is wildfire.
URL or file name when the subtype is vulnerability (if applicable).
Panorama.Monitor.Logs.NATDestinationIP String If destination NAT performed, the post-NAT destination IP address.
Panorama.Monitor.Logs.NATDestinationPort String Post-NAT destination port.
Panorama.Monitor.Logs.NATSourceIP String If source NAT performed, the post-NAT source IP address.
Panorama.Monitor.Logs.NATSourcePort String Post-NAT source port.
Panorama.Monitor.Logs.PCAPid String The packet capture (pcap) ID is a 64 bit unsigned integral denoting an ID to correlate threat pcap files with extended pcaps taken as a part of that flow. All threat logs will contain either a pcap_id of 0 (no associated pcap), or an ID referencing the extended pcap file.
Panorama.Monitor.Logs.IPProtocol String IP protocol associated with the session.
Panorama.Monitor.Logs.Recipient String Only for the WildFire subtype, all other types do not use this field.
Specifies the name of the receiver of an email that WildFire determined to be malicious when analyzing an email link forwarded by the firewall.
Panorama.Monitor.Logs.Rule String Name of the rule that the session matched.
Panorama.Monitor.Logs.RuleID String ID of the rule that the session matched.
Panorama.Monitor.Logs.ReceiveTime String Time the log was received at the management plane.
Panorama.Monitor.Logs.Sender String Only for the WildFire subtype; all other types do not use this field.
Specifies the name of the sender of an email that WildFire determined to be malicious when analyzing an email link forwarded by the firewall.
Panorama.Monitor.Logs.SessionID String An internal numerical identifier applied to each session.
Panorama.Monitor.Logs.DeviceSN String The serial number of the firewall on which the session was logged.
Panorama.Monitor.Logs.Severity String Severity associated with the threat. Can be “informational”, “low”, “medium”, “high”, or “critical”.
Panorama.Monitor.Logs.SourceAddress String Original session source IP address.
Panorama.Monitor.Logs.SourceCountry String Source country or internal region for private addresses. Maximum length is 32 bytes.
Panorama.Monitor.Logs.SourceUser String Username of the user who initiated the session.
Panorama.Monitor.Logs.SourcePort String Source port utilized by the session.
Panorama.Monitor.Logs.ThreatCategory String Describes threat categories used to classify different types of threat signatures.
Panorama.Monitor.Logs.Name String Palo Alto Networks identifier for the threat. It is a description string followed by a 64-bit numerical identifier.
Panorama.Monitor.Logs.ID String Palo Alto Networks ID for the threat.
Panorama.Monitor.Logs.ToZone String The zone to which the session was destined.
Panorama.Monitor.Logs.TimeGenerated String Time that the log was generated on the dataplane.
Panorama.Monitor.Logs.URLCategoryList String A list of the URL filtering categories that the firewall used to enforce the policy.
Panorama.Monitor.Logs.Bytes String Total log bytes.
Panorama.Monitor.Logs.BytesReceived String Log bytes received.
Panorama.Monitor.Logs.BytesSent String Log bytes sent.
Panorama.Monitor.Logs.Vsys String Vsys on the firewall that generated the log.

Command Example

!pan-os-get-logs job_id=678

Human Readable Output

Query data Logs

TimeGenerated SourceAddress DestinationAddress Application Action Rule
2019/07/24 08:50:24 1.1.1.1 2.3.4.5 web-browsing deny any - any accept

pan-os-security-policy-match


Checks whether a session matches a specified security policy. This command is only available on Firewall instances.

Base Command

pan-os-security-policy-match

Input

Argument Name Description Required
application The application name. Optional
category The category name. Optional
destination The destination IP address. Required
destination-port The destination port. Optional
from The from zone. Optional
to The to zone. Optional
protocol The IP protocol value. Required
source The source IP address. Required
source-user The source user. Optional
target Target number of the firewall. Use only on a Panorama instance. Optional

Context Output

Path Type Description
Panorama.SecurityPolicyMatch.Query String Query for the session to test.
Panorama.SecurityPolicyMatch.Rules.Name String The matching rule name.
Panorama.SecurityPolicyMatch.Rules.Action String The matching rule action.
Panorama.SecurityPolicyMatch.Rules.Category String The matching rule category.
Panorama.SecurityPolicyMatch.Rules.Destination String The matching rule destination.
Panorama.SecurityPolicyMatch.Rules.From String The matching rule from zone.
Panorama.SecurityPolicyMatch.Rules.Source String The matching rule source.
Panorama.SecurityPolicyMatch.Rules.To String The matching rule to zone.
Panorama.SecurityPolicyMatch.QueryFields.Application String The application name.
Panorama.SecurityPolicyMatch.QueryFields.Category String The category name.
Panorama.SecurityPolicyMatch.QueryFields.Destination String The destination IP address.
Panorama.SecurityPolicyMatch.QueryFields.DestinationPort Number The destination port.
Panorama.SecurityPolicyMatch.QueryFields.From String The from zone.
Panorama.SecurityPolicyMatch.QueryFields.To String The to zone.
Panorama.SecurityPolicyMatch.QueryFields.Protocol String The IP protocol value.
Panorama.SecurityPolicyMatch.QueryFields.Source String The destination IP address.
Panorama.SecurityPolicyMatch.QueryFields.SourceUser String The source user.

Command Example

!pan-os-security-policy-match destination=1.2.3.4 protocol=1 source=2.3.4.5

Context Example

{
    "Panorama": {
        "SecurityPolicyMatch": {
            "Query": "<test><security-policy-match><source>2.3.4.5</source><destination>1.2.3.4</destination><protocol>1</protocol></security-policy-match></test>",
            "QueryFields": {
                "Destination": "1.2.3.4",
                "Protocol": "1",
                "Source": "2.3.4.5"
            },
            "Rules": {
                "Action": "allow",
                "Category": "any",
                "Destination": "any",
                "From": "any",
                "Name": "any - any accept",
                "Source": "any",
                "To": "any"
            }
        }
    }
}

Human Readable Output

Matching Security Policies

Name Action From To Source Destination
any - any accept allow any any any any

pan-os-list-static-routes


Lists the static routes of a virtual router.

Base Command

pan-os-list-static-routes

Input

Argument Name Description Required
virtual_router The name of the virtual router for which to list static routes. Required
template The template to use to run the command. Overrides the template parameter (Panorama instances). Optional
show_uncommitted Whether to show an uncommitted configuration. Default is “false” Optional

Context Output

Path Type Description
Panorama.StaticRoutes.Name String The name of the static route.
Panorama.StaticRoutes.BFDProfile String The BFD profile of the static route.
Panorama.StaticRoutes.Destination String The destination of the static route.
Panorama.StaticRoutes.Metric Number The metric (port) of the static route.
Panorama.StaticRoutes.NextHop String The next hop of the static route. Can be an IP address, FQDN, or a virtual router.
Panorama.StaticRoutes.RouteTable String The route table of a static route.
Panorama.StaticRoutes.VirtualRouter String The virtual router to which the static router belongs.
Panorama.StaticRoutes.Template String The template in which the static route is defined (Panorama instances only).
Panorama.StaticRoutes.Uncommitted Boolean Whether the static route is committed.

Command Example

!pan-os-list-static-routes virtual_router=virtual_router_test_DONT_DELETE

Context Example

{
    "Panorama": {
        "StaticRoutes": [
            {
                "BFDprofile": "None",
                "Destination": "2.3.4.5/32",
                "Metric": 14,
                "Name": "static_route_ip",
                "NextHop": "3.3.3.3",
                "RouteTable": "Unicast",
                "VirtualRouter": "virtual_router_test_DONT_DELETE"
            },
            {
                "Destination": "1.1.1.1/32",
                "Metric": 1012,
                "Name": "test_maya",
                "NextHop": "3.3.3.3",
                "VirtualRouter": "virtual_router_test_DONT_DELETE"
            }
        ]
    }
}

Human Readable Output

Displaying all Static Routes for the Virtual Router: virtual_router_test_DONT_DELETE

Name Destination NextHop RouteTable Metric BFDprofile
static_route_ip 2.3.4.5/32 3.3.3.3 Unicast 14 None
test_maya 1.1.1.1/32 3.3.3.3   1012  

pan-os-get-static-route


Returns the specified static route of a virtual router.

Base Command

pan-os-get-static-route

Input

Argument Name Description Required
virtual_router Name of the virtual router for which to display the static route. Required
static_route Name of the static route to display. Required
template The template for which to run the command. Overrides the template parameter (Panorama instances). Optional

Context Output

Path Type Description
Panorama.StaticRoutes.Name String The name of the static route.
Panorama.StaticRoutes.BFDProfile String The BFD profile of the static route.
Panorama.StaticRoutes.Destination String The destination of the static route.
Panorama.StaticRoutes.Metric Number The metric (port) of the static route.
Panorama.StaticRoutes.NextHop String The next hop of the static route. Can be an IP address, FQDN, or a virtual router.
Panorama.StaticRoutes.RouteTable String The route table of the static route.
Panorama.StaticRoutes.VirtualRouter String The virtual router to which the static router belongs.
Panorama.StaticRoutes.Template String The template in which the static route is defined (Panorama instances only).

Command Example

!pan-os-get-static-route static_route=static_route_ip virtual_router=virtual_router_test_DONT_DELETE

Context Example

{
    "Panorama": {
        "StaticRoutes": {
            "BFDprofile": "None",
            "Destination": "2.3.4.5/32",
            "Metric": 14,
            "Name": "static_route_ip",
            "NextHop": "3.3.3.3",
            "RouteTable": "Unicast",
            "VirtualRouter": "virtual_router_test_DONT_DELETE"
        }
    }
}

Human Readable Output

Static route: static_route_ip

BFDprofile Destination Metric Name NextHop RouteTable VirtualRouter
None 2.3.4.5/32 14 static_route_ip 3.3.3.3 Unicast virtual_router_test_DONT_DELETE

pan-os-add-static-route


Adds a static route.

Base Command

pan-os-add-static-route

Input

Argument Name Description Required
virtual_router Virtual Router to which the routes will be added. Required
static_route The name of the static route to add. The argument is limited to a maximum of 31 characters, is case-sensitive, and supports letters, numbers, spaces, hyphens, and underscores. Required
destination The IP address and network mask in Classless Inter-domain Routing (CIDR) notation: ip_address/mask. For example, 192.168.0.1/24 for IPv4 or 2001:db8::/32 for IPv6). Required
nexthop_type The type for the nexthop. Can be: “ip-address”, “next-vr”, “fqdn” or “discard”. Required
nexthop_value The next hop value. Required
metric The metric port for the static route (1-65535). Optional
interface The interface name in which to add the static route. Optional
template The template to use to run the command. Overrides the template parameter (Panorama instances). Optional

Context Output

Path Type Description
Panorama.StaticRoutes.Name String The name of the static route.
Panorama.StaticRoutes.BFDProfile String The BFD profile of the static route.
Panorama.StaticRoutes.Destination String The destination of the static route.
Panorama.StaticRoutes.Metric Number The metric (port) of the static route.
Panorama.StaticRoutes.NextHop String The next hop of the static route. Can be an IP address, FQDN, or a virtual router.
Panorama.StaticRoutes.RouteTable String The route table of the static route.
Panorama.StaticRoutes.VirtualRouter String The virtual router to which the static router belongs.
Panorama.StaticRoutes.Template String The template in which the static route is defined (Panorama instances only).

Command Example

!pan-os-add-static-route destination=2.3.4.5/32 nexthop_type="ip-address" nexthop_value=3.3.3.3 static_route=my_temp_route virtual_router=virtual_router_test_DONT_DELETE

Context Example

{
    "Panorama": {
        "StaticRoutes": {
            "@code": "20",
            "@status": "success",
            "msg": "command succeeded"
        }
    }
}

Human Readable Output

New uncommitted static route my_temp_route configuration added.

pan-os-delete-static-route


Deletes a static route.

Base Command

pan-os-delete-static-route

Input

Argument Name Description Required
route_name The name of the static route to delete. Required
virtual_router The virtual router from which the routes will be deleted. Required
template The template for to use to run the command. Overrides the template parameter (Panorama instances). Optional

Context Output

Path Type Description
Panorama.StaticRoutes.Name String The name of the static route.
Panorama.StaticRoutes.BFDProfile String The BFD profile of the static route.
Panorama.StaticRoutes.Destination String The destination of the static route.
Panorama.StaticRoutes.Metric Number The metric (port) of the static route.
Panorama.StaticRoutes.NextHop String The next hop of the static route. Can be an IP address, FQDN, or a virtual router.
Panorama.StaticRoutes.RouteTable String The route table of the static route.
Panorama.StaticRoutes.VirtualRouter String The virtual router to which the static router belongs.
Panorama.StaticRoutes.Template String The template in which the static route is defined (Panorama instances only).
Panorama.StaticRoutes.Deleted Boolean Whether the static route was deleted.

Command Example

!pan-os-delete-static-route route_name=my_temp_route virtual_router=virtual_router_test_DONT_DELETE

Context Example

{
    "Panorama": {
        "StaticRoutes": {
            "Deleted": true,
            "Name": "my_temp_route"
        }
    }
}

Human Readable Output

The static route: my_temp_route was deleted. Changes are not committed.

pan-os-show-device-version


Show firewall device software version.

Base Command

pan-os-show-device-version

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional

Context Output

Path Type Description
Panorama.Device.Info.Devicename String Devicename of the PAN-OS.
Panorama.Device.Info.Model String Model of the PAN-OS.
Panorama.Device.Info.Serial String Serial number of the PAN-OS.
Panorama.Device.Info.Version String Version of the PAN-OS.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "Device": {
            "Info": {
                "Devicename": "PA-VM",
                "Model": "PA-VM",
                "Serial": "000000000000000",
                "Version": "8.1.7"
            }
        }
    }
}

Human Readable Output

Device Version

Devicename Model Serial Version
PA-VM PA-VM 000000000000000 8.1.7

pan-os-download-latest-content-update


Downloads the latest app/threat dynamic update.

Base Command

pan-os-download-latest-content-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the download job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the download status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the download status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.Content.Download.JobID String The job ID of the app/threat download.
Panorama.Content.Download.Status String The app/threat download status.
Panorama.Content.Download.Details String The download job details.

pan-os-content-update-download-status


Checks the download status of a content update.

Base Command

pan-os-content-update-download-status

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
job_id Job ID to check. Required

Context Output

Path Type Description
Panorama.Content.Download.JobID String Job ID to monitor.
Panorama.Content.Download.Status String Download status.
Panorama.Content.Download.Details String Job ID details.

Command Example

!pan-os-content-update-download-status job_id=678

Human Readable Output

Content download status

JobID Status Details
678 Completed download succeeded with warnings

pan-os-install-latest-content-update


Installs the latest app/threat dynamic update.

Base Command

pan-os-install-latest-content-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the install job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the install status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the install status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.Content.Install.JobID String The job ID of the installation.
Panorama.Content.Install.Status String The installation status.
Panorama.Content.Install.Details String The install job details.

pan-os-content-update-install-status


Gets the installation status of the content update.

Base Command

pan-os-content-update-install-status

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
job_id Job ID of the content installation. Required

Context Output

Path Type Description
Panorama.Content.Install.JobID String Job ID of the content installation.
Panorama.Content.Install.Status String Content installation status.
Panorama.Content.Install.Details String Content installation status details.

Command Example

!pan-os-content-update-install-status job_id=878

Human Readable Output

Content install status

JobID Status Details
878 Completed installation succeeded with warnings

pan-os-check-latest-panos-software


Checks the PAN-OS software version from the repository.

Base Command

pan-os-check-latest-panos-software

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional

Context Output

Path Type Description
Panorama.LatestVersions unknown Latest software versions.

Command Example


### pan-os-download-panos-version

***
Downloads the target PAN-OS software version to install on the target device.

#### Base Command

`pan-os-download-panos-version`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| target | Serial number of the firewall on which to run the command. Use only for a Panorama instance. | Optional |
| target_version | The target version number to install. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.PANOS.Download.JobID | Number | Job ID of the PAN-OS download. |
| Panorama.PANOS.Download.Status | String | Status of the PAN-OS download. |

#### Command Example

```!pan-os-download-panos-version target_version=1```

#### Human Readable Output

>### Result
>
>|JobID|Status|
>|---|---|
>| 111 | Pending |

### pan-os-download-panos-status

***
Gets the download status of the target PAN-OS software.

#### Base Command

`pan-os-download-panos-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| target | Serial number of the firewall on which to run the command. Use only for a Panorama instance. | Optional |
| job_id | Job ID to check. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.PANOS.Download.JobID | String | Job ID of the PAN-OS download. |
| Panorama.PANOS.Download.Status | String | PAN-OS download status. |
| Panorama.PANOS.Download.Details | String | PAN-OS download details. |

#### Command Example

```!pan-os-download-panos-status job_id=999```

#### Human Readable Output

>### PAN-OS download status
>
>|JobID|Status|Details|
>|---|---|---|
>| 999 | Completed | download succeeded with warnings |

### pan-os-install-panos-version

***
Installs the target PAN-OS version on the specified target device.

#### Base Command

`pan-os-install-panos-version`

#### Input

| **Argument Name** | **Description**  | **Required** |
| --- |---| --- |
| target | Serial number of the firewall on which to run the command. Use only for a Panorama instance. | Optional |
| target_version | Target PAN-OS version to install. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.PANOS.Install.JobID | string | Job ID from the PAN-OS installation. |
| Panorama.PANOS.Install.Status | String | Status of the PAN-OS installation. |

#### Command Example

```!pan-os-install-panos-version target_version=1```

#### Human Readable Output

>### PAN-OS Installation
>
>|JobID|Status|
>|---|---|
>| 111 | Pending |

### pan-os-install-panos-status

***
Gets the installation status of the PAN-OS software.

#### Base Command

`pan-os-install-panos-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| target | Serial number of the firewall on which to run the command. Use only for a Panorama instance. | Optional |
| job_id | Job ID to check. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.PANOS.Install.JobID | Number | Job ID of the PAN-OS installation. |
| Panorama.PANOS.Install.Status | String | Status of the PAN-OS installation. |
| Panorama.PANOS.Install.Details | String | PAN-OS installation details. |

#### Command Example

```!pan-os-install-panos-status job_id=878```

#### Human Readable Output

>### PAN-OS installation status
>
>|JobID|Status|Details|
>|---|---|---|
>| 878 | Completed | installation succeeded with warnings |

### pan-os-device-reboot

***
Reboots the Firewall device.

#### Base Command

`pan-os-device-reboot`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| target | Serial number of the firewall on which to run the command. Use only for a Panorama instance. | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-device-reboot```

### pan-os-show-location-ip

***
Gets location information for an IP address.

#### Base Command

`pan-os-show-location-ip`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| ip_address | The IP address from which to return information. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Location.IP.country_code | String | The IP address location country code. |
| Panorama.Location.IP.country_name | String | The IP addres location country name. |
| Panorama.Location.IP.ip_address | String | The IP address. |
| Panorama.Location.IP.Status | String | Whether the IP address was found. |

#### Command Example

```!pan-os-show-location-ip ip_address=8.8.8.8```

#### Context Example

```json
{
    "Panorama": {
        "Location": {
            "IP": {
                "country_code": "US",
                "country_name": "United States",
                "ip_address": "8.8.8.8",
                "status": "Found"
            }
        }
    }
}

Human Readable Output

IP 8.8.8.8 location

ip_address country_name country_code
8.8.8.8 United States US

pan-os-get-licenses


Gets information about available PAN-OS licenses and their statuses.

Base Command

pan-os-get-licenses

Input

There are no input arguments for this command.

Context Output

Path Type Description
Panorama.License.Authcode String The authentication code of the license.
Panorama.License.Base-license-name String The base license name.
Panorama.License.Description String The description of the license.
Panorama.License.Expired String Whether the license has expired.
Panorama.License.Expires String When the license will expire.
Panorama.License.Feature String The feature of the license.
Panorama.License.Issued String When the license was issued.
Panorama.License.Serial String The serial number of the license.

Command Example


#### Human Readable Output

>|Authcode|Description|Feature|Serial|Expired|Expires|Issued|
>|---|---|---|---|---|---|---|
>| I9805928  | NFR Support | NFR Support | 007DEMISTO1t | no | Never | November 25, 2019 |

### pan-os-get-security-profiles

***
Gets information for the specified security profile.

#### Base Command

`pan-os-get-security-profiles`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| security_profile | The security profile for which to get information. Possible values are: data-filtering, file-blocking, spyware, url-filtering, virus, vulnerability, wildfire-analysis. | Optional |
| device-group | The device group for which to return security profiles. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Spyware.Name | String | The profile name. |
| Panorama.Spyware.Rules.Action | String | The rule action. |
| Panorama.Spyware.Rules.Category | String | The category for which to apply the rule. |
| Panorama.Spyware.Rules.Name | String | The rule name. |
| Panorama.Spyware.Rules.Packet-capture | String | Whether packet capture is enabled. |
| Panorama.Spyware.Rules.Severity | String | The rule severity. |
| Panorama.Spyware.Rules.Threat-name | String | The threat name to apply for the rule. |
| Panorama.URLFilter.Name | String | The profile name. |
| Panorama.URLFilter.Rules.Category.Action | String | The rule action to apply to the category. |
| Panorama.URLFilter.Rules.Category.Name | String | The category name. |
| Panorama.WildFire.Name | String | The WildFire profile name. |
| Panorama.WildFire.Rules.Analysis | String | The rule analysis. |
| Panorama.WildFire.Rules.Application | String | The application to apply for the rule. |
| Panorama.WildFire.Rules.File-type | String | The file type to apply for the rule. |
| Panorama.WildFire.Rules.Name | String | The rule name. |
| Panorama.Vulnerability.Name | String | The vulnerability profile name. |
| Panorama.Vulnerability.Rules.Vendor-id | String | The vendor ID to apply for the rule. |
| Panorama.Vulnerability.Rules.Packet-capture | String | Whether packet capture is enabled. |
| Panorama.Vulnerability.Rules.Host | String | The rule host. |
| Panorama.Vulnerability.Rules.Name | String | The rule name. |
| Panorama.Vulnerability.Rules.Category | String | The category to apply for the rule. |
| Panorama.Vulnerability.Rules.CVE | String | The CVE to apply for the rule. |
| Panorama.Vulnerability.Rules.Action | String | The rule action. |
| Panorama.Vulnerability.Rules.Severity | String | The rule severity. |
| Panorama.Vulnerability.Rules.Threat-name | String | The threat to apply for the rule. |
| Panorama.Antivirus.Name | String | The antivirus profile name. |
| Panorama.Antivirus.Rules.Action | String | The rule action. |
| Panorama.Antivirus.Rules.Name | String | The rule name. |
| Panorama.Antivirus.Rules.WildFire-action | String | The WildFire action. |
| Panorama.FileBlocking.Name | String | The file blocking profile name. |
| Panorama.FileBlocking.Rules.Action | String | The rule action. |
| Panorama.FileBlocking.Rules.Application | String | The application to apply for the rule. |
| Panorama.FileBlocking.Rules.File-type | String | The file type to apply for the rule. |
| Panorama.FileBlocking.Rules.Name | String | The rule name. |
| Panorama.DataFiltering.Name | String | The data filtering profile name. |
| Panorama.DataFiltering.Rules.Alert-threshold | String | The alert threshold. |
| Panorama.DataFiltering.Rules.Application | String | The application to apply for the rule. |
| Panorama.DataFiltering.Rules.Block-threshold | String | The block threshold. |
| Panorama.DataFiltering.Rules.Data-object | String | The data object. |
| Panorama.DataFiltering.Rules.Direction | String | The rule direction. |
| Panorama.DataFiltering.Rules.File-type | String | The file type to apply for the rule. |
| Panorama.DataFiltering.Rules.Log-severity | String | The log severity. |
| Panorama.DataFiltering.Rules.Name | String | The rule name. |

#### Command Example

```!pan-os-get-security-profiles security_profile=spyware```

#### Human Readable Output

>|Name|Rules|
>|---|---|
>| best-practice  | {'Name': 'simple-critical', 'Action': {'reset-both': None}, 'Category': 'any', 'Severity': 'critical', 'Threat-name': 'any', 'Packet-capture': 'disable'},<br/>{'Name': 'simple-high', 'Action': {'reset-both': None}, 'Category': 'any', 'Severity': 'high', 'Threat-name': 'any', 'Packet-capture': 'disable'},<br/>{'Name': 'simple-medium', 'Action': {'reset-both': None}, 'Category': 'any', 'Severity': 'medium', 'Threat-name': 'any', 'Packet-capture': 'disable'},<br/>{'Name': 'simple-informational', 'Action': {'default': None}, 'Category': 'any', 'Severity': 'informational', 'Threat-name': 'any', 'Packet-capture': 'disable'},<br/>{'Name': 'simple-low', 'Action': {'default': None}, 'Category': 'any', 'Severity': 'low', 'Threat-name': 'any', 'Packet-capture': 'disable'} |

### pan-os-apply-security-profile

***
Apply a security profile to specific rules or rules with a specific tag.

#### Base Command

`pan-os-apply-security-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
|-------------------| --- | --- |
| profile_type      | Security profile type. Can be 'data-filtering', 'file-blocking', 'spyware', 'url-filtering', 'virus, 'vulnerability', or wildfire-analysis.' | Required |
| rule_name         | The rule name to apply. | Required |
| profile_name      | The profile name to apply to the rule. | Required |
| pre_post          | The location of the rules. Can be 'pre-rulebase' or 'post-rulebase'. Mandatory for Panorama instances. | Optional |
| device-group      | The device group for which to apply security profiles. | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-apply-security-profile profile_name=test profile_type=spyware rule_name=rule1 pre_post="pre-rulebase"```

#### Human Readable Output
>
>The profile spyware = test has been applied to the rule rule1

### pan-os-remove-security-profile

***
Removes a security profile to specific rules or rules with a specific tag.

#### Base Command

`pan-os-remove-security-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_type | The security profile type. Possible values are: data-filtering, file-blocking, spyware, url-filtering, virus, vulnerability, wildfire-analysis. | Required |
| rule_name | The rule name to apply. | Required |
| pre_post | The location of the rules. Mandatory for Panorama instances. Possible values are: pre-rulebase, post-rulebase. | Optional |
| device-group | The device group for which to apply security profiles. | Optional |

#### Human Readable Output
>
>The profile test has been removed from the rule rule1

### pan-os-get-ssl-decryption-rules

***
Get SSL decryption rules.

#### Base Command

`pan-os-get-ssl-decryption-rules`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| pre_post | The location of the rules. Can be 'pre-rulebase' or 'post-rulebase'. Mandatory for Panorama instances. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.SSLRule.From | String | The SSL rule from the source. |
| Panorama.SSLRule.Name | String | The name of the SSL rule. |
| Panorama.SSLRule.Destination | String | The destination of the SSL rule. |
| Panorama.SSLRule.Target | String | The target of the SSL rule. |
| Panorama.SSLRule.Service | String | The SSL rule service. |
| Panorama.SSLRule.Action | String | The SSL rule action. |
| Panorama.SSLRule.Type | String | The SSL rule type. |
| Panorama.SSLRule.Source | String | The source of the SSL rule. |
| Panorama.SSLRule.To | String | The SSL rule to destination. |
| Panorama.SSLRule.UUID | String | The SSL rule UUID. |
| Panorama.SSLRule.Description | String | The SSL rule description. |
| Panorama.SSLRule.Source-user | String | The SSL rule source user. |
| Panorama.SSLRule.Category | String | The SSL rule category. |

#### Command Example

```!pan-os-get-ssl-decryption-rules pre_post="pre-rulebase"```

#### Human Readable Output

>|Name|UUID|Target|Service|Category|Type|From|To|Source|Destenation|Action|Source-user|
>|---|---|---|---|---|---|---|---|---|---|---|---|
>| test | some_uuid | negate: no | any | member: any | ssl-forward-proxy: null | any | any | any | any | no-decrypt | any |

### pan-os-get-wildfire-configuration

***
Retrieves the Wildfire configuration.

#### Base Command

`pan-os-get-wildfire-configuration`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| template | The template name. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.WildFire.Name | String | The file type. |
| Panorama.WildFire.Size-limit | String | The file size limit. |
| Panorama.WildFire.recurring | String | The schedule that is recurring. |

#### Command Example

```!pan-os-get-wildfire-configuration template=WildFire```

>### WildFire Configuration
>
> Report Grayware File: yes
>
>|Name|Size-limit|
>|---|---|
>| pe | 10 |
>| apk | 30 |

>### The updated schedule for Wildfire
>
>|recurring|
>|---|
>| every-min: {"action": "download-and-install"} |

### pan-os-url-filtering-block-default-categories

***
Set default categories to block in the URL filtering profile.

#### Base Command

`pan-os-url-filtering-block-default-categories`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The url-filtering profile name. Get the name by running the get-security-profiles command. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-url-filtering-block-default-categories profile_name=test```

#### Human Readable Output

>The default categories to block has been set successfully to test

### pan-os-get-anti-spyware-best-practice

***
Get anti-spyware best practices.

#### Base Command

`pan-os-get-anti-spyware-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Spyware.BotentDomain.Name | String | The botnet domain name. |
| Panorama.Spyware.BotentDomain.Action | String | The botnet domain action. |
| Panorama.Spyware.BotentDomain.Packet-capture | String | Whether packet capture is enabled. |
| Panorama.Spyware.BotentDomain.Sinkhole.ipv4-address | String | The botnet domain IPv4 address. |
| Panorama.Spyware.BotentDomain.Sinkhole.ipv6-address | String | The Botnet domain IPv6 address. |
| Panorama.Spyware.Rule.Category | String | The rule category. |
| Panorama.Spyware.Rule.Action | String | The rule action. |
| Panorama.Spyware.Rule.Name | String | The rule name. |
| Panorama.Spyware.Rule.Severity | String | The rule severity. |
| Panorama.Spyware.Rule.Threat-name | String | The rule threat name. |
| Panorama.Spyware.BotentDomain.Max_version | String | The botnet domain max version. |

#### Command Example

```!pan-os-get-anti-spyware-best-practice```

#### Human Readable Output

>### Anti Spyware Botnet-Domains Best Practice
>
>|Name|Action|Packet-capture|ipv4-address|ipv6-address|
>|---|---|---|---|---|
>| default-paloalto-dns | sinkhole: null | disable |  |  |
>| default-paloalto-cloud | allow: null | disable |  |  |
>|  |  |  | pan-sinkhole-default-ip | ::1 |

>### Anti Spyware Best Practice Rules
>
>|Name|Severity|Action|Category|Threat-name|
>|---|---|---|---|---|
>| simple-critical | critical | reset-both: null | any | any |
>| simple-high | high | reset-both: null | any | any |

### pan-os-get-file-blocking-best-practice

***
Get file-blocking best practices.

#### Base Command

`pan-os-get-file-blocking-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.FileBlocking.Rule.Action | String | The rule action. |
| Panorama.FileBlocking.Rule.Application | String | The rule application. |
| Panorama.FileBlocking.Rule.File-type | String | The rule file type. |
| Panorama.FileBlocking.Rule.Name | String | The rule name. |

#### Command Example

```!pan-os-get-file-blocking-best-practice```

#### Human Readable Output

>### File Blocking Profile Best Practice
>
>|Name|Action|File-type|Aplication|
>|---|---|---|---|
>| Block all risky file types | block | 7z,<br/>bat,<br/>cab,<br/>chm,<br/>class,<br/>cpl | any |
>| Block encrypted files | block | encrypted-rar,<br/>encrypted-zip| any |

### pan-os-get-antivirus-best-practice

***
Get anti-virus best practices.

#### Base Command

`pan-os-get-antivirus-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Antivirus.Decoder.Action | String | The rule action. |
| Panorama.Antivirus.Decoder.Name | String | The rule name. |
| Panorama.Antivirus.Decoder.WildFire-action | String | The WildFire action. |

#### Command Example

```!pan-os-get-antivirus-best-practice```

#### Human Readable Output

>### Antivirus Best Practice Profile
>
>|Name|Action|WildFire-action|
>|---|---|---|
>| http | default | default|
>| smtp default | default |

### pan-os-get-vulnerability-protection-best-practice

***
Get vulnerability-protection best practices.

#### Base Command

`pan-os-get-vulnerability-protection-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.Vulnerability.Rule.Action | String | The rule action. |
| Panorama.Vulnerability.Rule.CVE | String | The rule CVE. |
| Panorama.Vulnerability.Rule.Category | String | The rule category. |
| Panorama.Vulnerability.Rule.Host | String | The rule host. |
| Panorama.Vulnerability.Rule.Name | String | The rule name. |
| Panorama.Vulnerability.Rule.Severity | String | The rule severity. |
| Panorama.Vulnerability.Rule.Threat-name | String | The threat name. |
| Panorama.Vulnerability.Rule.Vendor-id | String | The vendor ID. |

#### Command Example

```!pan-os-get-vulnerability-protection-best-practice```

#### Human Readable Output

>### vulnerability Protection Best Practice Profile
>
>|Name|Action|Host|Severity|Category|Threat-name|CVE|Vendor-id|
>|---|---|---|---|---|---|---|---|
>| simple-client-critical | reset-both: null | client | critical | any | any | any | any |
>| simple-client-high | reset-both: null | client | high | any | any | any | any |

### pan-os-get-wildfire-best-practice

***
View WildFire best practices.

#### Base Command

`pan-os-get-wildfire-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.WildFire.Analysis | String | The WildFire analysis. |
| Panorama.WildFire.Application | String | The WildFire application. |
| Panorama.WildFire.File.File-size | String | The recommended file size. |
| Panorama.WildFire.File.Name | String | The file name. |
| Panorama.WildFire.File-type | String | The WildFire profile file type. |
| Panorama.WildFire.Name | String | The WildFire profile name. |
| Panorama.WildFire.SSLDecrypt | String | The SSL decrypt content. |
| Panorama.WildFire.Schedule.Action | String | The WildFire schedule action. |
| Panorama.WildFire.Schedule.Recurring | String | The WildFire schedule recurring. |

#### Command Example

```!pan-os-get-wildfire-best-practice```

#### Human Readable Output

>### WildFire Best Practice Profile
>
>|Name|Analysis|Aplication|File-type|
>|---|---|---|---|
>| default | public-cloud | any | any |

>### Wildfire Best Practice Schedule
>
>|Action|Recurring|
>|---|---|
>| download-and-install | every-minute |

>### Wildfire SSL Decrypt Settings
>
>|allow-forward-decrypted-content|
>|---|
>| yes |

>### Wildfire System Settings
>
>report-grayware-file: yes
>
>|Name|File-size|
>|---|---|
>| pe | 10 |
>| apk | 30 |

### pan-os-get-url-filtering-best-practice

***
View URL Filtering best practices.

#### Base Command

`pan-os-get-url-filtering-best-practice`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.URLFilter.Category.Action | String | The action to perform on the category. |
| Panorama.URLFilter.Category.Name | String | The category name. |
| Panorama.URLFilter.DeviceGroup | String | The device group name. |
| Panorama.URLFilter.Name | String | The Profile name. |
| Panorama.URLFilter.Header.log-container-page-only | String | The log container page only. |
| Panorama.URLFilter.Header.log-http-hdr-referer | String | The log HTTP header referer. |
| Panorama.URLFilter.Header.log-http-hdr-user | String | The log HTTP header user. |
| Panorama.URLFilter.Header.log-http-hdr-xff | String | The log HTTP header xff. |

#### Command Example

```!pan-os-get-url-filtering-best-practice```

#### Human Readable Output

>### URL Filtering Best Practice Profile Categories
>
>|Category|DeviceGroup|Name|
>|---|---|---|
>| {'Name': 'abortion', 'Action': 'alert'},<br/>{'Name': 'abused-drugs', 'Action': 'alert'} | Demisto sales lab | best-practice |

>### Best Practice Headers
>
>|log-container-page-only|log-http-hdr-referer|log-http-hdr-user|log-http-hdr-xff|
>|---|---|---|---|
>| yes | yes | yes | yes |

### pan-os-enforce-wildfire-best-practice

***
Enforces wildfire best practices to upload files to the maximum size, forwards all file types, and updates the schedule.

#### Base Command

`pan-os-enforce-wildfire-best-practice`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| template | The template name. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-enforce-wildfire-best-practice template=WildFire```

#### Human Readable Output

>The schedule was updated according to the best practice. Recurring every minute with the action of "download and install" The file upload for all file types is set to the maximum size.

### pan-os-create-antivirus-best-practice-profile

***
Creates an antivirus best practice profile.

#### Base Command

`pan-os-create-antivirus-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The name of the profile to create. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-antivirus-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-create-anti-spyware-best-practice-profile

***
Creates an Anti-Spyware best practice profile.

#### Base Command

`pan-os-create-anti-spyware-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The profile name to create. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-anti-spyware-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-create-vulnerability-best-practice-profile

***
Creates a vulnerability protection best practice profile.

#### Base Command

`pan-os-create-vulnerability-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The profile name. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-vulnerability-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-create-url-filtering-best-practice-profile

***
Creates a URL filtering best practice profile.

#### Base Command

`pan-os-create-url-filtering-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The profile name. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-url-filtering-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-create-file-blocking-best-practice-profile

***
Creates a file blocking best practice profile.

#### Base Command

`pan-os-create-file-blocking-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The name of the profile. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-file-blocking-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-create-wildfire-best-practice-profile

***
Creates a WildFire analysis best practice profile.

#### Base Command

`pan-os-create-wildfire-best-practice-profile`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| profile_name | The name of the profile. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!pan-os-create-wildfire-best-practice-profile profile_name=test```

#### Human Readable Output

>The profile test was created successfully.

### pan-os-show-user-id-interfaces-config

***
Shows the user ID interface configuration.

#### Base Command

`pan-os-show-user-id-interfaces-config`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| template | The template to use when running the command. Overrides the template parameter (Panorama instances). If not given, will use the integration parameter. | Optional |
| template_stack | The template stack to use when running the command. | Optional |
| vsys | The name of the virtual system to be configured. Will use the configured VSYS parameter if exists. If given a value, will override the VSYS parameter. If neither the VSYS parameter and this argument are entered, will default to 'vsys1'. . | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.UserInterfaces.Name | String | The name of the user interface. |
| Panorama.UserInterfaces.Zone | String | The zone to which the interface is connected |
| Panorama.UserInterfaces.EnableUserIdentification | String | Whether user identification is enabled. |

#### Command Example

```!pan-os-show-user-id-interfaces-config```

#### Context Example

```json
{
    "Panorama": {
        "UserInterfaces": {
            "EnableUserIdentification": "no",
            "Name": "ethernet1/1",
            "Zone": "test_zone"
        }
    }
}

Human Readable Output

User Interface Configuration

Name Zone EnableUserIdentification
ethernet1/1 test_zone no

pan-os-show-zones-config


Shows the zones configuration.

Base Command

pan-os-show-zones-config

Input

Argument Name Description Required
template The template to use when running the command. Overrides the template parameter (Panorama instances). If not given, will use the integration parameter. Optional
template_stack The template stack to use when running the command. Optional
vsys The name of the virtual system to be configured. Will use the configured VSYS parameter if exists. If given a value, will override the VSYS parameter. If neither the VSYS parameter and this argument are entered, will default to ‘vsys1’. . Optional

Context Output

Path Type Description
Panorama.Zone.Name String The name of the zone.
Panorama.Zone.Network String The network to which the zone connected
Panorama.Zone.EnableUserIdentification String Whether user identification is enabled.
Panorama.Zone.ZoneProtectionProfile String The zone protection profile.
Panorama.Zone.LogSetting String The log setting for the zone

Command Example


#### Context Example

```json
{
    "Panorama": {
        "Zone": {
            "EnableUserIdentification": "no",
            "LogSetting": null,
            "Name": "test_zone",
            "Network": {
                "tap": {
                    "member": "ethernet1/1"
                }
            },
            "ZoneProtectionProfile": null
        }
    }
}

Human Readable Output

Zone Configuration

Name Network EnableUserIdentification
test_zone tap: {“member”: “ethernet1/1”} no

pan-os-list-configured-user-id-agents


Retrieves list of user-ID agents configured in the system.

Base Command

pan-os-list-configured-user-id-agents

Input

Argument Name Description Required
template The template to use when running the command. Overrides the template parameter (Panorama instances). If not given, will use the integration parameter. Optional
template_stack The template stack to use when running the command. Optional
vsys The name of the virtual system to be configured. Will use the configured VSYS parameter if exists. If given a value, will override the VSYS parameter. If neither the VSYS parameter and this argument are entered, will default to ‘vsys1’. . Optional

Context Output

Path Type Description
Panorama.UserIDAgents.Name String The user-ID Agent name.
Panorama.UserIDAgents.Host String The user-ID Agent host.
Panorama.UserIDAgents.Port Number The user-ID Agent port.
Panorama.UserIDAgents.LdapProxy String Whether LDAP proxy is used in the user-ID agent.
Panorama.UserIDAgents.NtlmAuth String Whether NLTM authentication is used in the user-ID agent.
Panorama.UserIDAgents.EnableHipCollection String Whether HIP collection is enabled in the user-ID agent.
Panorama.UserIDAgents.IpUserMapping String Whether IP user mapping is enabled in the user-ID agent.
Panorama.UserIDAgents.SerialNumber Unknown The serial number associated with the user-ID agent.
Panorama.UserIDAgents.CollectorName String The user-ID agent collector name.
Panorama.UserIDAgents.Secret String The user-ID agent secret.
Panorama.UserIDAgents.Disabled String Whether the user-ID agent is disbaled.

Command Example


#### Context Example

```json
{
    "Panorama": {
        "UserIDAgents": [
            {
                "CollectorName": "demisto",
                "Disabled": "yes",
                "EnableHipCollection": null,
                "Host": "mine",
                "IpUserMapping": null,
                "LdapProxy": "yes",
                "Name": "testing",
                "NtlmAuth": "yes",
                "Port": "12",
                "Secret": "secret",
                "SerialNumber": null
            },
            {
                "CollectorName": null,
                "Disabled": null,
                "EnableHipCollection": null,
                "Host": null,
                "IpUserMapping": null,
                "LdapProxy": null,
                "Name": "withSerial",
                "NtlmAuth": null,
                "Port": null,
                "Secret": null,
                "SerialNumber": "panorama"
            }
        ]
    }
}

Human Readable Output

User ID Agents

Name SerialNumber Host Port CollectorName LdapProxy NtlmAuth
testing   mine 12 demisto yes yes
withSerial panorama          

pan-os-upload-content-update-file


Uploads a content file to Panorama.

Base Command

pan-os-upload-content-update-file

Input

Argument Name Description Required
entryID Entry ID of the file to upload. Required
category The category of the content. Possible values are: wildfire, anti-virus, content. Required

Context Output

Path Type Description
Panorama.Content.Upload.Status string Content upload status.
Panorama.Content.Upload.Message string Content upload message.

Command Example

pan-os-upload-content-update-file entryID="32@14183" category="content"

Human Readable Output

Results

Status Message
Success line: saved

pan-os-install-file-content-update


Installs specific content update file.

Base Command

pan-os-install-file-content-update

Input

Argument Name Description Required
version_name Update file name to be installed on PAN-OS. Required
category The category of the content. Possible values are: wildfire, anti-virus, content. Required
skip_validity_check Skips file validity check with PAN-OS update server. Use this option for air-gapped networks and only if you trust the content file. Possible values are: yes, no. Default is no. Required

Context Output

Path Type Description
Panorama.Content.Install.JobID string JobID of the installation.
Panorama.Content.Install.Status string Installation status.

Command Example

pan-os-install-file-content-update version_name="panupv2-all-contents-8322-6317" category="content" skip_validity_check="yes"

Human Readable Output

Results

JobID Status
30 Pending

pan-os-platform-get-arp-tables


Gets all ARP tables from all firewalls in the topology.

Base Command

pan-os-platform-get-arp-tables

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowArp.Summary.hostid String ID of the PAN-OS host
PANOS.ShowArp.Summary.max String Maximum number of supported ARP entries.
PANOS.ShowArp.Summary.total String Total number of current ARP entries.
PANOS.ShowArp.Summary.timeout String ARP entry timeout.
PANOS.ShowArp.Summary.dp String Firewall dataplane associated with the entry.
PANOS.ShowArp.Result.hostid String ID of the PAN-OS host.
PANOS.ShowArp.Result.interface String Network interface learned ARP entry.
PANOS.ShowArp.Result.ip String Layer 3 address.
PANOS.ShowArp.Result.mac String Layer 2 address.
PANOS.ShowArp.Result.port String Network interface matching entry.
PANOS.ShowArp.Result.status String ARP entry status.
PANOS.ShowArp.Result.ttl String Time to live.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowArp": {
            "Result": [
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.1",
                    "mac": "00:66:4b:da:ce:61",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "1799"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "1.1.1.1",
                    "mac": "00:0c:29:31:bf:8b",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "991"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.11",
                    "mac": "4c:32:75:c2:b2:49",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "533"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.12",
                    "mac": "28:f0:76:79:63:b0",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "1721"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.13",
                    "mac": "a8:60:b6:20:ba:5c",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "1723"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.14",
                    "mac": "20:79:18:93:5a:94",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "19"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.15",
                    "mac": "68:ff:7b:2e:ef:f2",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "1396"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "ip": "192.168.1.16",
                    "mac": "00:d8:61:52:b2:ee",
                    "port": "ethernet1/1",
                    "status": "  c  ",
                    "ttl": "794"
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/2",
                    "ip": "10.10.0.12",
                    "mac": "00:0c:29:ba:d7:d2",
                    "port": "ethernet1/2",
                    "status": "  c  ",
                    "ttl": "1558"
                }
            ],
            "Summary": [
                {
                    "dp": "dp0",
                    "hostid": "0111112222333444",
                    "max": "1500",
                    "timeout": "1800",
                    "total": "9"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS ARP Table

dp hostid max timeout total
dp0 0111112222333444 1500 1800 9

pan-os-platform-get-route-summary


Pulls all route summary information from the topology.

Base Command

pan-os-platform-get-route-summary

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowRouteSummary.Summary.hostid Number ID of the PAN-OS host.
PANOS.ShowRouteSummary.Summary.total Number Total number of routes.
PANOS.ShowRouteSummary.Summary.limit Number Maximum number of routes for the platform.
PANOS.ShowRouteSummary.Summary.active Number Active routes in the routing table .

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowRouteSummary": {
            "Summary": [
                {
                    "active": 5,
                    "hostid": "0111112222333444",
                    "limit": 2500,
                    "total": 5
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Route Summary

active hostid limit total
5 0111112222333444 2500 5

pan-os-platform-get-routes


Pulls all route information from the topology.

Base Command

pan-os-platform-get-routes

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowRoute.Summary.hostid String ID of the PAN-OS host.
PANOS.ShowRoute.Summary.interface String Next hop interface.
PANOS.ShowRoute.Summary.route_count Number Total number of routes seen on the virtual router interface.
PANOS.ShowRoute.Result.hostid String ID of the PAN-OS host.
PANOS.ShowRoute.Result.virtual_router String Virtual router this route belongs to.
PANOS.ShowRoute.Result.destination String Network destination of the route.
PANOS.ShowRoute.Result.nexthop String Next hop to destination.
PANOS.ShowRoute.Result.metric String Route metric.
PANOS.ShowRoute.Result.flags String Route flags.
PANOS.ShowRoute.Result.age Number Age of the route.
PANOS.ShowRoute.Result.interface String Next hop interface.
PANOS.ShowRoute.Result.route_table String The route table this route belongs to.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowRoute": {
            "Result": [
                {
                    "age": null,
                    "destination": "0.0.0.0/0",
                    "flags": "A S   ",
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "metric": "10",
                    "nexthop": "192.168.1.1",
                    "route_table": "unicast",
                    "virtual_router": "default"
                },
                {
                    "age": null,
                    "destination": "10.10.0.0/24",
                    "flags": "A C   ",
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/2",
                    "metric": "0",
                    "nexthop": "10.10.0.1",
                    "route_table": "unicast",
                    "virtual_router": "default"
                },
                {
                    "age": null,
                    "destination": "10.10.0.1/32",
                    "flags": "A H   ",
                    "hostid": "0111112222333444",
                    "interface": null,
                    "metric": "0",
                    "nexthop": "0.0.0.0",
                    "route_table": "unicast",
                    "virtual_router": "default"
                },
                {
                    "age": null,
                    "destination": "192.168.1.0/24",
                    "flags": "A C   ",
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "metric": "0",
                    "nexthop": "192.168.1.139",
                    "route_table": "unicast",
                    "virtual_router": "default"
                },
                {
                    "age": null,
                    "destination": "192.168.1.139/32",
                    "flags": "A H   ",
                    "hostid": "0111112222333444",
                    "interface": null,
                    "metric": "0",
                    "nexthop": "0.0.0.0",
                    "route_table": "unicast",
                    "virtual_router": "default"
                }
            ],
            "Summary": [
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/1",
                    "route_count": 2
                },
                {
                    "hostid": "0111112222333444",
                    "interface": "ethernet1/2",
                    "route_count": 1
                },
                {
                    "hostid": "0111112222333444",
                    "interface": null,
                    "route_count": 2
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Routes

hostid interface route_count
0111112222333444 ethernet1/1 2
0111112222333444 ethernet1/2 1
0111112222333444   2

pan-os-platform-get-system-info


Gets information from all PAN-OS systems in the topology.

Base Command

pan-os-platform-get-system-info

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowSystemInfo.Summary.hostid String ID of the PAN-OS host.
PANOS.ShowSystemInfo.Summary.ip_address String Management IP address.
PANOS.ShowSystemInfo.Summary.sw_version String System software version.
PANOS.ShowSystemInfo.Summary.family String Platform family.
PANOS.ShowSystemInfo.Summary.model String Platform model.
PANOS.ShowSystemInfo.Summary.uptime String Total system uptime.
PANOS.ShowSystemInfo.Summary.hostname String System hostname.
PANOS.ShowSystemInfo.Result.hostid String ID of the PAN-OS host.
PANOS.ShowSystemInfo.Result.ip_address String Management IP address.
PANOS.ShowSystemInfo.Result.netmask String Management netmask.
PANOS.ShowSystemInfo.Result.mac_address String Management MAC address.
PANOS.ShowSystemInfo.Result.uptime String Total system uptime.
PANOS.ShowSystemInfo.Result.family String Platform family.
PANOS.ShowSystemInfo.Result.model String Platform model.
PANOS.ShowSystemInfo.Result.sw_version String System software version.
PANOS.ShowSystemInfo.Result.operational_mode String Current operational mode.
PANOS.ShowSystemInfo.Result.ipv6_address String Management IPv6 address.
PANOS.ShowSystemInfo.Result.default_gateway String Management default gateway.
PANOS.ShowSystemInfo.Result.public_ip_address String Firewall public IP address.
PANOS.ShowSystemInfo.Result.hostname String Device hostname.
PANOS.ShowSystemInfo.Result.av_version String System anti-virus version.
PANOS.ShowSystemInfo.Result.av_release_date String Release date of the antivirus content,
PANOS.ShowSystemInfo.Result.app_version String App content version,
PANOS.ShowSystemInfo.Result.app_release_date String Release date of the application content.
PANOS.ShowSystemInfo.Result.threat_version String Threat content version.
PANOS.ShowSystemInfo.Result.threat_release_date String Release date of the threat content.
PANOS.ShowSystemInfo.Result.wildfire_version String Wildfire content version.
PANOS.ShowSystemInfo.Result.wildfire_release_date String Wildfire release date.
PANOS.ShowSystemInfo.Result.url_filtering_version String URL filtering content version.
PANOS.ShowSystemInfo.Result.global_protect_client_package_version String The GlobalProtect client package version.
PANOS.ShowSystemInfo.Result.advanced_routing string Advanced routing engine feature.
PANOS.ShowSystemInfo.Result.multi_vsys string Virtual system feature.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowSystemInfo": {
            "Result": [
                {
                    "app_release_date": "",
                    "app_version": "8475-7000",
                    "av_release_date": "",
                    "av_version": "0",
                    "default_gateway": "192.168.1.1",
                    "family": "vm",
                    "hostid": "0111112222333444",
                    "hostname": "vm-lab-fw1",
                    "ip_address": "2.2.2.2",
                    "ipv6_address": "unknown",
                    "mac_address": "00:0c:29:eb:35:ad",
                    "model": "PA-VM",
                    "netmask": "255.255.255.0",
                    "operational_mode": "normal",
                    "public_ip_address": "unknown",
                    "sw_version": "10.0.5",
                    "threat_release_date": "",
                    "threat_version": "8475-7000",
                    "uptime": "22 days, 0:20:49",
                    "url_filtering_version": "20220218.20012",
                    "wildfire_release_date": "",
                    "wildfire_version": "0",
                    "global_protect_client_package_version": "0.0.0",
                    "advanced_routing": "on",
                    "multi_vsys": "on"
                },
                {
                    "app_release_date": "2021/12/06 18:49:44 PST",
                    "app_version": "8496-7089",
                    "av_release_date": "",
                    "av_version": "0",
                    "default_gateway": "192.168.1.1",
                    "family": "pc",
                    "hostid": "1.1.1.1",
                    "hostname": "Panorama",
                    "ip_address": "1.1.1.1",
                    "ipv6_address": "unknown",
                    "mac_address": "00:0c:29:31:bf:8b",
                    "model": "Panorama",
                    "netmask": "255.255.255.0",
                    "operational_mode": "normal",
                    "public_ip_address": "unknown",
                    "sw_version": "10.0.7",
                    "threat_release_date": "",
                    "threat_version": "",
                    "uptime": "3 days, 13:56:06",
                    "url_filtering_version": "",
                    "wildfire_release_date": "",
                    "wildfire_version": "0",
                    "advanced_routing": "off",
                    "multi_vsys": "on"
                }
            ],
            "Summary": [
                {
                    "family": "vm",
                    "hostid": "0111112222333444",
                    "hostname": "vm-lab-fw1",
                    "ip_address": "2.2.2.2",
                    "model": "PA-VM",
                    "sw_version": "10.0.5",
                    "uptime": "22 days, 0:20:49"
                },
                {
                    "family": "pc",
                    "hostid": "1.1.1.1",
                    "hostname": "Panorama",
                    "ip_address": "1.1.1.1",
                    "model": "Panorama",
                    "sw_version": "10.0.7",
                    "uptime": "3 days, 13:56:06"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS System Info

family hostid hostname ip_address model sw_version uptime
vm 0111112222333444 vm-lab-fw1 2.2.2.2 PA-VM 10.0.5 22 days, 0:20:49
pc 1.1.1.1 Panorama 1.1.1.1 Panorama 10.0.7 3 days, 13:56:06

pan-os-platform-get-device-groups


Gets operational information for all device groups in the Panorama topology, including group names, hierarchy, and associated firewalls. If no filter is provided, the command returns all device groups within the Panorama instance.

Base Command

pan-os-platform-get-device-groups

Input

Argument Name Description Required
device_filter_string The Panorama hostname or serial number used to filter device groups. This command is specifically for Panorama and requires an exact match (substrings are not supported). Optional

Context Output

Path Type Description
PANOS.DeviceGroupOp.hostid String ID of the PAN-OS host.
PANOS.DeviceGroupOp.serial String Serial number of the firewall.
PANOS.DeviceGroupOp.connected String Whether the firewall is currently connected.
PANOS.DeviceGroupOp.hostname String Firewall hostname.
PANOS.DeviceGroupOp.last_commit_all_state_sp String State of the last commit.
PANOS.DeviceGroupOp.name String Device group name.

pan-os-platform-get-template-stacks


Gets the operational information of the template stacks in the topology.

Base Command

pan-os-platform-get-template-stacks

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional

Context Output

Path Type Description
PANOS.TemplateStackOp.hostid String ID of the PAN-OS host.
PANOS.TemplateStackOp.serial String Serial number of the firewall.
PANOS.TemplateStackOp.connected String Whether the firewall is currently connected.
PANOS.TemplateStackOp.hostname String Firewall hostname.
PANOS.TemplateStackOp.last_commit_all_state_tpl String State of last commit.
PANOS.TemplateStackOp.name String Template stack name.

Command example


#### Context Example

```json
{
    "PANOS": {
        "TemplateStackOp": [
            {
                "connected": "yes",
                "hostid": "1.1.1.1",
                "hostname": "vm-lab-fw1",
                "last_commit_all_state_tpl": "commit succeeded with warnings",
                "name": "LAB-STACK",
                "serial": "0111112222333444"
            },
            {
                "connected": "no",
                "hostid": "1.1.1.1",
                "hostname": "vm-lab-fw02",
                "last_commit_all_state_tpl": "commit succeeded with warnings",
                "name": "LAB-STACK",
                "serial": "0111112222333455"
            }
        ]
    }
}

Human Readable Output

PAN-OS Operational Template Stack status

connected hostid hostname last_commit_all_state_tpl name serial
yes 1.1.1.1 vm-lab-fw1 commit succeeded with warnings LAB-STACK 0111112222333444
no 1.1.1.1 vm-lab-fw02 commit succeeded with warnings LAB-STACK 0111112222333455

pan-os-platform-get-global-counters


Gets global counter information from all the PAN-OS firewalls in the topology.

Base Command

pan-os-platform-get-global-counters

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowCounters.Summary.hostid String Host ID.
PANOS.ShowCounters.Summary.name String Human readable counter name.
PANOS.ShowCounters.Summary.value Number Current counter value.
PANOS.ShowCounters.Summary.rate Number Packets per second rate.
PANOS.ShowCounters.Summary.desc String Human readable counter description.
PANOS.ShowCounters.Result.hostid String Host ID.
PANOS.ShowCounters.Result.category String The counter category.
PANOS.ShowCounters.Result.name String Human readable counter name.
PANOS.ShowCounters.Result.value Number Current counter value.
PANOS.ShowCounters.Result.rate Number Packets per second rate.
PANOS.ShowCounters.Result.aspect String PAN-OS aspect.
PANOS.ShowCounters.Result.desc String Human readable counter description.
PANOS.ShowCounters.Result.id String Counter ID.
PANOS.ShowCounters.Result.severity String Counter severity.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowCounters": {
            "Result": [
                {
                    "aspect": "pktproc",
                    "category": "packet",
                    "desc": "Packets received",
                    "hostid": "11111111111111",
                    "id": "17",
                    "name": "pkt_recv",
                    "rate": 15,
                    "severity": "info",
                    "value": 17981379
                    "severity": "info",
                    "value": 2
                },
                {
                    "aspect": "pktproc",
                    "category": "ssl",
                    "desc": "Number of failures when receiving SSL session cache msg from MP",
                    "hostid": "11111111111111",
                    "id": "3185",
                    "name": "ssl_sess_cache_msg_from_mp_failure",
                    "rate": 0,
                    "severity": "info",
                    "value": 1834071
                }
            ],
            "Summary": [
                {
                    "desc": "Packets received",
                    "hostid": "11111111111111",
                    "name": "pkt_recv",
                    "rate": 15,
                    "value": 17981379
                },
            ]
        }
    }
}

Human Readable Output

PAN-OS Global Counters

desc hostid name rate value
Packets received 11111111111111 pkt_recv 15 17981379
Full Burst Packets received on retry 11111111111111 pkt_recv_retry 0 422240
Packet receive error 11111111111111 pkt_recv_err 0 225
Packets drop due to passive VM in dpdk mode 11111111111111 pkt_recv_flush_passive 0 2351
Packets transmitted 11111111111111 pkt_sent 3 5334628
Packets successfully transmitted to host interface 11111111111111 pkt_sent_host 0 685046
STP BPDU packets received 11111111111111 pkt_stp_rcv 1 1271742
Sessions allocated 11111111111111 session_allocated 0 537466
Sessions freed 11111111111111 session_freed 0 537440
Sessions installed 11111111111111 session_installed 0 449506
Session aging timer modified by unverified RST 11111111111111 session_unverified_rst 0 15579
Session is closing or closed and still receive TCP pkt 11111111111111 session_pkt_in_closed_state 0 3
Session notifications retransmitted to offload processor 11111111111111 session_renotify 0 3
Packets dropped: 802.1q tag not configured 11111111111111 flow_rcv_dot1q_tag_err 0 24
Packets dropped: invalid interface 11111111111111 flow_no_interface 0 24
Packets dropped: IPv6 disabled on interface 11111111111111 flow_ipv6_disabled 1 1505760
Non-SYN TCP packets without session match 11111111111111 flow_tcp_non_syn 0 83
Packets dropped: non-SYN TCP without session match 11111111111111 flow_tcp_non_syn_drop 0 83
Packets dropped: unhandled IP broadcast 11111111111111 flow_fwd_l3_bcast_drop 0 254789
Packets dropped: no route for IP multicast 11111111111111 flow_fwd_l3_mcast_drop 2 2450999
Packets dropped: no ARP 11111111111111 flow_fwd_l3_noarp 0 204
Packet dropped at forwarding: noxmit 11111111111111 flow_fwd_drop_noxmit 0 294
Packets dropped: Packet too short to cover IP header 11111111111111 flow_parse_ip_hdr 0 4
Packets dropped: IPv6 packet truncated 11111111111111 flow_parse_ip6_truncated 0 4
Packets received: IPv6 multicast pkts with flow off 11111111111111 flow_ip6_mcast_off 1 1505760
IP broadcast pkt received 11111111111111 flow_bcast_pkt_rcv 0 255147
DHCP broadcast pkt received 11111111111111 flow_dhcp_bcast_pkt_rcv 0 4
ARP packets received 11111111111111 flow_arp_pkt_rcv 3 4069186
ARP packets transmitted 11111111111111 flow_arp_pkt_xmt 0 87285
ARP requests replied 11111111111111 flow_arp_pkt_replied 0 82094
ARP entry learned 11111111111111 flow_arp_pkt_learned 0 68
Gratuitous ARP packets received 11111111111111 flow_arp_rcv_gratuitous 0 21524
ARP receive error 11111111111111 flow_arp_rcv_err 0 21202
ARP resolution packets transmitted 11111111111111 flow_arp_resolve_xmt 0 2477
ND entry GC 11111111111111 flow_nd_neigh_gc 0 1060
Packets received from control plane 11111111111111 flow_host_pkt_rcv 0 137018
Packets transmitted to control plane 11111111111111 flow_host_pkt_xmt 0 685046
Packets dropped: receive error from control plane 11111111111111 flow_host_rcv_err 0 11
Packets dropped: decapsulation error from control plane 11111111111111 flow_host_decap_err 0 26
Device management session allowed 11111111111111 flow_host_service_allow 0 136965
Device management session denied 11111111111111 flow_host_service_deny 0 38658
Host vardata not sent: rate limit ok 11111111111111 flow_host_vardata_rate_limit_ok 0 3
Packet on VxLAN port without I bit 11111111111111 flow_tci_vxlan_without_vni 0 2
netconfig temporarily unavailable 11111111111111 device_invalid_netconf 0 404
netconfig switched 11111111111111 device_netconf_switch 0 4
tundconfig switched 11111111111111 device_tundconf_switch 0 1
Packets for which IP checksum validation was done in software 11111111111111 flow_ip_cksm_sw_validation 6 8478257
Packets not allowed to egress for pre-negotiation 11111111111111 flow_drop_preneg_egress 0 2
Application identified by simple signature 11111111111111 appid_ident_by_simple_sig 0 92740
Application identified by L4 dport first 11111111111111 appid_ident_by_dport_first 0 174358
The number of packets processed by Application identification 11111111111111 appid_proc 0 177586
The number of unknown applications caused by max. packets reached 11111111111111 appid_unknown_max_pkts 0 9
The number of unknown UDP applications after app engine 11111111111111 appid_unknown_udp 0 271
The number of unknown applications because of no data 11111111111111 appid_unknown_fini_empty 0 97562
The total number of dynamic_ip_port NAT translate called 11111111111111 nat_dynamic_port_xlat 0 242839
The total number of dynamic_ip_port NAT release called 11111111111111 nat_dynamic_port_release 0 242825
The total number of dfa match using software 11111111111111 dfa_sw 1 1096816
tcp reassembly case 1 11111111111111 tcp_case_1 0 1
tcp reassembly case 2 11111111111111 tcp_case_2 0 346
out-of-window packets dropped 11111111111111 tcp_drop_out_of_wnd 0 116
The number of sessions with sml exit in detector i 11111111111111 ctd_sml_exit_detector_i 0 200010
The number of decoder resume requests 11111111111111 ctd_sml_unset_suspend 0 30
Handle reset and url exit 11111111111111 ctd_handle_reset_and_url_exit 0 30
ctd switch decoder 11111111111111 ctd_switch_decoder 0 10
ctd stops to process packet 11111111111111 ctd_stop_proc 0 10
run detector_i 11111111111111 ctd_run_detector_i 0 200020
SML VM opcode exit 11111111111111 ctd_sml_vm_run_impl_opcodeexit 0 199980
Forward to varrcvr error: TCP in establishment when session went away 11111111111111 ctd_fwd_err_tcp_state 0 94652
The total usage of software for pscan 11111111111111 ctd_pscan_sw 1 1170516
appid was changed 11111111111111 ctd_appid_reassign 0 60011
decoder was changed 11111111111111 ctd_decoder_reassign 0 10
session processed by ctd 11111111111111 ctd_process 0 351954
Packets processed by slowpath 11111111111111 ctd_pkt_slowpath 1 1186304
Number of Policy Hit Count periodical update 11111111111111 ctd_hitcount_period_update 0 4237
Number of url logs 11111111111111 log_url_cnt 0 6060
Number of uid request logs 11111111111111 log_uid_req_cnt 0 241849
Number of traffic logs 11111111111111 log_traffic_cnt 0 449480
Time (us) spent on writing packet-diag logs 11111111111111 log_pkt_diag_us 0 16
Number of URL database request 11111111111111 url_db_request 0 1965
Number of URL reply 11111111111111 url_db_reply 9 4419218
The number of packets get dropped because of waiting for url category request 11111111111111 url_request_pkt_drop 0 1771
The session is not waiting for url 11111111111111 url_session_not_in_wait 0 10
The number of HSM up/down events received 11111111111111 ssl_hsm_up_down_event_rcv 0 2
Number of failures when receiving SSL session cache msg from MP 11111111111111 ssl_sess_cache_msg_from_mp_failure 0 1834071

pan-os-platform-get-bgp-peers


Retrieves all BGP peer information from the PAN-OS firewalls in the topology.

Base Command

pan-os-platform-get-bgp-peers

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.ShowBGPPeers.Summary.hostid String Host ID.
PANOS.ShowBGPPeers.Summary.peer String Name of the BGP peer.
PANOS.ShowBGPPeers.Summary.status String Peer connection status.
PANOS.ShowBGPPeers.Summary.incoming_accepted String Total accepted routes from the peer.
PANOS.ShowBGPPeers.Result.hostid String Host ID.
PANOS.ShowBGPPeers.Result.peer String Name of the BGP peer.
PANOS.ShowBGPPeers.Result.vr String Virtual router in which the peer resides.
PANOS.ShowBGPPeers.Result.remote_as String Remote AS (Autonomous System) of the peers
PANOS.ShowBGPPeers.Result.status String Peer connection statuss
PANOS.ShowBGPPeers.Result.peer_address String IP address and port of the peers
PANOS.ShowBGPPeers.Result.local_address String Local router address and port of the peer.
PANOS.ShowBGPPeers.Result.incoming_total String Total incoming routes from the peer.
PANOS.ShowBGPPeers.Result.incoming_accepted String Total accepted routes from the peer.
PANOS.ShowBGPPeers.Result.incoming_rejected String Total rejected routes from the peer.
PANOS.ShowBGPPeers.Result.policy_rejected String Total routes rejected by the peer by policy.
PANOS.ShowBGPPeers.Result.outgoing_total String Total routes advertised to the peer.
PANOS.ShowBGPPeers.Result.outgoing_advertised String Number of advertised routes to the peer.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ShowBGPPeers": {
            "Result": [
                {
                    "hostid": "11111111111111",
                    "incoming_accepted": 0,
                    "incoming_rejected": 0,
                    "incoming_total": 0,
                    "local_address": "10.10.0.1",
                    "outgoing_advertised": 0,
                    "outgoing_total": 0,
                    "peer": "testlab-server",
                    "peer_address": "10.10.0.12",
                    "policy_rejected": 0,
                    "remote_as": "64511",
                    "status": "Active",
                    "vr": "default"
                }
            ],
            "Summary": [
                {
                    "hostid": "11111111111111",
                    "incoming_accepted": 0,
                    "peer": "testlab-server",
                    "status": "Active"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS BGP Peers

hostid incoming_accepted peer status
11111111111111 0 testlab-server Active

pan-os-platform-get-available-software


Check the devices for software that is available to be installed.

Base Command

pan-os-platform-get-available-software

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.SoftwareVersions.Summary.hostid String Host ID.
PANOS.SoftwareVersions.Summary.version String The software version in Major.Minor.Maint format.
PANOS.SoftwareVersions.Summary.filename String Software version filename.
PANOS.SoftwareVersions.Summary.size String Size of the software in MB.
PANOS.SoftwareVersions.Summary.size_kb String Size of the software in KB.
PANOS.SoftwareVersions.Summary.release_notes String Link to version release notes on PAN knowledge base.
PANOS.SoftwareVersions.Summary.downloaded Boolean True if the software version is present on the system.
PANOS.SoftwareVersions.Summary.current Boolean True if this is the currently installed software on the system.
PANOS.SoftwareVersions.Summary.latest Boolean True if this is the most recently released software for this platform.
PANOS.SoftwareVersions.Summary.uploaded Boolean True if the software version has been uploaded to the system.

Command example


#### Context Example

```json
{
    "PANOS": {
        "SoftwareVersions": {
            "Summary": [
                {
                    "current": false,
                    "downloaded": false,
                    "filename": "PanOS_vm-10.2.0",
                    "hostid": "11111111111111",
                    "latest": true,
                    "release_notes": "https://www.paloaltonetworks.com/documentation/10-2/pan-os/pan-os-release-notes",
                    "size": "1010",
                    "size_kb": "1034657",
                    "uploaded": false,
                    "version": "10.2.0"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Available Software Versions

current downloaded filename hostid latest release_notes size size_kb uploaded version
false false PanOS_vm-10.2.0 11111111111111 true https://www.paloaltonetworks.com/documentation/10-2/pan-os/pan-os-release-notes 1010 1034657 false 10.2.0
false false PanOS_vm-10.1.5 11111111111111 false https://www.paloaltonetworks.com/documentation/10-1/pan-os/pan-os-release-notes 457 468174 false 10.1.5
false false PanOS_vm-10.1.4-h4 11111111111111 false https://www.paloaltonetworks.com/documentation/10-1/pan-os/pan-os-release-notes 407 416843 false 10.1.4-h4

pan-os-platform-get-ha-state


Get the HA state and associated details from the given device and any other details.

Base Command

pan-os-platform-get-ha-state

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.HAState.hostid String Host ID.
PANOS.HAState.active Boolean Whether this is the active firewall in a pair. True if standalone as well.
PANOS.HAState.status String String HA status.
PANOS.HAState.peer String HA peer.

Command example


#### Context Example

```json
{
    "PANOS": {
        "HAState": [
            {
                "active": true,
                "hostid": "11111111111111",
                "peer": "",
                "status": "HA Not enabled."
            },
            {
                "active": true,
                "hostid": "192.168.1.145",
                "peer": "",
                "status": "HA Not enabled."
            }
        ]
    }
}

Human Readable Output

PAN-OS HA State

active hostid status
true 11111111111111 HA Not enabled.
true 192.168.1.145 HA Not enabled.

pan-os-platform-get-jobs


Get all the jobs from the devices in the environment, or a single job when ID is specified.

Base Command

pan-os-platform-get-jobs

Input

Argument Name Description Required
device_filter_string String by which to filter the results to only show specific hostnames or serial numbers. Optional
target Single serial number to target with this command Optional
status Filter to return jobs by status. Optional
job_type Filter to return jobs by type. Optional
id Filter by ID. Optional
polling Whether to poll the job status until it reaches a terminal state (FIN). Only takes effect when a single job “id” is provided. Possible values are: true, false. Default is false. Optional
interval_in_seconds The interval between poll attempts, in seconds. Default is 30. Optional
timeout_in_seconds The maximum time to wait for the job to reach a terminal state, in seconds. If the timeout is reached before the job finishes, the command returns the last known result. Default is 3600. Optional

Context Output

Path Type Description
PANOS.JobStatus.hostid String Host ID.
PANOS.JobStatus.id Number ID of job.
PANOS.JobStatus.type String Job type.
PANOS.JobStatus.tfin String Time finished.
PANOS.JobStatus.status String Status of the job.
PANOS.JobStatus.result String The result of the job.
PANOS.JobStatus.user String The user who initiated the job.
PANOS.JobStatus.tenq String The time the job was enqueued into the system.
PANOS.JobStatus.stoppable String Whether the job can be stopped after it started.
PANOS.JobStatus.description String The job description.
PANOS.JobStatus.positionInQ String The position of the job in the current job queue.
PANOS.JobStatus.progress String The numerical progress of the job.
PANOS.JobStatus.warnings String The warnings of the job.

Command example


#### Context Example

```json
{
    "PANOS": {
        "JobStatus": [
            {
                "description": null,
                "hostid": "11111111111111",
                "id": 1,
                "positionInQ": "0",
                "progress": "01:02:18",
                "result": "OK",
                "status": "FIN",
                "stoppable": "no",
                "tenq": "2022/03/30 01:02:04",
                "tfin": "01:02:18",
                "type": "Downld",
                "user": null,
                "warnings": null
            },
            {
                "description": null,
                "hostid": "22222222222222",
                "id": 2,
                "positionInQ": "0",
                "progress": "100",
                "result": "OK",
                "status": "FIN",
                "stoppable": "no",
                "tenq": "2022/03/15 14:16:32",
                "tfin": "14:16:33",
                "type": "BuildXMLCache",
                "user": null,
                "warnings": null
            }
        ]
    }
}

Human Readable Output

PAN-OS Job Status

hostid id positionInQ progress result status stoppable tenq tfin type user warnings
11111111111111 1 0 100 OK FIN no 2022/03/15 14:17:16 14:18:17 Downld    
22222222222222 2 0 01:02:15 OK FIN no 2022/03/16 01:02:04 01:02:15 BuildXMLCache    

pan-os-platform-download-software


Download the provided software version onto the device.

Base Command

pan-os-platform-download-software

Input

Argument Name Description Required
version The software version to upgrade to, for example, 9.1.2. Required
device_filter_string String by which to filter the results to only install to sepecific devices or serial numbers. Optional
target Single serial number to target with this command Optional
sync If provided, runs the download synchronously. Make sure ‘execution-timeout’ is increased. Optional

Context Output

Path Type Description
PANOS.DownloadStatus.Summary.hostid String Host ID.
PANOS.DownloadStatus.Summary.started String Whether the download process started.

Command example

!pan-os-platform-download-software version=9.1.0

Context Example

{
    "PANOS": {
        "DownloadStatus": {
            "Summary": [
                {
                    "hostid": "11111111111111",
                    "started": true
                },
                {
                    "hostid": "192.168.1.145",
                    "started": true
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Software Download request Status

hostid started
11111111111111 true
192.168.1.145 true

pan-os-apply-dns-signature-policy


Allows assigning of EDL to the Anti-Spyware profile under “DNS Signature Policies”.

Base Command

pan-os-apply-dns-signature-policy

Input

Argument Name Description Required
anti_spyware_profile_name The name of the anti spyware profile. If the profile exists, the command will operate on it, otherwise, if a new name is given, a new Anti-Spyware profile will be created. Required
dns_signature_source The EDL name to link to the profile. Required
action Action on the DNS queries. Possible values are: alert, allow, block, sinkhole. Required
packet_capture Allows capturing packets on match. Select “single-packet” to capture the first packet of the session or “extended-capture” to set between 1-50 packets. Packet capture can be very CPU intensive and can degrade firewall performance. Only use this feature when necessary and make sure you turn it off after you have collected the required packets. Possible values are: disable, single-packet, extended-capture. Default is disable. Optional

Context Output

There is no context output for this command.

Human Readable Output

success

pan-os-platform-reboot


Reboot the given device by hostid. Warning: This command has no confirmation and the device
will immediately reboot.

Base Command

pan-os-platform-reboot

Input

Argument Name Description Required
target ID of host (serial or hostname) to reboot. Required

Context Output

Path Type Description
PANOS.RestartStatus.Summary.hostid String Host ID.
PANOS.RestartStatus.Summary.started String Whether the system reboot has started.

Command example

!pan-os-platform-reboot target=11111111111111

Context Example

{
    "PANOS": {
        "RestartStatus": {
            "Summary": [
                {
                    "hostid": "11111111111111",
                    "started": true
                }
            ]
        }
    }
}

pan-os-platform-get-system-status


Checks the status of the given device, checking whether it’s up or down and if the operational mode is normal. Supports polling to wait until the device becomes available.

Base Command

pan-os-platform-get-system-status

Input

Argument Name Description Required
target ID of host (serial or hostname) to check. Required
polling Whether to use polling. When set to “true”, the command polls the target device until it becomes available (operational mode “normal”) or the timeout is reached. Possible values are: true, false. Default is false. Optional
interval_in_seconds The interval (in seconds) between polling attempts. Default is 30. Optional
timeout The timeout (in seconds) when polling. Default is 1200. Optional

Context Output

Path Type Description
PANOS.SystemStatus.hostid String Host ID.
PANOS.SystemStatus.up String Whether the host device is up or still unavailable.

Command example

!pan-os-platform-get-system-status target=11111111111111

Context Example

{
    "PANOS": {
        "SystemStatus": {
          "hostid": "11111111111111",
          "up": true
        }
    }
}

pan-os-platform-update-ha-state


Checks the status of the given device, checking whether it’s up or down and the operational mode normal.

Base Command

pan-os-platform-update-ha-state

Input

Argument Name Description Required
hostid ID of host (serial or hostname) to update the state. Required
state New state. Required

Context Output

Path Type Description
PANOS.HAStateUpdate.hostid String Host ID.
PANOS.HAStateUpdate.state String New HA state.

Command example

!pan-os-platform-update-ha-state hostid=11111111111111 state=functional

Context Example

{
    "PANOS": {
        "HAStateUpdate": {
          "hostid": "11111111111111",
          "state": "functional"
        }
    }
}

pan-os-hygiene-check-log-forwarding


Checks that at least one log forwarding profile is configured according to best practices.

Base Command

pan-os-hygiene-check-log-forwarding

Input

Argument Name Description Required
device_filter_string String by which to filter so that only the given device is checked. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the hygiene check.
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check.
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed.
PANOS.ConfigurationHygiene.Summary.issue_count String Total number of matching issues.
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String The parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue.
PANOS.ConfigurationHygiene.Result.description String Human readable description of the issue.
PANOS.ConfigurationHygiene.Result.name String The affected object name.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Result": [
                {
                    "container_name": "LAB",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile-1"
                },
                {
                    "container_name": "LAB",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile-1-1"
                },
                {
                    "container_name": "shared",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile"
                }
            ],
            "Summary": [
                {
                    "description": "Fails if there are no valid log forwarding profiles configured.",
                    "issue_code": "BP-V-1",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                },
                {
                    "description": "Fails if the configured log forwarding profile has no match list.",
                    "issue_code": "BP-V-2",
                    "issue_count": 3,
                    "result": "\u274c"
                },
                {
                    "description": "Fails if enhanced application logging is not configured.",
                    "issue_code": "BP-V-3",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if there are no valid log forwarding profiles configured. BP-V-1 0 ✔️
Fails if the configured log forwarding profile has no match list. BP-V-2 3
Fails if enhanced application logging is not configured. BP-V-3 0 ✔️

pan-os-hygiene-check-vulnerability-profiles


Checks the configured vulnerability profiles to ensure at least one meets best practices.

Base Command

pan-os-hygiene-check-vulnerability-profiles

Input

Argument Name Description Required
device_filter_string String by which to filter so that only the given device is checked. Optional
minimum_block_severities Comma-separated list of severities that must be in drop/reset/block-ip mode. Default is critical,high. Optional
minimum_alert_severities Comma-separated list of severities that must be in alert/default or higher mode. Default is medium,low. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the hygiene check.
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check.
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed.
PANOS.ConfigurationHygiene.Summary.issue_count Nunber Total number of matching issues.
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String The parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue.
PANOS.ConfigurationHygiene.Result.description String Human readable description of the issue.
PANOS.ConfigurationHygiene.Result.name String The affected object name

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Summary": [
                {
                    "description": "Fails if no vulnerability profile is configured for visibility.",
                    "issue_code": "BP-V-4",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if no vulnerability profile is configured for visibility. BP-V-4 0 ✔️

pan-os-platform-install-software


Install the given software version onto the device. Download the software first with panorama-download-panos-version.

Base Command

pan-os-platform-install-software

Input

Argument Name Description Required
version Software version to upgrade to, for example: 9.1.2. Required
device_filter_string SString by which to filter to only install to specific devices or serial numbers. Optional
sync If provided, runs the download synchronously. Make sure ‘execution-timeout’ is increased. Optional
target Single serial number to target with this command Optional

Context Output

Path Type Description
PANOS.InstallStatus.Summary.hostid String Host ID,
PANOS.InstallStatus.Summary.started String Whether the download process has started.

Command example

!pan-os-platform-install-software version=9.1.0

Context Example

{
    "PANOS": {
        "InstallStatus": {
            "Summary": [
                {
                    "hostid": "1111111111111",
                    "started": true
                },
                {
                    "hostid": "192.168.1.145",
                    "started": true
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Software Install request Status

hostid started
1111111111111 true
192.168.1.145 true

pan-os-hygiene-check-log-forwarding


Checks that at least one log forwarding profile is configured according to best practices.

Base Command

pan-os-hygiene-check-log-forwarding

Input

Argument Name Description Required
device_filter_string String by which to filter so that only the given device is checked. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the hygiene check.
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check.
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed.
PANOS.ConfigurationHygiene.Summary.issue_count Number Total number of matching issues.
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String The parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue.
PANOS.ConfigurationHygiene.Result.description String Human readable description of the issue.
PANOS.ConfigurationHygiene.Result.name String The affected object name.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Result": [
                {
                    "container_name": "LAB",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile-1"
                },
                {
                    "container_name": "LAB",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile-1-1"
                },
                {
                    "container_name": "shared",
                    "description": "Log forwarding profile missing log type 'threat'.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-2",
                    "name": "test_fwd_profile"
                }
            ],
            "Summary": [
                {
                    "description": "Fails if there are no valid log forwarding profiles configured.",
                    "issue_code": "BP-V-1",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                },
                {
                    "description": "Fails if the configured log forwarding profile has no match list.",
                    "issue_code": "BP-V-2",
                    "issue_count": 3,
                    "result": "\u274c"
                },
                {
                    "description": "Fails if enhanced application logging is not configured.",
                    "issue_code": "BP-V-3",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if there are no valid log forwarding profiles configured. BP-V-1 0 ✔️
Fails if the configured log forwarding profile has no match list. BP-V-2 3
Fails if enhanced application logging is not configured. BP-V-3 0 ✔️

pan-os-hygiene-check-vulnerability-profiles


Checks the configured vulnerability profiles to ensure at least one meets best practices.

Base Command

pan-os-hygiene-check-vulnerability-profiles

Input

Argument Name Description Required
device_filter_string String by which to filter so that only the given device is checked. Optional
minimum_block_severities Comma-separated list of severities that must be in drop/reset/block-ip mode. Default is critical,high. Optional
minimum_alert_severities Comma-separated list of severities that must be in alert/default or higher mode. Default is medium,low. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the hygiene check.
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check.
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed.
PANOS.ConfigurationHygiene.Summary.issue_count Number Total number of matching issues.
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String The parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue.
PANOS.ConfigurationHygiene.Result.description String Human readable description of the issue.
PANOS.ConfigurationHygiene.Result.name String The affected object name.

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Summary": [
                {
                    "description": "Fails if no vulnerability profile is configured for visibility.",
                    "issue_code": "BP-V-4",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if no vulnerability profile is configured for visibility. BP-V-4 0 ✔️

pan-os-hygiene-check-spyware-profiles


Checks the configured Anti-spyware profiles to ensure at least one meets best practices.

Base Command

pan-os-hygiene-check-spyware-profiles

Input

Argument Name Description Required
device_filter_string String to filter to only check given devices. Optional
minimum_block_severities csv list of severities that must be in drop/reset/block-ip mode. Default is critical,high. Optional
minimum_alert_severities csv list of severities that must be in alert/default or higher mode. Default is medium,low. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the check
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed
PANOS.ConfigurationHygiene.Summary.issue_count String Total number of matching issues
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygiene.Result.description String Human readable description of issue
PANOS.ConfigurationHygiene.Result.name String The affected object name

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Summary": [
                {
                    "description": "Fails if no spyware profile is configured for visibility.",
                    "issue_code": "BP-V-5",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if no spyware profile is configured for visibility. BP-V-5 0 ✔️

pan-os-hygiene-check-url-filtering-profiles


Checks the configured URL Filtering profiles to ensure at least one meets best practices.

Base Command

pan-os-hygiene-check-url-filtering-profiles

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the check
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed
PANOS.ConfigurationHygiene.Summary.issue_count String Total number of matching issues
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygiene.Result.description String Human readable description of issue
PANOS.ConfigurationHygiene.Result.name String The affected object name

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Summary": [
                {
                    "description": "Fails if no spyware profile is configured for url-filtering",
                    "issue_code": "BP-V-6",
                    "issue_count": 0,
                    "result": "\u2714\ufe0f"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails if no spyware profile is configured for url-filtering BP-V-6 0 ✔️

pan-os-hygiene-conforming-url-filtering-profiles


Returns a list of existing PANOS URL filtering objects that conform to best practices.

Base Command

pan-os-hygiene-conforming-url-filtering-profiles

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional

Context Output

Path Type Description
PANOS.PanosObject.hostid String Host ID.
PANOS.PanosObject.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.PanosObject.name String The PAN-OS object name
PANOS.PanosObject.object_type String The PAN-OS-Python object type

Command example


#### Context Example

```json
{
    "PANOS": {
        "PanosObject": [
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Outbound-URL",
                "object_type": "URLFilteringProfile"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Exception-URL",
                "object_type": "URLFilteringProfile"
            }
        ]
    }
}

Human Readable Output

PAN-OS Objects

container_name hostid name object_type
shared 192.168.1.145 Outbound-URL URLFilteringProfile
shared 192.168.1.145 Exception-URL URLFilteringProfile

pan-os-hygiene-conforming-spyware-profiles


Returns all Anti-spyware profiles that conform to best practices.

Base Command

pan-os-hygiene-conforming-spyware-profiles

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional
minimum_block_severities csv list of severities that must be in drop/reset/block-ip mode. Default is critical,high. Optional
minimum_alert_severities csv list of severities that must be in alert/default or higher mode. Default is medium,low. Optional

Context Output

Path Type Description
PANOS.PanosObject.hostid String Host ID.
PANOS.PanosObject.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.PanosObject.name String The PAN-OS object name
PANOS.PanosObject.object_type String The PAN-OS-Python object type

Command example


#### Context Example

```json
{
    "PANOS": {
        "PanosObject": [
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Outbound-AS",
                "object_type": "AntiSpywareProfile"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Inbound-AS",
                "object_type": "AntiSpywareProfile"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Internal-AS",
                "object_type": "AntiSpywareProfile"
            }
        ]
    }
}

Human Readable Output

PAN-OS Objects

container_name hostid name object_type
shared 192.168.1.145 Outbound-AS AntiSpywareProfile
shared 192.168.1.145 Inbound-AS AntiSpywareProfile
shared 192.168.1.145 Internal-AS AntiSpywareProfile

pan-os-hygiene-conforming-vulnerability-profiles


Returns all Vulnerability profiles that conform to best practices.

Base Command

pan-os-hygiene-conforming-vulnerability-profiles

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional
minimum_block_severities csv list of severities that must be in drop/reset/block-ip mode. Default is critical,high. Optional
minimum_alert_severities csv list of severities that must be in alert/default or higher mode. Default is medium,low. Optional

Context Output

Path Type Description
PANOS.PanosObject.hostid String Host ID.
PANOS.PanosObject.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.PanosObject.name String The PAN-OS object name
PANOS.PanosObject.object_type String The PAN-OS-Python object type

Command example


#### Context Example

```json
{
    "PANOS": {
        "PanosObject": [
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Outbound-VP",
                "object_type": "VulnerabilityProfile"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Inbound-VP",
                "object_type": "VulnerabilityProfile"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Internal-VP",
                "object_type": "VulnerabilityProfile"
            }
        ]
    }
}

Human Readable Output

PAN-OS Objects

container_name hostid name object_type
shared 192.168.1.145 Outbound-VP VulnerabilityProfile
shared 192.168.1.145 Inbound-VP VulnerabilityProfile
shared 192.168.1.145 Internal-VP VulnerabilityProfile

pan-os-hygiene-check-security-zones


Check configured security zones have correct settings.

Base Command

pan-os-hygiene-check-security-zones

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the check
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed
PANOS.ConfigurationHygiene.Summary.issue_count String Total number of matching issues
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygiene.Result.description String Human readable description of issue
PANOS.ConfigurationHygiene.Result.name String The affected object name

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Result": [
                {
                    "container_name": "LAB",
                    "description": "Security zone has no log forwarding setting.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-7",
                    "name": "TEST_ZONE"
                }
            ],
            "Summary": [
                {
                    "description": "Fails when a security zone has no log forwarding setting.",
                    "issue_code": "BP-V-7",
                    "issue_count": 1,
                    "result": "\u274c"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails when a security zone has no log forwarding setting. BP-V-7 1

pan-os-hygiene-check-security-rules


Check security rules are configured correctly.

Base Command

pan-os-hygiene-check-security-rules

Input

Argument Name Description Required
device_filter_string String to filter to only check given device. Optional

Context Output

Path Type Description
PANOS.ConfigurationHygiene.Summary.description String The description of the check
PANOS.ConfigurationHygiene.Summary.issue_code String The shorthand code for this hygiene check
PANOS.ConfigurationHygiene.Summary.result String Whether the check passed or failed
PANOS.ConfigurationHygiene.Summary.issue_count String Total number of matching issues
PANOS.ConfigurationHygiene.Result.hostid String Host ID.
PANOS.ConfigurationHygiene.Result.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygiene.Result.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygiene.Result.description String Human readable description of issue
PANOS.ConfigurationHygiene.Result.name String The affected object name

Command example


#### Context Example

```json
{
    "PANOS": {
        "ConfigurationHygiene": {
            "Result": [
                {
                    "container_name": "shared",
                    "description": "Security rule is not configured to log at session end.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-8",
                    "name": "Test-webapp-rule"
                },
                {
                    "container_name": "shared",
                    "description": "Security rule has no log forwarding profile.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-9",
                    "name": "Test-webapp-rule"
                },
                {
                    "container_name": "shared",
                    "description": "Security rule has no profile group or configured threat profiles.",
                    "hostid": "192.168.1.145",
                    "issue_code": "BP-V-10",
                    "name": "Test-webapp-rule"
                }
            ],
            "Summary": [
                {
                    "description": "Fails when a security rule is not configured to log at session end.",
                    "issue_code": "BP-V-8",
                    "issue_count": 1,
                    "result": "\u274c"
                },
                {
                    "description": "Fails when a security rule has no log forwarding profile configured.",
                    "issue_code": "BP-V-9",
                    "issue_count": 1,
                    "result": "\u274c"
                },
                {
                    "description": "Fails when a security rule has no configured profiles or profile groups.",
                    "issue_code": "BP-V-10",
                    "issue_count": 1,
                    "result": "\u274c"
                }
            ]
        }
    }
}

Human Readable Output

PAN-OS Configuration Hygiene Check

description issue_code issue_count result
Fails when a security rule is not configured to log at session end. BP-V-8 1
Fails when a security rule has no log forwarding profile configured. BP-V-9 1
Fails when a security rule has no configured profiles or profile groups. BP-V-10 1

pan-os-hygiene-fix-log-forwarding


Fix log forwarding issues identified by pan-os-hygiene-check-log-forwarding.

Base Command

pan-os-hygiene-fix-log-forwarding

Input

Argument Name Description Required
issue Dictionary of Hygiene issue, from a hygiene check command. Can be a list. Required

Context Output

Path Type Description
PANOS.ConfigurationHygieneFix.hostid String Host ID
PANOS.ConfigurationHygieneFix.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygieneFix.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygieneFix.description String Human readable description of issue
PANOS.ConfigurationHygieneFix.name String The affected object name

Command example

!pan-os-hygiene-fix-log-forwarding issue=${PANOS.ConfigurationHygiene.Result}

Context Example

{
  "PANOS": {
    "ConfigurationHygieneFix": [
      {
        "container_name": "LAB",
        "description": "Enabled Enhanced Application Logging.",
        "hostid": "192.168.1.145",
        "issue_code": "BP-V-2",
        "name": "test_fwd_profile-1"
      }
    ]
  }
}

pan-os-hygiene-fix-security-zone-log-settings


Fixes security zones that are configured without a valid log forwarding profile.

Base Command

pan-os-hygiene-fix-security-zone-log-settings

Input

Argument Name Description Required
issue Dictionary of Hygiene issue, from a hygiene check command. Can be a list. Required
log_forwarding_profile_name Name of log forwarding profile to set. Required

Context Output

Path Type Description
PANOS.ConfigurationHygieneFix.hostid String Host ID
PANOS.ConfigurationHygieneFix.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygieneFix.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygieneFix.description String Human readable description of issue
PANOS.ConfigurationHygieneFix.name String The affected object name

Command example

!pan-os-hygiene-fix-security-zone-log-settings issue=${PANOS.ConfigurationHygiene.Result} log_forwarding_profile_name="test-fwd-profile"

Context Example

{
  "PANOS": {
    "ConfigurationHygieneFix": [
      {
        "container_name": "LAB",
        "description": "Set log forwarding profile test-fwd-profile",
        "hostid": "192.168.1.145",
        "issue_code": "BP-V-7",
        "name": "TEST_ZONE"
      }
    ]
  }
}

pan-os-hygiene-fix-security-rule-log-settings


Fixed security rules that have incorrect log settings by adding a log forwarding profile and setting

Base Command

pan-os-hygiene-fix-security-rule-log-settings

Input

Argument Name Description Required
issue Dictionary of Hygiene issue, from a hygiene check command. Can be list. Required
log_forwarding_profile_name Name of log forwarding profile to use as log setting. Required

Context Output

Path Type Description
PANOS.ConfigurationHygieneFix.hostid String Host ID
PANOS.ConfigurationHygieneFix.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygieneFix.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygieneFix.description String Human readable description of issue
PANOS.ConfigurationHygieneFix.name String The affected object name

Command example

!pan-os-hygiene-fix-security-rule-log-settings issue=${PANOS.ConfigurationHygiene.Result.[0]} log_forwarding_profile_name="test-fwd-profile""

Context Example

{
  "PANOS": {
    "ConfigurationHygieneFix": [
      {
        "container_name": "shared",
        "description": "Set log forwarding profile to test-fwd-profile-3 andenabled log at session end.",
        "hostid": "192.168.1.145",
        "issue_code": "BP-V-8",
        "name": "Test-webapp-rule"
      }
    ]
  }
}

pan-os-hygiene-fix-security-rule-profile-settings


Fixed security rules that have incorrect log settings by adding a log forwarding profile and setting

Base Command

pan-os-hygiene-fix-security-rule-profile-settings

Input

Argument Name Description Required
issue Dictionary of Hygiene issue, from a hygiene check command. Required
security_profile_group_name Name of Security profile group to use as log setting. Required

Context Output

Path Type Description
PANOS.ConfigurationHygieneFix.hostid String Host ID
PANOS.ConfigurationHygieneFix.container_name String What parent container (DG, Template, VSYS) this object belongs to.
PANOS.ConfigurationHygieneFix.issue_code String The shorthand code for the issue
PANOS.ConfigurationHygieneFix.description String Human readable description of issue
PANOS.ConfigurationHygieneFix.name String The affected object name

Command example

!pan-os-hygiene-fix-security-rule-log-settings issue=${PANOS.ConfigurationHygiene.Result.[0]} log_forwarding_profile_name="test-fwd-profile""

Context Example

{
  "PANOS": {
    "ConfigurationHygieneFix": [
      {
        "container_name": "shared",
        "description": "Set security profile group Alert-Only",
        "hostid": "192.168.1.145",
        "issue_code": "BP-V-10",
        "name": "Test-webapp-rule"
      }
    ]
  }
}

pan-os-config-get-object


Searches and returns a reference for the given object type and name. If no name is provided, all objects of the given type will be returned.

Base Command

pan-os-config-get-object

Input

Argument Name Description Required
object_type The type of object to search; see https://pandevice.readthedocs.io/en/latest/module-objects.html. Possible values are: AddressObject, AddressGroup, ServiceGroup, ServiceObject, ApplicationObject, ApplicationGroup, LogForwardingProfile, SecurityProfileGroup. Required
device_filter_string If provided, only objects from the given device are returned. Optional
object_name The name of the object reference to return if looking for a specific object. Supports regex if “use_regex” is set. Optional
parent The parent vsys or device group to search. If not provided, all will be returned. Optional
use_regex Enables regex matching on object name. Optional

Context Output

Path Type Description
PANOS.PanosObject.hostid String Host ID.
PANOS.PanosObject.container_name String The parent container (DG, Template, VSYS) this object belongs to.
PANOS.PanosObject.name String The PAN-OS object name.
PANOS.PanosObject.object_type String The PAN-OS python object type.

Command example

!pan-os-config-get-object object_type="AddressObject"

Context Example

{
    "PANOS": {
        "PanosObject": [
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Sinkhole-IPv4",
                "object_type": "AddressObject"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "Sinkhole-IPv6",
                "object_type": "AddressObject"
            },
            {
                "container_name": "shared",
                "hostid": "192.168.1.145",
                "name": "test-shared",
                "object_type": "AddressObject"
            }
        ]
    }
}

Human Readable Output

PAN-OS Objects

container_name hostid name object_type
shared 192.168.1.145 Sinkhole-IPv4 AddressObject
shared 192.168.1.145 Sinkhole-IPv6 AddressObject
shared 192.168.1.145 test-shared AddressObject

pan-os-platform-get-device-state


Get the device state from the provided device. Note; This will attempt to connect directly to the provided target to get the device state. If the IP address as reported in “show system info” is unreachable, this command will fail.

Base Command

pan-os-platform-get-device-state

Input

Argument Name Description Required
target Serial number of the device from which to fetch the device state. Required
filename Name of the file for the state file to be saved. Default will use the hostname as the filename. Optional
ip_address The IP Address of a Panorama managed firewall. Default will use Panorama-supplied IP Address. Optional

Context Output

Path Type Description
InfoFile.Name String Filename.
InfoFile.EntryID String Entry ID.
InfoFile.Size String Size of the file.
InfoFile.Type String Type of the file.
InfoFile.Info String Basic information of the file.

pan-os-push-to-template


Pushes the given PAN-OS template to the given devices or all devices that belong to the template.

Base Command

pan-os-push-to-template

Input

Argument Name Description Required
template The template to push. Optional
validate-only Whether to validate the policy. Possible values are: true, false. Default is false. Optional
description The push description. Optional
serial_number The serial number for a virtual system commit. If provided, the commit will be a virtual system commit. Optional

Context Output

Path Type Description
Panorama.Push.Template String The device group in which the policies were pushed.
Panorama.Push.JobID Number The job ID of the policies that were pushed.
Panorama.Push.Status String The push status.
Panorama.Push.Warnings String The push warnings.
Panorama.Push.Errors String The push errors.

Command example

!pan-os-push-to-template template=LAB

Context Example

{
    "Panorama": {
        "Push": {
            "JobID": "564",
            "Status": "Pending",
            "Template": "LAB"
        }
    }
}

Human Readable Output

Push to Template

JobID Status
564 Pending

pan-os-push-to-template-stack


Pushes the given PAN-OS template-stack to the given devices or all devices that belong to the template stack.

Base Command

pan-os-push-to-template-stack

Input

Argument Name Description Required
template-stack The template-stack to push. Required
validate-only Whether to validate the policy. Possible values are: true, false. Default is false. Optional
description The push description. Optional
serial_number The serial number for a virtual system commit. If provided, the commit will be a virtual system commit. Optional

Context Output

Path Type Description
Panorama.Push.TemplateStack String The device group in which the policies were pushed.
Panorama.Push.JobID Number The job ID of the policies that were pushed.
Panorama.Push.Status String The push status.
Panorama.Push.Warnings String The push warnings.
Panorama.Push.Errors String The push errors.

Command example

!pan-os-push-to-template-stack template-stack=LAB-STACK

Context Example

{
    "Panorama": {
        "Push": {
            "JobID": "565",
            "Status": "Pending",
            "TemplateStack": "LAB-STACK"
        }
    }
}

Human Readable Output

Push to Template

JobID Status
565 Pending

pan-os-get-running-config


Pull the running config file

Base Command

pan-os-get-running-config

Input

Argument Name Description Required
target The target device. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-get-running-config target=00000000000

Context Example

{
    "File": {
        "EntryID": "3678@268ee30b-69fa-4496-8ab8-51cdeb19c452",
        "Info": "text/plain",
        "MD5": "da7faf4c6440d87a3e50ef93536ed81a",
        "Name": "running_config",
        "SHA1": "7910271adc8b3e9de28b804442a11a5160d4adda",
        "SHA256": "a4da4cbee7f3e411fbf76f2595d7dfcffce85bd6b3c000dac7a17e58747d1a2b",
        "SHA512": "e90d995061b5771f068c07e727ece3b57eeabdac424dabe8f420848e482e2ad18411c030bd4b455f589d8cdae9a1dae942bfef1ebd038104dd975e168cfb7d19",
        "SSDeep": "3072:KGH5vDQ4MEa4fM0EYRCmgQKQZyVlxgW0ITUj4MO2jCKH2:ZLMGyQKQZaw2",
        "Size": 1284823,
        "Type": "ASCII text, with very long lines"
    }
}

pan-os-get-merged-config


Pull the merged config file

Base Command

pan-os-get-merged-config

Input

Argument Name Description Required
target The serial number of the device. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-get-merged-config target=0000000000

Context Example

{
    "File": {
        "EntryID": "3682@268ee30b-69fa-4496-8ab8-51cdeb19c452",
        "Info": "text/plain",
        "MD5": "3204cc188e4b4a6616b449441d4d1ad4",
        "Name": "merged_config",
        "SHA1": "0b058a2ae4b595f80599ef0aeffda640ff386e95",
        "SHA256": "7178b16cb30880c93345ff80810af4e1428573a28d1ee354d5c79b03372cc027",
        "SHA512": "edf5b851eab40588e4e338071de3c18cc8d198d811ea0759670c0aa4c8028fa3b7870b9554c4b7d85f8429641d7cd6f6217a6b37500e24ad9c60b6cf39b39f3b",
        "SSDeep": "3072:OGH5vDQ4MEa4fM0EYRCmgQKQZyVlxDW0ITUj4MO2jCKH2:tLMGyQKQZtw2",
        "Size": 1322335,
        "Type": "ASCII text, with very long lines"
    }
}

pan-os-list-templates


Returns a list of available templates. (Used only in Panorama instances).

Base Command

pan-os-list-templates

Input

Argument Name Description Required
template_name The name of the template to retrieve. If not provided then all available templates will be brought. Optional
limit The maximum number of templates to retrieve. This value will be used by default if page argument was not provided. Default is 50. Optional
page_size The page size of the templates to return. Default is 50. Optional
page The page at which to start listing templates. This must be a positive number. Optional

Context Output

Path Type Description
Panorama.Template.Name String The name of the template.
Panorama.Template.Description String The description of the template.
Panorama.Template.Variable.Name String The variable name of the template.
Panorama.Template.Variable.Type String The type of the template.
Panorama.Template.Variable.Value String The value of the variable of the template.
Panorama.Template.Variable.Description String The description of the variable of the template.

Command example

!pan-os-list-templates limit=20

Context Example

{
    "Panorama": {
        "Template": [
            {
                "Description": null,
                "Name": "test-1",
                "Variable": []
            },
            {
                "Description": "test description",
                "Name": "test-2",
                "Variable": [
                    {
                        "Description": "variable-1-test",
                        "Name": "$variable-1",
                        "Type": "ip-netmask",
                        "Value": "1.1.1.1"
                    },
                    {
                        "Description": null,
                        "Name": "$variable-2",
                        "Type": "fqdn",
                        "Value": "google.com"
                    }
                ]
            }
        ]
    }
}

Human Readable Output

Templates

Description Name Variable
  test-1  
test description test-2 $variable-1,
$variable-2

pan-os-list-nat-rules


Returns a list of NAT rules of either a Panorama/firewall instance.

Base Command

pan-os-list-nat-rules

Input

Argument Name Description Required
name The name of the NAT rule to retrieve. If not mentioned, will bring all the NAT rules. Optional
device-group The device group in which the NAT rules are part of. Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
show_uncommitted Whether to show the un-committed rules or not. Possible values are: true, false. Default is false. Optional
disabled Whether to retrieve the disabled rules. If not mentioned, will retrieve all the NAT rules. Possible values are: yes, no. Optional
nat_type The type of the NAT rules to retrieve. If not mentioned, will retrieve all the NAT rules. Possible values are: ipv4, nat64, nptv6. Optional
tags A comma-separated list of tags of the NAT rules to retrieve. If not mentioned, will retrieve all the NAT rules. Optional
query Free query to retrieve NAT rule. If not mentioned, will retrieve all the NAT rules. Optional
limit The maximum number of rules to retrieve. Will be used by default if page argument was not provided. Default is 50. Optional
page_size The page size of the NAT rules to return. Default is 50. Optional
page The page at which to start listing NAT rules. Must be a positive number. Optional

Context Output

Path Type Description
Panorama.NAT.Name String The name of the rule.
Panorama.NAT.Location String The device group that the rule is part of.
Panorama.NAT.Tags String The tags in which the rule is part of.
Panorama.NAT.SourceZone String The source zone of the rule.
Panorama.NAT.DestinationZone String The destination zone of the rule.
Panorama.NAT.SourceAddress String The source address of the rule.
Panorama.NAT.DestinationAddress String The destination address of the rule.
Panorama.NAT.DestinationInterface String The destination interface of the rule.
Panorama.NAT.Service String The service in which the rule has.
Panorama.NAT.Description String The description of the rule.
Panorama.NAT.SourceTranslation Unknown The source translation of the rule.
Panorama.NAT.DestinationTranslation Unknown The destination translation of the rule.
Panorama.NAT.DynamicDestinationTranslation Unknown The dynamic destination translation of the rule.
Panorama.NAT.Disabled String Whether the rule is disabled.

Command example

!pan-os-list-nat-rules pre_post=pre-rulebase show_uncommitted=true

Context Example

{
    "Panorama": {
        "NAT": [
            {
                "Description": "Test",
                "DestinationAddress": "Test_Bla_Bla",
                "DestinationInterface": "any",
                "DestinationTranslation": {
                    "TranslatedAddress": "1.1.1.1/24",
                    "TranslatedPort": "1234"
                },
                "DestinationZone": "Admin",
                "Disabled": "yes",
                "DynamicDestinationTranslation": null,
                "Name": "Test",
                "Service": "Test_group",
                "SourceAddress": "bad-url.com",
                "SourceTranslation": {
                    "StaticIp": {
                        "TranslatedAddress": "1.2.3.4"
                    }
                },
                "SourceZone": "any",
                "Tags": null
            },
            {
                "Description": "Desc",
                "DestinationAddress": "Test_Bla_Bla",
                "DestinationInterface": "any",
                "DestinationTranslation": null,
                "DestinationZone": "Admin",
                "Disabled": "yes",
                "DynamicDestinationTranslation": {
                    "DistributionMethod": "ip-hash",
                    "TranslatedAddress": "bad-url.com"
                },
                "Name": "Test",
                "Service": "XSOAR_Test",
                "SourceAddress": "bad-url.com",
                "SourceTranslation": null,
                "SourceZone": [
                    "Admin",
                    "Bla"
                ],
                "Tags": [
                    "test",
                    "tag"
                ]
            }
        ]
    }
}

Human Readable Output

Nat Policy Rules

Name Tags Source Zone Destination Zone Source Address Disabled Destination Address Destination Interface Service Description
Test   any Admin bad-url.com yes Test_Bla_Bla any Test_group Test
Test test,
tag
Admin,
multicast
Admin bad-url.com yes Test_Bla_Bla any XSOAR_Test Desc

pan-os-create-nat-rule


Creates a new NAT rule in a Panorama/firewall instance.

Base Command

pan-os-create-nat-rule

Input

Argument Name Description Required
rulename The name of the NAT rule to create. Required
description The description that the new NAT rule should have. Optional
device-group The device-group in which the new rule should be created (Panorama instances only). Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
nat_type The NAT type in which the rule will be created. Possible values are: ipv4, nat64, nptv6. Default is ipv4. Optional
source_zone A comma-separated list of source zones. Default is any. Optional
destination_zone A comma-separated list of destination zones. Optional
destination_interface The page at which to start listing nat-rules, must be a positive number. Default is any. Optional
service The service in which the rule will be created with. Default is any. Optional
source_address A comma-separated list of address object names, address group object names, or EDL object names. Default is any. Optional
destination_address A comma-separated list of address object names, address group object names, or EDL object names. Default is any. Optional
source_translation_type The source translation type in which the rule will be created. Possible values are: static-ip, dynamic-ip, dynamic-ip-and-port, none. Default is none. Optional
source_translated_address_type The source translation address type in which the rule will be created. Possible values are: translated-address, interface-address. Default is translated-address. Optional
source_translated_address A comma-separated list of source translation addresses. If source_translation_type == static_ip, must be a single value. Optional
source_translated_interface The source translation interface. Optional
destination_translation_type The destination translation type. Possible values are: static_ip, dynamic_ip, none. Default is none. Optional
destination_translated_address A comma-separated list of destination translated addresses. Optional
destination_translated_port The destination translated port. Optional
destination_translation_distribution_method The destination translation distribution method. Possible values are: round-robin, source-ip-hash, ip-modulo, ip-hash, least-sessions. Optional
negate_destination Whether to use negate destination. Possible values are: yes, no. Optional
destination_dns_rewrite_direction The DNS rewrite direction. Possible values are: forward, reverse. Optional
audit_comment An audit comment for the rule. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-create-nat-rule rulename=test pre_post="pre-rulebase" source_translated_address_type="interface-address" source_translated_interface=a2 source_translation_type="dynamic-ip-and-port" destination_translation_type=dynamic_ip destination_translated_address=1.1.1.1

Human Readable Output

Nat rule test was created successfully.

pan-os-delete-nat-rule


Deletes a NAT rule.

Base Command

pan-os-delete-nat-rule

Input

Argument Name Description Required
rulename The name of the NAT rule to delete. Can be retrieved from the pan-os-list-nat-rules command. Optional
device-group The device-group from which the NAT rule should be deleted. Only for a Panorama instance. Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-delete-nat-rule rulename=test pre_post="pre-rulebase"

Human Readable Output

Nat rule test was deleted successfully.

pan-os-edit-nat-rule


Edits a NAT rule.

Base Command

pan-os-edit-nat-rule

Input

Argument Name Description Required
rulename The name of the NAT rule to edit. Can be retrieved from the pan-os-list-nat-rules command. Required
device-group The device-group that the NAT rule is part of. (Panorama instances only). Optional
pre_post The pre rule or post rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
behavior The operation to perform on the rule. Possible values are: replace, add, remove. Default is replace. Optional
element_to_change The element to change. Possible values are: tags, service, nat_type, description, source_zone, destination_zone, source_address, destination_address, destination_interface, negate_destination, source_translation_dynamic_ip_and_port, source_translation_interface, source_translation_dynamic_ip, source_translation_static_ip, destination_translation_port, destination_translation_ip, destination_translation_dynamic_port, destination_translation_dynamic_ip, destination_translation_dynamic_distribution_method, disabled, audit-comment. Required
element_value The value of the element to change. Can be a list for certain elements. Required

Context Output

There is no context output for this command.

Command example

!pan-os-edit-nat-rule rulename=test element_to_change=source_translation_static_ip behavior=replace pre_post="pre-rulebase" element_value=3.3.3.3

Human Readable Output

Nat rule test was edited successfully.

pan-os-list-virtual-routers


Returns a list of virtual routers of either a Panorama/firewall instance.

Base Command

pan-os-list-virtual-routers

Input

Argument Name Description Required
virtual_router The name of the virtual router to retrieve. If not mentioned, will bring all the virtual routers. Optional
template The template that the virtual router is part of. Use only for Panorama instances. Optional
show_uncommitted Whether to show the un-committed virtual routers or not. Possible values are: true, false. Default is false. Optional
limit The maximum number of virtual routers to retrieve. Will be used by default if the page argument was not provided. Default is 50. Optional
page_size The size of virtual-routers to return. Default is 50. Optional
page The page at which to start listing virtual-routers. Must be a positive number. Optional

Context Output

Path Type Description
Panorama.VirtualRouter.Name String The name of the virtual router.
Panorama.VirtualRouter.Interface Unknown The interface(s) that the virtual router uses.
Panorama.VirtualRouter.RIP Unknown Information about the RIP of the virtual router.
Panorama.VirtualRouter.OSPF Unknown Information about the OSPF of the virtual router.
Panorama.VirtualRouter.OSPFv3 Unknown Information about the OSPFv3 of the virtual router.
Panorama.VirtualRouter.BGP Unknown Information about the BGP of the virtual router.
Panorama.VirtualRouter.RedistributionProfile Unknown The redistribution profile(s) that the virtual router uses.
Panorama.VirtualRouter.Multicast Unknown Information about the multicast of the virtual router.
Panorama.VirtualRouter.StaticRoute Unknown The static routes(s) that the virtual router uses.
Panorama.VirtualRouter.ECMP Unknown The ECMP defined for the virtual router.

Command example

!pan-os-list-virtual-routers show_uncommitted=true

Context Example

{
    "Panorama": {
        "VirtualRouter": [
            {
                "BGP": {
                    "enable": "no",
                    "routing-options": {
                        "graceful-restart": {
                            "enable": "yes"
                        }
                    }
                },
                "Interface": null,
                "Multicast": {},
                "Name": "virtual-router-1",
                "OSPF": {
                    "enable": "no"
                },
                "OSPFv3": {
                    "enable": "no"
                },
                "RIP": {
                    "enable": "no"
                },
                "RedistributionProfile": {},
                "StaticRoute": {
                    "ip": {
                        "static-route": {
                            "entry": [
                                {
                                    "@name": "static_route_ip",
                                    "bfd": {
                                        "profile": "None"
                                    },
                                    "destination": "1.1.1.1",
                                    "metric": "14",
                                    "nexthop": {
                                        "ip-address": "1.1.1.1"
                                    },
                                    "path-monitor": {
                                        "enable": "no",
                                        "failure-condition": "any",
                                        "hold-time": "2"
                                    },
                                    "route-table": {
                                        "unicast": null
                                    }
                                },
                                {
                                    "@name": "static_route_ip2",
                                    "bfd": {
                                        "profile": "None"
                                    },
                                    "destination": "1.1.1.1",
                                    "metric": "188",
                                    "nexthop": {
                                        "ip-address": "1.1.1.1"
                                    },
                                    "path-monitor": {
                                        "enable": "no",
                                        "failure-condition": "any",
                                        "hold-time": "2"
                                    },
                                    "route-table": {
                                        "unicast": null
                                    }
                                },
                                {
                                    "@name": "static_route_ip3",
                                    "destination": "1.1.1.1/32",
                                    "nexthop": {
                                        "ip-address": "1.1.1.1"
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {
                "BGP": {
                    "enable": "no",
                    "routing-options": {
                        "graceful-restart": {
                            "enable": "yes"
                        }
                    }
                },
                "Interface": "loopback",
                "Multicast": {
                    "enable": "no",
                    "rp": {
                        "local-rp": {
                            "candidate-rp": {
                                "interface": "loopback"
                            }
                        }
                    }
                },
                "Name": "virtual-router-2",
                "OSPF": {
                    "enable": "no"
                },
                "OSPFv3": {
                    "enable": "no"
                },
                "RIP": {
                    "auth-profile": {
                        "entry": {
                            "@name": "213"
                        }
                    },
                    "enable": "no",
                    "export-rules": {
                        "entry": {
                            "@name": "test1"
                        }
                    },
                    "interface": {
                        "entry": {
                            "@name": "loopback",
                            "bfd": {
                                "profile": "Inherit-vr-global-setting"
                            },
                            "default-route": {
                                "disable": {}
                            },
                            "enable": "yes",
                            "mode": "normal"
                        }
                    }
                },
                "RedistributionProfile": {
                    "entry": [
                        {
                            "@name": "test1",
                            "action": {
                                "no-redist": {}
                            },
                            "priority": "1"
                        },
                        {
                            "@name": "test-2",
                            "action": {
                                "no-redist": {}
                            },
                            "priority": "123"
                        }
                    ]
                },
                "StaticRoute": {
                    "ip": {
                        "static-route": {
                            "entry": {
                                "@name": "test",
                                "bfd": {
                                    "profile": "None"
                                },
                                "destination": "1.1.1.1",
                                "metric": "10",
                                "nexthop": {
                                    "ip-address": "2.2.2.2"
                                },
                                "path-monitor": {
                                    "enable": "no",
                                    "failure-condition": "any",
                                    "hold-time": "2"
                                },
                                "route-table": {
                                    "unicast": {}
                                }
                            }
                        }
                    }
                }
            }
        ]
    }
}

Human Readable Output

Virtual Routers

BGP Interface Multicast Name OSPF OSPFv3 RIP RedistributionProfile StaticRoute
no     virtual-router-1 no no no   static_route_ip,
static_route_ip2,
static_route_ip3
no loopback no virtual-router-2 no no no test1,
test-2
test

pan-os-list-redistribution-profiles


Returns a list of redistribution-profiles of a specific virtual-router of either a Panorama/firewall instance.

Base Command

pan-os-list-redistribution-profiles

Input

Argument Name Description Required
name Redistribution profile name. Optional
virtual_router The name of the virtual router that has the redistribution profiles retrieve. Can be retrieved from pan-os-list-virtual-routers. Required
template The template that the redistribution profiles and virtual-router are part of. Use only for Panorama instances. Optional
limit The maximum number of redistribution-profiles to retrieve. Default is 50. Optional

Context Output

Path Type Description
Panorama.RedistributionProfile.Name String The name of the profile.
Panorama.RedistributionProfile.Priority String The priority of the profile.
Panorama.RedistributionProfile.Action String The action of the profile.
Panorama.RedistributionProfile.FilterInterface Unknown The filter interface(s).
Panorama.RedistributionProfile.FilterType Unknown The filter type(s).
Panorama.RedistributionProfile.FilterDestination Unknown The filter destination(s).
Panorama.RedistributionProfile.FilterNextHop Unknown The filter next hop.
Panorama.RedistributionProfile.BGP Unknown The BGP of the profile.
Panorama.RedistributionProfile.OSPF Unknown The OSPF of the profile.

Command example

!pan-os-list-redistribution-profiles virtual_router=test

Context Example

{
    "Panorama": {
        "RedistributionProfile": [
            {
                "Action": "redist",
                "BGP": {
                    "Community": [
                        "local-as",
                        "no-export"
                    ],
                    "ExtendedCommunity": "0x4164ACFCE33404EA"
                },
                "FilterDestination": "1.1.1.1",
                "FilterInterface": "loopback",
                "FilterNextHop": "2.2.2.2",
                "FilterType": [
                    "bgp",
                    "connect",
                    "ospf",
                    "rip",
                    "static"
                ],
                "Name": "test1",
                "OSPF": {
                    "Area": [
                        "1.1.1.1",
                        "2.2.2.2"
                    ],
                    "PathType": [
                        "ext-1",
                        "ext-2",
                        "inter-area",
                        "intra-area"
                    ],
                    "Tag": "1"
                },
                "Priority": "1"
            },
            {
                "Action": "no-redist",
                "BGP": null,
                "FilterDestination": null,
                "FilterInterface": null,
                "FilterNextHop": null,
                "FilterType": null,
                "Name": "test-2",
                "OSPF": null,
                "Priority": "123"
            }
        ]
    }
}

Human Readable Output

Redistribution profiles for virtual router test-guy

Name Priority Action Filter Type Filter Destination Filter Next Hop BGP OSPF
test1 1 redist bgp,
connect,
ospf,
rip,
static
1.1.1.1 2.2.2.2 Community: local-as,
no-export
ExtendedCommunity: 0x4164ACFCE33404EA
PathType: ext-1,
ext-2,
inter-area,
intra-area
Area: 1.1.1.1,
2.2.2.2
Tag: 1
test-2 123 no-redist          

pan-os-create-redistribution-profile


Creates a new redistribution-profile under a virtual-router for a Panorama/firewall instance.

Base Command

pan-os-create-redistribution-profile

Input

Argument Name Description Required
name The name of the of the redistribution profile to create. Required
virtual_router The virtual router that the redistribution profile will be created on. Required
template The template that the virtual-router is in. Use only for Panorama instances. Optional
filter_source_type Comma-separated list of the filter source types. Possible values are: bgp, ospf, rip, static. Optional
destination A comma-separated list of destination to filter by. Optional
nexthop A comma-separated list of next-hops to filter by. Optional
interface A comma-separated list of interfaces to filter by. Optional
priority The priority of the profile. (1-255). Required
action The action of the profile. Possible values are: redist, no-redist. Optional
filter_ospf_area A comma-separated list of areas for the OSPF. Optional
filter_ospf_tag A comma-separated list of tags for the OSPF. Optional
filter_ospf_path_type A comma-separated list of path types for the OSPF. Possible values are: ext-1, ext-2, inter-area, intra-area. Optional
filter_bgp_community A comma-separated list of community filters for the BGP. 32-bit value in decimal or hex or in AS:VAL format where AS and VAL are each in 0 - 65535 range. (Max 10 values). Optional
filter_bgp_extended_community A comma-separated list of community filters for the BGP. 64-bit value in hex, or in TYPE:AS:VAL, TYPE:IP:VAL format. TYPE is 16-bit, the other two are 16-bit and 32-bit each. (Max 5 values). Optional

Context Output

There is no context output for this command.

Command example

!pan-os-create-redistribution-profile name=test virtual_router=virtual-router-1 priority=12 action=redist filter_bgp_extended_community=0x4164ACFCE33404EA filter_source_type=bgp,ospf filter_bgp_community=13,89 filter_ospf_path_type="ext-1" interface=loopback filter_ospf_tag=1.1.1.1,2.2.2.2 filter_ospf_area=1.1.1.1,2.2.2.2 nexthop=1.1.1.1

Human Readable Output

Redistribution profile test was created successfully.

pan-os-edit-redistribution-profile


Edits a redistribution-profile in a virtual-router.

Base Command

pan-os-edit-redistribution-profile

Input

Argument Name Description Required
name The name of the redistribution-profile to edit. Required
virtual_router The name of the virtual-router that the redistribution-profile is part of. Required
template The template that the virtual-router is in. Only for Panorama instances. Optional
element_to_change The element to change. Possible values are: filter_type, filter_destination, filter_nexthop, filter_interface, priority, action, filter_ospf_area, filter_ospf_tag, filter_ospf_path_type, filter_bgp_community, filter_bgp_extended_community. Required
element_value The value of the element to change. Can be a list for all the elements except priority and action. Required
behavior The operation to perform on the profile. Possible values are: replace, add, remove. Default is replace. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-edit-redistribution-profile virtual_router=virtual-router-name name=test element_to_change=filter_type element_value=bgp,ospf

Human Readable Output

Redistribution profile test was edited successfully.

pan-os-delete-redistribution-profile


Deletes a redistribution-profile from a virtual-router.

Base Command

pan-os-delete-redistribution-profile

Input

Argument Name Description Required
name The name of the redistribution-profile to delete. Required
virtual_router The name of the virtual-router that the redistribution-profile is part of. Required
template The template that the virtual-router is in. Only for panorama instances. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-delete-redistribution-profile virtual_router=test1 name=test

Human Readable Output

Redistribution profile test was deleted successfully.

pan-os-list-pbf-rules


Returns a list of pbf-rules of either a Panorama/firewall instance.

Base Command

pan-os-list-pbf-rules

Input

Argument Name Description Required
rulename The name of the pbf-rule to retrieve. If not mentioned, will bring all the pbf rules. Optional
device-group The device-group that the pbf-rules are part of. Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
show_uncommitted Whether to show the un-committed rules or not. Possible values are: true, false. Default is false. Optional
disabled Whether to retrieve the disabled rules. If not mentioned, will retrieve all the PBF rules. Possible values are: yes, no. Optional
action The action of the PBF rules to retrieve. If not mentioned, will retrieve all the PBF rules. Possible values are: discard, forward, no-pbf. Optional
tags A comma-separated list of tags of the PBF rules to retrieve. If not mentioned, will retrieve all the PBF rules. Optional
query Free query to retrieve PBF rule. If not mentioned, will retrieve all the PBF rules. Optional
limit The maximum number of rules to retrieve. Will be used by default if page argument was not provided. Default is 50. Optional
page_size The size of pbf-rules to return. Default is 50. Optional
page The page at which to start listing pbf-rules. Must be a positive number. Optional

Context Output

Path Type Description
Panorama.PBF.Name String The name of the PBF rule.
Panorama.PBF.Description String The description of the PBF rule.
Panorama.PBF.Tags Unknown The tags of the PBF rule.
Panorama.PBF.SourceZone Unknown The source-zones of the PBF rule.
Panorama.PBF.SourceInterface Unknown The source-interfaces of the PBF rule.
Panorama.PBF.SourceAddress Unknown The source-addresses of the PBF rule.
Panorama.PBF.SourceUser Unknown The source-users of the PBF rule.
Panorama.PBF.DestinationAddress Unknown The destination-addresses of the PBF rule.
Panorama.PBF.EnforceSymmetricReturn Unknown The enforce-symmetric-return of the PBF rule.
Panorama.PBF.Target Unknown The target of the PBF rule.
Panorama.PBF.Application Unknown The applications of the PBF rule.
Panorama.PBF.Service Unknown The services of the PBF rule.
Panorama.PBF.Disabled String Whether the rule is disabled.

Command example

!pan-os-list-pbf-rules pre_post="pre-rulebase" show_uncommitted=true debug-mode=true

Context Example

{
    "Panorama": {
        "PBF": [
            {
                "Action": {
                    "forward": {
                        "egress-interface": "Test"
                    }
                },
                "Application": "acronis-snapdeploy",
                "Description": "Test policy based forwarding rule with a twist",
                "DestinationAddress": "bad-url.com",
                "Disabled": "no",
                "EnforceSymmetricReturn": {
                    "enabled": "no"
                },
                "Name": "Test_PBF",
                "Service": "service-https",
                "SourceAddress": "any",
                "SourceInterface": null,
                "SourceUser": "any",
                "SourceZone": "Test_Zone",
                "Tags": "test",
                "Target": {
                    "negate": "no"
                }
            },
            {
                "Action": {
                    "forward": {
                        "egress-interface": "ethernet1/1"
                    }
                },
                "Application": "any",
                "Description": "TEst2",
                "DestinationAddress": "any",
                "Disabled": "no",
                "EnforceSymmetricReturn": {
                    "enabled": "yes",
                    "nexthop-address-list": {
                        "entry": [
                            {
                                "@name": "1.1.1.1"
                            },
                            {
                                "@name": "2.2.2.2"
                            }
                        ]
                    }
                },
                "Name": "Test_PBF4",
                "Service": "any",
                "SourceAddress": "any",
                "SourceInterface": null,
                "SourceUser": "any",
                "SourceZone": "internal",
                "Tags": null,
                "Target": null
            }
        ]
    }
}

Human Readable Output

Policy Based Forwarding Rules

Action Description Destination Address Disabled Name Source Address Source User Source Zone Tags
forward Test policy based forwarding rule with a twist bad-url.com no Test_PBF any any Test_Zone test
forward TEst2 any no Test_PBF4 any any internal  

pan-os-create-pbf-rule


Creates a new policy-based-forwarding (PBF) rule in a Panorama/firewall instance.

Base Command

pan-os-create-pbf-rule

Input

Argument Name Description Required
rulename The name of the PBF-rule to create. Required
description The description that the new PBF-rule should have. Optional
device-group The device-group in which the new rule should be created. Only for Panorama instance. Optional
pre_post The pre rule or post rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
tags The tags that the rule will be created with. Optional
source_zone A comma-separated list of source zones. Default is any. Optional
source_address A comma-separated list of source addresses. Default is any. Optional
source_user A comma-separated list of source users. Default is any. Optional
service The service in which the rule will be created with. Default is any. Optional
destination_address A comma-separated list of destination addresses. Default is any. Optional
application A comma-separated list of applications. Default is any. Optional
action The action that the rule will be created with. Possible values are: forward, discard, no-pbf. Required
egress_interface The egress interface the rule will be created with. Must be provided if action == forward. Optional
nexthop The next-hop. Relevant only when action = forward. Possible values are: ip-address, fqdn, none. Default is none. Optional
nexthop_value The next-hop value when action = forward. Could be an IP address or FQDN. Required when nexthop is not none. Optional
enforce_symmetric_return Whether to enforce symmetric return. Possible values are: yes, no. Default is no. Optional
negate_source Whether to negate the source. Possible values are: yes, no. Default is no. Optional
negate_destination Whether to negate the destination. Possible values are: yes, no. Default is no. Optional
nexthop_address_list The nexthop addresses list for the symmetric return. Optional
audit_comment An audit comment for the rule. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-create-pbf-rule rulename=test4 pre_post="pre-rulebase" enforce_symmetric_return=yes nexthop_address_list=1.1.1.1,2.2.2.2 action=forward description="this is just a description" egress_interface=a2 nexthop="ip-address" nexthop_value=1.1.1.1 negate_source=yes source_zone=1.1.1.1,2.2.2.2 destination_address=1.1.1.1,2.2.2.2 service=dns,service-https

Human Readable Output

PBF rule test4 was created successfully.

pan-os-edit-pbf-rule


Edits a redistribution-profile in a virtual-router.

Base Command

pan-os-edit-pbf-rule

Input

Argument Name Description Required
rulename The name of the PBF rule to edit. Can be retrieved from the pan-os-list-pbf-rules command. Required
device-group The device-group that the PBF rule is in. Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional
element_to_change The element to change. Possible values are: source_zone, source_address, source_user, service, destination_address, application, negate_source, negate_destination, nexthop_address_list, enforce_symmetric_return, action_forward_egress_interface, action_forward_nexthop_ip, action_forward_nexthop_fqdn, action_forward_discard, action_forward_no_pbf, disabled, audit-comment. Required
element_value The value of the element to change. Can be a list for some of the elements. When element_to_change == ‘action_forward_egress_interface’, the action of the rule will be changed to ‘forward’ automatically. Required

Context Output

There is no context output for this command.

Command example

!pan-os-edit-pbf-rule rulename=test4 element_to_change=nexthop_address_list element_value="1.1.1.1,2.2.2.2" pre_post="pre-rulebase"

Human Readable Output

PBF test4 was edited successfully.

pan-os-delete-pbf-rule


Deletes a PBF rule.

Base Command

pan-os-delete-pbf-rule

Input

Argument Name Description Required
rulename The name of the pbf-rule to delete. Can be retrieved from the pan-os-list-pbf-rules command. Required
device-group The device-group from which the pbf-rule should be deleted. Only for a Panorama instance. Optional
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: pre-rulebase, post-rulebase. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-delete-pbf-rule rulename=test4 pre_post="pre-rulebase"

Human Readable Output

PBF rule test4 was deleted successfully.

pan-os-list-application-groups


Returns a list of application-groups of either a Panorama/firewall instance.

Base Command

pan-os-list-application-groups

Input

Argument Name Description Required
name The name of the application-group to retrieve. If not mentioned, will bring all the application-groups. Optional
device-group The device-group that the nat-rules are part of. Optional
show_uncommitted Whether to show the un-committed application-groups or not. Possible values are: true, false. Default is false. Optional
limit The maximum number of application-groups to retrieve. Will be used by default if page argument was not provided. Default is 50. Optional
page_size The page size of the application-groups to return. Default is 50. Optional
page The page at which to start listing application-groups. Must be a positive number. Optional

Context Output

Path Type Description
Panorama.ApplicationGroup.Name String The name of the application-group object.
Panorama.ApplicationGroup.Applications Unknown The list of the applications that the application-group has.
Panorama.ApplicationGroup.Members Number The number of the application that are part of the application-group

Command example

!pan-os-list-application-groups show_uncommitted=true

Context Example

{
    "Panorama": {
        "ApplicationGroup": [
            {
                "Applications": [
                    "1c-enterprise"
                ],
                "Members": 1,
                "Name": "test"
            },
            {
                "Applications": [
                    "2ch-base",
                    "4shared"
                ],
                "Members": 2,
                "Name": "test-2"
            },
            {
                "Applications": [
                    "1c-enterprise",
                    "4shared"
                ],
                "Members": 2,
                "Name": "test-3"
            }
        ]
    }
}

Human Readable Output

Application groups

Applications Members Name
1c-enterprise 1 test
2ch-base,
4shared
2 test-2
1c-enterprise,
4shared
2 test-3

pan-os-create-application-group


Creates a new application group rule in a Panorama/firewall instance.

Base Command

pan-os-create-application-group

Input

Argument Name Description Required
name The name for the application-group to be created with. Required
applications Comma-separated list of applications. Can be retrieved using the command pan-os-list-applications. Required
device-group The device-group in which the application-group should be created. Only for Panorama instance. Optional

Context Output

Path Type Description
Panorama.ApplicationGroup.Name String The name of the application-group object.
Panorama.ApplicationGroup.Applications Unknown The list of the applications that the application-group has.
Panorama.ApplicationGroup.Members Number The number of the applications that are part of the application-group.

Command example

!pan-os-create-application-group name=test-3 applications=1c-enterprise,4shared

Context Example

{
    "Panorama": {
        "ApplicationGroup": {
            "Applications": [
                "1c-enterprise",
                "4shared"
            ],
            "Members": 2,
            "Name": "test-3"
        }
    }
}

Human Readable Output

application-group test-3 was created successfully.

pan-os-edit-application-group


Edits an application-group.

Base Command

pan-os-edit-application-group

Input

Argument Name Description Required
name The name for the application-group to be edit. Can be retrieved from the pan-os-list-application-groups command. Required
applications Comma-separated list of applications. Can be retrieved using the command pan-os-list-applications. Required
device-group The device-group in which the application-group should be created. Only for a Panorama instance. Optional
action The action to perform on the application-group. Possible values are: add, remove. Default is add. Required

Context Output

Path Type Description
Panorama.ApplicationGroup.Name String The name of the application-group object.
Panorama.ApplicationGroup.Applications Unknown The list of the applications that the application-group has.
Panorama.ApplicationGroup.Members Number The number of the applications that are part of the application-group

Command example

!pan-os-edit-application-group name=test-3 action=remove applications=4shared

Context Example

{
    "Panorama": {
        "ApplicationGroup": {
            "Applications": [
                "1c-enterprise"
            ],
            "Members": 1,
            "Name": "test-3"
        }
    }
}

Human Readable Output

application-group test-3 was edited successfully.

pan-os-delete-application-group


Deletes an application-group

Base Command

pan-os-delete-application-group

Input

Argument Name Description Required
name The name of the application-group to delete. Can be retrieved from the pan-os-list-application-groups command. Required
device-group The device-group in which the application-group is part of. Only for a Panorama instance. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-delete-application-group name=test-3

Human Readable Output

application-group test-3 was deleted successfully.

pan-os-list-tag


Returns a list of tags from Panorama.

Base Command

pan-os-list-tag

Input

Argument Name Description Required
include_shared_tags Whether to include shared tags in the list. Possible values are: Yes, No. Default is No. Optional
device-group The device group that the tags are part of. Optional

Context Output

Path Type Description
Panorama.Tag.name String The name of the tag.
Panorama.Tag.color String The color of the tag.
Panorama.Tag.comment String The comment in the tag.
Panorama.Tag.disable-override String Whether overriding the tag is disabled.
Panorama.Tag.location String The tag’s device group location.

Command example

!pan-os-list-tag include_shared_tags=No

Context Example

{
    "Panorama": {
        "Tag": [
            {
                "name": "tag1",
                "color": "color13"
            },
            {
                "name": "tag2",
                "color": "color39"
            },
            {
                "name": "tag3",
                "color": "color39",
                "disable-override": "no",
                "comments": "text text text"
            }
        ]
    }
}

Human Readable Output

Tags

Name Color Comment
tag1 color13  
tag2 color39  
tag3 color39 text text text

pan-os-create-tag


Creates a new tag in Panorama.

Base Command

pan-os-create-tag

Input

Argument Name Description Required
name The name for the new tag to be created. Required
device_group The device group that the tag will be part of. Optional
disable_override Whether to disable overriding the tag. Possible values are: true, false. Default is false. Optional
is_shared Whether the tag should be generated in a shared location. Possible values are: true, false. Default is false. Optional
comment The comment for the tag. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-create-tag name="testtag" comment="some comment" is_shared=false

Human Readable Output

The tag with name “testtag” was created successfully.

pan-os-edit-tag


Edits a tag in Panorama.

Base Command

pan-os-edit-tag

Input

Argument Name Description Required
name The existing name for the tag to be edited. Required
new_name The new name for the tag to be replaced with. Optional
device_group The device group of the tag. Optional
disable_override Whether to disable overriding the tag. Possible values are: true, false. Default is false. Optional
comment The comment for the tag. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-edit-tag name="testtag" new_name="newtesttag" comment="some comment"

Human Readable Output

The tag with name “testtag” was edited successfully.

pan-os-delete-tag


Deletes a tag from Panorama.

Base Command

pan-os-delete-tag

Input

Argument Name Description Required
name The name of the tag to delete. Required

Context Output

There is no context output for this command.

Command example

!pan-os-delete-tag name="testtag"

Human Readable Output

The tag with name “testtag” was deleted successfully.

pan-os-list-device-groups


Returns a list of all device groups from Panorama.

Base Command

pan-os-list-device-groups

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
Panorama.DeviceGroupNames string The list of device groups.

pan-os-export-tech-support-file


Exports a tech support file (TSF).

Base Command

pan-os-export-tech-support-file

Input

Argument Name Description Required
interval_in_seconds The polling interval (in seconds). Default is 30. Optional
timeout The polling timeout (in seconds). Default is 1200. Optional

Context Output

There is no context output for this command.

Command example


#### Human Readable Output

>Waiting for tech support file export with job ID 101 to finish...

### pan-os-list-security-profile-group

***
Returns a list of security profile groups from Panorama.

#### Base Command

`pan-os-list-security-profile-group`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| group_name | A given group name to return the data for. | Optional |
| device-group | The device group that the security profile groups are part of. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.ProfileGroup.name | String | The name of the group. |
| Panorama.ProfileGroup.location | String | The security profile group's device group location. |
| Panorama.ProfileGroup.virus | String | The antivirus profile. |
| Panorama.ProfileGroup.spyware | String | The anti-spyware profile. |
| Panorama.ProfileGroup.vulnerability | String | The vulnerability protection profile. |
| Panorama.ProfileGroup.url-filtering | String | The URL filtering profile. |
| Panorama.ProfileGroup.file-blocking | String | The file blocking profile. |
| Panorama.ProfileGroup.data-filtering | String | The data filtering profile. |
| Panorama.ProfileGroup.wildfire-analysis | String | The WildFire analysis profile. |
| Panorama.ProfileGroup.disable-override | String | Whether overriding the security profile group is disabled. |

#### Command example

```!pan-os-list-security-profile-group```

#### Context Example

```json
{
    "Panorama": {
        "ProfileGroups": [
            {
                "name": "test_security_pg",
                "location": "TestDevice",
                "virus": "default",
                "spyware": "default",
            },
            {
                "name": "test_security_pg_v2",
                "file-blocking": "basic file blocking",
                "data-filtering": "test data filtering",
                "wildfire-analysis": "test wildfire analysis",
                "url-filtering": "default",
                "location": "TestDevice",
                "spyware": "default",
                "vulnerability": "vulnerability",
                "virus": "default",
            }
        ]
    }
}

Human Readable Output

Security Profile Groups

Name Location Antivirus Profile Anti-Spyware Profile Vulnerability Protection Profile URL Filtering Profile File Blocking Profile Data Filtering Profile WildFire Analysis Profile
test_security_pg TestDevice default default          
test_security_pg_v2 TestDevice default default vulnerability default basic file blocking test data filtering test wildfire analysis

pan-os-create-security-profile-group


Creates a new security profile group in Panorama.

Base Command

pan-os-create-security-profile-group

Input

Argument Name Description Required
group_name The name of the security profile group. Required
device-group The device group that the security profile group will be part of. Optional
antivirus_profile The antivirus profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
anti_spyware_profile The anti-spyware profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
vulnerability_protection_profile The vulnerability protection profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
URL_filtering_profile The URL filtering profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
file_blocking_profile The file blocking profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
data_filtering_profile The data filtering profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional
wildfire_analysis_profile The WildFire analysis profile of the group. You can get the possible values for this argument by running the pan-os-get-security-profile command. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-create-security-profile-group group_name="test_security_pg" antivirus_profile="default" anti_spyware_profile="default"

Human Readable Output

Successfull created Security Profile Group: “test-spg”

pan-os-edit-security-profile-group


Edits the security profile group in Panorama.

Base Command

pan-os-edit-security-profile-group

Input

Argument Name Description Required
group_name The name of the security profile group to edit. Required
profile_to_change The profile to edit in the group. Possible values are: Antivirus Profile, Anti-Spyware Profile, Vulnerability Protection Profile, URL Filtering Profile, File Blocking Profile, Data Filtering Profile, WildFire Analysis Profile. Required
profile_value The value to set in the profile. You can get the possible values for this argument by running the pan-os-get-security-profile command. Required
device-group The device group that the security profile group is part of. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-edit-security-profile-group group_name="test-spg" profile_to_change="Antivirus Profile" profile_value="default"

Human Readable Output

Successfull edited Security Profile Group: “test-spg”

pan-os-delete-security-profile-group


Deletes a security profile group from Panorama.

Base Command

pan-os-delete-security-profile-group

Input

Argument Name Description Required
group_name The name of the security profile group to delete. Required
device-group The device group that the security profile group is part of. Optional

Context Output

There is no context output for this command.

Command example

!pan-os-delete-security-profile-group group_name="test-spg"

Human Readable Output

Successfull deleted Security Profile Group: “test-spg”

pan-os-get-audit-comment


Gets the audit comment of a rule.

Base Command

pan-os-get-audit-comment

Input

Argument Name Description Required
rule_name The rule name to apply. Required
rule_type The rule type. Possible values are: Security Rule, NAT Rule, PBF Rule. Required
pre_post The pre-rule or post-rule (Panorama instances only). Possible values are: Pre, Post. Optional
device-group The device group that the rule is part of. Optional

Context Output

Path Type Description
Panorama.AuditComment.comment String The audit comment of the rule.
Panorama.AuditComment.rule_name String The rule name.
Panorama.AuditComment.rule_type String The rule type.

Command example

!pan-os-get-audit-comment rule_name="test" rule_type="Security Rule" pre_post=Post

Context Example

{
    "Panorama": {
        "AuditComment": [
            {
                "comment": "some comment",
                "rule_name": "test",
                "rule_type": "Security Rule"
            }
        ]
    }
}

Human Readable Output

Audit Comment for Rule: test

Comment Rule Name Rule Type
some comment test Security Rule

pan-os-add-profile-exception


Add an exception to a Vulnerability Protection profile or Anti Spyware profile.

Base Command

pan-os-add-profile-exception

Input

Argument Name Description Required
profile_name The profile name. Required
threat The requested threat name/CVE/ID to add. Required
profile_type The profile type. Possible values are: Vulnerability Protection Profile, Anti Spyware Profile. Optional
action The action type of the exception. Default value is ‘default’. Possible values are: Alert, Allow, Block IP, Drop, Reset Both, Reset Client, Reset Server. Optional
packet_capture To configure the firewall to take a packet capture when it detects a threat. Possible values are: Disable, Single Packet, Extended Capture. Optional
exempt_ip An IP address. Configure an exempt IP address to enforce a threat exception only when the associated session has either a matching source or destination IP address; for all other sessions, the threat is enforced based on the default signature action. Optional
device_group Device group. This will override any configured device group or vsys. Optional
ip_track_by Specify whether to Track By and block the IP address using the IP source or the IP source and destination. If action = Block IP , this argument is mandatory. Possible values are: Source, Source And Destination. Optional
ip_duration_sec Specify the time period (in seconds) after which to trigger the action. If action = Block IP , this argument is mandatory. Optional

Command example

!pan-os-add-profile-exception profile_name="test1" threat="10002" action="Drop"

Context Output

There is no context output for this command.

pan-os-list-profile-exception


List the exceptions in a Vulnerability Protection profile or Anti Spyware profile.

Base Command

pan-os-list-profile-exception

Input

Argument Name Description Required
profile_name The profile name. Required
profile_type The profile type. Possible values are: Vulnerability Protection Profile, Anti Spyware Profile. Optional

Command example

!pan-os-list-profile-exception profile_name="test1" profile_type="Vulnerability Protection Profile"

Context Output

Path Type Description
Panorama.Vulnerability.Name String The profile name.
Panorama.Vulnerability.Exception.name String The exception name.
Panorama.Vulnerability.Exception.id String The exception ID.
Panorama.Vulnerability.Exception.CVE String The exception CVE if exist.
Panorama.Vulnerability.Exception.action String The exception action.
Panorama.Vulnerability.Exception.exempt-id String The exception exempt ID.
Panorama.Vulnerability.Exception.packet-capture String The exception packet capture.
Panorama.Spyware.Name String The profile name.
Panorama.Spyware.Exception.name String The exception name.
Panorama.Spyware.Exception.id String The exception ID.
Panorama.Spyware.Exception.CVE String The exception CVE if exist.
Panorama.Spyware.Exception.action String The exception action.
Panorama.Spyware.Exception.exempt-id String The exception exempt ID.
Panorama.Spyware.Exception.packet-capture String The exception packet capture.

pan-os-delete-profile-exception


Delete an exception to a Vulnerability Protection profile or Anti Spyware profile.

Base Command

pan-os-delete-profile-exception

Input

Argument Name Description Required
profile_name The profile name. Required
threat The requested threat name/CVE/ID to delete. Required
profile_type The profile type. Possible values are: Vulnerability Protection Profile, Anti Spyware Profile. Optional

Command example

!pan-os-delete-profile-exception profile_name="test2" threat="10005" profile_type="Vulnerability Protection Profile"

Context Output

There is no context output for this command.

pan-os-edit-profile-exception


Edit an exception to a Vulnerability Protection profile or Anti Spyware profile.

Base Command

pan-os-edit-profile-exception

Input

Argument Name Description Required
profile_name The profile name. Required
threat The requested threat name/CVE/ID to edit. Required
profile_type The profile type. Possible values are: Vulnerability Protection Profile, Anti Spyware Profile. Optional
action The action type of the exception. Default value is ‘default’. Possible values are: Alert, Allow, Block IP, Drop, Reset Both, Reset Client, Reset Server. Optional
packet_capture To configure the firewall to take a packet capture when it detects a threat. Possible values are: Disable, Single Packet, Extended Capture. Optional
exempt_ip An IP address. Configure an exempt IP address to enforce a threat exception only when the associated session has either a matching source or destination IP address; for all other sessions, the threat is enforced based on the default signature action. Optional
device_group Device group. This will override any configured device group or vsys. Optional
ip_track_by Specify whether to Track By and block the IP address using the IP source or the IP source and destination. If action = Block IP , this argument is mandatory. Possible values are: Source, Source And Destination. Optional
ip_duration_sec Specify the time period (in seconds) after which to trigger the action. If action = Block IP , this argument is mandatory. Optional

Command example

!pan-os-edit-profile-exception profile_name="test1" threat="10002" action="Drop"

Context Output

There is no context output for this command.

pan-os-create-master-key


Create a default master key that encrypts all the private keys and passwords in the configuration.

This command is harmful because it invalidates the current API key and requires re-configuration of the integration instance.

Base Command

pan-os-create-master-key

Input

Argument Name Description Required
master_key The encryption master key. Must be exactly 16 characters. Required
lifetime_in_hours The lifetime of the key in hours. Required
reminder_in_hours The time to be notified of the key’s expiration in hours. Required

Limitations

  • All changes to the configuration of the Palo Alto firewall or Panorama need to be committed before running this command. This can be done using using the pan-os-commit and pan-os-push-status commands.

  • The command invalidates the current API key and requires obtaining a new one using the instructions in the PAN-OS and Panorama API usage guide. All subsequent commands will raise an “Invalid Credential” error until a new API key is obtained and the integration instance is updated accordingly.

Command example

!pan-os-create-master-key master_key="MyFakeMasterKey1" lifetime_in_hours=2160 reminder_in_hours=1992

Context Output

There is no context output for this command.

Human Readable Output

Master key changed successfully. All key material has been re-encrypted with new master key and committed via jobid 1234.

⚠️ The current API key is no longer valid! (by design). Generate a new API key and update it in the integration instance configuration to keep using the integration.

pan-os-update-master-key


Update the default master key that encrypts all the private keys and passwords in the configuration.

This command is harmful because it invalidates the current API key and requires re-configuration of the integration instance.

Base Command

pan-os-update-master-key

Input

Argument Name Description Required
current_master_key The current encryption master key. Required
new_master_key The new encryption master key. Must be exactly 16 characters. Required
lifetime_in_hours The lifetime of the key in hours. Required
reminder_in_hours The time to be notified of the key’s expiration in hours. Required

Limitations

  • All changes to the configuration of the Palo Alto firewall or Panorama need to be committed before running this command. This can be done using using the pan-os-commit and pan-os-push-status commands.

  • The command invalidates the current API key and requires obtaining a new one using the instructions in the PAN-OS and Panorama API usage guide. All subsequent commands will raise an “Invalid Credential” error until a new API key is obtained and the integration instance is updated accordingly.

Command example

!pan-os-update-master-key current_master_key="MyFakeMasterKey1" new_master_key="MyFakeMasterKey2" lifetime_in_hours=2160 reminder_in_hours=1992

Context Output

There is no context output for this command.

Human Readable Output

Master key changed successfully. All key material has been re-encrypted with new master key and committed via jobid 2468.

⚠️ The current API key is no longer valid! (by design). Generate a new API key and update it in the integration instance configuration to keep using the integration.

pan-os-get-master-key-details


Show the details of the default master key that encrypts all the private keys and passwords in the configuration.

Base Command

pan-os-get-master-key-details

Input

There are no input arguments for this command.

Command example


#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Panorama.MasterKey.auto-renew-mkey | String | Whether the master key will be automatically renewed on expiry. |
| Panorama.MasterKey.expire-at | String | The date and time when the key is set to expire. |
| Panorama.MasterKey.hours-to-expiry | String | The number of hours remaining before the key expires. |
| Panorama.MasterKey.hours-to-reminder | String | The number of hours remaining before being notified that the key is set to expire. |
| Panorama.MasterKey.minutes-to-expiry | String | The number of minutes remaining before the key expires. |
| Panorama.MasterKey.minutes-to-reminder | String | The number of minutes remaining before being notified that the key is set to expire. |
| Panorama.MasterKey.on-hsm | String | Whether the master key is encrypted using a key stored on a Hardware Security Module (HSM). |
| Panorama.MasterKey.remind-at | String | The date and time when to be notified that the key is set to expire. |
| Panorama.MasterKey.seconds-to-expiry | String | The number of seconds remaining before the key expires. |
| Panorama.MasterKey.seconds-to-reminder | String | The number of seconds remaining before being notified that the key is set to expire. |

#### Context Example

```json
{
    "Panorama": {
        "MasterKey": {
            "auto-renew-mkey": "0",
            "expire-at": "2025/02/18 04:26:05",
            "hours-to-expiry": "2138",
            "hours-to-reminder": "1992",
            "minutes-to-expiry": "128288",
            "minutes-to-reminder": "119520",
            "on-hsm": "no",
            "remind-at": "2024/11/27 04:26:05",
            "seconds-to-expiry": "7697336",
            "seconds-to-reminder": "7171200"
        }
    }
}

Human Readable Output

Master Key Details

Auto-renew master key Encrypted on HSM Remind at Expire at
0 no 2024/11/27 04:26:05 2025/02/18 04:26:05

pan-os-get-certificate-info


Gathers the name, expiration date, and expiration status of certificates configured locally on a Firewall or pushed from Panorama, as seen under Certificate Management.

Base Command

pan-os-get-certificate-info

Input

Argument Name Description Required
show_expired_only Show only expired certificates. Optional

Command example

!pan-os-get-certificate-info show_expired_only=true

Context Output

Path Type Description
Panorama.Certificate.name String Name of the certificate.
Panorama.Certificate.device String Device where this certificate was found.
Panorama.Certificate.subject String Subject of the certificate.
Panorama.Certificate.expiration_date String Expiration date of the certificate.
Panorama.Certificate.expiration_status String Status of certificate - Expired, Expiring in 30 days, Expiring in 60 days, Expiring in 90 days or Valid.
Panorama.Certificate.location String Where this certificate was configured - Firewall or Panorama.
Panorama.Certificate.cert_type String Whether this certificate was pushed from Panorama, Local to Firewall, or Predefined on Panorama or a Firewall.
Panorama.Certificate.devices_using_certificate Unknown List of devices using this certificate if it was pushed from Panorama.

Context Example

{
    "Panorama": {
        "Certificate": [
            {
                "name": "ACME Root CA",
                "device": "panorama.test",
                "subject": "/CN=acme-root-ca.acme.com",
                "expiration_date": "May  9 16:35:16 2026 GMT",
                "expiration_status": "Valid",
                "location": "Panorama",
                "cert_type": "Pushed",
                "devices_using_certificate": [
                                                "111111111111111"
                                            ]
            }
        ]
    }
}

Human Readable Output

Certificates Information

name device subject expiration_date expiration_status location cert_type devices_using_certificate
ACME Root CA panorama.test /CN=acme-root-ca.acme.com May 9 16:35:16 2026 GMT Valid Panorama Pushed 111111111111111

pan-os-check-dynamic-updates-status


Checks for the latest available dynamic update versions and returns a list of latest available / currently installed content.
When running from a Panorama instance, the target argument must be specified.

Base Command

pan-os-check-dynamic-updates-status

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Mandatory for Panorama instances. Optional

Context Output

Path Type Description
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.features String Features provided by this version.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.released-on Date Date this version was released.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.AntiVirus.CurrentlyInstalled.version String Version of the dynamic update package.
Panorama.DynamicUpdates.AntiVirus.IsUpToDate Boolean Whether the latest available AntiVirus package is active on the device.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.features String Features provided by this version.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.released-on Date Date this version was released.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.AntiVirus.LatestAvailable.version String Version of the dynamic update package.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.features String Features provided by this version.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.released-on Date Date this version was released.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.Content.CurrentlyInstalled.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.Content.CurrentlyInstalled.version String Version of the dynamic update package.
Panorama.DynamicUpdates.Content.IsUpToDate Boolean Whether the latest available App/Threat package is active on the device.
Panorama.DynamicUpdates.Content.LatestAvailable.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.Content.LatestAvailable.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.Content.LatestAvailable.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.Content.LatestAvailable.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.Content.LatestAvailable.features String Features provided by this version.
Panorama.DynamicUpdates.Content.LatestAvailable.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.Content.LatestAvailable.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.Content.LatestAvailable.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.Content.LatestAvailable.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.Content.LatestAvailable.released-on Date Date this version was released.
Panorama.DynamicUpdates.Content.LatestAvailable.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.Content.LatestAvailable.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.Content.LatestAvailable.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.Content.LatestAvailable.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.Content.LatestAvailable.version String Version of the dynamic update package.
Panorama.DynamicUpdates.ContentTypesOutOfDate.Count Number The total number of active Dynamic Updates that have updates available.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.features String Features provided by this version.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.released-on Date Date this version was released.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.GP.CurrentlyInstalled.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.GP.CurrentlyInstalled.version String Version of the dynamic update package.
Panorama.DynamicUpdates.GP.IsUpToDate Boolean Whether the latest available GP Clientless VPN package is active on the device.
Panorama.DynamicUpdates.GP.LatestAvailable.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.GP.LatestAvailable.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.GP.LatestAvailable.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.GP.LatestAvailable.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.GP.LatestAvailable.features String Features provided by this version.
Panorama.DynamicUpdates.GP.LatestAvailable.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.GP.LatestAvailable.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.GP.LatestAvailable.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.GP.LatestAvailable.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.GP.LatestAvailable.released-on Date Date this version was released.
Panorama.DynamicUpdates.GP.LatestAvailable.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.GP.LatestAvailable.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.GP.LatestAvailable.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.GP.LatestAvailable.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.GP.LatestAvailable.version String Version of the dynamic update package.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.features String Features provided by this version.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.released-on Date Date this version was released.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.WildFire.CurrentlyInstalled.version String Version of the dynamic update package.
Panorama.DynamicUpdates.WildFire.IsUpToDate Boolean Whether the latest available WildFire package is active on the device.
Panorama.DynamicUpdates.WildFire.LatestAvailable.app-version String Version of the dynamic update package.
Panorama.DynamicUpdates.WildFire.LatestAvailable.current String Whether this version is currently active on the device.
Panorama.DynamicUpdates.WildFire.LatestAvailable.downloaded String Whether this version has been downloaded to the device.
Panorama.DynamicUpdates.WildFire.LatestAvailable.feature-desc String Detailed description of features provided.
Panorama.DynamicUpdates.WildFire.LatestAvailable.features String Features provided by this version.
Panorama.DynamicUpdates.WildFire.LatestAvailable.filename String Filename of the dynamic update.
Panorama.DynamicUpdates.WildFire.LatestAvailable.installing String Whether this version is being installed by a running job on the device.
Panorama.DynamicUpdates.WildFire.LatestAvailable.previous String Whether this version was previously active on the system and can be reverted to.
Panorama.DynamicUpdates.WildFire.LatestAvailable.release-notes String Link to release notes about the dynamic update package.
Panorama.DynamicUpdates.WildFire.LatestAvailable.released-on Date Date this version was released.
Panorama.DynamicUpdates.WildFire.LatestAvailable.sha256 String SHA256 hash of the dynamic update package file.
Panorama.DynamicUpdates.WildFire.LatestAvailable.size String Size of the dynamic update package file.
Panorama.DynamicUpdates.WildFire.LatestAvailable.size-kb String Size (in KB) of the dynamic update file.
Panorama.DynamicUpdates.WildFire.LatestAvailable.update-type String Type of update package (Full, Incremental, etc)
Panorama.DynamicUpdates.WildFire.LatestAvailable.version String Version of the dynamic update package.

pan-os-download-latest-antivirus-update


Downloads the latest antivirus dynamic update.

Base Command

pan-os-download-latest-antivirus-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the download job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the download status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the download status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.AntiVirus.Download.JobID String The job ID of the antivirus download.
Panorama.AntiVirus.Download.Status String The antivirus download status.
Panorama.AntiVirus.Download.Details String The download job details.

pan-os-download-latest-wildfire-update


Downloads the latest WildFire dynamic update.

Base Command

pan-os-download-latest-wildfire-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the download job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the download status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the download status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.WildFire.Download.JobID String The job ID of the WildFire download.
Panorama.WildFire.Download.Status String The WildFire download status.
Panorama.WildFire.Download.Details String The download job details.

pan-os-download-latest-gp-update


Downloads the latest GlobalProtect Clientless VPN dynamic update.

Base Command

pan-os-download-latest-gp-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the download job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the download status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the download status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.GP.Download.JobID String The job ID of the GlobalProtect Clientless VPN download.
Panorama.GP.Download.Status String The GlobalProtect Clientless VPN download status.
Panorama.GP.Download.Details String The download job details.

pan-os-install-latest-antivirus-update


Installs the latest Antivirus update.

Base Command

pan-os-install-latest-antivirus-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the install job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the install status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the install status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.AntiVirus.Install.JobID String The job ID of the installation.
Panorama.AntiVirus.Install.Status String The installation status.
Panorama.AntiVirus.Install.Details String The install job details.

pan-os-install-latest-wildfire-update


Installs the latest WildFire dynamic update.

Base Command

pan-os-install-latest-wildfire-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the install job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the install status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the install status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.WildFire.Install.JobID String The job ID of the installation.
Panorama.WildFire.Install.Status String The installation status.
Panorama.WildFire.Install.Details String The install job details.

pan-os-install-latest-gp-update


Installs the latest GlobalProtect Clientless VPN dynamic update.

Base Command

pan-os-install-latest-gp-update

Input

Argument Name Description Required
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
polling When set to false, the function will not use polling and will immediately return the ID of the install job. Possible values are: true, false. Optional
timeout_in_seconds The polling timeout in seconds. When polling is enabled, the command will keep checking the install status until this timeout is reached. Default is 3600. Optional
interval_in_seconds The polling interval in seconds. Controls how often the install status is checked. Default is 30. Optional

Context Output

Path Type Description
Panorama.GP.Install.JobID String The job ID of the installation.
Panorama.GP.Install.Status String The installation status.
Panorama.GP.Install.Details String The install job details.

pan-os-disable-rule


Disables a policy rule.

Base Command

pan-os-disable-rule

Input

Argument Name Description Required
rulename The name of the rule to disable. Required
pre_post The pre-rule or post-rule (Panorama instances). Possible values are: pre-rulebase, post-rulebase. Optional
device-group The device group where the rule is configured (Panorama instances). Optional
target Serial number of the firewall on which to run the command. Use only for a Panorama instance. Optional
rulebase The rulebase from which to disable the rule. Possible values are: security, application-override, authentication, decryption, nat, pbf. Default is security. Required
vsys The Firewall VSYS to disable the rule on. Use for disabling local rules on a firewall via Panorama or to specify a different VSYS than set in Integration parameters. Default is vsys1. Optional

Context Output

Path Type Description
Panorama.SecurityRule.Name string The rule name.
Panorama.SecurityRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.NAT.Name string The rule name.
Panorama.NAT.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.SSLRule.Name string The rule name.
Panorama.SSLRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.PBF.Name string The rule name.
Panorama.PBF.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.AuthRule.Name string The rule name.
Panorama.AuthRule.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.AppOverride.Name string The rule name.
Panorama.AppOverride.DeviceGroup string The device group for the rule (Panorama instances).
Panorama.CleanedUpRules.Category string The category of rule that was cleaned up (Local or Panorama).
Panorama.CleanedUpRules.AppliedAction string The applied cleanup action (Disabled or Deleted).
Panorama.CleanedUpRules.DeviceGroup string The device group where the rule was cleaned up from (Panorama instances).
Panorama.CleanedUpRules.PrePost string The location where the rule was cleaned up from (Panorama instances).
Panorama.CleanedUpRules.RuleName string The name of the rule.
Panorama.CleanedUpRules.Rulebase string The rulebase where the rule was cleaned up from (e.g. ‘security’, ‘nat’).
Panorama.CleanedUpRules.Target string The target firewall serial number, if provided.
Panorama.CleanedUpRules.Vsys string The VSYS where the rule was cleaned up from.

pan-os-get-rule-hitcounts


Gets rule hit counts from the firewall. When connected to Panorama this command can be run on any firewall managed by it.

Base Command

pan-os-get-rule-hitcounts

Input

Argument Name Description Required
device_filter_string The string by which to filter the results to only show specific hostnames or serial numbers. Optional
target The target number of the firewall. Used only on a Panorama instance. Optional
rulebase The firewall rulebase to check. Possible values are: application-override, authentication, decryption, dos, nat, network-packet-broker, pbf, qos, sdwan, security, tunnel-inspect. Default is security. Optional
vsys The firewall VSYS name to check. Returns results for all VSYS if left blank. Default is all. Optional
rules Comma-separated list of rule names to check. Returns results for all rules if left blank. Default is all. Optional
unused_only If set to true, only returns rules with a hit count of 0. Possible values are: true, false. Default is false. Optional
no_new_hits_since Shows rules that have had hits, but not after the date provided (in the format YYYY/MM/DD HH:MM:SS). Optional
pre_post The pre-rule or post-rule (Panorama instances only). When set, only rules pushed from Panorama at the specified position are returned. Possible values are: pre-rulebase, post-rulebase. Optional

Context Output

Path Type Description
PANOS.RuleHitCount.first_hit_timestamp Date Timestamp when the rule was first hit.
PANOS.RuleHitCount.from_dg_name String Name of the device group the rule is inherited from.
PANOS.RuleHitCount.hit_count Number Number of hits for the rule.
PANOS.RuleHitCount.hostid String Serial number of the firewall the entry was fetched from.
PANOS.RuleHitCount.instanceName String Name of the PAN-OS Integration Instance used to fetch the entry.
PANOS.RuleHitCount.instanceType String The type of the PAN-OS Integration Instance running the command (panorama or firewall).
PANOS.RuleHitCount.is_from_panorama Boolean Indicates if the rule was pushed from Panorama (true) or is local to the firewall (false).
PANOS.RuleHitCount.last_hit_timestamp Date Timestamp when the rule was most recently hit.
PANOS.RuleHitCount.last_reset_timestamp Date Timestamp when hit count data was last reset.
PANOS.RuleHitCount.latest String Value of the “latest” property returned by the API.
PANOS.RuleHitCount.name String Name of the rule.
PANOS.RuleHitCount.position String Indicates the position of the rule pushed from Panorama (pre_rulebase or post_rulebase).
PANOS.RuleHitCount.rule_creation_timestamp Date Timestamp when the rule was created.
PANOS.RuleHitCount.rule_modification_timestamp Date Timestamp when the rule was most recently modified.
PANOS.RuleHitCount.rulebase String The rulebase of the rule (such as security, nat, and so on).
PANOS.RuleHitCount.vsys String The name of the firewall VSYS.

<~PLATFORM>

License Requirements

The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:

  • Fetch incidents

</~PLATFORM>

Configuration parameters

  • server — Server URL (e.g., https://192.168.0.1) (required)
  • credentials
  • port — Port (e.g 443)
  • device_group — Device group - Panorama instances only (write shared for Shared location)
  • vsys — Vsys - Firewall instances only
  • template — Template - Panorama instances only
  • use_url_filtering — Use URL Filtering for auto enrichment
  • additional_suspicious — URL Filtering Additional suspicious categories. CSV list of categories that will be considered suspicious.
  • additional_malicious — URL Filtering Additional malicious categories. CSV list of categories that will be considered malicious.
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • key — API Key (Deprecated)
  • integrationReliability — Source Reliability
  • feedExpirationPolicy
  • feedExpirationInterval
  • isFetch — Fetch incidents
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • max_fetch — Max incidents per fetch (for each selected Log Type Query)
  • log_types — Log Type
  • fetch_job_polling_max_num_attempts — Log type max number of job pulling attempts
  • traffic_query — Traffic Log Type Query
  • threat_query — Threat Log Type Query
  • url_query — URL Log Type Query
  • data_query — Data Log Type Query
  • correlation_query — Correlation Log Type Query
  • system_query — System Log Type Query
  • wildfire_query — Wildfire Submission Log Type Query
  • decryption_query — Decryption Log Type Query
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval

Commands (289)

  • pan-os

    Runs any command supported in the API.

  • pan-os-add-profile-exception

    Add an exception to a Vulnerability Protection profile or Anti Spyware profile.

  • pan-os-add-static-route

    Adds a static route.

  • pan-os-apply-dns-signature-policy

    Enables assigning EDL to the anti-spyware profile under "DNS Signature Policies".

  • pan-os-apply-security-profile

    Applies a security profile to specific rules or rules with a specific tag.

  • pan-os-block-vulnerability

    Sets a vulnerability signature to block mode.

  • pan-os-check-dynamic-updates-status

    Checks for the latest available dynamic update versions and returns a list of latest available / currently installed content. When running from a Panorama instance, the 'target' argument must be specified.

  • pan-os-check-latest-panos-software

    Checks the PAN-OS software version from the repository.

  • pan-os-check-logs-status

    Checks the status of a logs query.

  • pan-os-check-traffic-logs-status Deprecated

    Deprecated. Use the pan-os-check-logs-status command instead.

  • pan-os-commit

    Commits a configuration to the Palo Alto firewall or Panorama, validates if a commit was successful if using polling="true" otherwise does not validate if the commit was successful. Committing to Panorama does not push the configuration to the firewalls. To push the configuration, run the panorama-push-to-device-group command.

  • pan-os-commit-status

    Returns commit status for a configuration.

  • pan-os-config-get-object

    Searches and returns a reference for the given object type and name. If no name is provided, all objects of the given type will be returned. Note this ONLY returns the object name and its location in the configuration hierachy, not the entire object.

  • pan-os-content-update-download-status

    Checks the download status of an app/threat dynamic update.

  • pan-os-content-update-install-status

    Gets the installation status of the app/threat dynamic update.

  • pan-os-create-address

    Creates an address object.

  • pan-os-create-address-group

    Creates a static or dynamic address group.

  • pan-os-create-anti-spyware-best-practice-profile

    Creates an Anti-Spyware best practice profile.

  • pan-os-create-antivirus-best-practice-profile

    Creates an antivirus best practice profile.

  • pan-os-create-application-group

    Creates a new application group rule in a Panorama/firewall instance.

  • pan-os-create-custom-url-category

    Creates a custom URL category.

  • pan-os-create-edl

    Creates an external dynamic list.

  • pan-os-create-file-blocking-best-practice-profile

    Creates a file blocking best practice profile.

  • pan-os-create-master-key

    Create a default master key that encrypts all the private keys and passwords in the configuration. This command is harmful because it invalidates the current API key and requires re-configuration of the integration instance.

  • pan-os-create-nat-rule

    Creates a new NAT rule in a Panorama/firewall instance.

  • pan-os-create-pbf-rule

    Creates a new policy-based-forwarding (PBF) rule in a Panorama/firewall instance.

  • pan-os-create-redistribution-profile

    Creates a new redistribution-profile under a virtual-router for a Panorama/firewall instance.

  • pan-os-create-rule

    Creates a policy rule.

  • pan-os-create-security-profile-group

    Creates a new security profile group in Panorama.

  • pan-os-create-service

    Creates a service.

  • pan-os-create-service-group

    Creates a service group.

  • pan-os-create-tag

    Creates a new tag in Panorama.

  • pan-os-create-url-filter

    Creates a URL filtering rule.

  • pan-os-create-url-filtering-best-practice-profile

    Creates a URL filtering best practice profile.

  • pan-os-create-vulnerability-best-practice-profile

    Creates a vulnerability protection best practice profile.

  • pan-os-create-wildfire-best-practice-profile

    Creates a WildFire analysis best practice profile.

  • pan-os-custom-block-rule

    Creates a custom block policy rule.

  • pan-os-delete-address

    Deletes an address object.

  • pan-os-delete-address-group

    Deletes an address group.

  • pan-os-delete-application-group

    Deletes an application-group.

  • pan-os-delete-custom-url-category

    Deletes a custom URL category.

  • pan-os-delete-edl

    Deletes an external dynamic list.

  • pan-os-delete-nat-rule

    Deletes a NAT rule.

  • pan-os-delete-pbf-rule

    Deletes a PBF rule.

  • pan-os-delete-profile-exception

    Delete an exception to a Vulnerability Protection profile or Anti Spyware profile.

  • pan-os-delete-redistribution-profile

    Deletes a redistribution-profile from a virtual-router.

  • pan-os-delete-rule

    Deletes a policy rule.

  • pan-os-delete-security-profile-group

    Deletes a security profile group from Panorama.

  • pan-os-delete-service

    Deletes a service.

  • pan-os-delete-service-group

    Deletes a service group.

  • pan-os-delete-static-route

    Deletes a static route.

  • pan-os-delete-tag

    Deletes a tag from Panorama.

  • pan-os-delete-url-filter

    Deletes a URL filtering rule.

  • pan-os-device-reboot

    Reboots the firewall device.

  • pan-os-disable-rule

    Disables a policy rule.

  • pan-os-download-latest-antivirus-update

    Downloads the latest antivirus dynamic update.

  • pan-os-download-latest-content-update

    Downloads the latest app/threat dynamic update.

  • pan-os-download-latest-gp-update

    Downloads the latest GlobalProtect Clientless VPN dynamic update.

  • pan-os-download-latest-wildfire-update

    Downloads the latest WildFire dynamic update.

  • pan-os-download-panos-status

    Gets the download status of the target PAN-OS software.

  • pan-os-download-panos-version

    Downloads the target PAN-OS software version to install on the target device.

  • pan-os-edit-address

    Edits an address object.

  • pan-os-edit-address-group

    Edits a static or dynamic address group.

  • pan-os-edit-application-group

    Edits an application-group.

  • pan-os-edit-custom-url-category

    Adds or removes sites to and from a custom URL category.

  • pan-os-edit-edl

    Modifies an element of an external dynamic list.

  • pan-os-edit-nat-rule

    Edits a NAT rule.

  • pan-os-edit-pbf-rule

    Edits a PBF rule.

  • pan-os-edit-profile-exception

    Edit an exception to a Vulnerability Protection profile or Anti Spyware profile.

  • pan-os-edit-redistribution-profile

    Edits a redistribution-profile in a virtual-router.

  • pan-os-edit-rule

    Edits a policy rule.

  • pan-os-edit-security-profile-group

    Edits the security profile group in Panorama.

  • pan-os-edit-service-group

    Edits a service group.

  • pan-os-edit-tag

    Edits a tag in Panorama.

  • pan-os-edit-url-filter

    Edits a URL filtering rule.

  • pan-os-enforce-wildfire-best-practice

    Enforces Wildfire best practices to upload files to the maximum size, forwards all file types, and updates the schedule.

  • pan-os-export-tech-support-file

    Exports a tech support file (TSF).

  • pan-os-get-address

    Returns address details for the supplied address name.

  • pan-os-get-address-group

    Gets details for the specified address group.

  • pan-os-get-anti-spyware-best-practice

    Get anti-spyware best practices.

  • pan-os-get-antivirus-best-practice

    Gets anti-virus best practices.

  • pan-os-get-audit-comment

    Gets the audit comment of a rule.

  • pan-os-get-certificate-info

    Gathers the name, expiration date, and expiration status of certificates configured locally on a Firewall or pushed from Panorama, as seen under Certificate Management.

  • pan-os-get-custom-url-category

    Returns information for a custom URL category.

  • pan-os-get-edl

    Returns information for an external dynamic list.

  • pan-os-get-file-blocking-best-practice

    Gets file-blocking best practices.

  • pan-os-get-licenses

    Gets information about available PAN-OS licenses and their statuses.

  • pan-os-get-logs

    Retrieves the data of a logs query.

  • pan-os-get-master-key-details

    Show the details of the default master key that encrypts all the private keys and passwords in the configuration.

  • pan-os-get-merged-config

    Pull the merged config file.

  • pan-os-get-pcap

    Returns a PCAP file. The recommended maximum file size is 5 MB. If the limit is exceeded, you may need to connect to the firewall and run the scp export command in order to export the PCAP file. See the Palo Alto Networks documentation.

  • pan-os-get-predefined-threats-list

    Gets the predefined threats list from a Firewall or Panorama and stores it as a JSON file in the context.

  • pan-os-get-rule-hitcounts

    Gets rule hit counts from the firewall. When connected to Panorama this command can be run on any firewall managed by it.

  • pan-os-get-running-config

    Pull the running config file.

  • pan-os-get-security-profiles

    Gets information for the specified security profile.

  • pan-os-get-service

    Returns service details for the supplied service name.

  • pan-os-get-service-group

    Returns details for the specified service group.

  • pan-os-get-ssl-decryption-rules

    Gets SSL decryption rules.

  • pan-os-get-static-route

    Returns the specified static route of a virtual router.

  • pan-os-get-traffic-logs Deprecated

    Deprecated. Use the pan-os-get-logs command instead.

  • pan-os-get-url-category

    Gets a URL category from URL filtering. This command is only available on firewall devices.

  • pan-os-get-url-category-from-cloud

    Returns a URL category from URL filtering. This command is only available on firewall devices.

  • pan-os-get-url-category-from-host

    Returns a URL category from URL filtering. This command is only available on firewall devices.

  • pan-os-get-url-filter

    Returns information for a URL filtering rule.

  • pan-os-get-url-filtering-best-practice

    Views URL filtering best practices.

  • pan-os-get-vulnerability-protection-best-practice

    Gets vulnerability-protection best practices.

  • pan-os-get-wildfire-best-practice

    Views WildFire best practices.

  • pan-os-get-wildfire-configuration

    Retrieves the Wildfire configuration.

  • pan-os-hygiene-check-log-forwarding

    Checks that at least one log forwarding profile is configured according to best practices.

  • pan-os-hygiene-check-security-rules

    Checks that security rules are configured correctly.

  • pan-os-hygiene-check-security-zones

    Checks that configured security zones have correct settings.

  • pan-os-hygiene-check-spyware-profiles

    Checks the configured anti-spyware profiles to ensure at least one meets best practices.

  • pan-os-hygiene-check-url-filtering-profiles

    Checks the configured URL filtering profiles to ensure at least one meets best practices of blocking the following URL categories - abused-drugs, adult, command-and-control, compromised-websites, gambling, grayware, hacking, malware, phishing, questionable, ransomware, scanning-activity, weapons.

  • pan-os-hygiene-check-vulnerability-profiles

    Checks the configured vulnerability profiles to ensure at least one meets best practices.

  • pan-os-hygiene-conforming-spyware-profiles

    Returns all anti-spyware profiles that conform to best practices.

  • pan-os-hygiene-conforming-url-filtering-profiles

    Returns a list of existing PANOS URL filtering objects that conform to best practices.

  • pan-os-hygiene-conforming-vulnerability-profiles

    Returns all vulnerability profiles that conform to best practices.

  • pan-os-hygiene-fix-log-forwarding

    Fixes log forwarding issues identified by pan-os-hygiene-check-log-forwarding.

  • pan-os-hygiene-fix-security-rule-log-settings

    Fixes security rules that have incorrect log settings by adding a log forwarding profile and setting.

  • pan-os-hygiene-fix-security-rule-profile-settings

    Fixes security rules that have incorrect log settings by adding a log forwarding profile and setting.

  • pan-os-hygiene-fix-security-zone-log-settings

    Fixes security zones that are configured without a valid log forwarding profile.

  • pan-os-install-file-content-update

    Installs a specific content update file.

  • pan-os-install-latest-antivirus-update

    Installs the latest Antivirus update.

  • pan-os-install-latest-content-update

    Installs the latest app/threat dynamic update.

  • pan-os-install-latest-gp-update

    Installs the latest GlobalProtect Clientless VPN dynamic update.

  • pan-os-install-latest-wildfire-update

    Installs the latest WildFire dynamic update.

  • pan-os-install-panos-status

    Gets the installation status of the PAN-OS software.

  • pan-os-install-panos-version

    Installs the target PAN-OS version on the specified target device.

  • pan-os-list-address-groups

    Returns a list of address groups.

  • pan-os-list-addresses

    Returns a list of addresses.

  • pan-os-list-application-groups

    Returns a list of application-groups of either a Panorama/firewall instance.

  • pan-os-list-applications

    Returns a list of applications.

  • pan-os-list-configured-user-id-agents

    Retrieves a list of user-ID agents configured in the system.

  • pan-os-list-device-groups

    Returns all device groups from Panorama (use only in Panorama instances).

  • pan-os-list-edls

    Returns a list of external dynamic lists.

  • pan-os-list-nat-rules

    Returns a list of NAT rules of either a Panorama/firewall instance.

  • pan-os-list-pbf-rules

    Returns a list of pbf-rules of either a Panorama/firewall instance.

  • pan-os-list-pcaps

    Returns a list of all PCAP files by PCAP type. Not available for threat PCAPs.

  • pan-os-list-profile-exception

    List the exceptions in a Vulnerability Protection profile or Anti Spyware profile.

  • pan-os-list-redistribution-profiles

    Returns a list of redistribution-profiles of a specific virtual-router of either a Panorama/firewall instance.

  • pan-os-list-rules

    Returns a list of predefined Security Rules. (When passing a query, all other arguments are overridden. Make sure the query includes all the filters you want).

  • pan-os-list-security-profile-group

    Returns a list of security profile groups from Panorama.

  • pan-os-list-service-groups

    Returns a list of service groups.

  • pan-os-list-services

    Returns a list of addresses.

  • pan-os-list-static-routes

    Lists the static routes of a virtual router.

  • pan-os-list-tag

    Returns a list of tags from Panorama.

  • pan-os-list-templates

    Returns a list of available templates. (To be used only in Panorama instances).

  • pan-os-list-virtual-routers

    Returns a list of virtual routers of either a Panorama/firewall instance.

  • pan-os-move-rule

    Changes the location of a policy rule.

  • pan-os-platform-download-software

    Downloads the provided software version onto the device.

  • pan-os-platform-get-arp-tables

    Gets all ARP tables from all firewalls in the topology.

  • pan-os-platform-get-available-software

    Checks the devices for software that is available to be installed.

  • pan-os-platform-get-bgp-peers

    Retrieves all BGP peer information from the PAN-OS firewalls in the topology.

  • pan-os-platform-get-device-groups

    Gets operational information for all device groups in the Panorama topology, including group names, hierarchy, and associated firewalls. If no filter is provided, the command returns all device groups within the Panorama instance.

  • pan-os-platform-get-device-state

    Get the device state from the provided device. Note; This will attempt to connect directly to the provided target to get the device state. If the IP address as reported in "show system info" is unreachable, this command will fail.

  • pan-os-platform-get-global-counters

    Gets global counter information from all the PAN-OS firewalls in the topology.

  • pan-os-platform-get-ha-state

    Gets the HA state and associated details from the given device and any other details.

  • pan-os-platform-get-jobs

    Gets all the jobs from the devices in the environment, or a single job when the ID is specified. Supports polling when a single job ID is provided.

  • pan-os-platform-get-route-summary

    Pulls all route summary information from the topology.

  • pan-os-platform-get-routes

    Pulls all route summary information from the topology.

  • pan-os-platform-get-system-info

    Gets information from all PAN-OS systems in the topology.

  • pan-os-platform-get-system-status

    Checks the status of the given device, checking whether it's up or down and if the operational mode is normal. Supports polling to wait until the device becomes available.

  • pan-os-platform-get-template-stacks

    Gets the operational information of the template stacks in the topology.

  • pan-os-platform-install-software

    Installs the given software version onto the device. Downloads the software first with panorama-download-panos-version.

  • pan-os-platform-reboot

    Reboots the given device by host ID. Warning: This command has no confirmation and the device will immediately reboot. This command can be disruptive.

  • pan-os-platform-update-ha-state

    Checks the status of the given device, checking whether it's up or down and if the operational mode is normal.

  • pan-os-push-status

    Returns the push status for a configuration.

  • pan-os-push-to-device-group

    Pushes rules from PAN-OS to the configured device group. In order to push the configuration to Prisma Access managed tenants (single or multi tenancy), use the device group argument with the device group which is associated with the tenant ID. Validates if a push has been successful if polling="true".

  • pan-os-push-to-template

    Pushes the given PAN-OS template to the given devices or all devices that belong to the template.

  • pan-os-push-to-template-stack

    Pushes the given PAN-OS template-stack to the given devices or all devices that belong to the template stack.

  • pan-os-query-logs

    The query logs in Panorama.

  • pan-os-query-traffic-logs Deprecated

    Deprecated. Use the pan-os-query-logs command instead.

  • pan-os-refresh-edl

    Refreshes the specified external dynamic list.

  • pan-os-register-ip-tag

    Registers IP addresses to a tag.

  • pan-os-register-user-tag

    Registers users to a tag. This command is only available for PAN-OS version 9.x and above.

  • pan-os-remove-security-profile

    Removes a security profile to specific rules or rules with a specific tag.

  • pan-os-security-policy-match

    Checks whether a session matches a specified security policy. This command is only available on firewall instances.

  • pan-os-show-device-version

    Show firewall device software version.

  • pan-os-show-location-ip

    Gets location information for an IP address.

  • pan-os-show-user-id-interfaces-config

    Shows the user ID interface configuration.

  • pan-os-show-zones-config

    Shows the zones configuration.

  • pan-os-unregister-ip-tag

    Unregisters IP addresses from a tag.

  • pan-os-unregister-user-tag

    Unregisters users from a tag. This command is only available for PAN-OS version 9.x and higher.

  • pan-os-update-master-key

    Update the default master key that encrypts all the private keys and passwords in the configuration. This command is harmful because it invalidates the current API key and requires re-configuration of the integration instance.

  • pan-os-upload-content-update-file

    Uploads a content file to Panorama.

  • pan-os-url-filtering-block-default-categories

    Sets default categories to block in the URL filtering profile.

  • panorama Deprecated

    Runs any command supported in the API.

  • panorama-add-static-route Deprecated

    Adds a static route.

  • panorama-apply-security-profile Deprecated

    Applies a security profile to specific rules or rules with a specific tag.

  • panorama-block-vulnerability Deprecated

    Sets a vulnerability signature to block mode.

  • panorama-check-latest-panos-software Deprecated

    Checks the PAN-OS software version from the repository.

  • panorama-check-logs-status Deprecated

    Checks the status of a log query.

  • panorama-check-traffic-logs-status Deprecated

    Deprecated. Use the pan-os-check-logs-status command instead.

  • panorama-commit Deprecated

    Commits a configuration to the Palo Alto firewall or Panorama, but does not validate if the commit was successful. Committing to Panorama does not push the configuration to the firewalls. To push the configuration, run the panorama-push-to-device-group command.

  • panorama-commit-status Deprecated

    Returns commit status for a configuration.

  • panorama-content-update-download-status Deprecated

    Checks the download status of a content update.

  • panorama-content-update-install-status Deprecated

    Gets the installation status of the content update.

  • panorama-create-address Deprecated

    Creates an address object.

  • panorama-create-address-group Deprecated

    Creates a static or dynamic address group.

  • panorama-create-anti-spyware-best-practice-profile Deprecated

    Creates an anti-spyware best practice profile.

  • panorama-create-antivirus-best-practice-profile Deprecated

    Creates an antivirus best practice profile.

  • panorama-create-custom-url-category Deprecated

    Creates a custom URL category.

  • panorama-create-edl Deprecated

    Creates an external dynamic list.

  • panorama-create-file-blocking-best-practice-profile Deprecated

    Creates a file blocking best practice profile.

  • panorama-create-rule Deprecated

    Creates a policy rule.

  • panorama-create-service Deprecated

    Creates a service. Note: overrides the service if it already exists.

  • panorama-create-service-group Deprecated

    Creates a service group.

  • panorama-create-url-filter Deprecated

    Creates a URL filtering rule.

  • panorama-create-url-filtering-best-practice-profile Deprecated

    Creates a URL filtering best practice profile.

  • panorama-create-vulnerability-best-practice-profile Deprecated

    Creates a vulnerability protection best practice profile.

  • panorama-create-wildfire-best-practice-profile Deprecated

    Creates a WildFire analysis best practice profile.

  • panorama-custom-block-rule Deprecated

    Creates a custom block policy rule.

  • panorama-delete-address Deprecated

    Deletes an address object.

  • panorama-delete-address-group Deprecated

    Deletes an address group.

  • panorama-delete-custom-url-category Deprecated

    Deletes a custom URL category.

  • panorama-delete-edl Deprecated

    Deletes an external dynamic list.

  • panorama-delete-rule Deprecated

    Deletes a policy rule.

  • panorama-delete-service Deprecated

    Deletes a service.

  • panorama-delete-service-group Deprecated

    Deletes a service group.

  • panorama-delete-static-route Deprecated

    Deletes a static route.

  • panorama-delete-url-filter Deprecated

    Deletes a URL filtering rule.

  • panorama-device-reboot Deprecated

    Reboots the firewall device.

  • panorama-download-latest-content-update Deprecated

    Downloads the latest content update.

  • panorama-download-panos-status Deprecated

    Gets the download status of the target PAN-OS software.

  • panorama-download-panos-version Deprecated

    Downloads the target PAN-OS software version to install on the target device.

  • panorama-edit-address-group Deprecated

    Edits a static or dynamic address group.

  • panorama-edit-custom-url-category Deprecated

    Adds or removes sites to and from a custom URL category.

  • panorama-edit-edl Deprecated

    Modifies an element of an external dynamic list.

  • panorama-edit-rule Deprecated

    Edits a policy rule.

  • panorama-edit-service-group

    Edits a service group.

  • panorama-edit-url-filter Deprecated

    Edits a URL filtering rule.

  • panorama-enforce-wildfire-best-practice Deprecated

    Enforces wildfire best practices to upload files to the maximum size, forwards all file types, and updates the schedule.

  • panorama-get-address Deprecated

    Returns address details for the supplied address name.

  • panorama-get-address-group Deprecated

    Gets details for the specified address group.

  • panorama-get-anti-spyware-best-practice Deprecated

    Gets anti-spyware best practices.

  • panorama-get-antivirus-best-practice Deprecated

    Get anti-virus best practices.

  • panorama-get-custom-url-category Deprecated

    Returns information for a custom URL category.

  • panorama-get-edl Deprecated

    Returns information for an external dynamic list.

  • panorama-get-file-blocking-best-practice Deprecated

    Gets file-blocking best practices.

  • panorama-get-licenses Deprecated

    Gets information about available PAN-OS licenses and their statuses.

  • panorama-get-logs

    Retrieves the data of a logs query.

  • panorama-get-pcap Deprecated

    Returns information for a Panorama PCAP file. The recommended maximum file size is 5 MB. If the limit is exceeded, you may need to SSH the firewall and run the scp export command to export the PCAP file. See the Palo Alto Networks documentation.

  • panorama-get-predefined-threats-list Deprecated

    Gets the predefined threats list from a firewall or Panorama and stores it as a JSON file in the context.

  • panorama-get-security-profiles Deprecated

    Gets information for the specified security profile.

  • panorama-get-service Deprecated

    Returns service details for the supplied service name.

  • panorama-get-service-group Deprecated

    Returns details for the specified service group.

  • panorama-get-ssl-decryption-rules Deprecated

    Get SSL decryption rules.

  • panorama-get-static-route Deprecated

    Returns the specified static route of a virtual router.

  • panorama-get-traffic-logs Deprecated

    Deprecated. Use the pan-os-get-logs command instead.

  • panorama-get-url-category

    Gets a URL category from URL filtering. This command is only available on firewall devices.

  • panorama-get-url-category-from-cloud Deprecated

    Returns a URL category from URL filtering. This command is only available on firewall devices.

  • panorama-get-url-category-from-host Deprecated

    Returns a URL category from URL filtering. This command is only available on firewall devices.

  • panorama-get-url-filter Deprecated

    Returns information for a URL filtering rule.

  • panorama-get-url-filtering-best-practice Deprecated

    Views URL filtering best practices.

  • panorama-get-vulnerability-protection-best-practice Deprecated

    Gets vulnerability-protection best practices.

  • panorama-get-wildfire-best-practice Deprecated

    Views WildFire best practices.

  • panorama-get-wildfire-configuration Deprecated

    Retrieves the Wildfire configuration.

  • panorama-install-file-content-update Deprecated

    Installs a specific content update file.

  • panorama-install-latest-content-update Deprecated

    Installs the latest content update.

  • panorama-install-panos-status Deprecated

    Gets the installation status of the PAN-OS software.

  • panorama-install-panos-version Deprecated

    Installs the target PAN-OS version on the specified target device.

  • panorama-list-address-groups Deprecated

    Returns a list of address groups.

  • panorama-list-addresses Deprecated

    Returns a list of addresses.

  • panorama-list-applications Deprecated

    Returns a list of applications.

  • panorama-list-configured-user-id-agents Deprecated

    Retrieves list of user ID agents configured in the system.

  • panorama-list-edls Deprecated

    Returns a list of external dynamic lists.

  • panorama-list-pcaps Deprecated

    Returns a list of all PCAP files by PCAP type. Not available for threat PCAPs.

  • panorama-list-rules Deprecated

    Returns a list of predefined security rules.

  • panorama-list-service-groups Deprecated

    Returns a list of service groups.

  • panorama-list-services Deprecated

    Returns a list of addresses.

  • panorama-list-static-routes Deprecated

    Lists the static routes of a virtual router.

  • panorama-move-rule Deprecated

    Changes the location of a policy rule.

  • panorama-push-status Deprecated

    Returns the push status for a configuration.

  • panorama-push-to-device-group Deprecated

    Pushes rules from PAN-OS to the configured device group. In order to push the configuration to Prisma Access managed tenants (single or multi- tenancy), use the device group argument with the device group associated with the tenant ID.

  • panorama-query-logs Deprecated

    Queries logs in Panorama.

  • panorama-query-traffic-logs Deprecated

    Deprecated. Use the pan-os-query-logs command instead.

  • panorama-refresh-edl Deprecated

    Refreshes the specified external dynamic list.

  • panorama-register-ip-tag Deprecated

    Registers IP addresses to a tag.

  • panorama-register-user-tag Deprecated

    Registers users to a tag. This command is only available for PAN-OS version 9.x and higher.

  • panorama-security-policy-match Deprecated

    Checks whether a session matches a specified security policy. This command is only available on firewall instances.

  • panorama-show-device-version Deprecated

    Shows the firewall device software version.

  • panorama-show-location-ip Deprecated

    Gets location information for an IP address.

  • panorama-show-user-id-interfaces-config Deprecated

    Shows the user ID interface configuration.

  • panorama-show-zones-config Deprecated

    Shows the zones configuration.

  • panorama-unregister-ip-tag Deprecated

    Unregisters IP addresses from a tag.

  • panorama-unregister-user-tag Deprecated

    Unregisters users from a tag. This command is only available for PAN-OS version 9.x and higher.

  • panorama-upload-content-update-file Deprecated

    Uploads a content file to Panorama.

  • panorama-url-filtering-block-default-categories Deprecated

    Sets default categories to block in the URL filtering profile.

  • url

    Gets a URL category from URL filtering.

import json
from typing import cast
from unittest.mock import MagicMock, patch

import dateparser
import demistomock as demisto
import panos.errors
import pytest
import requests_mock
from CommonServerPython import CommandResults, DemistoException
from defusedxml import ElementTree
from freezegun import freeze_time
from datetime import datetime
from panos.device import Vsys
from panos.firewall import Firewall
from panos.objects import LogForwardingProfile, LogForwardingProfileMatchList
from panos.panorama import DeviceGroup, Panorama, Template, TemplateStack
from pytest_mock import MockerFixture
from requests_mock.mocker import Mocker as RequestsMock
from test_data import fetch_incidents_input, mock_rules

integration_firewall_params = {
    "port": "443",
    "vsys": "vsys1",
    "server": "https://1.1.1.1",
    "key": "thisisabogusAPIKEY!",
}

mock_demisto_args = {"threat_id": "11111", "vulnerability_profile": "mock_vuln_profile"}

integration_panorama_params = {
    "port": "443",
    "device_group": "Lab-Devices",
    "server": "https://1.1.1.1",
    "key": "thisisabogusAPIKEY!",
    "template": "test",
}


def load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


@pytest.fixture(autouse=True)
def set_params(mocker):
    mocker.patch.object(demisto, "params", return_value=integration_firewall_params)
    mocker.patch.object(demisto, "args", return_value=mock_demisto_args)


@pytest.fixture
def patched_requests_mocker(requests_mock):
    """
    This function mocks various PANOS API responses so we can accurately test the instance
    """
    base_url = "{}:{}/api/".format(integration_firewall_params["server"], integration_firewall_params["port"])
    # Version information
    mock_version_xml = """
    <response status = "success">
        <result>
            <sw-version>9.0.6</sw-version>
            <multi-vsys>off</multi-vsys>
            <model>Panorama</model>
            <serial>FAKESERIALNUMBER</serial>
        </result>
    </response>
    """
    version_path = "{}{}{}".format(base_url, "?type=version&key=", integration_firewall_params["key"])
    requests_mock.get(version_path, text=mock_version_xml, status_code=200)
    mock_response_xml = """
    <response status="success" code="20">
    <msg>command succeeded</msg>
    </response>
    """
    requests_mock.post(base_url, text=mock_response_xml, status_code=200)
    return requests_mock


def test_panorama_get_os_version(patched_requests_mocker):
    import Panorama
    from Panorama import get_pan_os_version

    Panorama.URL = "https://1.1.1.1:443/api/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    r = get_pan_os_version()
    assert r == "9.0.6"


def test_panorama_override_vulnerability(patched_requests_mocker):
    import Panorama
    from Panorama import panorama_override_vulnerability

    Panorama.URL = "https://1.1.1.1:443/api/"
    r = panorama_override_vulnerability(mock_demisto_args["threat_id"], mock_demisto_args["vulnerability_profile"], "reset-both")
    assert r["response"]["@status"] == "success"


def test_add_argument_list():
    from Panorama import add_argument_list

    list_argument = ["foo", "bar"]

    response_with_member = add_argument_list(list_argument, "test", True)
    expected_with_member = "<test><member>foo</member><member>bar</member></test>"
    assert response_with_member == expected_with_member

    response_with_member_field_name = add_argument_list(list_argument, "member", True)
    expected_with_member_field_name = "<member>foo</member><member>bar</member>"
    assert response_with_member_field_name == expected_with_member_field_name


def test_add_argument():
    from Panorama import add_argument

    argument = "foo"

    response_with_member = add_argument(argument, "test", True)
    expected_with_member = "<test><member>foo</member></test>"
    assert response_with_member == expected_with_member

    response_without_member = add_argument(argument, "test", False)
    expected_without_member = "<test>foo</test>"
    assert response_without_member == expected_without_member


def test_add_argument_yes_no():
    from Panorama import add_argument_yes_no

    arg = "No"
    field = "test"
    option = True

    response_option_true = add_argument_yes_no(arg, field, option)
    expected_option_true = "<option><test>no</test></option>"
    assert response_option_true == expected_option_true

    option = False
    response_option_false = add_argument_yes_no(arg, field, option)
    expected_option_false = "<test>no</test>"
    assert response_option_false == expected_option_false


def test_add_argument_target():
    from Panorama import add_argument_target

    response = add_argument_target("foo", "bar")
    expected = '<bar><devices><entry name="foo"/></devices></bar>'
    assert response == expected


@pytest.mark.parametrize(
    "disabled, rules_file, expected_results_file",
    [
        ("yes", "test_data/filter_rules_sample.json", "test_data/filter_rules_expected_result.json"),
    ],
)
def test_filter_rules_by_status(disabled: str, rules_file: str, expected_results_file: str):
    from Panorama import filter_rules_by_status

    with open(rules_file) as f:
        rules = json.loads(f.read())

    with open(expected_results_file) as f:
        expected_result = json.loads(f.read())

    result = filter_rules_by_status(disabled, rules)
    assert result == expected_result


def test_get_address(mocker):
    """
    Given:
     - an address_name argument which does not exist

    When:
     - running the panorama_get_address function

    Then:
     - Ensure the return value is an empty dictionary
    """
    import Panorama
    from Panorama import panorama_get_address

    exception_msg = "Object was not found, verify that the name is correct and that the instance was committed."
    mocker.patch.object(Panorama, "http_request", side_effect=Exception(exception_msg))
    result = panorama_get_address("TEST")
    assert result == {}


def test_get_address_command(mocker):
    """
    Given:
     - an address_name argument which does not exist

    When:
     - running the panorama_get_address_command function

    Then:
     - Ensure the return value is None, without any errors, and return_results contains the correct informative message.
    """
    import Panorama
    from Panorama import panorama_get_address_command

    mocker.patch.object(Panorama, "panorama_get_address", return_value={})
    return_results_mock = mocker.patch.object(Panorama, "return_results")
    result = panorama_get_address_command({"name": "TEST"})
    assert not result
    assert return_results_mock.call_args[0][0] == "Address name TEST was not found"


def test_prettify_addresses_arr():
    from Panorama import prettify_addresses_arr

    addresses_arr = [
        {"@name": "my_name", "fqdn": "a.com"},
        {"@name": "my_name2", "fqdn": "b.com"},
        {"@name": "test", "ip-netmask": "1.1.1.1", "tag": None},
    ]
    response = prettify_addresses_arr(addresses_arr)
    expected = [
        {"Name": "my_name", "FQDN": "a.com"},
        {"Name": "my_name2", "FQDN": "b.com"},
        {"Name": "test", "IP_Netmask": "1.1.1.1"},
    ]
    assert response == expected


def test_prettify_address():
    from Panorama import prettify_address

    address = {"@name": "my_name", "ip-netmask": "1.1.1.1", "description": "lala"}
    response = prettify_address(address)
    expected = {"Name": "my_name", "IP_Netmask": "1.1.1.1", "Description": "lala"}
    assert response == expected


def test_prettify_address_tag_none():
    from Panorama import prettify_address

    address = {"@name": "test", "ip-netmask": "1.1.1.1", "tag": None}
    response = prettify_address(address)
    expected = {"Name": "test", "IP_Netmask": "1.1.1.1"}
    assert response == expected


def test_prettify_address_group():
    from Panorama import prettify_address_group

    address_group_static = {"@name": "foo", "static": {"member": "address object"}}
    response_static = prettify_address_group(address_group_static)
    expected_address_group_static = {"Name": "foo", "Type": "static", "Addresses": "address object"}
    assert response_static == expected_address_group_static

    address_group_dynamic = {"@name": "foo", "dynamic": {"filter": "1.1.1.1 and 2.2.2.2"}}
    response_dynamic = prettify_address_group(address_group_dynamic)
    expected_address_group_dynamic = {"Name": "foo", "Type": "dynamic", "Match": "1.1.1.1 and 2.2.2.2"}
    assert response_dynamic == expected_address_group_dynamic

    address_group_dynamic_tag_none = {"@name": "foo", "dynamic": {"filter": "1.1.1.1 or 2.2.2.2"}, "tag": None}
    response_dynamic_tag_none = prettify_address_group(address_group_dynamic_tag_none)
    expected_address_group_dynamic_tag_none = {"Name": "foo", "Type": "dynamic", "Match": "1.1.1.1 or 2.2.2.2"}
    assert response_dynamic_tag_none == expected_address_group_dynamic_tag_none


def test_prettify_service():
    from Panorama import prettify_service

    service = {"@name": "service_name", "description": "foo", "protocol": {"tcp": {"port": "443"}}}
    response = prettify_service(service)
    expected = {"Name": "service_name", "Description": "foo", "Protocol": "tcp", "DestinationPort": "443"}
    assert response == expected


def test_prettify_service_tag_none():
    from Panorama import prettify_service

    service = {"@name": "service_name", "description": "foo", "protocol": {"tcp": {"port": "443"}}, "tag": None}
    response = prettify_service(service)
    expected = {"Name": "service_name", "Description": "foo", "Protocol": "tcp", "DestinationPort": "443"}
    assert response == expected


def test_prettify_service_group():
    from Panorama import prettify_service_group

    service_group = {"@name": "sg", "members": {"member": ["service1", "service2"]}}
    response = prettify_service_group(service_group)
    expected = {"Name": "sg", "Services": ["service1", "service2"]}
    assert response == expected


def test_prettify_service_group_tag_none():
    from Panorama import prettify_service_group

    service_group = {"@name": "sg_group", "members": {"member": ["service1", "service2"]}, "tag": None}
    response = prettify_service_group(service_group)
    expected = {"Name": "sg_group", "Services": ["service1", "service2"]}
    assert response == expected


def test_prettify_custom_url_category():
    from Panorama import prettify_custom_url_category

    custom_url_category = {"@name": "foo", "list": {"member": ["a", "b", "c"]}}
    response = prettify_custom_url_category(custom_url_category)
    expected = {"Name": "foo", "Sites": ["a", "b", "c"]}
    assert response == expected


def test_panorama_create_custom_url_category_8_x(mocker):
    """
    Given:
     - an only > 9.x valid argument for custom url category creation

    When:
     - running the panorama_create_custom_url_category function
     - mocking the pan-os version to be 8.x

    Then:
     - a proper error is raised
    """
    from Panorama import panorama_create_custom_url_category

    mocker.patch("Panorama.get_pan_os_major_version", return_value=8)
    custom_url_category_name = "name"
    description = "test_desc"
    type_ = "URL List"

    with pytest.raises(DemistoException, match="The type and categories arguments are only relevant for PAN-OS 9.x versions."):
        panorama_create_custom_url_category(custom_url_category_name, type_=type_, description=description)


def test_panorama_create_custom_url_category_9_x(mocker):
    """
    Given:
     - a non valid argument for custom url category creation

    When:
     - running the panorama_create_custom_url_category function
     - mocking the pan-os version to be 9.x

    Then:
     - a proper error is raised
    """
    from Panorama import panorama_create_custom_url_category

    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    custom_url_category_name = "name"
    type_ = "URL List"
    categories = "phishing"
    sites = "a.com"
    description = "test_desc"

    with pytest.raises(DemistoException, match="The type argument is mandatory for PAN-OS 9.x versions."):
        panorama_create_custom_url_category(custom_url_category_name, sites=sites, description=description)

    with pytest.raises(DemistoException, match="Exactly one of the sites and categories arguments should be defined."):
        panorama_create_custom_url_category(custom_url_category_name, type_=type_, sites=sites, categories=categories)

    with pytest.raises(DemistoException, match="URL List type is only for sites, Category Match is only for categories."):
        panorama_create_custom_url_category(custom_url_category_name, type_=type_, categories=categories)


def test_create_url_filter_params_8_x(mocker):
    """
    Given:
     - a valid argument for url filter creation

    When:
     - running the create_url_filter_params utility function
     - mocking the pan-os version to be 8.x

    Then:
     - a proper xml element is generated
    """
    from Panorama import create_url_filter_params

    mocker.patch("Panorama.get_pan_os_major_version", return_value=8)
    url_filter_name = "name"
    action = "alert"
    url_category_list = "adult"
    description = "test_desc"

    url_filter_params = create_url_filter_params(
        url_filter_name, action, url_category_list=url_category_list, description=description
    )
    assert url_filter_params["element"].find("<action>block</action>") != -1  # if not -1, then it is found


def test_create_url_filter_params_9_x(mocker):
    """
    Given:
     - a valid argument for url filter creation

    When:
     - running the create_url_filter_params utility function
     - mocking the pan-os version to be 9.x

    Then:
     - a proper xml element is generated
    """
    from Panorama import create_url_filter_params

    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    url_filter_name = "name"
    action = "alert"
    url_category_list = "adult"
    description = "test_desc"

    url_filter_params = create_url_filter_params(
        url_filter_name, action, url_category_list=url_category_list, description=description
    )
    assert url_filter_params["element"].find("<action>block</action>") == -1  # if  -1, then it is not found


def test_edit_url_filter_non_valid_args_8_x(mocker):
    """
    Given:
     - a non valid argument for edit url filter

    When:
     - running the edit_url_filter function
     - mocking the pan-os version to be 8.x

    Then:
     - a proper error is raised
    """
    from Panorama import panorama_edit_url_filter

    url_filter_object = {
        "@name": "fw_test_pb_dont_delete",
        "action": "block",
        "allow": {"member": ["Demisto- block sites", "test3"]},
        "allow-list": {"member": "www.thepill2.com"},
        "block": {"member": ["abortion", "abused-drugs"]},
        "block-list": {"member": "www.thepill.com"},
        "credential-enforcement": {
            "allow": {"member": ["Demisto- block sites", "test3"]},
            "block": {"member": ["abortion", "abused-drugs"]},
            "log-severity": "medium",
        },
        "description": "gogo",
    }
    mocker.patch("Panorama.get_pan_os_major_version", return_value=8)
    mocker.patch("Panorama.panorama_get_url_filter", return_value=url_filter_object)
    url_filter_name = "fw_test_pb_dont_delete"
    element_to_change = "allow_categories"
    element_value = "gambling"
    add_remove_element = "remove"

    err_msg = (
        "Only the override_allow_list, override_block_list, description properties can be"
        " changed in PAN-OS 8.x or earlier versions."
    )
    with pytest.raises(DemistoException, match=err_msg):
        panorama_edit_url_filter(url_filter_name, element_to_change, element_value, add_remove_element)


def test_edit_url_filter_non_valid_args_9_x(mocker):
    """
    Given:
     - a non valid argument for edit url filter

    When:
     - running the edit_url_filter function
     - mocking the pan-os version to be 9.x

    Then:
     - a proper error is raised
    """
    from Panorama import panorama_edit_url_filter

    url_filter_object = {
        "@name": "fw_test_pb_dont_delete",
        "allow": {"member": "Test_pb_custom_url_DONT_DELETE"},
        "credential-enforcement": {
            "block": {"member": ["gambling", "abortion"]},
            "log-severity": "medium",
        },
        "description": "wowo",
    }
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    mocker.patch("Panorama.panorama_get_url_filter", return_value=url_filter_object)
    url_filter_name = "fw_test_pb_dont_delete"
    element_to_change = "override_block_list"
    element_value = "gambling"
    add_remove_element = "remove"

    err_msg = (
        "Only the allow_categories, block_categories, description properties can be changed in PAN-OS 9.x or later versions."
    )
    with pytest.raises(DemistoException, match=err_msg):
        panorama_edit_url_filter(url_filter_name, element_to_change, element_value, add_remove_element)


def http_mock(url: str, method: str, body: dict = {}):
    return body


@pytest.mark.parametrize(
    "category_name, items",
    [
        (
            "category_name",
            ["www.good.com"],
        )
    ],
)
def test_remove_from_custom_url_category(category_name, items, mocker):
    """
    Given:
     - a valid argument for edit custom url group

    When:
     - running the custom_url_category_remove_items function

    Then:
     - checks an assertion
    """
    import Panorama
    from Panorama import panorama_custom_url_category_remove_items

    return_results_mock = mocker.patch.object(Panorama, "return_results")

    mocker.patch(
        "Panorama.panorama_get_custom_url_category",
        return_value={"description": "description", "list": {"member": "www.test.com"}},
    )
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    mocker.patch("Panorama.http_request", side_effect=http_mock)

    panorama_custom_url_category_remove_items(category_name, items, "URL List")
    demisto_result_got = return_results_mock.call_args.args[0]["Contents"]
    assert "www.test.com" in demisto_result_got["element"]


class TestQueryLogsCommand:
    @staticmethod
    def create_logs_query_queue(status_count, no_logs_found):
        response_queue = [
            MockedResponse(
                text='<response status="success" code="19"><result><msg><line>query '
                "job enqueued with jobid 1</line></msg><job>1</job></result></response>",
                status_code=200,
            )
        ]

        for _ in range(status_count):
            response_queue.append(
                MockedResponse(
                    text='<response status="success"><result><job><tenq>15:05:47</tenq><tdeq>15:05:47</tdeq><tlast>'
                    '01:00:00</tlast><status>ACT</status><id>1238</id></job><log><logs count="0" progress="20"/'
                    "></log></result></response>",
                    status_code=200,
                )
            )

        if no_logs_found:
            # job has finished without finding any logs
            response_queue.append(
                MockedResponse(
                    text='<response status="success"><result><job><tenq>15:05:47</tenq><tdeq>15:05:47</tdeq><tlast>'
                    '15:06:52</tlast><status>FIN</status><id>1</id></job><log><logs count="0"'
                    ' progress="100"/></log></result></response>',
                    status_code=200,
                )
            )

        else:
            with open("test_data/query_logs.xml") as f:
                # job has finished with finding logs
                response_queue.append(MockedResponse(text=f.read(), status_code=200))

        return response_queue

    def test_query_logs_command_without_polling(self, mocker):
        """
        Given
        - an api response indicating a log query job has been created

        When
        - querying logs without polling

        Then
        - make sure polling is not triggered.
        - make sure context output indicates that the status of the job is 'Pending'
        """
        import Panorama
        import requests
        from Panorama import panorama_query_logs_command

        Panorama.API_KEY = "thisisabogusAPIKEY!"
        mocker.patch.object(
            requests,
            "request",
            return_value=MockedResponse(
                text='<response status="success" code="19"><result><msg><line>query '
                "job enqueued with jobid 1</line></msg><job>1</job></result></response>",
                status_code=200,
            ),
        )

        command_result = panorama_query_logs_command({"log-type": "traffic", "polling": "false"})
        assert command_result.outputs == {
            "JobID": "1",
            "Status": "Pending",
            "LogType": "traffic",
            "Message": "query job enqueued with jobid 1",
        }
        assert not command_result.scheduled_command
        assert command_result.readable_output == "### Query Logs:\n|JobID|Status|\n|---|---|\n| 1 | Pending |\n"

    @pytest.mark.parametrize(
        "status_count, no_logs_found", [(1, False), (2, True), (3, False), (5, True), (8, False), (10, True)]
    )
    def test_query_logs_command_with_polling(self, mocker, status_count, no_logs_found):
        """
        Given
        - a queue of api responses
        - responses indicating query logs succeeded or not succeeded.
        - a status count which means how many times polling was done.

        When
        - querying logs with polling

        Then
        - make sure the readable output indicating polling is active is printed only once.
        - make sure context is returned only at the end of polling, and that the context is valid
          if there are logs available and if there aren't.
        """
        import Panorama
        import requests
        from CommonServerPython import ScheduledCommand
        from Panorama import panorama_query_logs_command

        Panorama.API_KEY = "thisisabogusAPIKEY!"
        mocker.patch.object(
            requests, "request", side_effect=self.create_logs_query_queue(status_count=status_count, no_logs_found=no_logs_found)
        )
        mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported", return_value=None)

        command_result = panorama_query_logs_command({"log-type": "traffic", "polling": "true"})
        assert command_result.readable_output == "Fetching traffic logs for job ID 1..."
        assert not command_result.outputs  # no context should be returned until polling is done.

        polling_args = {"query_log_job_id": "1", "hide_polling_output": True, "polling": True, "log-type": "traffic"}

        command_result = panorama_query_logs_command(polling_args)
        while command_result.scheduled_command:  # if scheduled_command is set, it means that command should still poll
            assert not command_result.readable_output  # make sure that indication of polling is printed only once
            assert not command_result.outputs  # make sure no context output is being returned to war-room during polling
            command_result = panorama_query_logs_command(polling_args)

        if no_logs_found:
            assert command_result.outputs == {"JobID": "1", "LogType": "traffic", "Logs": [], "Status": "Completed"}
        else:
            partial_expected_outputs = {"JobID": "1", "LogType": "traffic", "Status": "Completed"}
            assert partial_expected_outputs.items() <= command_result.outputs.items()
            assert "Logs" in command_result.outputs
            assert command_result.outputs["Logs"]  # make sure there are log outputs available.


def test_prettify_edl():
    from Panorama import prettify_edl

    edl = {"@name": "edl_name", "type": {"my_type": {"url": "abc.com", "description": "my_desc"}}}
    response = prettify_edl(edl)
    expected = {"Name": "edl_name", "Type": "my_type", "URL": "abc.com", "Description": "my_desc"}
    assert response == expected


def test_build_traffic_logs_query():
    """
    Given:
     - a valid arguments for traffic logs query generation

    When:
     - running the build_traffic_logs_query utility function

    Then:
     - a proper query is generated
        (addr.src in 192.168.1.222) and (app eq netbios-dg) and (action eq allow) and (port.dst eq 138)
    """
    from Panorama import build_traffic_logs_query

    source = "192.168.1.222"
    application = "netbios-dg"
    action = "allow"
    to_port = "138"
    response = build_traffic_logs_query(source, None, None, application, to_port, action)
    expected = "(addr.src in 192.168.1.222) and (app eq netbios-dg) and (port.dst eq 138) and (action eq allow)"
    assert response == expected


def test_prettify_traffic_logs():
    from Panorama import prettify_traffic_logs

    traffic_logs = [
        {"action": "my_action1", "category": "my_category1", "rule": "my_rule1"},
        {"action": "my_action2", "category": "my_category2", "rule": "my_rule2"},
    ]
    response = prettify_traffic_logs(traffic_logs)
    expected = [
        {"Action": "my_action1", "Category": "my_category1", "Rule": "my_rule1"},
        {"Action": "my_action2", "Category": "my_category2", "Rule": "my_rule2"},
    ]
    assert response == expected


def test_build_logs_query():
    """
    Given:
     - a valid arguments for logs query generation

    When:
     - running the build_logs_query utility function

    Then:
     - a proper query is generated
        ((url contains 'demisto.com') or (url contains 'paloaltonetworks.com'))
    """
    from Panorama import build_logs_query

    urls_as_string = "demisto.com, paloaltonetworks.com"
    response = build_logs_query(None, None, None, None, None, None, None, None, None, urls_as_string, None, None)
    expected = "((url contains 'demisto.com') or (url contains 'paloaltonetworks.com'))"
    assert response == expected


def test_prettify_logs():
    from Panorama import prettify_logs

    traffic_logs = [
        {"action": "my_action1", "category": "my_category1", "rule": "my_rule1", "natdport": "100", "bytes": "12"},
        {"action": "my_action2", "category": "my_category2", "rule": "my_rule2", "natdport": "101", "bytes_sent": "11"},
    ]
    response = prettify_logs(traffic_logs)
    expected = [
        {
            "Action": "my_action1",
            "CategoryOrVerdict": "my_category1",
            "Rule": "my_rule1",
            "NATDestinationPort": "100",
            "Bytes": "12",
        },
        {
            "Action": "my_action2",
            "CategoryOrVerdict": "my_category2",
            "Rule": "my_rule2",
            "NATDestinationPort": "101",
            "BytesSent": "11",
        },
    ]
    assert response == expected


prepare_security_rule_inputs = [
    ("after", "test_rule_name", ["user1"], "<source-user><member>user1</member></source-user>"),
    ("after", "test_rule_name", ["user1,user2"], "<source-user><member>user1,user2</member></source-user>"),
]


@pytest.mark.parametrize("where, dst, source_user, expected_result", prepare_security_rule_inputs)
def test_prepare_security_rule_params(where, dst, source_user, expected_result):
    """
    Given:
     - valid arguments for the prepare_security_rule_params function

    When:
     - running the prepare_security_rule_params utility function

    Then:
     - a valid security rule dictionary is returned.
    """
    from Panorama import prepare_security_rule_params

    params = prepare_security_rule_params(
        api_action="set",
        action="drop",
        destination=["any"],
        source=["any"],
        rulename="test",
        where=where,
        dst=dst,
        source_user=source_user,
    )
    assert expected_result in params.get("element", "")


prepare_security_rule_fail_inputs = [
    ("top", "test_rule_name"),
    ("bottom", "test_rule_name"),
]


@pytest.mark.parametrize("where, dst", prepare_security_rule_fail_inputs)
def test_prepare_security_rule_params_fail(where, dst):
    """
    Given:
     - a non valid arguments for the prepare_security_rule_params function

    When:
     - running the prepare_security_rule_params utility function

    Then:
     - a proper exception is raised
    """
    from Panorama import prepare_security_rule_params

    err_msg = "Please provide a dst rule only when the where argument is before or after."
    with pytest.raises(DemistoException, match=err_msg):
        prepare_security_rule_params(
            api_action="set", action="drop", destination=["any"], source=["any"], rulename="test", where=where, dst=dst
        )


def test_build_policy_match_query():
    """
    Given:
     - a valid arguments for policy match query generation

    When:
     - running the build_policy_match_query utility function

    Then:
     - a proper xml is generated
    """
    from Panorama import build_policy_match_query

    source = "1.1.1.1"
    destination = "6.7.8.9"
    protocol = "1"
    application = "gmail-base"
    response = build_policy_match_query(application, None, destination, None, None, None, protocol, source)
    expected = (
        "<test><security-policy-match><source>1.1.1.1</source><destination>6.7.8.9</destination>"
        "<protocol>1</protocol><application>gmail-base</application></security-policy-match></test>"
    )
    assert response == expected


def test_panorama_register_ip_tag_command_wrongful_args(mocker):
    """
    Given:
     - a non valid arguments for the panorama_register_ip_tag_command function

    When:
     - running the panorama_register_ip_tag_command function

    Then:
     - a proper exception is raised
    """
    from Panorama import panorama_register_ip_tag_command

    args = {"IPs": "1.1.1.1", "tag": "test_tag", "persistent": "false", "timeout": "5"}

    mocker.patch("Panorama.get_pan_os_major_version", return_value=8)
    with pytest.raises(DemistoException, match="The timeout argument is only applicable on 9.x PAN-OS versions or higher."):
        panorama_register_ip_tag_command(args)


def test_prettify_matching_rule():
    from Panorama import prettify_matching_rule

    matching_rule = {"action": "my_action1", "@name": "very_important_rule", "source": "6.7.8.9", "destination": "any"}
    response = prettify_matching_rule(matching_rule)
    expected = {"Action": "my_action1", "Name": "very_important_rule", "Source": "6.7.8.9", "Destination": "any"}
    assert response == expected


def test_prettify_static_route():
    from Panorama import prettify_static_route

    static_route = {"@name": "name1", "destination": "1.2.3.4", "metric": "10", "nexthop": {"fqdn": "demisto.com"}}
    virtual_router = "my_virtual_router"
    response = prettify_static_route(static_route, virtual_router)
    expected = {
        "Name": "name1",
        "Destination": "1.2.3.4",
        "Metric": 10,
        "NextHop": "demisto.com",
        "VirtualRouter": "my_virtual_router",
    }
    assert response == expected


def test_validate_search_time():
    from Panorama import validate_search_time

    assert validate_search_time("2019/12/26")
    assert validate_search_time("2019/12/26 00:00:00")
    with pytest.raises(Exception):
        assert validate_search_time("219/12/26 00:00:00")
        assert validate_search_time("219/10/35")


def test_show_user_id_interface_config_command():
    """
    Given:
     - missing template and template_stack arguments for the show_user_id_interface_config_command command

    When:
     - running the show_user_id_interface_config_request function

    Then:
     - a proper exception is raised
    """
    from Panorama import show_user_id_interface_config_command

    args = {}
    str_match = (
        "In order to show the User Interface configuration in your Panorama, "
        "supply either the template or the template_stack arguments."
    )
    with pytest.raises(DemistoException, match=str_match):
        show_user_id_interface_config_command(args)


def test_prettify_user_interface_config():
    from Panorama import prettify_user_interface_config

    raw_response = [
        {
            "@name": "internal",
            "network": {"layer3": {"member": "ethernet1/2"}, "log-setting": "ToLoggingService"},
            "enable-user-identification": "yes",
        },
        {"@name": "External", "network": {"tap": {"member": "ethernet1/1"}, "log-setting": "ToLoggingService"}},
    ]
    response = prettify_user_interface_config(raw_response)
    expected = [
        {"Name": "ethernet1/2", "Zone": "internal", "EnableUserIdentification": "yes"},
        {"Name": "ethernet1/1", "Zone": "External", "EnableUserIdentification": "no"},
    ]
    assert response == expected


def test_list_configured_user_id_agents_command(mocker):
    """
    Given:
     - missing template and template_stack arguments for the list_configured_user_id_agents_command command

    When:
     - running the list_configured_user_id_agents_request function

    Then:
     - a proper exception is raised
    """
    from Panorama import list_configured_user_id_agents_command

    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    args = {}
    str_match = (
        "In order to show the the User ID Agents in your Panorama, supply either the template or the template_stack arguments."
    )
    with pytest.raises(DemistoException, match=str_match):
        list_configured_user_id_agents_command(args)


def test_prettify_configured_user_id_agents__multi_result():
    from Panorama import prettify_configured_user_id_agents

    raw_response = [
        {"@name": "testing2", "serial-number": "panorama2"},
        {
            "@name": "fullinfo",
            "host-port": {
                "port": "67",
                "ntlm-auth": "yes",
                "ldap-proxy": "yes",
                "collectorname": "demisto",
                "secret": "secret",
                "host": "what",
            },
            "ip-user-mappings": "yes",
        },
    ]
    response = prettify_configured_user_id_agents(raw_response)
    expected = [
        {
            "Name": "testing2",
            "Host": None,
            "Port": None,
            "NtlmAuth": "no",
            "LdapProxy": "no",
            "CollectorName": None,
            "Secret": None,
            "EnableHipCollection": "no",
            "SerialNumber": "panorama2",
            "IpUserMapping": "no",
            "Disabled": "no",
        },
        {
            "Name": "fullinfo",
            "Host": "what",
            "Port": "67",
            "NtlmAuth": "yes",
            "LdapProxy": "yes",
            "CollectorName": "demisto",
            "Secret": "secret",
            "EnableHipCollection": "no",
            "SerialNumber": None,
            "IpUserMapping": "yes",
            "Disabled": "no",
        },
    ]
    assert response == expected


def test_prettify_configured_user_id_agents__single_result():
    from Panorama import prettify_configured_user_id_agents

    raw_response = {
        "@name": "fullinfo",
        "host-port": {
            "port": "67",
            "ntlm-auth": "yes",
            "ldap-proxy": "yes",
            "collectorname": "demisto",
            "secret": "secret",
            "host": "what",
        },
        "ip-user-mappings": "yes",
    }
    response = prettify_configured_user_id_agents(raw_response)
    expected = {
        "Name": "fullinfo",
        "Host": "what",
        "Port": "67",
        "NtlmAuth": "yes",
        "LdapProxy": "yes",
        "CollectorName": "demisto",
        "Secret": "secret",
        "EnableHipCollection": "no",
        "SerialNumber": None,
        "IpUserMapping": "yes",
        "Disabled": "no",
    }
    assert response == expected


def test_prettify_rule():
    from Panorama import prettify_rule

    with open("test_data/rule.json") as f:
        rule = json.load(f)

    with open("test_data/prettify_rule.json") as f:
        expected_pretty_rule = json.load(f)

    pretty_rule = prettify_rule(rule)
    del pretty_rule["DeviceGroup"]

    assert pretty_rule == expected_pretty_rule


class TestPcap:
    @staticmethod
    def test_list_pcaps_flow_with_no_existing_pcaps(mocker):
        """
        Given -
            a response which indicates there are no pcap files on the firewall.

        When -
            listing all the available pcap files.

        Then -
            make sure that a message which indicates there are no Pcaps is printed out.
        """
        from Panorama import panorama_list_pcaps_command

        no_pcaps_response = MockedResponse(
            text='<?xml version="1.0"?>\n<response status="success">\n  '
            "<result>\n    <dir-listing/>\n  </result>\n</response>\n",
            status_code=200,
        )

        mocker.patch("Panorama.http_request", return_value=no_pcaps_response)
        results_mocker = mocker.patch.object(demisto, "results")
        panorama_list_pcaps_command({"pcapType": "filter-pcap"})
        assert results_mocker.called
        assert results_mocker.call_args.args[0] == "PAN-OS has no Pcaps of type: filter-pcap."

    @staticmethod
    @pytest.mark.parametrize(
        "api_response, expected_context, expected_markdown_table",
        [
            (
                '<?xml version="1.0"?>\n<response status="success">\n  <result>\n    <dir-listing>\n      '
                "<file>/pcap</file>\n      <file>/pcap_test</file>\n    </dir-listing>\n  </result>\n</response>\n",
                ["pcap", "pcap_test"],
                "### List of Pcaps:\n|Pcap name|\n|---|\n| pcap |\n| pcap_test |\n",
            ),
            (
                '<?xml version="1.0"?>\n<response status="success">\n  <result>\n    <dir-listing>\n      '
                "<file>/pcap_test</file>\n    </dir-listing>\n  </result>\n</response>\n",
                ["pcap_test"],
                "### List of Pcaps:\n|Pcap name|\n|---|\n| pcap_test |\n",
            ),
        ],
    )
    def test_list_pcaps_flow(mocker, api_response, expected_context, expected_markdown_table):
        """
        Given
            - a response which indicates there are two pcaps in the firewall.
            - a response which indicates there is only one pcap in the firewall.

        When -
            listing all the available pcap files.

        Then -
            make sure the response is parsed correctly.
        """
        from Panorama import panorama_list_pcaps_command

        pcaps_response = MockedResponse(text=api_response, status_code=200)
        mocker.patch("Panorama.http_request", return_value=pcaps_response)
        results_mocker = mocker.patch.object(demisto, "results")
        panorama_list_pcaps_command({"pcapType": "filter-pcap"})
        called_args = results_mocker.call_args[0][0]
        assert list(*called_args["EntryContext"].values()) == expected_context
        assert called_args["HumanReadable"] == expected_markdown_table

    @staticmethod
    def test_get_specific_pcap_flow_which_does_not_exist(mocker):
        """
        Given -
           a response which indicates there are no pcap files on the firewall.

        When -
           trying to download a pcap file.

        Then -
           make sure that the error message from the api is actually returned.
        """
        from Panorama import panorama_get_pcap_command

        no_pcaps_response = MockedResponse(
            text='<?xml version="1.0"?>\n<response status="error">\n  <msg>\n    '
            "<line>test.pcap not present</line>\n  </msg>\n</response>\n",
            status_code=200,
            headers={"Content-Type": "application/xml"},
        )
        mocker.patch("Panorama.http_request", return_value=no_pcaps_response)
        with pytest.raises(Exception, match="line: test.pcap not present"):
            panorama_get_pcap_command({"pcapType": "filter-pcap", "from": "test"})

    @staticmethod
    def test_get_filter_pcap_without_from_argument(mocker):
        """
        Given -
           a filter-pcap type without 'from' argument

        When -
           trying to download a filter pcap file.

        Then -
           make sure that the error message which states that the 'from' argument should be returned is presented.
        """
        from Panorama import panorama_get_pcap_command

        no_pcaps_response = MockedResponse(
            text='<?xml version="1.0"?>\n<response status="error">\n  <msg>\n    '
            "<line>test.pcap not present</line>\n  </msg>\n</response>\n",
            status_code=200,
            headers={"Content-Type": "application/xml"},
        )
        mocker.patch("Panorama.http_request", return_value=no_pcaps_response)
        with pytest.raises(Exception, match="cannot download filter-pcap without the from argument"):
            panorama_get_pcap_command({"pcapType": "filter-pcap"})


class TestPanoramaListApplicationsCommand:
    @staticmethod
    @pytest.mark.parametrize("panorama_version", [8, 9])
    def test_panorama_list_applications_command(mocker, panorama_version):
        """
        Given
           - http response of the list of applications.
           - panorama version 8 & 9.

        When
           - getting a list of all the applications in panorama 8/9.

        Then
           - a valid context output is returned.
        """
        from Panorama import panorama_list_applications_command

        mocker.patch("Panorama.http_request", return_value=load_json("test_data/list_applications_response.json"))
        mocker.patch("Panorama.get_pan_os_major_version", return_value=panorama_version)

        res = mocker.patch("demistomock.results")
        panorama_list_applications_command({"predefined": "false"})

        assert res.call_args.args[0]["Contents"] == {
            "@name": "test-playbook-app",
            "@loc": "Lab-Devices",
            "subcategory": "infrastructure",
            "category": "networking",
            "technology": "client-server",
            "description": "test-playbook-application-do-not-delete",
            "risk": "1",
        }

    @staticmethod
    @pytest.mark.parametrize("panorama_version", [8, 9])
    def test_panorama_list_applications_command_main_flow(mocker, panorama_version):
        """
        Given
         - integrations parameters.
         - pan-os-list-applications command arguments including device_group

        When -
            running the pan-os-list-applications command through the main flow

        Then
         - make sure the context output is returned as expected.
         - make sure the device group gets overriden by the command arguments.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(demisto, "args", return_value={"predefined": "false", "device-group": "new-device-group"})
        mocker.patch.object(demisto, "command", return_value="pan-os-list-applications")

        request_mock = mocker.patch("Panorama.http_request", return_value=load_json("test_data/list_applications_response.json"))
        mocker.patch("Panorama.get_pan_os_major_version", return_value=panorama_version)
        res = mocker.patch("demistomock.results")
        main()

        assert res.call_args.args[0]["Contents"] == {
            "@name": "test-playbook-app",
            "@loc": "Lab-Devices",
            "subcategory": "infrastructure",
            "category": "networking",
            "technology": "client-server",
            "description": "test-playbook-application-do-not-delete",
            "risk": "1",
        }
        # make sure that device group is getting overriden by the device-group from command arguments.
        assert request_mock.call_args.kwargs["body"] == {
            "type": "config",
            "action": "get",
            "key": "thisisabogusAPIKEY!",
            "xpath": "/config/devices/entry/device-group/entry[@name='new-device-group']/application/entry",
        }


def test_get_security_profiles_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-get-security-profiles command arguments including device_group

    When -
        running the pan-os-get-security-profiles command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(demisto, "args", return_value={"device-group": "new-device-group"})
    mocker.patch.object(demisto, "command", return_value="pan-os-get-security-profiles")
    expected_security_profile_response = load_json("test_data/get_security_profiles_response.json")
    request_mock = mocker.patch("Panorama.http_request", return_value=expected_security_profile_response)
    res = mocker.patch("demistomock.results")
    main()

    assert res.call_args.args[0]["Contents"] == expected_security_profile_response

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["params"] == {
        "action": "get",
        "type": "config",
        "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='new-device-group']/profiles",
        "key": "thisisabogusAPIKEY!",
    }


def test_apply_security_profiles_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-apply-security-profile command arguments including device_group

    When -
        running the pan-os-apply-security-profile command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "device-group": "new-device-group",
            "profile_type": "data-filtering",
            "profile_name": "test-profile",
            "rule_name": "rule-test",
            "pre_post": "rule-test",
        },
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-apply-security-profile")
    request_mock = mocker.patch("Panorama.http_request")

    res = mocker.patch("demistomock.results")
    main()

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["params"] == {
        "action": "set",
        "type": "config",
        "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='new-device-group']"
        "/rule-test/security/rules/entry[@name='rule-test']",
        "key": "thisisabogusAPIKEY!",
        "element": "<profile-setting><profiles><data-filtering>"
        "<member>test-profile</member></data-filtering></profiles>"
        "</profile-setting>",
    }
    assert res.call_args.args[0] == "The profile data-filtering = test-profile has been applied to the rule rule-test"


def test_apply_security_profiles_command_when_one_already_exists(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-apply-security-profile command arguments including device_group
     - same profile as already exists in the rule

    When -
        running the pan-os-apply-security-profile command through the main flow

    Then
     - Ensure the request is what's already in the API (the 'element' parameter contains all profiles in the XML)
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "device-group": "new-device-group",
            "profile_type": "spyware",
            "profile_name": "strict",
            "rule_name": "rule-test",
            "pre_post": "rule-test",
        },
    )
    mocker.patch("Panorama.dict_safe_get", return_value={"virus": {"member": "Tap"}, "spyware": {"member": "strict"}})
    mocker.patch.object(demisto, "command", return_value="pan-os-apply-security-profile")
    request_mock = mocker.patch("Panorama.http_request")

    res = mocker.patch("demistomock.results")
    main()

    assert request_mock.call_args.kwargs["params"] == {
        "action": "set",
        "type": "config",
        "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='new-device-group']"
        "/rule-test/security/rules/entry[@name='rule-test']",
        "key": "thisisabogusAPIKEY!",
        "element": "<profile-setting><profiles><spyware><member>strict</member></spyware>"
        "<virus><member>Tap</member></virus></profiles></profile-setting>",
    }
    assert res.call_args.args[0] == "The profile spyware = strict has been applied to the rule rule-test"


def test_remove_security_profiles_command(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-remove-security-profile command arguments

    When -
        running the pan-os-remove-security-profile command through the main flow

    Then
     - Ensure the given profile type has been removed from the given rule
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "device-group": "new-device-group",
            "profile_type": "spyware",
            "rule_name": "rule-test",
            "pre_post": "rule-test",
        },
    )
    mocker.patch("Panorama.dict_safe_get", return_value={"virus": {"member": "Tap"}, "spyware": {"member": "strict"}})
    mocker.patch.object(demisto, "command", return_value="pan-os-remove-security-profile")
    request_mock = mocker.patch("Panorama.http_request")

    res = mocker.patch("demistomock.results")
    main()

    assert request_mock.call_args.kwargs["params"] == {
        "action": "set",
        "type": "config",
        "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='new-device-group']"
        "/rule-test/security/rules/entry[@name='rule-test']",
        "key": "thisisabogusAPIKEY!",
        "element": "<profile-setting><profiles><virus><member>Tap</member></virus></profiles></profile-setting>",
    }
    assert res.call_args.args[0] == "The profile spyware has been removed from the rule rule-test"


class TestPanoramaEditRuleCommand:
    EDIT_SUCCESS_RESPONSE = {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE = {"response": {"@status": "success", "result": "Successfully added comment for xpath"}}

    @staticmethod
    @pytest.fixture()
    def reset_device_group():
        import Panorama

        Panorama.DEVICE_GROUP = ""

    @staticmethod
    def test_sanity(mocker, reset_device_group):
        import Panorama

        args = {
            "rulename": "TestRule",
            "element_to_change": "source",
            "element_value": "2.3.4.5,3.3.3.3",
            "behaviour": "add",
        }
        commited_rule_item = {
            "response": {
                "@status": "success",
                "@code": "19",
                "result": {
                    "@total-count": "1",
                    "@count": "1",
                    "source": {
                        "member": ["1.1.1.1", "3.3.3.3", "2.3.4.5"],
                    },
                },
            }
        }
        mocker.patch("Panorama.http_request", return_value=commited_rule_item)
        Panorama.panorama_edit_rule_command(args)

    @staticmethod
    def test_add_to_element_on_uncommited_rule(mocker, reset_device_group):
        import Panorama

        args = {
            "rulename": "TestRule",
            "element_to_change": "source",
            "element_value": "2.3.4.5",
            "behaviour": "add",
        }
        uncommited_rule_item = {
            "response": {
                "@status": "success",
                "@code": "19",
                "result": {
                    "@total-count": "1",
                    "@count": "1",
                    "source": {
                        "@admin": "admin",
                        "@dirtyId": "1616",
                        "@time": "2021/11/27 10:55:18",
                        "member": {
                            "@admin": "admin",
                            "@dirtyId": "1616",
                            "@time": "2021/11/27 10:55:18",
                            "#text": "3.3.3.3",
                        },
                    },
                },
            }
        }
        mocker.patch("Panorama.http_request", return_value=uncommited_rule_item)
        Panorama.panorama_edit_rule_command(args)

    @staticmethod
    def test_edit_rule_to_disabled_flow(mocker, reset_device_group):
        """
        Given -
            arguments to change a pre-rule to 'disabled'

        When -
            running panorama_edit_rule_command function.

        Then -
            make sure the entire command flow succeeds.
        """
        from Panorama import panorama_edit_rule_command

        args = {
            "rulename": "test",
            "element_to_change": "disabled",
            "element_value": "yes",
            "behaviour": "replace",
            "pre_post": "pre-rulebase",
        }
        mocker.patch("Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_SUCCESS_RESPONSE)
        results_mocker = mocker.patch.object(demisto, "results")
        panorama_edit_rule_command(args)
        assert results_mocker.called

    @staticmethod
    def test_edit_rule_to_disabled_with_no_element_value(mocker):
        """
        Given -
            arguments to change a pre-rule to 'disabled' when the element value should be set to 'no'

        When -
            running panorama_edit_rule_command function.

        Then -
            make sure that the `params['element']` contains the 'no' element value.
        """
        from Panorama import panorama_edit_rule_command

        args = {
            "rulename": "test",
            "element_to_change": "disabled",
            "element_value": "no",
            "behaviour": "replace",
            "pre_post": "pre-rulebase",
        }
        http_req_mocker = mocker.patch("Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_SUCCESS_RESPONSE)
        panorama_edit_rule_command(args)
        assert http_req_mocker.call_args.kwargs.get("body").get("element") == "<disabled>no</disabled>"

    @staticmethod
    def test_edit_rule_main_flow_disable_rule(mocker):
        """
        Given
         - panorama integrations parameters.
         - pan-os-edit-rule command arguments including device_group.
         - arguments to disable the rule

        When -
            running the pan-os-edit-rule command through the main flow

        Then
         - make sure the context output is returned as expected.
         - make sure the device group gets overriden by the command arguments.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "rulename": "test",
                "element_to_change": "disabled",
                "element_value": "no",
                "behaviour": "replace",
                "pre_post": "pre-rulebase",
                "device-group": "new device group",
            },
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-rule")
        request_mock = mocker.patch("Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_SUCCESS_RESPONSE)

        res = mocker.patch("demistomock.results")
        main()

        # make sure that device group is getting overriden by the device-group from command arguments.
        assert request_mock.call_args.kwargs["body"] == {
            "type": "config",
            "action": "edit",
            "key": "thisisabogusAPIKEY!",
            "element": "<disabled>no</disabled>",
            "xpath": "/config/devices/entry/device-group/entry[@name='new device group']/pre-rulebase"
            "/security/rules/entry[@name='test']/disabled",
        }
        assert res.call_args.args[0]["Contents"] == {
            "response": {"@status": "success", "@code": "20", "msg": "command succeeded"}
        }

    @staticmethod
    def test_edit_rule_main_flow_update_audit_comment(mocker):
        """
        Given
         - panorama integrations parameters.
         - pan-os-edit-rule command arguments including device_group.
         - arguments to edit audit comment of a rule

        When -
            running the pan-os-edit-rule command through the main flow

        Then
         - make sure the context output is returned as expected.
         - make sure the device group gets overriden by the command arguments.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "rulename": "test",
                "element_to_change": "audit-comment",
                "element_value": "some string",
                "behaviour": "replace",
                "pre_post": "pre-rulebase",
                "device-group": "new device group",
            },
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-rule")
        request_mock = mocker.patch(
            "Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE
        )

        res = mocker.patch("demistomock.results")
        main()

        # make sure that device group is getting overriden by the device-group from command arguments.
        assert request_mock.call_args.kwargs["body"] == {
            "type": "op",
            "cmd": "<set><audit-comment><xpath>/config/devices/entry[@name='localhost.localdomain']/device-group"
            "/entry[@name='new device group']/pre-rulebase/security/rules/entry[@name='test']"
            "</xpath><comment>some string</comment></audit-comment></set>",
            "key": "thisisabogusAPIKEY!",
        }
        assert res.call_args.args[0]["Contents"] == TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE

    @staticmethod
    def test_edit_rule_main_flow_remove_profile_setting_group(mocker):
        """
        Given
         - panorama integrations parameters.
         - pan-os-edit-rule command arguments including device_group.
         - arguments to remove a profile-setting group.
        When
         - running the pan-os-edit-rule command through the main flow.

        Then
         - make sure the API request body is correct.
         - make sure the message is correct for the user.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "rulename": "test",
                "element_to_change": "profile-setting",
                "element_value": "some string",
                "behaviour": "remove",
                "pre_post": "pre-rulebase",
                "device-group": "new device group",
            },
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-rule")
        request_mock = mocker.patch(
            "Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE
        )

        res = mocker.patch("demistomock.results")
        main()

        # Check: 'action' == set (not edit)
        assert request_mock.call_args.kwargs["body"]["action"] == "set"
        # Ensure 'element' wasn't sent with a group (since we removed the profile-setting group)
        assert request_mock.call_args.kwargs["body"]["element"] == "<profile-setting><group/></profile-setting>"
        # Make sure the message is correct for the user
        assert res.call_args.args[0]["HumanReadable"] == "Rule edited successfully."


def test_panorama_edit_address_group_command_main_flow_edit_description(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-edit-address-group command arguments including device_group and description to add.

    When -
        running the pan-os-edit-address-group command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={"name": "test", "description": "test", "match": "1.1.1.1", "device-group": "new device group"},
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-address-group")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )

    res = mocker.patch("demistomock.results")
    main()

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == {
        "action": "edit",
        "type": "config",
        "key": "thisisabogusAPIKEY!",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device group']"
        "/address-group/entry[@name='test']/description",
        "element": "<description>test</description>",
    }
    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert res.call_args.args[0]["HumanReadable"] == "Address Group test was edited successfully."


def test_panorama_edit_address_group_command_pre_11(mocker: MockerFixture):
    """
    Given
     - PAN-OS version is lower than 11.

    When
     - Running the pan-os-edit-address-group command for a static group.

    Then
     - Make sure the group is edited correctly.
    """
    import Panorama

    Panorama.DEVICE_GROUP = integration_panorama_params["device_group"]

    mocker.patch(
        "Panorama.panorama_get_address_group",
        return_value={"@name": "Name", "@loc": "Loc", "static": {"member": ["1", "2"]}},
    )
    mocker.patch("Panorama.return_results")
    request_mock = mocker.patch("Panorama.http_request")
    args = {"type": "static", "element_to_add": "3", "name": "Name"}

    Panorama.panorama_edit_address_group_command(args)

    assert "<member>1</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]
    assert "<member>2</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]
    assert "<member>3</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]


def test_panorama_edit_address_group_command_v_11(mocker: MockerFixture):
    """
    Given
     - PAN-OS version is greater than 11.

    When
     - Running the pan-os-edit-address-group command for a static group.

    Then
     - Make sure the group is edited correctly.
    """
    import Panorama

    Panorama.DEVICE_GROUP = integration_panorama_params["device_group"]

    mocker.patch(
        "Panorama.panorama_get_address_group",
        return_value={
            "@name": "Name",
            "@loc": "Loc",
            "static": {
                "member": [
                    {"@admin": "admin", "@dirtyId": "1", "@time": "2025/01/01 00:00:00", "#text": "1"},
                    {"@admin": "admin", "@dirtyId": "2", "@time": "2025/01/01 00:00:00", "#text": "2"},
                ]
            },
        },
    )
    mocker.patch("Panorama.return_results")
    request_mock = mocker.patch("Panorama.http_request")
    args = {"type": "static", "element_to_add": "3", "name": "Name"}

    Panorama.panorama_edit_address_group_command(args)

    assert "<member>1</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]
    assert "<member>2</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]
    assert "<member>3</member>" in request_mock.call_args_list[0].kwargs["body"]["element"]


def test_panorama_edit_address_group_command_remove_single_address(mocker):
    """
    Given
     - pan-os-edit-address-group command arguments including a single address to remove.

    When
     - running the pan-os-edit-address-group command through the main flow

    Then
     - make sure an exception is raised because address group must always have at least one address.
    """
    import Panorama

    Panorama.DEVICE_GROUP = integration_panorama_params["device_group"]

    mocker.patch(
        "Panorama.http_request",
        return_value={
            "response": {
                "@status": "success",
                "result": {"entry": {"@name": "test5", "static": {"member": ["5.5.5.5"]}, "description": "dfdf"}},
            }
        },
    )

    with pytest.raises(DemistoException) as exc_info:
        Panorama.panorama_edit_address_group_command(
            {"name": "test", "device-group": "Shared", "type": "static", "element_to_remove": "5.5.5.5"}
        )

    assert exc_info.type == DemistoException
    assert (
        exc_info.value.message == "cannot remove ['5.5.5.5'] addresses from address group test, "
        "address-group test must have at least one address in its configuration"
    )


@pytest.mark.parametrize(
    "action, existing_url_categories_mock, category",
    [("add", {"list": {"member": []}}, "category1"), ("remove", {"list": {"member": ["category2"]}}, "category2")],
)
def test_panorama_edit_custom_url_category_command_main_flow(mocker, action, existing_url_categories_mock, category):
    """
    Given
     - integrations parameters.
     - pan-os-edit-custom-url-category command arguments: categories, device-group and action.

    When -
        running the pan-os-edit-custom-url-category command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={"name": "test", "action": action, "categories": ["category1"], "device-group": "new device group"},
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-custom-url-category")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch("Panorama.panorama_get_custom_url_category", return_value=existing_url_categories_mock)
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)

    res = mocker.patch("demistomock.results")
    main()

    expected_body_request = {
        "action": "edit",
        "type": "config",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device group']/profiles/custom-url-category"
        "/entry[@name='test']",
        "element": f"<entry name='test'><list><member>{category}</member></list><type>Category Match</type></entry>",
        "key": "thisisabogusAPIKEY!",
    }

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == expected_body_request
    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}


def test_panorama_edit_custom_url_category_command_main_flow_with_sites(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-edit-custom-url-category command arguments: sites, device-group and action = 'add'.

    When -
        running the pan-os-edit-custom-url-category command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the sites are being HTML escaped correctly for the site.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    existing_url_categories_mock = {"list": {"member": []}}
    expected_site = "example.com/?a=b&amp;c=d"

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={"name": "test", "action": "add", "sites": ["example.com/?a=b&c=d"], "device-group": "new device group"},
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-custom-url-category")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch("Panorama.panorama_get_custom_url_category", return_value=existing_url_categories_mock)
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)

    res = mocker.patch("demistomock.results")
    main()

    expected_body_request = {
        "action": "edit",
        "element": f"<entry name='test'><list><member>{expected_site}</member></list><type>URL List</type></entry>",
        "key": "thisisabogusAPIKEY!",
        "type": "config",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device "
        "group']/profiles/custom-url-category/entry[@name='test']",
    }
    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == expected_body_request
    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}


def test_panorama_list_edls_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - EDLs from panorama (including un-committed).

    When -
        running the pan-os-list-edls command through the main flow.

    Then
     - make sure the context output is returned as expected.
     - make sure the http request was sent as expected.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(demisto, "args", return_value={})
    mocker.patch.object(demisto, "command", return_value="pan-os-list-edls")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value=load_json("test_data/list-edls-including-un-committed-edl.json")
    )

    result = mocker.patch("demistomock.results")
    main()

    assert request_mock.call_args.kwargs["params"] == {
        "action": "get",
        "type": "config",
        "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']/external-list/entry",
        "key": "thisisabogusAPIKEY!",
    }

    assert list(result.call_args.args[0]["EntryContext"].values())[0] == [
        {"Name": "test-1", "Type": "domain", "URL": "http://test.com", "Recurring": "hourly", "DeviceGroup": "Lab-Devices"},
        {"Name": "test-2", "Type": "ip", "URL": "http://test1.com", "Recurring": "five-minute", "DeviceGroup": "Lab-Devices"},
    ]


def test_panorama_edit_edl_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-edit-edl command arguments including device_group

    When -
        running the pan-os-edit-edl command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "name": "test",
            "element_to_change": "description",
            "element_value": "edl1",
            "device-group": "new device group",
        },
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-edl")
    mocker.patch("Panorama.panorama_get_edl", return_value={"type": {"test": "test"}})
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )

    res = mocker.patch("demistomock.results")
    main()

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == {
        "action": "edit",
        "type": "config",
        "key": "thisisabogusAPIKEY!",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device group']/external-list/ent"
        "ry[@name='test']/type/test/description",
        "element": "<description>edl1</description>",
    }
    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}


def test_panorama_edit_service_group_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-edit-service-group command arguments including device_group

    When -
        running the pan-os-edit-service-group command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(demisto, "args", return_value={"name": "test", "tag": "tag1", "device-group": "new device group"})
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-service-group")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )

    res = mocker.patch("demistomock.results")
    main()

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == {
        "action": "edit",
        "type": "config",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device group']/service-group/entry[@name='test']/tag",
        "element": "<tag><member>tag1</member></tag>",
        "key": "thisisabogusAPIKEY!",
    }

    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}


def test_panorama_edit_url_filter_command_main_flow(mocker):
    """
    Given
     - integrations parameters.
     - pan-os-edit-url-filter command arguments including device_group

    When -
        running the pan-os-edit-url-filter command through the main flow

    Then
     - make sure the context output is returned as expected.
     - make sure the device group gets overriden by the command arguments.
    """
    from Panorama import main

    mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
    mocker.patch.object(
        demisto, "args", return_value={"name": "test", "element_to_change": "description", "device-group": "new device group"}
    )
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-url-filter")
    request_mock = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch("Panorama.panorama_get_url_filter", return_value={})
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)

    res = mocker.patch("demistomock.results")
    main()

    # make sure that device group is getting overriden by the device-group from command arguments.
    assert request_mock.call_args.kwargs["body"] == {
        "action": "edit",
        "type": "config",
        "key": "thisisabogusAPIKEY!",
        "xpath": "/config/devices/entry/device-group/entry[@name='new device group']"
        "/profiles/url-filtering/entry[@name='test']/description",
        "element": "<description>None</description>",
    }

    assert res.call_args.args[0]["Contents"] == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}


class MockedResponse:
    def __init__(self, text, status_code, reason="", headers=None):
        self.status_code = status_code
        self.text = text
        self.reason = reason
        self.headers = headers


class TestPanoramaCommitCommand:
    COMMIT_POLLING_ARGS = {
        "device-group": "some_device",
        "admin_name": "some_admin_name",
        "description": "a simple commit",
        "polling": "true",
    }

    EXPECTED_COMMIT_REQUEST_URL_PARAMS = {
        "action": "partial",
        "cmd": "<commit><description>a simple commit</description>"
        "<partial><device-group><member>some_device</member></device-group>"
        "<admin><member>some_admin_name</member></admin></partial></commit>",
        "key": "APIKEY",
        "type": "commit",
    }

    @staticmethod
    def create_mock_responses(job_commit_status_count):
        mocked_responses = [  # panorama commit api response mock
            MockedResponse(
                text='<response status="success" code="19"><result><msg><line>Commit job '
                "enqueued with jobid 123</line></msg><job>123</job></result></response>",
                status_code=200,
            )
        ]

        mocked_responses += [  # add a mocked response indicating that the job is still in progress
            MockedResponse(
                text='<response status="success"><result><job><tenq>2022/07/16 07:50:04</tenq><tdeq>07:50:04<'
                "/tdeq><id>123</id><user>app</user><type>Commit</type><status>ACT</status><queued>NO</queued>"
                "<stoppable>no</stoppable><result>PEND</result><tfin>Still Active</tfin><description></"
                "description><positionInQ>0</positionInQ><progress>69</progress><warnings></warnings>"
                "<details></details></job></result></response>",
                status_code=200,
            )
            for _ in range(job_commit_status_count)
        ]

        mocked_responses += [  # add a mocked response indicating that the job has finished.
            MockedResponse(
                text='<response status="success"><result><job><tenq>2022/07/16 07:26:05</tenq><tdeq>07:26:05</tdeq>'
                "<id>7206</id><user>app</user><type>Commit</type><status>FIN</status><queued>NO</queued>"
                "<stoppable>no</stoppable><result>OK</result><tfin>07:26:24</tfin><description></description>"
                "<positionInQ>0</positionInQ><progress>100</progress><details><line>Configuration "
                "committed successfully</line></details><warnings></warnings></job></result></response>",
                status_code=200,
            )
        ]

        return mocked_responses

    @pytest.mark.parametrize(
        "args, expected_request_params, request_result, expected_demisto_result",
        [
            pytest.param(
                {
                    "device-group": "some_device",
                    "admin_name": "some_admin_name",
                    "description": "a simple commit",
                    "polling": "false",
                },
                {
                    "action": "partial",
                    "cmd": "<commit><description>a simple commit</description>"
                    "<partial><device-group><member>some_device</member></device-group>"
                    "<admin><member>some_admin_name</member></admin></partial></commit>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "commit",
                },
                MockedResponse(
                    text='<response status="success" code="19"><result><msg>'
                    "<line>Commit job enqueued with jobid 19420</line></msg>"
                    "<job>19420</job></result></response>",
                    status_code=200,
                    reason="",
                ),
                {
                    "Description": "a simple commit",
                    "JobID": "19420",
                    "Status": "Pending",
                    "Scope": "Partial",
                    "Details": "device-group=some_device; admin=some_admin_name",
                },
                id="only admin changes commit",
            ),
            pytest.param(
                {"device-group": "some_device", "force_commit": "true", "polling": "false"},
                {
                    "action": "partial",
                    "cmd": "<commit><force></force>"
                    "<partial><device-group><member>some_device</member></device-group></partial></commit>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "commit",
                },
                MockedResponse(
                    text='<response status="success" code="19"><result><msg>'
                    "<line>Commit job enqueued with jobid 19420</line></msg>"
                    "<job>19420</job></result></response>",
                    status_code=200,
                    reason="",
                ),
                {
                    "Description": "",
                    "JobID": "19420",
                    "Status": "Pending",
                    "Scope": "Partial",
                    "Details": "device-group=some_device",
                },
                id="force commit",
            ),
            pytest.param(
                {"device-group": "some_device", "exclude_device_network_configuration": "true", "polling": "false"},
                {
                    "action": "partial",
                    "cmd": "<commit><partial>"
                    "<device-group><member>some_device</member></device-group>"
                    "<device-and-network>excluded</device-and-network>"
                    "</partial></commit>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "commit",
                },
                MockedResponse(
                    text='<response status="success" code="19"><result><msg>'
                    "<line>Commit job enqueued with jobid 19420</line></msg>"
                    "<job>19420</job></result></response>",
                    status_code=200,
                    reason="",
                ),
                {
                    "Description": "",
                    "JobID": "19420",
                    "Status": "Pending",
                    "Scope": "Partial",
                    "Details": "device-group=some_device; exclude_device_network_configuration=true",
                },
                id="device and network excluded",
            ),
            pytest.param(
                {"device-group": "some_device", "exclude_shared_objects": "true", "polling": "false"},
                {
                    "action": "partial",
                    "cmd": "<commit><partial>"
                    "<device-group><member>some_device</member></device-group>"
                    "<shared-object>excluded</shared-object>"
                    "</partial></commit>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "commit",
                },
                MockedResponse(
                    text='<response status="success" code="19"><result><msg>'
                    "<line>Commit job enqueued with jobid 19420</line></msg>"
                    "<job>19420</job></result></response>",
                    status_code=200,
                    reason="",
                ),
                {
                    "Description": "",
                    "JobID": "19420",
                    "Status": "Pending",
                    "Scope": "Partial",
                    "Details": "device-group=some_device; exclude_shared_objects=true",
                },
                id="exclude shared objects",
            ),
            pytest.param(
                {"device-group": "some_device", "polling": "false"},
                {
                    "action": "partial",
                    "cmd": "<commit>" "<partial><device-group><member>some_device</member></device-group></partial>" "</commit>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "commit",
                },
                MockedResponse(
                    text='<response status="success" code="19"><result><msg>'
                    "<line>Commit job enqueued with jobid 19420</line></msg>"
                    "<job>19420</job></result></response>",
                    status_code=200,
                    reason="",
                ),
                {
                    "Description": "",
                    "JobID": "19420",
                    "Status": "Pending",
                    "Scope": "Partial",
                    "Details": "device-group=some_device",
                },
                id="no args",
            ),
        ],
    )
    def test_panorama_commit_command_without_polling(
        self, mocker, args, expected_request_params, request_result, expected_demisto_result
    ):
        """
        Given:
            - commit command arguments and the expected api request without polling

        When:
            - Running panorama-commit command

        Then:
            - Assert the request url is as expected
            - Assert that panorama commit returns the correct context output
        """
        import Panorama
        import requests
        from Panorama import panorama_commit_command

        Panorama.API_KEY = "thisisabogusAPIKEY!"
        request_mock = mocker.patch.object(requests, "request", return_value=request_result)
        command_result = panorama_commit_command(args)

        called_request_params = request_mock.call_args.kwargs["data"]  # The body part of the request
        assert called_request_params == expected_request_params  # check that the URL is sent as expected.
        assert command_result.outputs == expected_demisto_result  # check context is valid

    @pytest.mark.parametrize(
        "args, expected_commit_request_url_params, api_response_queue",
        [
            pytest.param(
                COMMIT_POLLING_ARGS, EXPECTED_COMMIT_REQUEST_URL_PARAMS, create_mock_responses(job_commit_status_count=1)
            ),
            pytest.param(
                COMMIT_POLLING_ARGS, EXPECTED_COMMIT_REQUEST_URL_PARAMS, create_mock_responses(job_commit_status_count=10)
            ),
            pytest.param(
                COMMIT_POLLING_ARGS, EXPECTED_COMMIT_REQUEST_URL_PARAMS, create_mock_responses(job_commit_status_count=5)
            ),
            pytest.param(
                COMMIT_POLLING_ARGS, EXPECTED_COMMIT_REQUEST_URL_PARAMS, create_mock_responses(job_commit_status_count=8)
            ),
            pytest.param(
                COMMIT_POLLING_ARGS, EXPECTED_COMMIT_REQUEST_URL_PARAMS, create_mock_responses(job_commit_status_count=13)
            ),
            pytest.param(
                COMMIT_POLLING_ARGS,
                EXPECTED_COMMIT_REQUEST_URL_PARAMS,
                create_mock_responses(job_commit_status_count=0),  # commit job finished instantly (very very rare case!)
            ),
        ],
    )
    def test_panorama_commit_command_with_polling(self, mocker, args, expected_commit_request_url_params, api_response_queue):
        """
        Given:
            - pan-os-commit command arguments
            - expected structure of the URL to commit pan-os configuration
            - a queue for api responses of the following:
                1) first value in the queue is the panorama commit api response
                2) panorama job status api response which indicates job isn't done yet (different number each time)
                3) last value in the queue is the panorama job status that indicates it has finished and succeeded

        When:
            - running pan-os-commit with polling argument.

        Then:
            - make sure that the panorama_commit_command function querying for the commit job ID status until its done.
            - make sure that eventually after polling the panorama_commit_command, that it returns the expected output.
        """
        import Panorama
        import requests
        from CommonServerPython import ScheduledCommand
        from Panorama import panorama_commit_command

        Panorama.API_KEY = "APIKEY"
        request_mock = mocker.patch.object(requests, "request", side_effect=api_response_queue)
        mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported", return_value=None)

        command_result = panorama_commit_command(args)
        description = args.get("description")

        called_request_params = request_mock.call_args.kwargs["data"]  # The body part of the request
        assert called_request_params == expected_commit_request_url_params  # check that the URL is sent as expected.
        assert command_result.readable_output == f'Waiting for commit "{description}" with job ID 123 to finish...'

        polling_args = {
            "commit_job_id": "123",
            "description": description,
            "hide_polling_output": True,
            "polling": True,
            "device-group": args.get("device-group"),
            "admin_name": args.get("admin_name"),
            "template": args.get("template"),
            "exclude_device_network_configuration": args.get("exclude_device_network_configuration"),
            "exclude_shared_objects": args.get("exclude_shared_objects"),
        }

        command_result = panorama_commit_command(polling_args)
        while command_result.scheduled_command:  # if scheduled_command is set, it means that command should still poll
            assert not command_result.readable_output  # make sure that indication of polling is printed only once
            assert not command_result.outputs  # make sure no context output is being returned to war-room during polling
            command_result = panorama_commit_command(polling_args)

        # last response of the command should be job status and the commit description
        assert command_result.outputs == {
            "JobID": "123",
            "Description": description,
            "Status": "Success",
            "Scope": "Partial",
            "Details": "device-group=some_device; admin=some_admin_name",
        }


class TestPanoramaPushToDeviceGroupCommand:
    @staticmethod
    def create_mock_responses(push_to_devices_job_status_count):
        mocked_responses = [  # panorama commit api response mock
            MockedResponse(
                text='<response status="success" code="19"><result><msg><line>Push job '
                "enqueued with jobid 123</line></msg><job>123</job></result></response>",
                status_code=200,
            )
        ]

        mocked_responses += [  # add a mocked response indicating that the job is still in progress
            MockedResponse(
                text='<response status="success"><result><job><tenq>2022/07/16 07:50:04</tenq><tdeq>07:50:04<'
                "/tdeq><id>123</id><user>app</user><type>CommitAll</type><status>ACT</status><queued>NO</queued>"
                "<stoppable>no</stoppable><result>PEND</result><tfin>Still Active</tfin><description></"
                "description><positionInQ>0</positionInQ><progress>69</progress><warnings></warnings>"
                "<details></details></job></result></response>",
                status_code=200,
            )
            for _ in range(push_to_devices_job_status_count)
        ]

        with open("test_data/push_to_device_success.xml") as data_file:
            mocked_responses += [MockedResponse(text=data_file.read(), status_code=200)]

        return mocked_responses

    @pytest.mark.parametrize(
        "api_response_queue",
        [
            create_mock_responses(push_to_devices_job_status_count=1),
            create_mock_responses(push_to_devices_job_status_count=3),
            create_mock_responses(push_to_devices_job_status_count=5),
            create_mock_responses(push_to_devices_job_status_count=8),
            create_mock_responses(push_to_devices_job_status_count=10),
        ],
    )
    def test_panorama_push_to_devices_command_with_polling(self, mocker, api_response_queue):
        """
        Given:
            - pan-os-push-to-device-group command arguments including device-group.
            - a queue for api responses of the following:
                1) first value in the queue is the panorama push to the device group api response
                2) panorama job status api response which indicates job isn't done yet (different number each time)
                3) last value in the queue is the panorama job status that indicates it has finished and succeeded

        When:
            - running pan-os-push-to-device-group with polling argument = True

        Then:
            - make sure that the panorama_push_to_device_group_command function querying for
              the push job ID status until its done.
            - make sure that eventually after polling the panorama_push_to_device_group_command,
              that it returns the expected output.
            - make sure readable output is printed out only once.
            - make sure context output is returned only when polling is finished.
            - make sure the device-group from argument overrides the device-group from parameter in context.
        """
        import Panorama
        import requests
        from CommonServerPython import ScheduledCommand
        from Panorama import panorama_push_to_device_group_command

        args = {"description": "a simple push", "polling": "true", "device-group": "device-group-from-command-arg"}

        # mimcs the piece of code which decides which device-group will be set into DEVICE_GROUP parameter.
        Panorama.DEVICE_GROUP = args.get("device-group") or "device-group-from-integration-params"

        Panorama.API_KEY = "APIKEY"
        mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported", return_value=None)
        mocker.patch.object(requests, "request", side_effect=api_response_queue)

        command_result = panorama_push_to_device_group_command(args)
        description = args.get("description")

        assert (
            command_result.readable_output == f"Waiting for Job-ID 123 to finish "
            f"push changes to device-group {Panorama.DEVICE_GROUP}..."
        )

        polling_args = {"push_job_id": "123", "description": description, "hide_polling_output": True, "polling": True}

        command_result = panorama_push_to_device_group_command(polling_args)
        while command_result.scheduled_command:  # if scheduled_command is set, it means that command should still poll
            assert not command_result.readable_output  # make sure that indication of polling is printed only once
            assert not command_result.outputs  # make sure no context output is being returned to war-room during polling
            command_result = panorama_push_to_device_group_command(polling_args)

        assert command_result.outputs.get("JobID") == "123"
        assert command_result.outputs.get("DeviceGroup") == "device-group-from-command-arg"
        assert command_result.outputs.get("Status") == "Completed"
        assert command_result.outputs.get("Details")
        assert command_result.outputs.get("Warnings")
        assert command_result.outputs.get("Description") == "a simple push"


@pytest.mark.parametrize(
    "args, expected_request_params, request_result, expected_demisto_result",
    [
        pytest.param(
            {"polling": "false"},
            {
                "action": "all",
                "cmd": '<commit-all><shared-policy><device-group><entry name="some_device"/>'
                "</device-group></shared-policy></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"DeviceGroup": "some_device", "JobID": "19420", "Status": "Pending"},
            id="no args",
        ),
        pytest.param(
            {"serial_number": "1337", "polling": "false"},
            {
                "action": "all",
                "cmd": '<commit-all><shared-policy><device-group><entry name="some_device">'
                '<devices><entry name="1337"/></devices></entry></device-group>'
                "</shared-policy></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"DeviceGroup": "some_device", "JobID": "19420", "Status": "Pending"},
            id="serial number",
        ),
        pytest.param(
            {"include-template": "false", "polling": "false"},
            {
                "action": "all",
                "cmd": '<commit-all><shared-policy><device-group><entry name="some_device"/>'
                "</device-group><include-template>no</include-template></shared-policy>"
                "</commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"DeviceGroup": "some_device", "JobID": "19420", "Status": "Pending"},
            id="do not include template",
        ),
    ],
)
def test_panorama_push_to_device_group_command(mocker, args, expected_request_params, request_result, expected_demisto_result):
    """
    Given:
        - command args
        - request result
    When:
        - Running panorama-push-to-device-group command
    Then:
        - Assert the request url is as expected
        - Assert demisto results contain the relevant result information
    """
    import Panorama
    import requests
    from Panorama import panorama_push_to_device_group_command

    request_mock = mocker.patch.object(requests, "request", return_value=request_result)
    Panorama.DEVICE_GROUP = "some_device"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    result = panorama_push_to_device_group_command(args)

    called_request_params = request_mock.call_args.kwargs["data"]  # The body part of the request
    assert called_request_params == expected_request_params

    assert result.outputs == expected_demisto_result


@pytest.mark.parametrize(
    "args, expected_request_params, request_result, expected_demisto_result",
    [
        pytest.param(
            {},
            {
                "action": "all",
                "cmd": "<commit-all><template><name>some_template</name></template></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"Panorama.Push(val.JobID == obj.JobID)": {"Template": "some_template", "JobID": "19420", "Status": "Pending"}},
            id="no args",
        ),
        pytest.param(
            {"validate-only": "true"},
            {
                "action": "all",
                "cmd": "<commit-all><template><name>some_template</name>"
                "<validate-only>yes</validate-only></template></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"Panorama.Push(val.JobID == obj.JobID)": {"Template": "some_template", "JobID": "19420", "Status": "Pending"}},
            id="with validate",
        ),
        pytest.param(
            {"serial_number": "1337"},
            {
                "action": "all",
                "cmd": "<commit-all><template><name>some_template</name><device><member>1337</member>"
                "</device></template></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {"Panorama.Push(val.JobID == obj.JobID)": {"Template": "some_template", "JobID": "19420", "Status": "Pending"}},
            id="with device",
        ),
    ],
)
def test_panorama_push_to_template_command(mocker, args, expected_request_params, request_result, expected_demisto_result):
    """
    Given:
        - command args
        - request result
    When:
        - Running panorama-push-to-device-group command
    Then:
        - Assert the request url is as expected
        - Assert demisto results contain the relevant result information
    """
    import Panorama
    import requests
    from Panorama import panorama_push_to_template_command

    return_results_mock = mocker.patch.object(Panorama, "return_results")
    request_mock = mocker.patch.object(requests, "request", return_value=request_result)
    Panorama.TEMPLATE = "some_template"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    panorama_push_to_template_command(args)

    called_request_params = request_mock.call_args.kwargs["data"]  # The body part of the request
    assert called_request_params == expected_request_params

    demisto_result_got = return_results_mock.call_args.args[0]["EntryContext"]
    assert demisto_result_got == expected_demisto_result


@pytest.mark.parametrize(
    "args, expected_request_params, request_result, expected_demisto_result",
    [
        pytest.param(
            {"template-stack": "some_template_stack"},
            {
                "action": "all",
                "cmd": "<commit-all><template-stack><name>some_template_stack</name></template-stack></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {
                "Panorama.Push(val.JobID == obj.JobID)": {
                    "TemplateStack": "some_template_stack",
                    "JobID": "19420",
                    "Status": "Pending",
                }
            },
            id="no args",
        ),
        pytest.param(
            {"validate-only": "true", "template-stack": "some_template_stack"},
            {
                "action": "all",
                "cmd": "<commit-all><template-stack><name>some_template_stack</name>"
                "<validate-only>yes</validate-only></template-stack></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {
                "Panorama.Push(val.JobID == obj.JobID)": {
                    "TemplateStack": "some_template_stack",
                    "JobID": "19420",
                    "Status": "Pending",
                }
            },
            id="with validate",
        ),
        pytest.param(
            {"serial_number": "1337", "template-stack": "some_template_stack"},
            {
                "action": "all",
                "cmd": "<commit-all><template-stack><name>some_template_stack<"
                "/name><device><member>1337</member>"
                "</device></template-stack></commit-all>",
                "key": "thisisabogusAPIKEY!",
                "type": "commit",
            },
            MockedResponse(
                text='<response status="success" code="19"><result><msg>'
                "<line>Commit job enqueued with jobid 19420</line></msg>"
                "<job>19420</job></result></response>",
                status_code=200,
                reason="",
            ),
            {
                "Panorama.Push(val.JobID == obj.JobID)": {
                    "TemplateStack": "some_template_stack",
                    "JobID": "19420",
                    "Status": "Pending",
                }
            },
            id="with device",
        ),
    ],
)
def test_panorama_push_to_template_stack_command(mocker, args, expected_request_params, request_result, expected_demisto_result):
    """
    Given:
        - command args
        - request result
    When:
        - Running panorama-push-to-device-group command
    Then:
        - Assert the request url is as expected
        - Assert demisto results contain the relevant result information
    """
    import Panorama
    import requests
    from Panorama import panorama_push_to_template_stack_command

    return_results_mock = mocker.patch.object(Panorama, "return_results")
    request_mock = mocker.patch.object(requests, "request", return_value=request_result)
    Panorama.TEMPLATE = "some_template"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    panorama_push_to_template_stack_command(args)

    called_request_params = request_mock.call_args.kwargs["data"]  # The body part of the request
    assert called_request_params == expected_request_params

    demisto_result_got = return_results_mock.call_args.args[0]["EntryContext"]
    assert demisto_result_got == expected_demisto_result


def test_get_url_category__url_length_gt_1278(mocker):
    """
    Given:
        - Error in response indicating the url to get category for is over the allowed length (1278 chars)

    When:
        - Run get_url_category command

    Then:
        - Validate a commandResult is returned with detailed readable output
    """

    # prepare
    import Panorama
    import requests
    from Panorama import panorama_get_url_category_command

    Panorama.DEVICE_GROUP = ""
    mocked_res_dict = {
        "response": {
            "@status": "error",
            "@code": "20",
            "msg": {"line": "test -> url Node can be at most 1278 characters, but current length: 1288"},
        }
    }
    mocked_res_obj = requests.Response()
    mocked_res_obj.status_code = 200
    mocked_res_obj._content = json.dumps(mocked_res_dict).encode("utf-8")
    mocker.patch.object(requests, "request", return_value=mocked_res_obj)
    mocker.patch.object(Panorama, "xml2json", return_value=mocked_res_obj._content)
    return_results_mock = mocker.patch.object(Panorama, "return_results")

    # run
    panorama_get_url_category_command(
        url_cmd="url", url="test_url", additional_suspicious=[], additional_malicious=[], reliability="B - Usually reliable"
    )

    # validate
    assert return_results_mock.call_args[0][0][1].readable_output == "URL Node can be at most 1278 characters."


def test_get_url_category_multiple_categories_for_url(mocker):
    """
    Given:
        - response indicating the url has multiple categories.

    When:
        - Run get_url_category command

    Then:
        - Validate a commandResult is returned with detailed readable output
        - Validate only a single DBot score is returned for the URL.
    """
    # prepare
    import Panorama
    import requests
    from Panorama import panorama_get_url_category_command

    Panorama.DEVICE_GROUP = ""
    mocked_res_dict = {
        "response": {
            "@cmd": "status",
            "@status": "success",
            "result": "https://someURL.com not-resolved (Base db) expires in 5 seconds\n"
            "https://someURL.com shareware-and-freeware online-storage-and-backup low-risk (Cloud db)",
        }
    }
    mocked_res_obj = requests.Response()
    mocked_res_obj.status_code = 200
    mocked_res_obj._content = json.dumps(mocked_res_dict).encode("utf-8")
    mocker.patch.object(requests, "request", return_value=mocked_res_obj)
    mocker.patch.object(Panorama, "xml2json", return_value=mocked_res_obj._content)
    return_results_mock = mocker.patch.object(Panorama, "return_results")

    # run
    panorama_get_url_category_command(
        url_cmd="url", url="test_url", additional_suspicious=[], additional_malicious=[], reliability="B - Usually reliable"
    )

    # validate
    for i in range(3):
        assert return_results_mock.call_args[0][0][0].outputs[i].get("Category") in [
            "shareware-and-freeware",
            "online-storage-and-backup",
            "low-risk",
        ]

    # category with highest dbot-score
    assert return_results_mock.call_args[0][0][1].indicator.dbot_score.score == 1


def test_get_url_category_multiple_categories_for_url_missing_categories(mocker):
    """
    Given:
        - response indicating the url has multiple categories.
    When:
        - Run panorama_get_url_category function
    Then:
        - Validate all the expected categories are present
    """
    # prepare
    import Panorama
    import requests
    from Panorama import panorama_get_url_category

    Panorama.DEVICE_GROUP = ""
    mocked_res_dict = {
        "response": {
            "@cmd": "status",
            "@status": "success",
            "result": "https://someURL.com not-resolved (Base db) expires in 5 seconds\n"
            "https://someURL.com shareware-and-freeware online-storage-and-backup low-risk (Cloud db) artificial-intelligence encrypted-dns",  # noqa: E501
        }
    }
    mocked_res_obj = requests.Response()
    mocked_res_obj.status_code = 200
    mocked_res_obj._content = json.dumps(mocked_res_dict).encode("utf-8")
    mocker.patch.object(Panorama, "xml2json", return_value=mocked_res_obj._content)
    mocker.patch.object(requests, "request", return_value=mocked_res_obj)

    # run
    results = panorama_get_url_category(url_cmd="url", url="test_url")

    expected_results = [
        "shareware-and-freeware",
        "online-storage-and-backup",
        "low-risk",
        "encrypted-dns",
        "artificial-intelligence",
    ]

    # validate
    for category in expected_results:
        assert category in results


class TestDevices:
    def test_with_fw(self):
        import Panorama

        Panorama.VSYS = "this is a FW instance"
        assert list(Panorama.devices()) == [(None, None)]

    def test_with_specific_target_and_vsys(self):
        import Panorama

        Panorama.VSYS = None  # this a Panorama instance
        assert list(Panorama.devices(targets=["target"], vsys_s=["vsys1", "vsys2"])) == [("target", "vsys1"), ("target", "vsys2")]

    def test_with_specific_target_only(self, requests_mock):
        import Panorama

        with open("test_data/devices_list.xml") as data_file:
            requests_mock.get(Panorama.URL, text=data_file.read())
        Panorama.VSYS = None  # this a Panorama instance
        assert list(Panorama.devices(targets=["target1"])) == [("target1", "vsys1"), ("target1", "vsys2")]

    def test_without_specify(self, requests_mock):
        import Panorama

        with open("test_data/devices_list.xml") as data_file:
            requests_mock.get(Panorama.URL, text=data_file.read())
        Panorama.VSYS = None  # this a Panorama instance
        assert list(Panorama.devices()) == [("target1", "vsys1"), ("target1", "vsys2"), ("target2", None)]


def load_xml_root_from_test_file(xml_file: str):
    """Given an XML file, loads it and returns the root element XML object."""
    return ElementTree.parse(xml_file).getroot()


MOCK_PANORAMA_SERIAL = "111222334455"
MOCK_FIREWALL_1_SERIAL = "111111111111111"
MOCK_FIREWALL_2_SERIAL = "222222222222222"
MOCK_FIREWALL_3_SERIAL = "333333333333333"


def mock_software_object():
    """Mocks PanDevice.software"""

    class MockSoftwareObject:
        versions = {
            "9.1.0": {
                "version": "9.1.0",
                "filename": "Pan-9.1.0",
                "size": 150,
                "size_kb": 150000,
                "release_notes": "https://releasenotes.paloaltonetworks.com",
                "downloaded": True,
                "current": True,
                "latest": True,
                "uploaded": True,
            }
        }

        def check(self):
            pass

        def download(self, *args, **kwargs):
            pass

        def install(self, *args, **kwargs):
            pass

    return MockSoftwareObject()


@pytest.fixture
def mock_firewall():
    mock_firewall = MagicMock(spec=Firewall)
    mock_firewall.serial = MOCK_FIREWALL_1_SERIAL
    mock_firewall.hostname = None
    mock_firewall.software = mock_software_object()
    return mock_firewall


@pytest.fixture
def mock_panorama():
    mock_panorama = MagicMock(spec=Panorama)
    mock_panorama.serial = MOCK_PANORAMA_SERIAL
    mock_panorama.hostname = None
    mock_panorama.software = mock_software_object()
    return mock_panorama


def mock_device_groups():
    mock_device_group = MagicMock(spec=DeviceGroup)
    mock_device_group.name = "test-dg"
    return [mock_device_group]


def mock_templates():
    mock_template = MagicMock(spec=Template)
    mock_template.name = "test-template"
    return [mock_template]


def mock_template_stack():
    mock_template_stack = MagicMock(spec=TemplateStack)
    mock_template_stack.name = "test-template-stack"
    return [mock_template_stack]


def mock_vsys():
    mock_vsys = MagicMock(spec=Vsys)
    mock_vsys.name = "vsys1"
    return [mock_vsys]


def mock_address_objects():
    from Panorama import AddressObject

    mock_object_1 = MagicMock(spec=AddressObject)
    mock_object_1.name = "test-address-1"

    mock_object_2 = MagicMock(spec=AddressObject)
    mock_object_2.name = "test-address-2"
    return [mock_object_1, mock_object_2]


def mock_good_log_fowarding_profile():
    good_log_forwarding_profile = LogForwardingProfile()
    good_log_forwarding_profile.enhanced_logging = True
    return [good_log_forwarding_profile]


def mock_bad_log_fowarding_profile():
    bad_Log_forwarding_profile = LogForwardingProfile()
    bad_Log_forwarding_profile.enhanced_logging = False
    bad_Log_forwarding_profile.name = "test-bad"
    return [bad_Log_forwarding_profile]


def mock_good_log_forwarding_profile_match_list():
    return [
        LogForwardingProfileMatchList(log_type="traffic"),
        LogForwardingProfileMatchList(log_type="threat"),
    ]


def mock_good_vulnerability_profile():
    from Panorama import VulnerabilityProfile, VulnerabilityProfileRule

    vulnerability_profile = VulnerabilityProfile()
    vulnerability_profile.name = "good-vuln-profile"
    vulnerability_profile.children = [
        VulnerabilityProfileRule(severity=["critical"], is_reset_both=True),
        VulnerabilityProfileRule(severity=["high"], is_reset_both=True),
        VulnerabilityProfileRule(severity=["medium"], is_alert=True),
        VulnerabilityProfileRule(severity=["low"], is_alert=True),
    ]

    return vulnerability_profile


def mock_bad_vulnerability_profile():
    from Panorama import VulnerabilityProfile, VulnerabilityProfileRule

    vulnerability_profile = VulnerabilityProfile()
    vulnerability_profile.name = "bad-vuln-profile"
    vulnerability_profile.children = [
        VulnerabilityProfileRule(severity=["critical"], is_reset_both=True),
        VulnerabilityProfileRule(severity=["high"], is_reset_both=True),
        VulnerabilityProfileRule(severity=["medium"], is_alert=True),
    ]

    return vulnerability_profile


def mock_good_spyware_profile():
    from Panorama import AntiSpywareProfile, AntiSpywareProfileRule

    antispyware_profile = AntiSpywareProfile()
    antispyware_profile.name = "good-spyware-profile"
    antispyware_profile.children = [
        AntiSpywareProfileRule(severity=["critical"], is_reset_both=True),
        AntiSpywareProfileRule(severity=["high"], is_reset_both=True),
        AntiSpywareProfileRule(severity=["medium"], is_alert=True),
        AntiSpywareProfileRule(severity=["low"], is_alert=True),
    ]

    return antispyware_profile


def mock_bad_spyware_profile():
    from Panorama import AntiSpywareProfile, AntiSpywareProfileRule

    antispyware_profile = AntiSpywareProfile()
    antispyware_profile.name = "bad-spyware-profile"
    antispyware_profile.children = [
        AntiSpywareProfileRule(severity=["critical"], is_reset_both=True),
        AntiSpywareProfileRule(severity=["high"], is_reset_both=True),
        AntiSpywareProfileRule(severity=["medium"], is_alert=True),
    ]

    return antispyware_profile


def mock_good_security_zones():
    from Panorama import Zone

    zone = Zone()
    zone.log_setting = "example-log-setting"
    return [Zone(log_setting="example"), Zone(log_setting="second_example")]


def mock_bad_security_zones():
    from Panorama import Zone

    zone = Zone()
    zone.log_setting = "example-log-setting"
    return [Zone(name="test-bad"), Zone(log_setting="second_example")]


def mock_good_security_rules():
    from Panorama import SecurityRule

    return [SecurityRule(group="spg", log_setting="example", log_end=True)]


def mock_bad_security_rules():
    from Panorama import SecurityRule

    return [
        # Missing SPG
        SecurityRule(name="test-bad", log_setting="example", log_end=True),
        # Missing log profile
        SecurityRule(name="test-bad-no-lfp", group="spg", log_end=True),
        # Missing log at session end
        SecurityRule(
            name="test-bad-no-spg",
            group="spg",
            log_setting="example",
        ),
    ]


def mock_good_url_filtering_profile():
    from Panorama import BestPractices, URLFilteringProfile

    url_filtering_profile = URLFilteringProfile()
    url_filtering_profile.name = "good-url-filtering-profile"
    url_filtering_profile.block = BestPractices.URL_BLOCK_CATEGORIES
    return url_filtering_profile


def mock_bad_url_filtering_profile():
    from Panorama import URLFilteringProfile

    url_filtering_profile = URLFilteringProfile()
    url_filtering_profile.name = "bad-url-filtering-profile"
    url_filtering_profile.block = ["hacking"]
    return url_filtering_profile


def mock_issue_with_underscores():
    return {
        "container_name": "test-dg",
        "issue_code": "BP-V-1",
        "description": "Log forwarding profile is missing enhanced application logging",
        "name": "test-bad",
        "hostid": MOCK_FIREWALL_1_SERIAL,
    }


def mock_enhanced_log_forwarding_issue_dict():
    return {
        "containername": "test-dg",
        "issuecode": "BP-V-1",
        "description": "Log forwarding profile is missing enhanced application logging",
        "name": "test-bad",
        "hostid": MOCK_FIREWALL_1_SERIAL,
    }


def mock_security_zone_no_log_setting_issue_dict():
    return {
        "containername": "test-dg",
        "issuecode": "BP-V-7",
        "description": "Security zone has no log forwarding setting",
        "name": "test-bad",
        "hostid": MOCK_FIREWALL_1_SERIAL,
    }


def mock_security_rule_log_settings_issue_dict():
    return {
        "containername": "test-dg",
        "issuecode": "BP-V-8",
        "description": "Security rule has no log setting",
        "name": "test-bad-no-lfp",
        "hostid": MOCK_FIREWALL_1_SERIAL,
    }


def mock_security_rule_security_profile_group_issue_dict():
    return {
        "containername": "test-dg",
        "issuecode": "BP-V-10",
        "description": "Security rule has no security profile group",
        "name": "test-bad-no-spg",
        "hostid": MOCK_FIREWALL_1_SERIAL,
    }


@pytest.fixture
def mock_topology(mock_panorama, mock_firewall):
    from Panorama import Topology

    topology = Topology()
    topology.panorama_objects = {
        MOCK_PANORAMA_SERIAL: mock_panorama,
    }
    topology.firewall_objects = {MOCK_FIREWALL_1_SERIAL: mock_firewall}
    topology.ha_active_devices = {MOCK_PANORAMA_SERIAL: mock_panorama, MOCK_FIREWALL_1_SERIAL: mock_firewall}
    topology.ha_pair_serials = {
        MOCK_PANORAMA_SERIAL: "1.1.1.1",
        MOCK_FIREWALL_1_SERIAL: MOCK_FIREWALL_2_SERIAL,
    }
    return topology


@pytest.fixture
def mock_firewall_topology(mock_firewall):
    from Panorama import Topology

    topology = Topology()
    topology.firewall_objects = {MOCK_FIREWALL_1_SERIAL: mock_firewall}
    return topology


@pytest.fixture
def mock_single_device_topology(mock_panorama):
    from Panorama import Topology

    topology = Topology()
    topology.panorama_objects = {
        MOCK_PANORAMA_SERIAL: mock_panorama,
    }
    topology.ha_active_devices = {
        MOCK_PANORAMA_SERIAL: mock_panorama,
    }
    return topology


class TestTopology:
    """Tests the Topology class and all of it's methods"""

    SHOW_HA_STATE_ENABLED_XML = "test_data/show_ha_state_enabled.xml"
    SHOW_HA_STATE_PANORAMA_ENABLED = "test_data/show_ha_state_panorama_enabled.xml"
    SHOW_HA_STATE_DISABLED_XML = "test_data/show_ha_state_disabled.xml"
    SHOW_DEVICES_ALL_XML = "test_data/panorama_show_devices_all.xml"

    @patch("Panorama.Topology.get_all_child_firewalls")
    @patch("Panorama.run_op_command")
    def test_add_firewall_device_object(self, patched_run_op_command, _, mock_firewall):
        """
        Given the XML output of show ha state and a firewall object, test it is correctly added to the topology.
        """
        from Panorama import Topology

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestTopology.SHOW_HA_STATE_DISABLED_XML)
        topology = Topology()
        topology.add_device_object(mock_firewall)

        assert MOCK_FIREWALL_1_SERIAL in topology.firewall_objects

    @patch("Panorama.Topology.get_all_child_firewalls")
    @patch("Panorama.run_op_command")
    def test_add_panorama_device_object(self, patched_run_op_command, _, mock_panorama):
        """
        Given the output of show_ha_state with no entries, assert that the Panorama device has been added to the topolog
        as a panorama type device.
        """
        from Panorama import Topology

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestTopology.SHOW_HA_STATE_DISABLED_XML)
        topology = Topology()
        topology.add_device_object(mock_panorama)

        assert MOCK_PANORAMA_SERIAL in topology.panorama_objects
        assert MOCK_PANORAMA_SERIAL in topology.ha_active_devices
        assert MOCK_PANORAMA_SERIAL not in topology.firewall_objects

    @patch("Panorama.Topology.get_all_child_firewalls")
    @patch("Panorama.run_op_command")
    def test_add_panorama_device_object_with_ha(self, patched_run_op_command, _, mock_panorama):
        """
        Given a Panorama where High availability is active, test that it is correctly added to the topology.
        """
        from Panorama import Topology

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestTopology.SHOW_HA_STATE_PANORAMA_ENABLED)
        topology = Topology()
        topology.add_device_object(mock_panorama)

        assert MOCK_PANORAMA_SERIAL in topology.panorama_objects
        assert MOCK_PANORAMA_SERIAL in topology.ha_active_devices
        assert MOCK_PANORAMA_SERIAL in topology.ha_pair_serials
        assert MOCK_PANORAMA_SERIAL not in topology.firewall_objects

    @patch("Panorama.run_op_command")
    def test_get_all_child_firewalls(self, patched_run_op_command, mock_panorama):
        """
        Given the output of show devices all, assert that all the devices are added correctly to the topology with the correct
        HA State information.
        """
        from Panorama import Topology

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestTopology.SHOW_DEVICES_ALL_XML)
        topology = Topology()

        topology.get_all_child_firewalls(mock_panorama)
        # 222... firewall should be Active, with 111... as it's peer
        assert MOCK_FIREWALL_2_SERIAL in topology.ha_active_devices
        assert topology.ha_active_devices.get(MOCK_FIREWALL_2_SERIAL) == MOCK_FIREWALL_1_SERIAL

        # 333... is standalone
        assert MOCK_FIREWALL_3_SERIAL in topology.ha_active_devices
        assert topology.ha_active_devices.get(MOCK_FIREWALL_3_SERIAL) == "STANDALONE"

    @patch("Panorama.run_op_command")
    def test_get_active_devices(self, patched_run_op_command, mock_panorama):
        """
        Given a topology with a mixture of active and passive devices, assert that active_devices() returns the correct lists
        of objects.
        """
        from Panorama import Topology

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestTopology.SHOW_DEVICES_ALL_XML)
        topology = Topology()
        topology.add_device_object(mock_panorama)

        result_list = list(topology.active_devices())
        # Should be 3; panorama, one active firewall in a pair, and one stanadlone firewall (from panorama_show_devices_all.xml)
        assert len(result_list) == 3

        # Same as above by try filtering by serial number
        result_list = list(topology.active_devices(filter_str=MOCK_FIREWALL_3_SERIAL))
        assert len(result_list) == 1

        # Now try just getting the "top level" devices - should only return Panorama
        result_list = list(topology.active_top_level_devices())
        assert len(result_list) == 1
        assert isinstance(result_list[0], Panorama)

    @patch("Panorama.Template.refreshall", return_value=mock_templates())
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_get_containers(self, _, __, ___, ____, mock_panorama):
        """
        Given a list of device groups, vsys and templates, and a device, assert that get_all_object_containers() correctly returns
        the specified containers.
        """
        from Panorama import Topology

        topology = Topology()
        topology.add_device_object(mock_panorama)
        result = topology.get_all_object_containers()

        # Because it's panorama, should be; [shared, device-group, template]
        assert len(result) == 3


class TestUtilityFunctions:
    """Tests all the utility fucntions like dataclass_to_dict, etc"""

    SHOW_JOBS_ALL_XML = "test_data/show_jobs_all.xml"

    def test_dataclass_from_dict(self, mock_panorama):
        """Given a dictionary and dataclass type, assert that it is correctly converted into the dataclass."""
        from Panorama import CommitStatus, dataclass_from_dict

        example_dict = {"job-id": "10", "commit-type": "whatever", "status": "OK", "device-type": "firewall"}

        mock_panorama.hostname = None
        result_dataclass: CommitStatus = dataclass_from_dict(mock_panorama, example_dict, CommitStatus)
        assert result_dataclass.job_id
        assert result_dataclass.commit_type
        assert result_dataclass.status
        assert result_dataclass.device_type
        # With no hostname, hostid should be the serial number of the device
        assert result_dataclass.hostid == MOCK_PANORAMA_SERIAL

        mock_panorama.hostname = "test"
        mock_panorama.serial = None
        result_dataclass: CommitStatus = dataclass_from_dict(mock_panorama, example_dict, CommitStatus)
        # With a hostname and no serial, hostid shold be the hostname
        assert result_dataclass.hostid == "test"

    def test_flatten_xml_to_dict(self):
        """Given an XML element, assert that it is converted into a flat dictionary."""
        from Panorama import ShowJobsAllResultData, flatten_xml_to_dict

        xml_element = load_xml_root_from_test_file(TestUtilityFunctions.SHOW_JOBS_ALL_XML)
        result_element = xml_element.find("./result/job")
        result = flatten_xml_to_dict(result_element, {}, ShowJobsAllResultData)
        assert "type" in result

    def test_resolve_host_id(self, mock_panorama):
        """Given a device object, test the hostid, the unique ID of the device from the perspective of the new commands,
        can always be resolved as either the hostname or serial number. Pan-os-python will populate only one of these, depending
        on how the device has been connected."""
        from Panorama import resolve_host_id

        mock_panorama.hostname = None
        result = resolve_host_id(mock_panorama)

        assert result == MOCK_PANORAMA_SERIAL

        mock_panorama.hostname = "test"
        mock_panorama.serial = None
        result = resolve_host_id(mock_panorama)

        assert result == "test"

    def test_resolve_container_name(self, mock_panorama):
        """Same as hostid but resolve it for a container, like a device group or template. This will always return the name
        attribute unless it's a device itself, which is the case for shared objects."""
        from Panorama import resolve_container_name

        # Test the "shared" container
        assert resolve_container_name(mock_panorama) == "shared"

        device_group = mock_device_groups()[0]
        assert resolve_container_name(device_group) == "test-dg"

    def test_dataclass_to_command_results(self):
        """Given a list of dataclasses, check that this function correctly converts it to a commandResults object."""
        from Panorama import PanosObjectReference, dataclasses_to_command_results

        test_dataclass = PanosObjectReference(
            hostid=MOCK_FIREWALL_1_SERIAL, container_name="test", object_type="TestObject", name="test_name"
        )
        results = dataclasses_to_command_results(test_dataclass)
        # Check we get the right table headers when no additional arguments are given
        assert "container_name|hostid|name|object_type" in results.readable_output
        assert "### PAN-OS Object" in results.readable_output

        results = dataclasses_to_command_results(
            test_dataclass, override_table_name="Test Table", override_table_headers=["hostid", "name", "container_name"]
        )
        # When we provide overrides, check they are rendered correctly in the readable output
        assert "hostid|name|container_name" in results.readable_output
        assert "### Test Table" in results.readable_output


class TestPanoramaCommand:
    """
    Test all the commands relevant to Panorama
    All of these commands use the real XML in test_data to ensure it is parsed and converted to dataclasses correctly.
    """

    SHOW_DEVICEGROUPS_XML = "test_data/show_device_groups.xml"
    SHOW_TEMPLATESTACK_XML = "test_data/show_template_stack.xml"

    @patch("Panorama.run_op_command")
    def test_get_device_groups(self, patched_run_op_command, mock_topology):
        """Given the output XML for show device groups, assert it is parsed into the dataclasses correctly."""
        from Panorama import PanoramaCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestPanoramaCommand.SHOW_DEVICEGROUPS_XML)

        result = PanoramaCommand.get_device_groups(mock_topology)
        assert len(result) == 3
        assert result[0].name
        assert result[0].hostid
        assert result[0].connected
        assert result[0].serial
        assert result[0].last_commit_all_state_sp
        # Support for missing hostname
        assert not result[2].hostname

    @patch("Panorama.run_op_command")
    def test_get_template_stacks(self, patched_run_op_command, mock_topology):
        """Given the output XML for show template-stacks, assert it is parsed into the dataclasses correctly."""
        from Panorama import PanoramaCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestPanoramaCommand.SHOW_TEMPLATESTACK_XML)
        result = PanoramaCommand.get_template_stacks(mock_topology)
        assert len(result) == 2
        assert result[0].name
        assert result[0].hostid
        assert result[0].connected
        assert result[0].serial
        assert result[0].last_commit_all_state_tpl

    @patch("Panorama.run_op_command")
    def test_get_template_stacks_without_hostname(self, patched_run_op_command, mock_topology):
        """Given the output XML for show template-stacks without hostname, assert it is parsed into the dataclasses correctly."""
        from Panorama import PanoramaCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file("test_data/show_template_stack_without_hostname.xml")
        result = PanoramaCommand.get_template_stacks(mock_topology)
        assert len(result) == 2
        assert result[0].name
        assert not result[0].hostname
        assert result[0].hostid
        assert result[0].connected
        assert result[0].serial
        assert result[0].last_commit_all_state_tpl


class TestUniversalCommand:
    """Test all the commands relevant to both Panorama and Firewall devices"""

    SHOW_SYSTEM_INFO_XML = "test_data/show_system_info.xml"
    SHOW_JOB_XML = "test_data/show_jobs_all.xml"
    SHOW_COMMIT_JOB_XML = "test_data/show_commit_jobs_all.xml"
    SHOW_JOB_WITH_FAILED_XML = "test_data/show_jobs_with_failed.xml"

    @patch("Panorama.run_op_command")
    def test_get_system_info(self, patched_run_op_command, mock_topology):
        """Given the output XML for show system info, assert it is parsed into the dataclasses correctly."""
        import Panorama

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestUniversalCommand.SHOW_SYSTEM_INFO_XML)

        result = Panorama.UniversalCommand.get_system_info(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                assert value

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    def test_get_system_info_ignores_platform_injected_args(self, mocker):
        """
        Regression (CRTX-240383): when the command is invoked with a brand-scoped context
        (e.g. as an Agentix action), the platform injects a 'using-brand' arg. The
        pan-os-platform-get-system-info handler must not forward it to the typed
        get_system_info() function (which would raise 'unexpected keyword argument').
        """
        import Panorama

        mocker.patch.object(Panorama, "get_topology", return_value=MagicMock())
        mocker.patch.object(
            demisto,
            "args",
            return_value={"device_filter_string": "fw1", "target": "007", "using-brand": "Panorama"},
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-platform-get-system-info")
        mocker.patch.object(demisto, "params", return_value=integration_firewall_params)
        get_system_info_mock = mocker.patch.object(Panorama, "get_system_info", return_value=MagicMock())
        mocker.patch.object(Panorama, "dataclasses_to_command_results", return_value=MagicMock())
        mocker.patch.object(Panorama, "return_results")

        Panorama.main()

        # Must have been called without raising, and without the platform-injected key.
        get_system_info_mock.assert_called_once()
        _, kwargs = get_system_info_mock.call_args
        assert "using-brand" not in kwargs
        assert kwargs == {"device_filter_string": "fw1", "target": "007"}

    def test_get_available_software(self, mock_topology):
        """
        Test we can convert result from PanDevice.software.check() into the correct dataclasses
        This does not use patching, but instead the mock objects themselves from mock_topology
        """
        from Panorama import UniversalCommand

        result = UniversalCommand.get_available_software(mock_topology)
        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    @patch("Panorama.run_op_command")
    def test_get_jobs(self, patched_run_op_command, mock_topology):
        """Given the output XML for show jobs all assert it is parsed into the dataclasses correctly."""
        from Panorama import UniversalCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestUniversalCommand.SHOW_JOB_XML)

        result = UniversalCommand.show_jobs(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result:
            for key, value in result_dataclass.__dict__.items():
                # Nullable Values
                if key not in ["description", "user", "details", "warnings"]:
                    assert value

    @patch("Panorama.run_op_command")
    @patch("Panorama.demisto.debug")
    def test_get_jobs_with_failed(self, patched_debug, patched_run_op_command):
        """Given the output XML for show jobs with a failed job, assert it is skipped."""
        from Panorama import Panorama, ShowJobsAllResultData, UniversalCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestUniversalCommand.SHOW_JOB_WITH_FAILED_XML)
        MockTopology = type("MockTopology", (), {"all": lambda *x, **y: [Panorama(hostname="123")]})

        result = UniversalCommand.show_jobs(MockTopology())

        assert patched_debug.call_args_list[0].args[0] == (
            "'ShowJobsAllResultData' cannot be instantiated with element: "
            '{"job": {"type": "Failed-Job", "details": {"line": "job failed because of configd restart"}, "warnings": null}}'
            '\nerror=TypeError("ShowJobsAllResultData.__init__() missing 9 required positional arguments: '
            "'id', 'tfin', 'status', 'result', 'user', 'tenq', 'stoppable', 'positionInQ', and 'progress'\")"
        )
        assert isinstance(result, ShowJobsAllResultData)
        assert result.__dict__ == {
            "description": "description",
            "hostid": "123",
            "id": 7,
            "positionInQ": "0",
            "progress": "100",
            "result": "OK",
            "status": "FIN",
            "stoppable": "no",
            "tenq": "2024/08/25 22:07:53",
            "tfin": "2024/08/25 22:09:00",
            "type": "Job Type",
            "user": None,
            "warnings": None,
        }

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_polling_terminal(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: polling=true, a single job id, and a job whose status is 'FIN'.
        When: get_jobs_command is invoked.
        Then: The returned CommandResults have no scheduled_command (polling stops).
        """
        from Panorama import ShowJobsAllResultData, get_jobs_command

        patched_get_topology.return_value = mock_topology
        patched_get_jobs.return_value = ShowJobsAllResultData(
            hostid="fw1",
            id=7,
            type="Commit",
            tfin="2024/08/25 22:09:00",
            status="FIN",
            result="OK",
            user="admin",
            tenq="2024/08/25 22:07:53",
            stoppable="no",
            positionInQ=0,
            progress=100,
            warnings=None,
            description="",
        )

        result = get_jobs_command({"polling": "true", "id": "7"})

        assert result.scheduled_command is None
        assert result.outputs["status"] == "FIN"
        assert result.outputs["result"] == "OK"
        assert result.outputs["id"] == 7

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_polling_still_running(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: polling=true, a single job id, and a job whose status is 'ACT' (still running).
        When: get_jobs_command is invoked.
        Then: The returned CommandResults have a scheduled_command (polling continues).
        """
        from Panorama import ShowJobsAllResultData, get_jobs_command

        patched_get_topology.return_value = mock_topology
        patched_get_jobs.return_value = ShowJobsAllResultData(
            hostid="fw1",
            id=7,
            type="Commit",
            tfin="",
            status="ACT",
            result="PEND",
            user="admin",
            tenq="2024/08/25 22:07:53",
            stoppable="no",
            positionInQ=0,
            progress=50,
            warnings=None,
            description="",
        )

        result = get_jobs_command({"polling": "true", "id": "7"})

        assert result.scheduled_command is not None

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_no_polling(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: no polling argument (defaults to false).
        When: get_jobs_command is invoked without polling.
        Then: The returned CommandResults have no scheduled_command regardless of status.
        """
        from Panorama import ShowJobsAllResultData, get_jobs_command

        patched_get_topology.return_value = mock_topology
        patched_get_jobs.return_value = ShowJobsAllResultData(
            hostid="fw1",
            id=7,
            type="Commit",
            tfin="",
            status="ACT",
            result="PEND",
            user="admin",
            tenq="2024/08/25 22:07:53",
            stoppable="no",
            positionInQ=0,
            progress=50,
            warnings=None,
            description="",
        )

        result = get_jobs_command({"id": "7"})

        assert result.scheduled_command is None

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_polling_no_id_raises(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: polling=true but no id argument.
        When: get_jobs_command is invoked.
        Then: A DemistoException is raised, since polling requires a single job id.
        """
        from Panorama import get_jobs_command

        patched_get_topology.return_value = mock_topology

        with pytest.raises(DemistoException, match="The 'id' argument is required when 'polling' is set to true."):
            get_jobs_command({"polling": "true"})

        # get_jobs must not be called when the validation fails.
        patched_get_jobs.assert_not_called()

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_polling_ignores_status_and_job_type(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: polling=true, a single job id, and status/job_type filters provided.
        When: get_jobs_command is invoked.
        Then: get_jobs is called with status=None and job_type=None (filters ignored),
              so a still-running job is not filtered out and polling can continue.
        """
        from Panorama import ShowJobsAllResultData, get_jobs_command

        patched_get_topology.return_value = mock_topology
        patched_get_jobs.return_value = ShowJobsAllResultData(
            hostid="fw1",
            id=7,
            type="Downloadxxx",
            tfin="",
            status="ACT",
            result="PEND",
            user="admin",
            tenq="2024/08/25 22:07:53",
            stoppable="no",
            positionInQ=0,
            progress=50,
            warnings=None,
            description="",
        )

        result = get_jobs_command({"polling": "true", "id": "7", "status": "FIN", "job_type": "Commit"})

        # Filters must be dropped while polling by id.
        assert patched_get_jobs.call_args.kwargs["status"] is None
        assert patched_get_jobs.call_args.kwargs["job_type"] is None
        assert patched_get_jobs.call_args.kwargs["id"] == "7"
        # Job is still running, so polling should continue.
        assert result.scheduled_command is not None

    @patch("Panorama.get_topology")
    @patch("Panorama.get_jobs")
    def test_get_jobs_command_no_polling_keeps_status_and_job_type(self, patched_get_jobs, patched_get_topology, mock_topology):
        """
        Given: no polling (defaults to false), a job id, and status/job_type filters.
        When: get_jobs_command is invoked.
        Then: get_jobs is called with the provided status/job_type (non-polling flow unchanged).
        """
        from Panorama import ShowJobsAllResultData, get_jobs_command

        patched_get_topology.return_value = mock_topology
        patched_get_jobs.return_value = ShowJobsAllResultData(
            hostid="fw1",
            id=7,
            type="Commit",
            tfin="2024/08/25 22:09:00",
            status="FIN",
            result="OK",
            user="admin",
            tenq="2024/08/25 22:07:53",
            stoppable="no",
            positionInQ=0,
            progress=100,
            warnings=None,
            description="",
        )

        get_jobs_command({"id": "7", "status": "FIN", "job_type": "Commit"})

        # Non-polling flow must keep applying the filters.
        assert patched_get_jobs.call_args.kwargs["status"] == "FIN"
        assert patched_get_jobs.call_args.kwargs["job_type"] == "Commit"

    def test_download_software(self, mock_topology):
        """
        Test the download software function returns the correct data.
        The pan-os-python download software actually doesn't return any output itself unless it errors, so we just check our
        dataclass is set correctly within the function and retuned.
        """
        from Panorama import UniversalCommand

        result = UniversalCommand.download_software(mock_topology, "9.1.0")
        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    def test_reboot(self, mock_topology):
        """
        Test the reboot function returns the corect data
        The pan-os-python reboot method actually doesn't return any output itself unless it errors, so we just check our
        dataclass is set correctly within the function and returned by this function.
        """
        from Panorama import UniversalCommand

        result = UniversalCommand.reboot(mock_topology, MOCK_PANORAMA_SERIAL)
        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

        # We also want to check that if an empty string is passed, an error is returned
        with pytest.raises(
            DemistoException,
            match="filter_str  is not the exact ID of a host in this topology; use a more specific filter string.",
        ):
            UniversalCommand.reboot(mock_topology, "")

        # Lets also check that if an invalid hostid is given, we also raise.
        with pytest.raises(
            DemistoException,
            match="filter_str badserialnumber is not the exact ID of "
            "a host in this topology; use a more specific filter string.",
        ):
            UniversalCommand.reboot(mock_topology, "badserialnumber")

    @patch("Panorama.run_op_command")
    def test_system_status(self, patched_run_op_command, mock_topology):
        """
        Given a topology object with a mixture of systems in it,
        assert that check_system_availability returns the correct status
        based on whether devices are connected or not.
        """
        from Panorama import UniversalCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestUniversalCommand.SHOW_SYSTEM_INFO_XML)
        # Check a normal, up device
        result = UniversalCommand.check_system_availability(mock_topology, MOCK_PANORAMA_SERIAL)
        assert result.up

        # Check a device that isn't in the topology
        result = UniversalCommand.check_system_availability(mock_topology, "fake")
        assert result
        assert not result.up

    def test_system_status_command_polling_device_up(self, mocker, mock_topology):
        """
        Given:
            - polling=true and a target device that is already up (operational_mode == 'normal').
        When:
            - Running system_status_command.
        Then:
            - Polling stops immediately (no scheduled_command) and the SystemStatus context is returned.
        """
        from CommonServerPython import ScheduledCommand
        from Panorama import CheckSystemStatus, system_status_command

        mocker.patch("Panorama.get_topology", return_value=mock_topology)
        mocker.patch(
            "Panorama.system_status",
            return_value=CheckSystemStatus(hostid=MOCK_PANORAMA_SERIAL, up=True),
        )
        mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported", return_value=None)

        args = {"target": MOCK_PANORAMA_SERIAL, "polling": "true"}
        result = system_status_command(args)

        assert result.scheduled_command is None
        assert result.outputs == {"hostid": MOCK_PANORAMA_SERIAL, "up": True}

    def test_system_status_command_polling_device_down(self, mocker, mock_topology):
        """
        Given:
            - polling=true and a target device that is not yet available (up=False).
        When:
            - Running system_status_command.
        Then:
            - A scheduled_command is returned so polling continues.
            - A "waiting" message is shown.
            - The last known status (up=False) is still written to context so that on
              polling timeout the war-room shows the final PANOS.SystemStatus entry.
        """
        from CommonServerPython import ScheduledCommand
        from Panorama import CheckSystemStatus, system_status_command

        mocker.patch("Panorama.get_topology", return_value=mock_topology)
        mocker.patch(
            "Panorama.system_status",
            return_value=CheckSystemStatus(hostid=MOCK_PANORAMA_SERIAL, up=False),
        )
        mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported", return_value=None)

        args = {"target": MOCK_PANORAMA_SERIAL, "polling": "true"}
        result = system_status_command(args)

        assert result.scheduled_command is not None
        assert "Waiting for device" in (result.readable_output or "")
        # Last-known status must still be in context so a polling timeout leaves the
        # war-room with a meaningful final entry rather than only a waiting message.
        assert result.outputs == {"hostid": MOCK_PANORAMA_SERIAL, "up": False}

    def test_system_status_command_no_polling(self, mocker, mock_topology):
        """
        Given:
            - polling not provided (default false) even if the device is not up.
        When:
            - Running system_status_command.
        Then:
            - The command does not poll (no scheduled_command) and returns the current status as-is.
        """
        from Panorama import CheckSystemStatus, system_status_command

        mocker.patch("Panorama.get_topology", return_value=mock_topology)
        mocker.patch(
            "Panorama.system_status",
            return_value=CheckSystemStatus(hostid=MOCK_PANORAMA_SERIAL, up=False),
        )

        args = {"target": MOCK_PANORAMA_SERIAL}
        result = system_status_command(args)

        assert result.scheduled_command is None
        assert result.outputs == {"hostid": MOCK_PANORAMA_SERIAL, "up": False}


class TestFirewallCommand:
    """Test all the commands relevant only to Firewall instances"""

    SHOW_ARP_XML = "test_data/show_arp_all.xml"
    SHOW_ROUTING_SUMMARY_XML = "test_data/show_routing_summary.xml"
    SHOW_ROUTING_ROUTE_XML = "test_data/show_routing_route.xml"
    SHOW_GLOBAL_COUNTERS_XML = "test_data/show_counter_global.xml"
    SHOW_BGP_PEERS_XML = "test_data/show_routing_protocol_bgp_peer.xml"
    SHOW_HA_STATE_XML = "test_data/show_ha_state_enabled.xml"
    SHOW_HA_PANORAMA_STATE_XML = "test_data/show_ha_state_panorama_enabled.xml"

    @patch("Panorama.run_op_command")
    def test_get_arp_table(self, patched_run_op_command, mock_topology):
        """Given the output XML for show arp, assert it is parsed into the dataclasses correctly."""
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_ARP_XML)
        result = FirewallCommand.get_arp_table(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                assert value

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    @patch("Panorama.run_op_command")
    def test_get_routing_summary(self, patched_run_op_command, mock_topology):
        """Given the output XML for show route summary, assert it is parsed into the dataclasses correctly."""
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_ROUTING_SUMMARY_XML)
        result = FirewallCommand.get_routing_summary(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                assert value

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    @patch("Panorama.run_op_command")
    def test_get_routes(self, patched_run_op_command, mock_topology):
        """Given the output XML for show route, assert it is parsed into the dataclasses correctly."""
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_ROUTING_ROUTE_XML)
        result = FirewallCommand.get_routes(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                # Attribute may be int 0
                assert value is not None

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    @patch("Panorama.run_op_command")
    def test_get_counters(self, patched_run_op_command, mock_topology):
        """Given the output XML for show counters, assert it is parsed into the dataclasses correctly."""
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_GLOBAL_COUNTERS_XML)
        result = FirewallCommand.get_counter_global(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                # Attribute may be int 0
                assert value is not None

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                assert value

    @patch("Panorama.run_op_command")
    def test_get_bgp_peers(self, patched_run_op_command, mock_topology):
        """
        Given the output XML for show routing protocol bgp peers,
        assert it is parsed into the dataclasses correctly.
        """
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_BGP_PEERS_XML)
        result = FirewallCommand.get_bgp_peers(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result.result_data:
            for value in result_dataclass.__dict__.values():
                # Attribute may be int 0
                assert value is not None

        # Check all attributes of summary data have values
        for result_dataclass in result.summary_data:
            for value in result_dataclass.__dict__.values():
                # Attribute may be int 0
                assert value is not None

    @patch("Panorama.run_op_command")
    def test_get_ha_status_firewall(self, patched_run_op_command, mock_firewall_topology):
        """
        Given the XML output for a HA firewall which is enabled, ensure the data class is parsed correctly
        """
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_HA_STATE_XML)
        result = FirewallCommand.get_ha_status(mock_firewall_topology)

        assert result.status != "HA Not enabled."
        assert result.active is not None
        assert result.hostid is not None
        assert result.peer is not None

    @patch("Panorama.run_op_command")
    def test_get_ha_status_panorama(self, patched_run_op_command, mock_topology):
        """Given the XML output for a HA firewall, ensure the dataclasses are parsed correctly"""
        from Panorama import FirewallCommand

        patched_run_op_command.return_value = load_xml_root_from_test_file(TestFirewallCommand.SHOW_HA_PANORAMA_STATE_XML)
        result = FirewallCommand.get_ha_status(mock_topology)
        # Check all attributes of result data have values
        for result_dataclass in result:
            for value in result_dataclass.__dict__.values():
                # Attribute may be int 0
                assert value is not None

    @patch("Panorama.run_op_command")
    def test_update_ha_state(self, patched_run_op_command, mock_topology):
        """
        Test the HA Update command returns the correct data
        """
        from Panorama import FirewallCommand

        result_dataclass = FirewallCommand.change_status(mock_topology, MOCK_FIREWALL_1_SERIAL, "operational")
        # Check all attributes of summary data have values
        for value in result_dataclass.__dict__.values():
            assert value


@pytest.mark.parametrize(
    "args, expected_request_params, request_result, expected_demisto_result",
    [
        pytest.param(
            {"anti_spyware_profile_name": "fake_profile_name", "dns_signature_source": "edl_name", "action": "allow"},
            {
                "action": "set",
                "type": "config",
                "xpath": "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='fakeDeviceGroup']"
                "/profiles/spyware/entry[@name='fake_profile_name']",
                "key": "fakeAPIKEY!",
                "element": "<botnet-domains>"
                "<lists>"
                '<entry name="edl_name"><packet-capture>disable</packet-capture>'
                "<action><allow/></action></entry>"
                "</lists>"
                "<sinkhole><ipv4-address>pan-sinkhole-default-ip</ipv4-address>"
                "<ipv6-address>::1</ipv6-address></sinkhole>"
                "</botnet-domains>",
            },
            MockedResponse(
                text='<response status="success" code="20"><msg>command succeeded</msg></response>', status_code=200, reason=""
            ),
            "**success**",
        ),
    ],
)
def test_panorama_apply_dns_command(mocker, args, expected_request_params, request_result, expected_demisto_result):
    """
    Given:
        - command args
        - request result
    When:
        - Running panorama-apply-dns
    Then:
        - Assert the request url is as expected
        - Assert Command results contains the relevant result information
    """
    import Panorama
    import requests
    from Panorama import apply_dns_signature_policy_command

    Panorama.API_KEY = "fakeAPIKEY!"
    Panorama.DEVICE_GROUP = "fakeDeviceGroup"
    Panorama.VSYS = ""  # ensure the Panorama (device-group) xpath is used, not a leaked firewall VSYS
    request_mock = mocker.patch.object(requests, "request", return_value=request_result)
    command_result: CommandResults = apply_dns_signature_policy_command(args)

    called_request_params = request_mock.call_args.kwargs["params"]  # The body part of the request
    assert called_request_params == expected_request_params
    assert command_result.readable_output == expected_demisto_result


def test_panorama_apply_dns_command2(mocker):
    """
    Given:
        - command args with a singel firewall
    When:
        - Running panorama-apply-dns
    Then:
        - Assert the request parameters are as expected
    """
    import Panorama
    from Panorama import apply_dns_signature_policy_command

    Panorama.API_KEY = "fakeAPIKEY!"
    Panorama.DEVICE_GROUP = "fakeDeviceGroup"
    Panorama.VSYS = ""  # ensure the Panorama (device-group) xpath is used, not a leaked firewall VSYS
    request_mock = mocker.patch.object(Panorama, "http_request", return_value={})
    apply_dns_signature_policy_command({"anti_spyware_profile_name": "fake_profile_name"})

    request_params = request_mock.call_args.kwargs["params"]  # The body part of the request
    assert (
        request_params.get("xpath")
        == "/config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='fakeDeviceGroup']/"
        "profiles/spyware/entry[@name='fake_profile_name']"
    )  # noqa


class TestHygieneFunctions:
    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    def test_check_log_forwarding(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate the log forwarding settings of a device
        """
        from Panorama import HygieneLookups, LogForwardingProfile, LogForwardingProfileMatchList

        # First, test that a correctly configured LFP and match list don't return a failure
        LogForwardingProfile.refreshall = MagicMock(return_value=mock_good_log_fowarding_profile())
        LogForwardingProfileMatchList.refreshall = MagicMock(return_value=mock_good_log_forwarding_profile_match_list())
        result = HygieneLookups.check_log_forwarding_profiles(mock_topology)
        assert len(result.result_data) == 0

        # Trim the "threat" log type and cause a missing log type error
        LogForwardingProfileMatchList.refreshall = MagicMock(return_value=[mock_good_log_forwarding_profile_match_list()[0]])
        result = HygieneLookups.check_log_forwarding_profiles(mock_topology)
        # Note; because we mock the topology with multiple devices,
        # it appears that the same LFP is missing in each Container.
        # This is expected.
        assert len(result.result_data) == 3
        assert result.result_data[0].description == "Log forwarding profile missing log type 'threat'."

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_check_vulnerability_profiles(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate the vulnerability profiles
        """
        from Panorama import BestPractices, HygieneLookups, VulnerabilityProfile

        # First, test that we can get the conforming threat profile, of which there should be one
        result = HygieneLookups.get_conforming_threat_profiles(
            [mock_good_vulnerability_profile(), mock_bad_vulnerability_profile()],
            minimum_block_severities=BestPractices.VULNERABILITY_BLOCK_SEVERITIES,
            minimum_alert_severities=BestPractices.VULNERABILITY_ALERT_THRESHOLD,
        )
        assert len(result) == 1

        VulnerabilityProfile.refreshall = MagicMock(
            return_value=[mock_good_vulnerability_profile(), mock_bad_vulnerability_profile()]
        )

        result = HygieneLookups.check_security_profiles(topology=mock_topology, profile_type="vulnerability")
        # Should return no results, as at least one vulnerability profile matches.
        assert len(result.result_data) == 0

        VulnerabilityProfile.refreshall = MagicMock(return_value=[mock_bad_vulnerability_profile()])

        result = HygieneLookups.check_security_profiles(topology=mock_topology, profile_type="vulnerability")
        # Should return one issue, as no Vulnerability profile matches.
        assert len(result.result_data) == 1

        # When both a good and bad profile exist and return_nonconforming_profiles is True, check we get 3 sets of details
        VulnerabilityProfile.refreshall = MagicMock(
            return_value=[mock_good_vulnerability_profile(), mock_bad_vulnerability_profile()]
        )

        result = HygieneLookups.check_security_profiles(
            topology=mock_topology, profile_type="vulnerability", return_nonconforming_profiles=True
        )
        assert len(result.result_data) == 3

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_check_spyware_profiles(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate the
        Spyware profiles given combinations of good and bad profile
        objects.
        """
        from Panorama import AntiSpywareProfile, HygieneLookups

        AntiSpywareProfile.refreshall = MagicMock(return_value=[mock_good_spyware_profile(), mock_bad_spyware_profile()])

        # Check when at least one good profile exists - should return no results
        result = HygieneLookups.check_security_profiles(topology=mock_topology, profile_type="spyware")
        assert not result.result_data

        # Check that when return_nonconforming_profiles is True we get back 3 sets of details
        result = HygieneLookups.check_security_profiles(
            topology=mock_topology, profile_type="spyware", return_nonconforming_profiles=True
        )
        assert len(result.result_data) == 3

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_check_url_filtering_profiles(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate the
        URL filtering profiles given combinations of good and bad
        profiles.
        """
        from Panorama import HygieneLookups, URLFilteringProfile

        URLFilteringProfile.refreshall = MagicMock(return_value=[mock_good_url_filtering_profile()])

        # Check when a good profile exists - should return no results
        result = HygieneLookups.check_security_profiles(topology=mock_topology, profile_type="url")
        assert not result.result_data

        # When there's only bad, should return a result
        URLFilteringProfile.refreshall = MagicMock(return_value=[mock_bad_url_filtering_profile()])

        # Check when a good profile exists - should return no results
        result = HygieneLookups.check_security_profiles(topology=mock_topology, profile_type="url")
        assert result.result_data

        # When both a good and bad profile exist and return_nonconforming_profiles is True, check we get 3 sets of details
        URLFilteringProfile.refreshall = MagicMock(
            return_value=[mock_good_url_filtering_profile(), mock_bad_url_filtering_profile()]
        )
        result = HygieneLookups.check_security_profiles(
            topology=mock_topology, profile_type="url", return_nonconforming_profiles=True
        )
        assert len(result.result_data) == 3

    @patch("Panorama.Template.refreshall", return_value=mock_templates())
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_template_stack())
    def test_check_security_zones(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate security zones given a comination of good and bad zones.
        """
        from Panorama import HygieneLookups, Zone

        Zone.refreshall = MagicMock(return_value=mock_good_security_zones())

        result = HygieneLookups.check_security_zones(mock_topology)
        # Result data should be empty as there are only good zones
        assert not result.result_data

        Zone.refreshall = MagicMock(return_value=mock_bad_security_zones())

        result = HygieneLookups.check_security_zones(mock_topology)
        # Result data should have one issue as there is a misconfigured security zone
        assert result.result_data
        assert "BP-V-7" in [x.issue_code for x in result.result_data]

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_check_security_rules(self, _, __, ___, ____, mock_topology):
        """
        Test the Hygiene Configuration lookups can validate security zones given a comination of good and bad zones.
        """
        from Panorama import HygieneLookups, SecurityRule

        SecurityRule.refreshall = MagicMock(return_value=mock_good_security_rules())

        result = HygieneLookups.check_security_rules(mock_topology)
        # Should not raise any issues
        assert not result.result_data

        SecurityRule.refreshall = MagicMock(return_value=mock_bad_security_rules())

        result = HygieneLookups.check_security_rules(mock_topology)
        # Should raise issues for each issue type
        assert result.result_data
        assert "BP-V-8" in [x.issue_code for x in result.result_data]
        assert "BP-V-9" in [x.issue_code for x in result.result_data]
        assert "BP-V-10" in [x.issue_code for x in result.result_data]

    def test_hygiene_issue_dict_to_object(self):
        """
        Tests the function can convert a given dictionary of
        an issue, returned by a hygiene lookup, back into the relevent
        object. This is to allow the check commands to pass their results directly into the fix commands via XSOAR.
        """
        from Panorama import ConfigurationHygieneIssue, hygiene_issue_dict_to_object

        result = hygiene_issue_dict_to_object(mock_enhanced_log_forwarding_issue_dict())
        assert isinstance(result[0], ConfigurationHygieneIssue)
        assert len(result) == 1
        for value in result[0].__dict__.values():
            assert value

        # If the issue is passed directly from the other command make sure this function works also
        result = hygiene_issue_dict_to_object(mock_issue_with_underscores())
        assert isinstance(result[0], ConfigurationHygieneIssue)
        assert len(result) == 1
        for value in result[0].__dict__.values():
            assert value

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_fix_log_forwarding_profile_enhanced_logging(self, _, __, ___, ____, mock_topology):
        """
        Tests wthe fix function for enabling enhanced application
        logging on log forwarding profiles, given an issue referring
        to a bad log forwarding profile.
        """
        from Panorama import HygieneRemediation, LogForwardingProfile, hygiene_issue_dict_to_object

        issues = hygiene_issue_dict_to_object(mock_enhanced_log_forwarding_issue_dict())

        LogForwardingProfile.refreshall = MagicMock(return_value=mock_bad_log_fowarding_profile())
        LogForwardingProfile.apply = MagicMock()

        result = HygieneRemediation.fix_log_forwarding_profile_enhanced_logging(mock_topology, issues)
        # Should be at least one result
        assert result
        for value in result[0].__dict__.values():
            assert value

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_fix_security_zone_no_log_setting(self, _, __, ___, ____, mock_topology):
        """
        Tests wthe fix function for setting a log forwarding profile on security zones when none is currently set
        """
        from Panorama import HygieneRemediation, Zone, hygiene_issue_dict_to_object

        issues = hygiene_issue_dict_to_object(mock_security_zone_no_log_setting_issue_dict())

        Zone.refreshall = MagicMock(return_value=mock_bad_security_zones())
        Zone.apply = MagicMock()

        result = HygieneRemediation.fix_security_zone_no_log_setting(mock_topology, issues, "test")
        # Should be at least one result, as we provided an issue.
        assert result
        for value in result[0].__dict__.values():
            assert value

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_fix_security_rule_log_settings(self, _, __, ___, ____, mock_topology):
        """
        Tests the function that adds a log forwarding profile to a security rule when one isn't present.
        """
        from Panorama import HygieneRemediation, SecurityRule, hygiene_issue_dict_to_object

        issues = hygiene_issue_dict_to_object(mock_security_rule_log_settings_issue_dict())

        SecurityRule.refreshall = MagicMock(return_value=mock_bad_security_rules())
        SecurityRule.apply = MagicMock()

        result = HygieneRemediation.fix_secuity_rule_log_settings(mock_topology, issues, "test")
        # Should be at least one result, as we provided an issue.
        assert result
        for value in result[0].__dict__.values():
            assert value

    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_fix_security_rule_profile_settings(self, _, __, ___, ____, mock_topology):
        """
        Tests the function that adds sets the security profile group when no SPG is currently provided
        """
        from Panorama import HygieneRemediation, SecurityRule, hygiene_issue_dict_to_object

        issues = hygiene_issue_dict_to_object(mock_security_rule_log_settings_issue_dict())

        SecurityRule.refreshall = MagicMock(return_value=mock_bad_security_rules())
        SecurityRule.apply = MagicMock()

        result = HygieneRemediation.fix_security_rule_security_profile_group(mock_topology, issues, "test")
        # Should be at least one result, as we provided an issue.
        assert result
        for value in result[0].__dict__.values():
            assert value


class TestObjectFunctions:
    @patch("Panorama.Template.refreshall", return_value=[])
    @patch("Panorama.TemplateStack.refreshall", return_value=[])
    @patch("Panorama.Vsys.refreshall", return_value=[])
    @patch("Panorama.DeviceGroup.refreshall", return_value=mock_device_groups())
    def test_get_objects(self, _, __, ___, ____, mock_single_device_topology):
        """
        Tests that we can get various object types and the filtering logic, by object type and name, works correctly.
        """
        from Panorama import AddressObject, ObjectGetter

        # Use side effects so that objects are only returned from one container
        AddressObject.refreshall = MagicMock(side_effect=[mock_address_objects(), []])

        # Test with no filter first
        result = ObjectGetter.get_object_reference(mock_single_device_topology, "AddressObject")
        assert "test-address-1" in [x.name for x in result]
        assert "test-address-2" in [x.name for x in result]

        # Same as above but with a filter on object name
        AddressObject.refreshall = MagicMock(side_effect=[mock_address_objects(), []])
        result = ObjectGetter.get_object_reference(mock_single_device_topology, "AddressObject", object_name="test-address-1")
        assert "test-address-1" in [x.name for x in result]
        assert "test-address-2" not in [x.name for x in result]

        # Same as above but include a regex filter
        AddressObject.refreshall = MagicMock(side_effect=[mock_address_objects(), []])
        result = ObjectGetter.get_object_reference(
            mock_single_device_topology, "AddressObject", object_name="test-address-\d+", use_regex="true"
        )
        assert "test-address-1" in [x.name for x in result]
        assert "test-address-2" in [x.name for x in result]

        # Test broken regex
        AddressObject.refreshall = MagicMock(side_effect=[mock_address_objects(), []])
        with pytest.raises(DemistoException):
            result = ObjectGetter.get_object_reference(
                mock_single_device_topology, "AddressObject", object_name="test-address-(\d+", use_regex="true"
            )
            assert not result


@pytest.mark.parametrize(
    "expected_request_params, target",
    [
        pytest.param(
            {"type": "op", "cmd": "<show><system><info/></system></show>", "key": "fakeAPIKEY!", "target": "fake-target"},
            "fake-target",
        ),
        pytest.param(
            {
                "type": "op",
                "cmd": "<show><system><info/></system></show>",
                "key": "fakeAPIKEY!",
            },
            None,
        ),
    ],
)
def test_add_target_arg(mocker, expected_request_params, target):
    """
    Given:
        - a call to the function with or without the target args
    When:
        - panorama_show_device_version_command - (or any other function with the target arg)
    Then:
        - Assert that the target param was added or not to the https request
    """
    import Panorama
    from Panorama import panorama_show_device_version

    Panorama.API_KEY = "fakeAPIKEY!"
    Panorama.DEVICE_GROUP = "fakeDeviceGroup"
    request_mock = mocker.patch.object(Panorama, "http_request", return_value={"response": {"result": {"system": "fake_data"}}})

    panorama_show_device_version(target)
    called_request_params = request_mock.call_args.kwargs["params"]
    assert called_request_params == expected_request_params


@pytest.mark.parametrize(
    "rule , expected_result",
    [
        pytest.param({"target": {"devices": {"entry": [{"@name": "fw1"}, {"@name": "fw2"}]}}}, True),
        pytest.param({"target": {"devices": {"entry": {"@name": "fw1"}}}}, True),
        pytest.param({"target": {"devices": {"entry": {"@name": "fw2"}}}}, False),
        pytest.param({"target": {"devices": {"entry": [{"@name": "fw1"}]}}}, True),
    ],
)
def test_target_filter(rule, expected_result):
    """
    Given:
        - a rule (dict) and a target (str) - 'fw1'
    When:
        - filtering rules by target
    Then:
        - return True if the rule contains the target and False otherwise
    """
    from Panorama import target_filter

    assert target_filter(rule, "fw1") == expected_result


def test_check_latest_version_hr(mocker):
    """
    Given:
        - a response from panorma of latest version
    When:
        - calling the command - pan-os-check-latest-panos-software
    Then:
        - filter the 5 latest results and present in a markdown
    """
    import requests
    from Panorama import panorama_check_latest_panos_software_command

    with open("test_data/latest_versions.xml") as xml_file:
        text = xml_file.read()
    with open("test_data/5_latest_version.md") as md_file:
        markdown_assert = md_file.read()
    mr = MockedResponse(text=text, status_code=200, reason="")
    mocker.patch.object(requests, "request", return_value=mr)
    command_res: CommandResults = panorama_check_latest_panos_software_command()

    assert markdown_assert == command_res.readable_output


def test_pan_os_get_running_config(mocker):
    """
    Given -
        A target serial number
    When -
        Returning the running config
    Then -
        File returned should be called 'running_config'
        The contents should be XML and not JSON
    """
    from Panorama import pan_os_get_running_config

    return_mock = """
    <response status='error' code='13'><msg><line>SOME_SERIAL_NUMBER not connected</line></msg></response>
    """
    mocker.patch("Panorama.http_request", return_value=return_mock)
    created_file = pan_os_get_running_config({"target": "SOME_SERIAL_NUMBER", "filename": "running_config"})
    assert created_file["File"] == "running_config"


def test_pan_os_get_merged_config(mocker):
    """
    Given -
        A target serial number
    When -
        Returning the merged config
    Then -
        File returned should be called 'merged_config'
        The contents should be XML and not JSON
    """
    from Panorama import pan_os_get_merged_config

    return_mock = """
    <response status='error' code='13'><msg><line>SOME_SERIAL_NUMBER not connected</line></msg></response>
    """
    mocker.patch("Panorama.http_request", return_value=return_mock)
    created_file = pan_os_get_merged_config({"target": "SOME_SERIAL_NUMBER"})
    assert created_file["File"] == "merged_config"


class TestPanOSListTemplatesCommand:
    def test_pan_os_list_templates_main_flow(self, mocker):
        """
        Given:
         - Panorama instance configuration.

        When:
         - running the pan-os-list-templates through the main flow.

        Then:
         - make sure the context output is parsed correctly.
         - make sure the xpath and the request is correct.
         - make sure "Variable=None' is parsed successfully.

        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value=load_json("test_data/list_templates_including_uncommitted.json")
        )
        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(demisto, "args", return_value={})
        mocker.patch.object(demisto, "command", return_value="pan-os-list-templates")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == [
            {
                "Name": "test-1",
                "Description": None,
                "Variable": [
                    {"Name": None, "Type": None, "Value": None, "Description": None},
                    {"Name": None, "Type": None, "Value": None, "Description": None},
                ],
            },
            {
                "Name": "test-2",
                "Description": "just a test description",
                "Variable": [
                    {
                        "Name": "$variable-1",
                        "Type": "ip-netmask",
                        "Value": "1.1.1.1",
                        "Description": "description for $variable-1",
                    }
                ],
            },
            {
                "Name": "test-3",
                "Description": None,
                "Variable": [],
            },
        ]

        assert mock_request.call_args.kwargs["params"] == {
            "type": "config",
            "action": "get",
            "key": "thisisabogusAPIKEY!",
            "xpath": "/config/devices/entry[@name='localhost.localdomain']/template",
        }

    def test_pan_os_list_templates_main_flow_firewall_instance(self):
        """
        Given:
         - Firewall instance configuration.

        When:
         - running the pan_os_list_templates_command function.

        Then:
         - make sure an exception is raised because hte pan-os-list-templates can run only on Panorama instances.
        """
        import Panorama
        from Panorama import pan_os_list_templates_command

        Panorama.VSYS = "vsys"  # VSYS are only firewall instances
        Panorama.DEVICE_GROUP = ""  # device-groups are only panorama instances.
        with pytest.raises(DemistoException):
            pan_os_list_templates_command({})


class TestPanOSListNatRulesCommand:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "false"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']"
                    "/device-group/entry[@name='Lab-Devices']/pre-rulebase/nat",
                },
            ),
            pytest.param(
                {"show_uncommitted": "false"},
                integration_firewall_params,
                {
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/nat",
                },
            ),
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "true", "name": "test"},
                integration_panorama_params,
                {
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']",
                },
            ),
            pytest.param(
                {"show_uncommitted": "true", "name": "test"},
                integration_firewall_params,
                {
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/nat/rules/entry[@name='test']",
                },
            ),
        ],
    )
    def test_pan_os_list_rules_command_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration.
         - Firewall instance configuration.
         - Panorama instance configuration to get a specific nat-rule.
         - Firewall instance configuration to get a specific nat-rule.

        When:
         - running the pan-os-list-nat-rules through the main flow.

        Then:
         - make sure the context output is parsed correctly.
         - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        expected_context = [
            {
                "Name": "test",
                "Tags": "test tag",
                "SourceZone": "1.1.1.1",
                "DestinationZone": "1.1.1.1",
                "SourceAddress": "any",
                "DestinationAddress": "any",
                "DestinationInterface": None,
                "Service": "any",
                "Description": None,
                "SourceTranslation": None,
                "DynamicDestinationTranslation": None,
                "DestinationTranslation": None,
                "Disabled": "yes",
            },
            {
                "Name": "test-2",
                "Tags": None,
                "SourceZone": "2.2.2.2",
                "DestinationZone": "2.2.2.2",
                "SourceAddress": "any",
                "DestinationAddress": "any",
                "DestinationInterface": None,
                "Service": "any",
                "Description": None,
                "SourceTranslation": None,
                "DynamicDestinationTranslation": None,
                "DestinationTranslation": None,
                "Disabled": "no",
            },
        ]

        mock_request = mocker.patch("Panorama.http_request", return_value=load_json("test_data/list-nat-rules-response.json"))
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-list-nat-rules")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == expected_context
        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestCreatePanOSNatRuleCommand:
    CREATE_NAT_RULE = {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "pre_post": "pre-rulebase",
                    "source_translation_type": "static-ip",
                    "source_translated_address": "1.1.1.1",
                    "source_translated_address_type": "translated-address",
                    "destination_translation_type": "none",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group"
                    "/entry[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']",
                    "element": "<source-translation><static-ip><translated-address>1.1.1.1<"
                    "/translated-address></static-ip></source-translation><description>test</description>",
                    "action": "set",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "source_translation_type": "static-ip",
                    "source_translated_address": "1.1.1.1",
                    "source_translated_address_type": "translated-address",
                    "destination_translation_type": "none",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/"
                    "entry[@name='vsys1']/rulebase/nat/rules/entry[@name='test']",
                    "element": "<source-translation><static-ip><translated-address>1.1.1.1<"
                    "/translated-address></static-ip></source-translation><description>test</description>",
                    "action": "set",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "destination_zone": "1.1.1.1",
                    "source_zone": "2.2.2.2",
                    "pre_post": "pre-rulebase",
                    "source_address": "1.1.1.1,2.2.2.2",
                    "source_translation_type": "dynamic-ip",
                    "source_translated_address_type": "translated-address",
                    "source_translated_address": "1.1.1.1,2.2.2.2",
                    "destination_translation_type": "none",
                    "audit_comment": "test comment",
                },
                integration_panorama_params,
                {
                    "action": "set",
                    "element": "<source-translation><dynamic-ip><translated-address><member>1.1.1.1</member>"
                    "<member>2.2.2.2</member></translated-address></dynamic-ip></source-translation><to>"
                    "<member>1.1.1.1</member></to><from><member>2.2.2.2</member></from><source><member>"
                    "1.1.1.1</member><member>2.2.2.2</member></source><description>test</description>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']",
                    "audit-comment": "test comment",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "destination_zone": "1.1.1.1",
                    "source_zone": "2.2.2.2",
                    "source_address": "1.1.1.1,2.2.2.2",
                    "source_translation_type": "dynamic-ip",
                    "source_translated_address_type": "translated-address",
                    "source_translated_address": "1.1.1.1,2.2.2.2",
                    "destination_translation_type": "none",
                    "audit_comment": "test comment",
                },
                integration_firewall_params,
                {
                    "action": "set",
                    "element": "<source-translation><dynamic-ip><translated-address><member>1.1.1.1</member>"
                    "<member>2.2.2.2</member></translated-address></dynamic-ip></source-translation><to>"
                    "<member>1.1.1.1</member></to><from><member>2.2.2.2</member></from><source>"
                    "<member>1.1.1.1</member><member>2.2.2.2</"
                    "member></source><description>test</description>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/"
                    "entry[@name='vsys1']/rulebase/nat/rules/entry[@name='test']",
                    "audit-comment": "test comment",
                },
            ),
        ],
    )
    def test_pan_os_create_nat_rule_command_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration with source_translation_type, source_translated_address
            and source_translated_address_type
         - Firewall instance configuration with source_translation_type, source_translated_address
            and source_translated_address_type
         - Panorama instance configuration with basic parameter configurations along with dynamic-ip
         - firewall instance configuration with basic parameter configurations along with dynamic-ip

        When:
         - running the pan-os-create-nat-rule through the main flow.

        Then:
         - make sure the xpath/element and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-create-nat-rule")

        main()
        assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"rulename": "test", "pre_post": "pre-rulebase"},
            integration_panorama_params,
            {
                "action": "delete",
                "key": "thisisabogusAPIKEY!",
                "type": "config",
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']",
            },
        ),
        pytest.param(
            {"rulename": "test"},
            integration_firewall_params,
            {
                "action": "delete",
                "key": "thisisabogusAPIKEY!",
                "type": "config",
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry"
                "[@name='vsys1']/rulebase/nat/rules/entry[@name='test']",
            },
        ),
    ],
)
def test_pan_os_delete_nat_rule_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance configuration with a specific rulename.
     - Firewall instance configuration with a specific rulename.

    When:
     - running the pan-os-delete-nat-rule through the main flow.

    Then:
     - make sure the xpath/element and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-delete-nat-rule")

    main()
    assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSEditNatRule:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "pre_post": "pre-rulebase",
                    "element_to_change": "source_translation_dynamic_ip",
                    "behavior": "replace",
                    "element_value": "1.1.1.1,2.2.2.2",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<translated-address><member>1.1.1.1</member><member>2.2.2.2</member></translated-address>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry["
                    "@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']/source-translation"
                    "/dynamic-ip/translated-address",
                },
            ),
            pytest.param(
                {"rulename": "test", "element_to_change": "source_zone", "behavior": "replace", "element_value": "1.1.1.1"},
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": "<from><member>1.1.1.1</member></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/nat/rules/entry[@name='test']/from",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "pre_post": "pre-rulebase",
                    "element_to_change": "destination_translation_dynamic_distribution_method",
                    "behavior": "replace",
                    "element_value": "Round Robin",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<distribution>Round Robin</distribution>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']/"
                    "dynamic-destination-translation/distribution",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "source_translation_static_ip",
                    "behavior": "replace",
                    "element_value": "1.1.1.1",
                },
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": "<translated-address>1.1.1.1</translated-address>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/"
                    "nat/rules/entry[@name='test']/source-translation/static-ip/translated-address",
                },
            ),
        ],
    )
    def test_pan_os_nat_rule_replace_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given
         - Panorama instance when replacing source_translation_dynamic_ip to a new value
         - Firewall instance when replacing source_zone to a new value.
         - Panorama instance when replacing destination_translation_dynamic_distribution_method to a new value.
         - Firewall instance when replacing source_translation_static_ip to a new value.

        When
         - running the pan-os-edit-nat-rule through the main flow.

        Then
         - make sure the xpath/element and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-nat-rule")

        main()
        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @pytest.mark.parametrize("nat_rule_object", ["destination_translation_ip", "destination_interface", "nat_type"])
    def test_pan_os_add_or_remove_un_listable_objects(self, nat_rule_object):
        """
        Given
         - un-listable nat-rules object.

        When
         - running the pan-os-edit-nat-rule command.

        Then
         - make sure an exception is raised.
        """
        from Panorama import pan_os_edit_nat_rule_command

        with pytest.raises(ValueError):
            pan_os_edit_nat_rule_command({"element_to_change": nat_rule_object, "rulename": "test", "action": "add"})

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "pre_post": "pre-rulebase",
                    "element_to_change": "source_zone",
                    "behavior": "add",
                    "element_value": "2.2.2.2,3.3.3.3",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<from><member>2.2.2.2</member><member>3.3.3.3</member><member>1.1.1.1</member></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']/from",
                },
            ),
            pytest.param(
                {"rulename": "test", "element_to_change": "source_zone", "behavior": "add", "element_value": "2.2.2.2,3.3.3.3"},
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": "<from><member>2.2.2.2/member><member>1.1.1.1/member></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/nat/rules/entry[@name='test']/from",
                },
            ),
        ],
    )
    def test_pan_os_nat_rule_add_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given
         - Panorama instance when adding a new value to source_zone.
         - Firewall instance when adding a new value to source_zone.

        When
         - running the pan-os-edit-nat-rule through the main flow.

        Then
         - make sure the xpath/element and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "from": {"member": "1.1.1.1"}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-nat-rule")

        main()
        assert mock_request.call_args.kwargs["params"]["xpath"] == expected_url_params["xpath"]
        assert "1.1.1.1" in mock_request.call_args.kwargs["params"]["element"]
        assert "2.2.2.2" in mock_request.call_args.kwargs["params"]["element"]
        assert "3.3.3.3" in mock_request.call_args.kwargs["params"]["element"]

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "pre_post": "pre-rulebase",
                    "element_to_change": "source_zone",
                    "behavior": "remove",
                    "element_value": "2.2.2.2,3.3.3.3",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<from><member>1.1.1.1</member></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/nat/rules/entry[@name='test']/from",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "source_zone",
                    "behavior": "remove",
                    "element_value": "2.2.2.2,3.3.3.3",
                },
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": "<from><member>1.1.1.1</member></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/"
                    "rulebase/nat/rules/entry[@name='test']/from",
                },
            ),
        ],
    )
    def test_pan_os_nat_rule_remove_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given
         - Panorama instance when removing a value from source_zone.
         - Firewall instance when removing a value from source_zone.

        When
         - running the pan-os-edit-nat-rule through the main flow.

        Then
         - make sure the xpath/element and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "from": {"member": ["1.1.1.1", "2.2.2.2", "3.3.3.3"]}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-nat-rule")

        main()
        assert mock_request.call_args.kwargs["params"]["xpath"] == expected_url_params["xpath"]
        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @staticmethod
    def test_pan_os_edit_nat_rule_command_audit_comment_main_flow(mocker):
        """
        Given
         - panorama integrations parameters.
         - pan-os-edit-nat-rule command arguments including device_group.
         - arguments to edit audit comment of a rule

        When -
            running the pan-os-edit-nat-rule command through the main flow

        Then
         - make sure the context output is returned as expected.
         - make sure the device group gets overriden by the command arguments.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "rulename": "test",
                "element_to_change": "audit-comment",
                "element_value": "some string",
                "pre_post": "pre-rulebase",
                "device-group": "new device group",
            },
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-nat-rule")
        request_mock = mocker.patch(
            "Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE
        )

        res = mocker.patch("demistomock.results")
        main()

        assert request_mock.call_args.kwargs["params"] == {
            "type": "op",
            "cmd": "<set><audit-comment><xpath>/config/devices/entry[@name='localhost.localdomain']/device-group"
            "/entry[@name='new device group']/pre-rulebase/nat/rules/entry[@name='test']"
            "</xpath><comment>some string</comment></audit-comment></set>",
            "key": "thisisabogusAPIKEY!",
        }
        assert res.call_args.args[0]["Contents"] == TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE


class TestPanOSListVirtualRouters:
    @pytest.mark.parametrize(
        "args, params, expected_url_params, mocked_response_path",
        [
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "false", "virtual_router": "test"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/"
                    "config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='test']",
                },
                "test_data/list-virtual-routers-response.json",
            ),
            pytest.param(
                {"show_uncommitted": "false", "virtual_router": "test"},
                integration_firewall_params,
                {
                    "type": "config",
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry[@name='test']",
                },
                "test_data/list-virtual-routers-response.json",
            ),
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "true", "virtual_router": "test"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']"
                    "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router"
                    "/entry[@name='test']",
                },
                "test_data/list-virtual-routers-response-un-commited-router.json",
            ),
            pytest.param(
                {"show_uncommitted": "true", "virtual_router": "test"},
                integration_firewall_params,
                {
                    "type": "config",
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry[@name='test']",
                },
                "test_data/list-virtual-routers-response-un-commited-router.json",
            ),
        ],
    )
    def test_pan_os_list_virtual_routers_command_main_flow(self, mocker, args, params, expected_url_params, mocked_response_path):
        """
        Given:
         - Panorama instance configuration and name to retrieve a specific virtual router that was committed.
         - Firewall instance configuration and name to retrieve a specific virtual router that was committed.
         - Panorama instance configuration and name to retrieve a specific virtual router that was not committed.
         - Firewall instance configuration and name to retrieve a specific virtual router that was not committed.

        When:
         - running the pan-os-list-virtual-routers through the main flow.

        Then:
         - make sure the context output is parsed correctly for both un-committed and committed cases.
         - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch("Panorama.http_request", return_value=load_json(mocked_response_path))
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-list-virtual-routers")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == [
            {
                "BGP": {"enable": "no", "routing-options": {"graceful-restart": {"enable": "yes"}}},
                "ECMP": {"algorithm": {"ip-modulo": "None"}},
                "Interface": None,
                "Multicast": None,
                "Name": "test",
                "OSPF": {"enable": "no"},
                "OSPFv3": {"enable": "no"},
                "RIP": {"enable": "no"},
                "RedistributionProfile": None,
                "StaticRoute": None,
            }
        ]
        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSListRedistributionProfiles:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"virtual_router": "virtual-router-1", "template": "test-override"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry["
                    "@name='test-override']/config/devices/entry[@name='localhost.localdomain']/network"
                    "/virtual-router/entry[@name='virtual-router-1']/protocol/redist-profile",
                },
            ),
            pytest.param(
                {"virtual_router": "virtual-router-1"},
                integration_firewall_params,
                {
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/"
                    "entry[@name='virtual-router-1']/protocol/redist-profile",
                },
            ),
        ],
    )
    def test_pan_os_list_redistribution_profiles_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration and name to retrieve redistribution profiles that were not committed.
         - Firewall instance configuration and name to retrieve redistribution profiles that were not committed.

        When:
         - running the pan-os-list-redistribution-profiles through the main flow.

        Then:
         - make sure the context output is parsed correctly.
         - make sure the xpath and the request is correct for both panorama/firewall and that template gets overriden
             when using panorama instance.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value=load_json("test_data/list-redistribution-profiles-un-committed-response.json")
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-list-redistribution-profiles")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == [
            {
                "Name": "test1",
                "Priority": "1",
                "Action": "redist",
                "FilterInterface": "loopback",
                "FilterType": ["bgp", "connect", "ospf", "rip", "static"],
                "FilterDestination": "1.1.1.1",
                "FilterNextHop": "2.2.2.2",
                "BGP": {"Community": ["local-as", "no-export"], "ExtendedCommunity": "0x4164ACFCE33404EA"},
                "OSPF": {"PathType": ["ext-1", "ext-2", "inter-area", "intra-area"], "Area": ["1.1.1.1", "2.2.2.2"], "Tag": "1"},
            },
            {
                "Name": "test-2",
                "Priority": "123",
                "Action": "no-redist",
                "FilterInterface": None,
                "FilterType": None,
                "FilterDestination": None,
                "FilterNextHop": None,
                "BGP": None,
                "OSPF": None,
            },
        ]

        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSCreateRedistributionProfile:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "priority": "12",
                    "action": "redist",
                    "filter_bgp_extended_community": "0x4164ACFCE33404EA",
                    "filter_source_type": "bgp,ospf",
                    "filter_ospf_area": "1.1.1.1,2.2.2.2",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/"
                    "config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry["
                    "@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']",
                    "element": "<priority>12</priority><action><redist/></action><filter><ospf><area><member>1.1.1.1<"
                    "/member><member>2.2.2.2</member></area></ospf><bgp><extended-community>"
                    "<member>0x4164ACFCE33404EA</member></extended-community></bgp><type><member>bgp"
                    "</member><member>ospf</member></type></filter>",
                    "action": "set",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "interface": "loopback",
                    "filter_ospf_tag": "1.1.1.1,2.2.2.2",
                    "filter_source_type": "ospf,bgp",
                    "filter_ospf_path_type": "ext-1,ext-2",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']",
                    "element": "<filter><ospf><path-type><member>ext-1</member><member>ext-2</member></path-type><tag>"
                    "<member>1.1.1.1</member><member>2.2.2.2</member></tag></ospf><type><member>"
                    "ospf</member><member>bgp</member></type><interface><member>loopback</member>"
                    "</interface></filter>",
                    "action": "set",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_create_redistribution_profile_command_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
        - Panorama instance configuration and arguments to create a redistribution-profile.
        - Firewall instance configuration and arguments to create a redistribution-profile.

        When:
        - running the pan-os-create-redistribution-profile through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-create-redistribution-profile")

        main()

        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSEditRedistributionProfile:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "priority",
                    "element_value": "50",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/"
                    "config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/priority",
                    "element": "<priority>50</priority>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_type",
                    "element_value": "bgp,ospf",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/config"
                    "/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/type",
                    "element": "<type><member>bgp</member><member>ospf</member></type>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_ospf_area",
                    "element_value": "1.1.1.1,2.2.2.2,3.3.3.3",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/config"
                    "/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/ospf/area",
                    "element": "<area><member>1.1.1.1</member><member>2.2.2.2</member><member>3.3.3.3</member></area>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_bgp_extended_community",
                    "element_value": "0x4164ACFCE33404EA",
                    "behavior": "replace",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/bgp/community",
                    "element": "<extended-community><member>0x4164ACFCE33404EA</member></extended-community>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_destination",
                    "element_value": "1.1.1.1,2.2.2.2",
                    "behavior": "replace",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/destination",
                    "element": "<destination><member>1.1.1.1</member><member>2.2.2.2</member></destination>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_redistribution_profile_command_replace_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests several cases where behavior == 'replace'

        Given:
        - Panorama instance configuration and priority object of a redistribution-profile to edit.
        - Panorama instance configuration and filter_type object of a redistribution-profile to edit.
        - Panorama instance configuration and filter_ospf_area object of a redistribution-profile to edit.
        - Firewall instance configuration and filter_bgp_extended_community object of a redistribution-profile to edit.
        - Firewall instance configuration and filter_destination object of a redistribution-profile to edit.

        When:
        - running the pan-os-edit-redistribution-profile through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-redistribution-profile")

        main()

        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_nexthop",
                    "element_value": "2.2.2.2,3.3.3.3",
                    "behavior": "add",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']"
                    "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router"
                    "/entry[@name='virtual-router']/protocol/redist-profile/entry"
                    "[@name='redistribution-profile']/filter/nexthop",
                    "element": "<nexthop><member>1.1.1.1</member><member>3.3.3.3</member><member>2.2.2.2</member></nexthop>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_nexthop",
                    "element_value": "2.2.2.2,3.3.3.3",
                    "behavior": "add",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/nexthop",
                    "element": "<nexthop><member>1.1.1.1</member><member>2.2.2.2</member><member>3.3.3.3</member></nexthop>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_redistribution_profile_command_add_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests cases where behavior == 'add'

        Given:
        - Panorama instance configuration and nexthop object of a redistribution-profile to add.
        - Firewall instance configuration and nexthop object of a redistribution-profile to add.

        When:
        - running the pan-os-edit-redistribution-profile through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "nexthop": {"member": "1.1.1.1"}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-redistribution-profile")

        main()
        assert mock_request.call_args.kwargs["params"]["xpath"] == expected_url_params["xpath"]
        assert "1.1.1.1" in mock_request.call_args.kwargs["params"]["element"]
        assert "2.2.2.2" in mock_request.call_args.kwargs["params"]["element"]
        assert "3.3.3.3" in mock_request.call_args.kwargs["params"]["element"]

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_ospf_area",
                    "element_value": "1.1.1.1",
                    "behavior": "remove",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']"
                    "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry"
                    "[@name='redistribution-profile']/filter/ospf/area",
                    "element": "<area />",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "virtual_router": "virtual-router",
                    "name": "redistribution-profile",
                    "element_to_change": "filter_ospf_area",
                    "element_value": "1.1.1.1",
                    "behavior": "remove",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                    "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']"
                    "/filter/ospf/area",
                    "element": "<area />",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_redistribution_profile_command_remove_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests cases where behavior == 'remove'

        Given:
        - Panorama instance configuration and area object of a redistribution-profile to remove.
        - Firewall instance configuration and area object of a redistribution-profile to remove.

        When:
        - running the pan-os-edit-redistribution-profile through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "area": {"member": ["1.1.1.1"]}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-redistribution-profile")

        main()
        assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"virtual_router": "virtual-router", "name": "redistribution-profile"},
            integration_panorama_params,
            {
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/template/entry[@name='test']/c"
                "onfig/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']",
                "action": "delete",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
        pytest.param(
            {"virtual_router": "virtual-router", "name": "redistribution-profile"},
            integration_firewall_params,
            {
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/network/virtual-router/entry"
                "[@name='virtual-router']/protocol/redist-profile/entry[@name='redistribution-profile']",
                "action": "delete",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
    ],
)
def test_pan_os_delete_redistribution_profile_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
    - Panorama instance configuration and arguments to delete a redistribution-profile.
    - Firewall instance configuration and arguments to delete a redistribution-profile.

    When:
    - running the pan-os-delete-redistribution-profile through the main flow.

    Then:
    - make sure the xpath and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-delete-redistribution-profile")

    main()

    assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSListPBFRulesCommand:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "true"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group"
                    "/entry[@name='Lab-Devices']/pre-rulebase/pbf",
                },
            ),
            pytest.param(
                {"show_uncommitted": "true"},
                integration_firewall_params,
                {
                    "type": "config",
                    "action": "get",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/pbf",
                },
            ),
        ],
    )
    def test_pan_os_list_pbf_command_un_committed_rules_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration and arguments to get all the un-committed PBF rules.
         - Firewall instance configuration and arguments to get all the un-committed PBF rules.

        When:
         - running the pan-os-list-pbf-rules through the main flow.

        Then:
         - make sure the context output is parsed correctly.
         - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        expected_context = [
            {
                "Name": "test",
                "Description": "this is a test description",
                "Tags": ["test tag", "dag_test_tag"],
                "SourceZone": "1.1.1.1",
                "SourceInterface": None,
                "SourceAddress": "1.1.1.1",
                "SourceUser": "pre-logon",
                "DestinationAddress": "1.1.1.1",
                "Action": {
                    "forward": {
                        "nexthop": {"ip-address": "2.2.2.2"},
                        "monitor": {"profile": "profile", "disable-if-unreachable": "no", "ip-address": "1.1.1.1"},
                        "egress-interface": "a2",
                    }
                },
                "EnforceSymmetricReturn": {"nexthop-address-list": {"entry": {"@name": "1.1.1.1"}}, "enabled": "yes"},
                "Target": {"negate": "no"},
                "Application": "3pc",
                "Service": "application-default",
                "Disabled": None,
            },
            {
                "Name": "test2",
                "Description": None,
                "Tags": None,
                "SourceZone": ["1.1.1.1", "2.2.2.2"],
                "SourceInterface": None,
                "SourceAddress": "any",
                "SourceUser": "any",
                "DestinationAddress": "any",
                "Action": {"no-pbf": {}},
                "EnforceSymmetricReturn": {"enabled": "no"},
                "Target": {"negate": "no"},
                "Application": "any",
                "Service": "any",
                "Disabled": "yes",
            },
            {
                "Name": "test3",
                "Description": None,
                "Tags": None,
                "SourceZone": None,
                "SourceInterface": "a2",
                "SourceAddress": "any",
                "SourceUser": "any",
                "DestinationAddress": "any",
                "Action": {"discard": {}},
                "EnforceSymmetricReturn": {"enabled": "no"},
                "Target": {"negate": "no"},
                "Application": "any",
                "Service": "any",
                "Disabled": "no",
            },
        ]

        mock_request = mocker.patch(
            "Panorama.http_request", return_value=load_json("test_data/list-pbf-rules-response-un-committed.json")
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-list-pbf-rules")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == expected_context
        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"pre_post": "pre-rulebase", "show_uncommitted": "false", "rulename": "test"},
                integration_panorama_params,
                {
                    "type": "config",
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']",
                },
            ),
            pytest.param(
                {"show_uncommitted": "false", "rulename": "test"},
                integration_firewall_params,
                {
                    "type": "config",
                    "action": "show",
                    "key": "thisisabogusAPIKEY!",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/pbf/rules/entry[@name='test']",
                },
            ),
        ],
    )
    def test_pan_os_list_pbf_command_committed_rules_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration and arguments to get a specific committed PBF rule.
         - Firewall instance configuration and arguments to get a specific committed PBF rule.

        When:
         - running the pan-os-list-pbf-rules through the main flow.

        Then:
         - make sure the context output is parsed correctly.
         - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        expected_context = [
            {
                "Name": "test",
                "Description": "this is a test description",
                "Tags": ["test tag", "dag_test_tag"],
                "SourceZone": "1.1.1.1",
                "SourceInterface": None,
                "Disabled": None,
                "SourceAddress": "1.1.1.1",
                "SourceUser": "pre-logon",
                "DestinationAddress": "1.1.1.1",
                "Action": {
                    "forward": {
                        "nexthop": {"ip-address": "2.2.2.2"},
                        "monitor": {"profile": "profile", "disable-if-unreachable": "no", "ip-address": "1.1.1.1"},
                        "egress-interface": "a2",
                    }
                },
                "EnforceSymmetricReturn": {"nexthop-address-list": {"entry": {"@name": "1.1.1.1"}}, "enabled": "yes"},
                "Target": {"negate": "no"},
                "Application": "3pc",
                "Service": "application-default",
            }
        ]

        mock_request = mocker.patch(
            "Panorama.http_request", return_value=load_json("test_data/list-pbf-rules-response-commited.json")
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-list-pbf-rules")
        result = mocker.patch("demistomock.results")

        main()

        assert list(result.call_args.args[0]["EntryContext"].values())[0] == expected_context
        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestCreatePBFRuleCommand:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "pre_post": "pre-rulebase",
                    "negate_source": "yes",
                    "action": "forward",
                    "egress_interface": "egress-interface",
                    "nexthop": "none",
                    "destination_address": "any",
                    "enforce_symmetric_return": "no",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']",
                    "element": "<action><forward><egress-interface>egress-interface</egress-interface></forward>"
                    "</action><enforce-symmetric-return><enabled>no</enabled></enforce-symmetric-return>"
                    "<destination><member>any</member></destination><description>test</description>"
                    "<negate-source>yes</negate-source>",
                    "action": "set",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "action": "no-pbf",
                    "source_zone": "1.1.1.1,2.2.2.2",
                    "enforce_symmetric_return": "yes",
                    "nexthop_address_list": "1.1.1.1,2.2.2.2",
                    "nexthop": "ip-address",
                    "nexthop_value": "1.1.1.1",
                },
                integration_firewall_params,
                {
                    "action": "set",
                    "element": "<action><no-pbf/></action><enforce-symmetric-return><enabled>yes</enabled>"
                    '<nexthop-address-list><entry name="1.1.1.1"/><entry name="2.2.2.2"/>'
                    "</nexthop-address-list></enforce-symmetric-return><description>test"
                    "</description><from><zone><member>1.1.1.1"
                    "</member><member>2.2.2.2</member></zone></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/pbf/rules/entry[@name='test']",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "action": "discard",
                    "destination_address": "1.1.1.1,2.2.2.2",
                    "tags": "tag1,tag2",
                    "nexthop_address_list": "1.1.1.1,2.2.2.2",
                    "nexthop": "fqdn",
                    "nexthop_value": "1.1.1.1/24",
                    "pre_post": "pre-rulebase",
                    "enforce_symmetric_return": "yes",
                    "audit_comment": "test comment",
                },
                integration_panorama_params,
                {
                    "action": "set",
                    "element": "<action><discard/></action><enforce-symmetric-return><enabled>yes</"
                    "enabled><nexthop-address-list><entry "
                    'name="1.1.1.1"/><entry name="2.2.2.2"/></nexthop-address-list>'
                    "</enforce-symmetric-return><destination>"
                    "<member>1.1.1.1</member><member>2.2.2.2</member>"
                    "</destination><description>test</description>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']",
                    "audit-comment": "test comment",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "description": "test",
                    "action": "forward",
                    "egress_interface": "egress-interface",
                    "source_zone": "all access zone external",
                    "nexthop": "none",
                    "enforce_symmetric_return": "no",
                    "audit_comment": "test comment",
                },
                integration_firewall_params,
                {
                    "action": "set",
                    "element": "<action><forward><egress-interface>egress-interface</egress-interface></forward>"
                    "</action><enforce-symmetric-return><enabled>no</enabled></enforce-symmetric-return>"
                    "<description>test</description><from><zone>"
                    "<member>all access zone external</member></zone></from>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/pbf/rules/entry[@name='test']",
                    "audit-comment": "test comment",
                },
            ),
        ],
    )
    def test_pan_os_create_pbf_rule_command_main_flow(self, mocker, args, params, expected_url_params):
        """
        Given:
         - Panorama instance configuration with forward action and egress_interface arguments.
         - Firewall instance configuration with no-pbf action and ip-address as nexthop arguments.
         - Panorama instance configuration with discard action and fqdn as nexthop arguments.
         - firewall instance configuration with basic parameter configurations along with dynamic-ip

        When:
         - running the pan-os-create-pbf-rule through the main flow.

        Then:
         - make sure the xpath/element and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-create-pbf-rule")

        main()
        assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSEditPBFRule:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "action_forward_egress_interface",
                    "element_value": "interface-1",
                    "pre_post": "pre-rulebase",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<egress-interface>interface-1</egress-interface>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']/"
                    "action/forward/egress-interface",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "action_forward_no_pbf",
                    "pre_post": "pre-rulebase",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<action><no-pbf/></action>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']/action",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "action_forward_discard",
                    "pre_post": "pre-rulebase",
                    "behavior": "replace",
                },
                integration_panorama_params,
                {
                    "action": "edit",
                    "element": "<action><discard/></action>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']/action",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "nexthop_address_list",
                    "element_value": "1.1.1.1,2.2.2.2",
                    "behavior": "replace",
                },
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": '<nexthop-address-list><entry name="1.1.1.1"/><entry name="2.2.2.2"/></nexthop-address-list>',
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/"
                    "entry[@name='vsys1']/rulebase/pbf/rules/entry[@name='test']"
                    "/enforce-symmetric-return/nexthop-address-list",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "source_zone",
                    "element_value": "1.1.1.1,2.2.2.2",
                    "behavior": "replace",
                },
                integration_firewall_params,
                {
                    "action": "edit",
                    "element": "<zone><member>1.1.1.1</member><member>2.2.2.2</member></zone>",
                    "key": "thisisabogusAPIKEY!",
                    "type": "config",
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry"
                    "[@name='vsys1']/rulebase/pbf/rules/entry[@name='test']/from/zone",
                },
            ),
        ],
    )
    def test_pan_os_edit_pbf_rule_command_replace_operation_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests several cases when behavior == 'replace'

        Given:
        - Panorama instance configuration and egress-interface object of a pbf-rule to edit.
        - Panorama instance configuration and action='no-pbf' object of a pbf-rule to edit.
        - Panorama instance configuration and action='discard' object of a pbf-rule to edit.
        - Firewall instance configuration and nexthop_address_list object of a pbf-rule to edit.
        - Firewall instance configuration and source_zone object of a pbf-rule to edit.

        When:
        - running the pan-os-edit-pbf-rule through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-pbf-rule")

        main()

        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "nexthop_address_list",
                    "element_value": "2.2.2.2,3.3.3.3",
                    "behavior": "add",
                    "pre_post": "pre-rulebase",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']/enforce-symmetric-return"
                    "/nexthop-address-list",
                    "element": '<nexthop-address-list><entry name="1.1.1.1"/><entry name="2.2.2.2"/>'
                    '<entry name="3.3.3.3"/></nexthop-address-list>',
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "nexthop_address_list",
                    "element_value": "2.2.2.2,3.3.3.3",
                    "behavior": "add",
                },
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
                    "/rulebase/pbf/rules/entry[@name='test']/enforce-symmetric-return/nexthop-address-list",
                    "element": '<nexthop-address-list><entry name="2.2.2.2"/><entry name="3.3.3.3"/>'
                    '<entry name="1.1.1.1"/></nexthop-address-list>',
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_pbf_rule_command_add_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests cases where behavior == 'add'

        Given:
        - Panorama instance configuration and nexthop-address-list object of a pbf-rule to add.
        - Firewall instance configuration and nexthop-address-list object of a pbf-rule to add.

        When:
        - running the pan-os-edit-pbf-rule through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "nexthop-address-list": {"member": "1.1.1.1"}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-pbf-rule")

        main()
        assert mock_request.call_args.kwargs["params"]["xpath"] == expected_url_params["xpath"]
        assert "1.1.1.1" in mock_request.call_args.kwargs["params"]["element"]
        assert "2.2.2.2" in mock_request.call_args.kwargs["params"]["element"]
        assert "3.3.3.3" in mock_request.call_args.kwargs["params"]["element"]

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {
                    "rulename": "test",
                    "element_to_change": "application",
                    "element_value": "application-1",
                    "behavior": "remove",
                    "pre_post": "pre-rulebase",
                },
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                    "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']/application",
                    "element": "<application><member>application-2</member></application>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {"rulename": "test", "element_to_change": "application", "element_value": "application-1", "behavior": "remove"},
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/"
                    "rulebase/pbf/rules/entry[@name='test']/application",
                    "element": "<application><member>application-2</member></application>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_pbf_rule_command_remove_action_main_flow(self, mocker, args, params, expected_url_params):
        """
        Tests cases where behavior == 'remove'

        Given:
        - Panorama instance configuration and address object of a PBF-rule to remove.
        - Firewall instance configuration and address object of a PBF-rule to remove.

        When:
        - running the pan-os-edit-pbf-rule through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        mock_request = mocker.patch(
            "Panorama.http_request",
            return_value={
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "application": {"member": ["application-1", "application-2"]}},
                }
            },
        )
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-pbf-rule")

        main()
        assert mock_request.call_args.kwargs["params"] == expected_url_params

    @staticmethod
    def test_pan_os_edit_pbf_rule_command_audit_comment_main_flow(mocker):
        """
        Given
         - panorama integrations parameters.
         - pan-os-edit-pbf-rule command arguments including device_group.
         - arguments to edit audit comment of a rule

        When -
            running the pan-os-edit-pbf-rule command through the main flow

        Then
         - make sure the context output is returned as expected.
         - make sure the device group gets overriden by the command arguments.
        """
        from Panorama import main

        mocker.patch.object(demisto, "params", return_value=integration_panorama_params)
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "rulename": "test",
                "element_to_change": "audit-comment",
                "element_value": "some string",
                "pre_post": "pre-rulebase",
                "device-group": "new device group",
            },
        )
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-pbf-rule")
        request_mock = mocker.patch(
            "Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE
        )

        res = mocker.patch("demistomock.results")
        main()

        assert request_mock.call_args.kwargs["params"] == {
            "type": "op",
            "cmd": "<set><audit-comment><xpath>/config/devices/entry[@name='localhost.localdomain']/device-group"
            "/entry[@name='new device group']/pre-rulebase/pbf/rules/entry[@name='test']"
            "</xpath><comment>some string</comment></audit-comment></set>",
            "key": "thisisabogusAPIKEY!",
        }
        assert res.call_args.args[0]["Contents"] == TestPanoramaEditRuleCommand.EDIT_AUDIT_COMMENT_SUCCESS_RESPONSE


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"rulename": "test", "pre_post": "pre-rulebase"},
            integration_panorama_params,
            {
                "action": "delete",
                "key": "thisisabogusAPIKEY!",
                "type": "config",
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/device-group/entry"
                "[@name='Lab-Devices']/pre-rulebase/pbf/rules/entry[@name='test']",
            },
        ),
        pytest.param(
            {"rulename": "test"},
            integration_firewall_params,
            {
                "action": "delete",
                "key": "thisisabogusAPIKEY!",
                "type": "config",
                "xpath": "/config/devices/entry[@name='localhost.localdomain']/vsys/entry"
                "[@name='vsys1']/rulebase/pbf/rules/entry[@name='test']",
            },
        ),
    ],
)
def test_pan_os_delete_pbf_rule_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance configuration with a specific rulename.
     - Firewall instance configuration with a specific rulename.

    When:
     - running the pan-os-delete-pbf-rule through the main flow.

    Then:
     - make sure the xpath/element and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-delete-pbf-rule")

    main()
    assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"name": "address", "element_to_change": "fqdn", "element_value": "1.1.1.1"},
            integration_panorama_params,
            {
                "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']/address" '/entry[@name="address"]/fqdn',  # noqa: ISC001
                "element": "<fqdn>1.1.1.1</fqdn>",
                "action": "edit",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
        pytest.param(
            {"name": "address", "element_to_change": "ip_range", "element_value": "1.1.1.1-1.1.1.8"},
            integration_panorama_params,
            {
                "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']"
                '/address/entry[@name="address"]/ip-range',
                "element": "<ip-range>1.1.1.1-1.1.1.8</ip-range>",
                "action": "edit",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
        pytest.param(
            {"name": "address", "element_to_change": "tag", "element_value": "tag1,tag2"},
            integration_firewall_params,
            {
                "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/address/entry[@name=\"address\"]/tag",
                "element": "<tag><member>tag1</member><member>tag2</member></tag>",
                "action": "edit",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
    ],
)
def test_pan_os_edit_address_group_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
    - Panorama instance configuration and fqdn object of an address to edit.
    - Panorama instance configuration and ip-range object of an address to edit.
    - Firewall instance configuration and tag object of an address to edit.

    When:
    - running the pan-os-edit-address through the main flow.

    Then:
    - make sure the xpath and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-edit-address")

    main()

    assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"show_uncommitted": "true"},
            integration_panorama_params,
            {
                "type": "config",
                "action": "get",
                "key": "thisisabogusAPIKEY!",
                "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']/application-group",
            },
        ),
        pytest.param(
            {"show_uncommitted": "true"},
            integration_firewall_params,
            {
                "type": "config",
                "action": "get",
                "key": "thisisabogusAPIKEY!",
                "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/application-group",
            },
        ),
    ],
)
def test_pan_os_list_application_groups_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance configuration to retrieve all un-committed applications-groups.
     - Firewall instance configuration to retrieve all un-committed applications-groups.

    When:
     - running the pan-os-list-application-groups through the main flow.

    Then:
     - make sure the context output is parsed correctly for both un-committed and committed cases.
     - make sure the xpath and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value=load_json("test_data/list_application_groups_un_committed.json")
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-list-application-groups")
    result = mocker.patch("demistomock.results")

    main()

    assert list(result.call_args.args[0]["EntryContext"].values())[0] == [
        {"Applications": ["application-3"], "Members": 1, "Name": "test"},
        {"Applications": ["application-1", "application-2"], "Members": 2, "Name": "test-2"},
    ]
    assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"name": "test", "applications": "application1,application2", "device-group": "test-device-group"},
            integration_panorama_params,
            {
                "xpath": "/config/devices/entry/device-group/entry[@name='test-device-group']"
                "/application-group/entry[@name='test']",
                "element": "<members><member>application1</member><member>application2</member></members>",
                "action": "set",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
        pytest.param(
            {"name": "test", "applications": "application1,application2"},
            integration_firewall_params,
            {
                "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/application-group/entry[@name='test']",
                "element": "<members><member>application1</member><member>application2</member></members>",
                "action": "set",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
    ],
)
def test_pan_os_create_application_group_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance configuration and arguments to create an application group.
     - Firewall instance configuration and arguments to create an application group.

    When:
     - running the pan-os-create-application-group through the main flow.

    Then:
     - make sure the xpath and the request is correct for both panorama/firewall.
     - make sure the context is returned correctly.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-create-application-group")
    result = mocker.patch("demistomock.results")

    main()
    assert list(result.call_args.args[0]["EntryContext"].values())[0] == {
        "Name": "test",
        "Applications": ["application1", "application2"],
        "Members": 2,
    }
    assert mock_request.call_args.kwargs["params"] == expected_url_params


class TestPanOSEditApplicationGroupCommand:
    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"name": "test", "applications": "application-2", "action": "add"},
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']"
                    "/application-group/entry[@name='test']/members",
                    "element": "<members><member>application-1</member><member>application-2</member></members>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {"name": "test", "applications": "application-2", "action": "add"},
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/application-group/entry[@name='test']/members",
                    "element": "<members><member>application-2</member><member>application-1</member></members>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_application_group_main_flow_add_action(self, mocker, args, params, expected_url_params):
        """
        Tests cases where action == 'add'

        Given:
        - Panorama instance configuration and applications object of an application-group to add.
        - Firewall instance configuration and applications object of an application-group to add.

        When:
        - running the pan-os-edit-application-group through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        responses = [
            {
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "members": {"member": "application-1"}},
                }
            },
            {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}},
            {
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {
                        "@total-count": "1",
                        "@count": "1",
                        "members": {
                            "@admin": "admin",
                            "@dirtyId": "809",
                            "@time": "2022/09/14 04:12:11",
                            "member": [
                                {"@admin": "admin", "@dirtyId": "809", "@time": "2022/09/14 04:12:11", "#text": "application-1"},
                                {"@admin": "admin", "@dirtyId": "809", "@time": "2022/09/14 04:12:11", "#text": "application-2"},
                            ],
                        },
                    },
                }
            },
        ]

        mock_request = mocker.patch("Panorama.http_request", side_effect=responses)
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-application-group")

        main()
        assert mock_request.mock_calls[1].kwargs["params"]["xpath"] == expected_url_params["xpath"]
        assert "application-1" in mock_request.mock_calls[1].kwargs["params"]["element"]
        assert "application-2" in mock_request.mock_calls[1].kwargs["params"]["element"]

    @pytest.mark.parametrize(
        "args, params, expected_url_params",
        [
            pytest.param(
                {"name": "test", "applications": "application-2", "action": "remove"},
                integration_panorama_params,
                {
                    "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']/application-group/"
                    "entry[@name='test']/members",
                    "element": "<members><member>application-1</member></members>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
            pytest.param(
                {"name": "test", "applications": "application-2", "action": "remove"},
                integration_firewall_params,
                {
                    "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/application-group/entry[@name='test']/members",
                    "element": "<members><member>application-1</member></members>",
                    "action": "edit",
                    "type": "config",
                    "key": "thisisabogusAPIKEY!",
                },
            ),
        ],
    )
    def test_pan_os_edit_application_group_main_flow_remove_action(self, mocker, args, params, expected_url_params):
        """
        Tests cases where action == 'remove'

        Given:
        - Panorama instance configuration and an application object of an application-group to remove.
        - Firewall instance configuration and an application object of an application-group to remove.

        When:
        - running the pan-os-edit-application-group through the main flow.

        Then:
        - make sure the xpath and the request is correct for both panorama/firewall.
        """
        from Panorama import main

        responses = [
            {
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {"@total-count": "1", "@count": "1", "members": {"member": ["application-1", "application-2"]}},
                }
            },
            {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}},
            {
                "response": {
                    "@status": "success",
                    "@code": "19",
                    "result": {
                        "@total-count": "1",
                        "@count": "1",
                        "members": {
                            "@admin": "admin",
                            "@dirtyId": "809",
                            "@time": "2022/09/14 04:12:11",
                            "member": [
                                {"@admin": "admin", "@dirtyId": "809", "@time": "2022/09/14 04:12:11", "#text": "application-1"}
                            ],
                        },
                    },
                }
            },
        ]

        mock_request = mocker.patch("Panorama.http_request", side_effect=responses)
        mocker.patch.object(demisto, "params", return_value=params)
        mocker.patch.object(demisto, "args", return_value=args)
        mocker.patch.object(demisto, "command", return_value="pan-os-edit-application-group")

        main()
        assert mock_request.mock_calls[1].kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"name": "test", "applications": "application-2", "action": "remove"},
            integration_panorama_params,
            {
                "xpath": "/config/devices/entry/device-group/entry[@name='Lab-Devices']/application-group/entry[@name='test']",
                "action": "delete",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
        pytest.param(
            {"name": "test", "applications": "application-2", "action": "remove"},
            integration_firewall_params,
            {
                "xpath": "/config/devices/entry/vsys/entry[@name='vsys1']/application-group/entry[@name='test']",
                "action": "delete",
                "type": "config",
                "key": "thisisabogusAPIKEY!",
            },
        ),
    ],
)
def test_pan_os_delete_application_group_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance with a name of the application-group to delete.
     - Firewall instance with a name of the application-group to delete.

    When:
     - running the pan-os-delete-application-group through the main flow.

    Then:
     - make sure the xpath/element and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request", return_value={"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    )
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-delete-application-group")

    main()
    assert mock_request.call_args.kwargs["params"] == expected_url_params


@pytest.mark.parametrize(
    "args, params, expected_url_params",
    [
        pytest.param(
            {"IPs": "2.2.2.2", "tag": "test"},
            integration_firewall_params,
            {
                "type": "user-id",
                "cmd": '<uid-message><version>2.0</version><type>update</type><payload><register><entry ip="2.2.2.2" '
                'persistent="1"><tag><member>test</member></tag></entry></register></payload></uid-message>',
                "key": "thisisabogusAPIKEY!",
                "vsys": "vsys1",
            },
        ),
        pytest.param(
            {"IPs": "2.2.2.2", "tag": "test"},
            integration_panorama_params,
            {
                "type": "user-id",
                "cmd": '<uid-message><version>2.0</version><type>update</type><payload><register><entry ip="2.2.2.2" '
                'persistent="1"><tag><member>test</member></tag></entry></register></payload></uid-message>',
                "key": "thisisabogusAPIKEY!",
            },
        ),
    ],
)
def test_pan_os_register_ip_tag_command_main_flow(mocker, args, params, expected_url_params):
    """
    Given:
     - Panorama instance with IP tag to register (without vsys).
     - Firewall instance with IP tag to register (with vsys).

    When:
     - running the pan-os-register-ip-tag through the main flow.

    Then:
     - make sure the params and the request is correct for both panorama/firewall.
    """
    from Panorama import main

    mock_request = mocker.patch(
        "Panorama.http_request",
        return_value={
            "response": {"@status": "success", "result": {"uid-response": {"version": "2.0", "payload": {"register": None}}}}
        },
    )
    mocker.patch("Panorama.get_pan_os_major_version", return_value=9)
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=args)
    mocker.patch.object(demisto, "command", return_value="pan-os-register-ip-tag")

    main()
    assert mock_request.call_args.kwargs["body"] == expected_url_params


@pytest.mark.parametrize(
    "args",
    [
        {"ip_netmask": "1", "ip_range": "2", "fqdn": "3", "ip_wildcard": "4", "name": "test"},
        {"ip_netmask": "1", "ip_range": "2", "fqdn": "3", "name": "test"},
        {"ip_netmask": "1", "ip_range": "2", "name": "test"},
        {"ip_netmask": "1", "fqdn": "3", "name": "test"},
        {"ip_range": "2", "fqdn": "3", "name": "test"},
        {"ip_range": "2", "fqdn": "3", "ip_wildcard": "4", "name": "test"},
    ],
)
def test_pan_os_create_address_main_flow_error(args):
    """
    Given:
     - more than one ip_netmask/ip_range/fqdn/ip_wildcard as command arguments

    When:
     - running the panorama_create_address_command function

    Then:
     - make sure an exception is raised saying only one of ip_netmask/ip_range/fqdn/ip_wildcard can
        be the command input.
    """
    from Panorama import panorama_create_address_command

    with pytest.raises(DemistoException):
        panorama_create_address_command(args)


@pytest.mark.parametrize(
    "device_group, vsys, response, args, error",
    [
        (
            "test",
            "",
            '<response status="success" code="19"> \
                <result total-count="0" count="0"> \
                </result> \
            </response>',
            {"name": "test", "tag": "not exist"},
            "Failed to create the address object since the tags `{'not exist'}` does not exist. "
            "You can use the `create_tag` argument to create the tag.",
        ),
        (
            "",
            "vsys1",
            '<response status="success" code="19"> \
                <result total-count="1" count="1"> \
                    <tag admin="admin" dirtyId="3" time="2023/04/23 01:41:22"> \
                        <entry name="exist" admin="admin" dirtyId="3" time="2023/04/23 01:18:03"/> \
                    </tag> \
                </result> \
            </response>',
            {"name": "test", "tag": "exist, not exist", "create_tag": "Yes"},
            "Please specify exactly one of the following arguments: fqdn, ip_netmask, ip_range, ip_wildcard.",
        ),
    ],
)
def test_pan_os_create_address_with_not_exist_tag(mocker, device_group, vsys, response, args, error):
    """
    Given:
     - Tags that does not exist in the system as command arguments

    When:
     - Running the panorama_create_address_command function

    Then:
     - Make sure an exception is raised saying only tags that already exist in system can be the command input.
    """
    from Panorama import panorama_create_address_command

    mocker.patch("Panorama.DEVICE_GROUP", device_group)
    mocker.patch("Panorama.VSYS", vsys)
    mocker.patch("Panorama.URL", "https://example.com")

    with requests_mock.Mocker() as m:
        m.get("https://example.com", text=response, status_code=200)
        m.post("https://example.com", text=response, status_code=200)

        with pytest.raises(DemistoException) as e:
            panorama_create_address_command(args)

        assert e.value.message == error


""" FETCH INCIDENTS """


class TestFetchIncidentsHelperFunctions:
    @pytest.mark.parametrize(
        "query, last_fetch, expected_result", fetch_incidents_input.test_add_time_filter_to_query_parameter_args
    )
    def test_add_time_filter_to_query_parameter(self, query, last_fetch, expected_result):
        """
        Given:
            - a query from parameters
        When:
            - every fetch incidents cycle starts
        Then:
            - add_time_filter_to_query_parameter function will append time_generated parameter to the original query to filleter
              according to the queries log type last fetch time.
        """
        from Panorama import add_time_filter_to_query_parameter

        assert add_time_filter_to_query_parameter(query, last_fetch, "time_generated") == expected_result

    @pytest.mark.parametrize("params, expected_result", fetch_incidents_input.test_parse_queries_args)
    def test_log_types_queries_to_dict(self, params, expected_result):
        """
        Given:
        - valid parameters dictionary

        When:
        - test_log_types_queries_to_dict function is called

        Then:
        - assert that the returned queries_dict value is valid
        """
        from Panorama import log_types_queries_to_dict

        assert log_types_queries_to_dict(params) == expected_result

    def test_incident_entry_to_incident_context(self):
        """
        Given:
        - raw incident entry represented by a dictionary

        When:
        - incident_entry_to_incident_context function is called

        Then:
        - assert that the returned context formatted incident entry is valid
        """
        from Panorama import DATE_FORMAT, incident_entry_to_incident_context

        raw_entry = {"seqno": "1", "time_generated": "2022/01/01 12:00", "type": "TYPE", "device_name": "dummy_device"}
        if occured := dateparser.parse("2022/01/01 12:00", settings={"TIMEZONE": "UTC"}):
            context_entry = {
                "name": "dummy_device 1",
                "occurred": occured.strftime(DATE_FORMAT),
                "rawJSON": json.dumps(raw_entry),
            }
        assert incident_entry_to_incident_context(raw_entry) == context_entry

    @pytest.mark.parametrize(
        "last_fetch_dict, first_fetch, queries_dict, expected_result",
        fetch_incidents_input.test_get_fetch_start_datetime_dict_args,
    )
    @freeze_time("2022-01-02 11:00:00 UTC")
    def test_get_fetch_start_datetime_dict(self, last_fetch_dict, first_fetch, queries_dict, expected_result):
        """
        Given:
        - last fetch dictionary
        - first fetch parameter
        - queries dictionary from parameters

        When:
        - get_fetch_start_datetime_dict function is called

        Then:
        - assert that the updated dictionary with fetch start time per log_type is valid
        """
        from Panorama import get_fetch_start_datetime_dict

        result_dict = get_fetch_start_datetime_dict(last_fetch_dict, first_fetch, queries_dict)
        assert fetch_incidents_input.assert_datetime_objects(result_dict.get("X_log_type"), expected_result.get("X_log_type"))
        assert fetch_incidents_input.assert_datetime_objects(result_dict.get("Y_log_type"), expected_result.get("Y_log_type"))

    @pytest.mark.parametrize("incident_entries, expected_result", fetch_incidents_input.test_parse_incident_entries_args)
    def test_get_parsed_incident_entries(self, incident_entries, expected_result):
        from Panorama import LastFetchTimes, LastIDs, get_parsed_incident_entries

        last_id_dict = LastIDs()
        last_fetch_dict = LastFetchTimes(Url="2022/01/01 12:00:00")

        res = get_parsed_incident_entries({"Url": incident_entries}, last_fetch_dict, last_id_dict)

        assert last_id_dict.get("Url") == expected_result[0]
        assert last_fetch_dict["Url"] == expected_result[1]  # type: ignore
        assert res == expected_result[2]

    @pytest.mark.parametrize(
        "incident_entries_dict, last_fetch_dict, last_id_dict, expected_result",
        fetch_incidents_input.get_parsed_incident_entries_args,
    )
    def test_get_parsed_incident_entries_2(self, mocker, incident_entries_dict, last_fetch_dict, last_id_dict, expected_result):
        from Panorama import get_parsed_incident_entries

        assert get_parsed_incident_entries(incident_entries_dict, last_fetch_dict, last_id_dict) == expected_result

    @pytest.mark.parametrize("response, debug_msg, expected_result", fetch_incidents_input.get_query_entries_by_id_request_args)
    def test_get_query_entries_by_id_request(self, mocker, response, debug_msg, expected_result):
        """
        Given:
            - A valid Panorama job id.

        When:
            1. The Panorama job has already finished.
            2. The Panorama job is still running (not finished).

        Then:
            1. Verify the command output is the returned response, and the debug message is called with 'FIN' status.
            2. Retry to query the job status in 1 second, and return empty dict if max retries exceeded.
        """
        from Panorama import get_query_entries_by_id_request

        mocker.patch("Panorama.http_request", return_value=response)
        assert get_query_entries_by_id_request("000", 1) == expected_result


class TestFetchIncidentsFlows:
    def test_first_fetch_with_no_incidents_flow(self, mocker):
        """
        Given:
        - first fetch cycle.

        When:
        - no incident returned from fetch request.

        Then:
        - no incidents should be returned.
        """

        from Panorama import fetch_incidents

        last_run = {}
        first_fetch = "24 hours"
        queries_dict = {"X_log_type": "(receive_time geq '2021/01/22 08:00:00)"}
        max_fetch = {"X_log_type": 10}

        mocker.patch("Panorama.get_query_entries", return_value={})

        new_last_run, incident_entries_list = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_list == []
        assert new_last_run["last_fetch_dict"] == {"X_log_type": ""}
        assert new_last_run["last_id_dict"] == {}

    def test_first_fetch_with_one_incident_flow(self, mocker):
        """
        Given:
        - first fetch cycle.

        When:
        - using fetch incidents.
        - one incident returned from fetch request.

        Then:
        - The only incident should be returned.
        - X_log_type last fetch should be created.
        - X_log_type last id should be created.
        """
        from Panorama import fetch_incidents

        last_run = {}
        first_fetch = "24 hours"
        queries_dict = {"X_log_type": "(receive_time geq '2021/01/01 08:00:00)"}
        max_fetch = {"X_log_type": 10}

        raw_entries = {
            "seqno": "000000001",
            "type": "X_log_type",
            "time_generated": "2022/1/1 12:00:00",
            "device_name": "device_for_test",
        }
        expected_parsed_incident_entries = {
            "name": "device_for_test 000000001",
            "occurred": "2022-01-01T12:00:00Z",
            "rawJSON": json.dumps(raw_entries),
        }
        fetch_start_datetime_dict = {"X_log_type": dateparser.parse("2022/1/1 11:00:00", settings={"TIMEZONE": "UTC"})}

        mocker.patch("Panorama.get_query_entries", return_value=[raw_entries])
        mocker.patch("Panorama.get_fetch_start_datetime_dict", return_value=fetch_start_datetime_dict)

        new_last_run, incident_entries_dict = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_dict[0] == expected_parsed_incident_entries
        assert new_last_run["last_fetch_dict"].get("X_log_type", "") == "2022-01-01 12:00:00"
        assert new_last_run["last_id_dict"].get("X_log_type", "") == {"device_for_test": "000000001"}

    def test_second_fetch_with_no_incidents_flow(self, mocker):
        """
        Given:
        - second fetch cycle.

        When:
        - using fetch incidents.
        - no new incidents are returned from the fetch request.

        Then:
        - no fetch incidents should be returned.
        - last_fetch_dict X_log_type value should not be updated.
        - last_id_dict X_log_type value should not be updated.
        """
        from Panorama import fetch_incidents

        last_run = {"last_fetch_dict": {"X_log_type": "2022-01-01T12:00:00"}, "last_id_dict": {"X_log_type": "000000001"}}
        first_fetch = "24 hours"
        queries_dict = {"X_log_type": "(receive_time geq '2021/01/01 08:00:00)"}
        max_fetch = {"X_log_type": 10}

        raw_entries = []
        expected_parsed_incident_entries = []

        fetch_start_datetime_dict = {"X_log_type": dateparser.parse("2022/1/1 12:00:00", settings={"TIMEZONE": "UTC"})}

        mocker.patch("Panorama.get_query_entries", return_value=raw_entries)
        mocker.patch("Panorama.get_fetch_start_datetime_dict", return_value=fetch_start_datetime_dict)

        new_last_run, incident_entries_dict = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_dict == expected_parsed_incident_entries
        assert new_last_run["last_fetch_dict"].get("X_log_type", "") == "2022-01-01T12:00:00"
        assert new_last_run["last_id_dict"].get("X_log_type", "") == "000000001"

    def test_second_fetch_with_two_incidents_with_same_log_type_flow(self, mocker):
        """
        Given:
        - second fetch cycle with.

        When:
        - using fetch incidents.
        - one incident with an existing log type is returned (X_log_type).
        - the incident has a time generated value that is greater than last fetch time.

        Then:
        - the fetched incident should be returned.
        - last_fetch_dict X_log_type value should be updated.
        - last_id_dict X_log_type value should be updated.
        """
        from Panorama import fetch_incidents

        last_run = {
            "last_fetch_dict": {"X_log_type": "2022-01-01 13:00:00"},
            "last_id_dict": {"X_log_type": {"dummy_device": "000000001"}},
        }
        first_fetch = "24 hours"
        queries_dict = {"X_log_type": "(receive_time geq '2021/01/01 08:00:00)"}
        max_fetch = {"X_log_type": 10}

        raw_entries = [
            {"seqno": "000000002", "type": "X_log_type", "time_generated": "2022/1/1 13:00:00", "device_name": "dummy_device"}
        ]

        expected_parsed_incident_entries = [
            {"name": "dummy_device 000000002", "occurred": "2022-01-01T13:00:00Z", "rawJSON": json.dumps(raw_entries[0])}
        ]
        fetch_start_datetime_dict = {"X_log_type": dateparser.parse("2022/1/1 12:00:00", settings={"TIMEZONE": "UTC"})}

        mocker.patch("Panorama.get_query_entries", return_value=raw_entries)
        mocker.patch("Panorama.get_fetch_start_datetime_dict", return_value=fetch_start_datetime_dict)

        new_last_run, incident_entries_dict = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_dict == expected_parsed_incident_entries
        assert new_last_run["last_fetch_dict"].get("X_log_type", "") == "2022-01-01 13:00:00"
        assert new_last_run["last_id_dict"].get("X_log_type", "") == {"dummy_device": "000000002"}

    def test_second_fetch_with_two_incidents_with_different_log_types_flow(self, mocker):
        """
        Given:
        - second fetch cycle.

        When:
        - using fetch incidents.
        - two incidents of two deferent log types (X_log_type, Y_log_type) are returned from the fetch time.
        - both incidents has the same generated time that is later than the last fetch run time.
        - one incident of X_log_type already have a last fetch run and last id, the second incident of type Y_log_Type don't.

        Then:
        - both incidents should be returned.
        - Y_log_type last fetch should be created.
        - Y_log_type last id is created.
        - X_log_type last fetch time will be updated.
        - X_log_type last id is updated.
        """
        from Panorama import fetch_incidents

        last_run = {
            "last_fetch_dict": {"X_log_type": "2022-01-01 12:00:00"},
            "last_id_dict": {"X_log_type": {"dummy_device1": "000000001"}},
        }
        first_fetch = "24 hours"
        queries_dict = {
            "X_log_type": "(receive_time geq '2021/01/01 08:00:00)",
            "Y_log_type": "(receive_time geq '2021/01/01 08:00:00)",
        }
        max_fetch = {"X_log_type": 10}

        raw_entries = [
            {"seqno": "000000002", "type": "X_log_type", "time_generated": "2022-01-01 13:00:00", "device_name": "dummy_device1"},
            {"seqno": "000000001", "type": "Y_log_type", "time_generated": "2022-01-01 13:00:00", "device_name": "dummy_device2"},
        ]

        fetch_incidents_request_result = {"X_log_type": [raw_entries[0]], "Y_log_type": [raw_entries[1]]}

        expected_parsed_incident_entries = [
            {"name": "dummy_device1 000000002", "occurred": "2022-01-01T13:00:00Z", "rawJSON": json.dumps(raw_entries[0])},
            {"name": "dummy_device2 000000001", "occurred": "2022-01-01T13:00:00Z", "rawJSON": json.dumps(raw_entries[1])},
        ]
        fetch_start_datetime_dict = {
            "X_log_type": dateparser.parse("2022/1/1 11:00:00", settings={"TIMEZONE": "UTC"}),
            "Y_log_type": dateparser.parse("2022/1/1 11:00:00", settings={"TIMEZONE": "UTC"}),
        }

        mocker.patch("Panorama.fetch_incidents_request", return_value=fetch_incidents_request_result)
        mocker.patch("Panorama.get_fetch_start_datetime_dict", return_value=fetch_start_datetime_dict)

        new_last_run, incident_entries_dict = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_dict == expected_parsed_incident_entries
        assert new_last_run["last_fetch_dict"].get("X_log_type", "") == "2022-01-01 13:00:00"
        assert new_last_run["last_id_dict"].get("X_log_type", "") == {"dummy_device1": "000000002"}
        assert new_last_run["last_fetch_dict"].get("Y_log_type", "") == "2022-01-01 13:00:00"
        assert new_last_run["last_id_dict"].get("Y_log_type", "") == {"dummy_device2": "000000001"}

    def test_second_fetch_with_two_incidents_with_different_log_types_and_different_last_fetch_flow(self, mocker):
        """
        Given:
        - second fetch cycle.

        When:
        - using fetch incidents.
        - two incidents of two deferent log types (X_log_type, Y_log_type) are returned from the fetch time.
        - both incidents has the same generated time that is later than the last fetch run time.
        - both incidents log types has a last fetch run and last id.

        Then:
        - both incidents should be returned.
        - Y_log_type last fetch should be created.
        - Y_log_type last id is created.
        - X_log_type last fetch time will be updated.
        - X_log_type last id is updated.
        """
        from Panorama import fetch_incidents

        last_run = {
            "last_fetch_dict": {"X_log_type": "2022-01-01 11:00:00", "Y_log_type": "2022-01-01 13:00:00"},
            "last_id_dict": {"X_log_type": {"dummy_device1": "000000001"}, "Y_log_type": {"dummy_device2": "000000002"}},
        }
        first_fetch = "24 hours"
        queries_dict = {
            "X_log_type": "(receive_time geq '2021/01/01 08:00:00)",
            "Y_log_type": "(receive_time geq '2021/01/01 08:00:00)",
        }
        max_fetch = {"X_log_type": 10}

        X_log_type_raw_entries = [
            {"seqno": "000000002", "type": "X_log_type", "time_generated": "2022-01-01 13:00:00", "device_name": "dummy_device1"}
        ]
        Y_log_type_raw_entries = [
            {"seqno": "000000003", "type": "Y_log_type", "time_generated": "2022-01-01 13:00:00", "device_name": "dummy_device2"}
        ]
        fetch_incidents_request_result = {"X_log_type": X_log_type_raw_entries, "Y_log_type": Y_log_type_raw_entries}

        expected_parsed_incident_entries = [
            {
                "name": "dummy_device1 000000002",
                "occurred": "2022-01-01T13:00:00Z",
                "rawJSON": json.dumps(X_log_type_raw_entries[0]),
            },
            {
                "name": "dummy_device2 000000003",
                "occurred": "2022-01-01T13:00:00Z",
                "rawJSON": json.dumps(Y_log_type_raw_entries[0]),
            },
        ]
        fetch_start_datetime_dict = {
            "X_log_type": dateparser.parse("2022/1/1 11:00:00", settings={"TIMEZONE": "UTC"}),
            "Y_log_type": dateparser.parse("2022/1/1 11:00:00", settings={"TIMEZONE": "UTC"}),
        }

        mocker.patch("Panorama.fetch_incidents_request", return_value=fetch_incidents_request_result)
        mocker.patch("Panorama.get_fetch_start_datetime_dict", return_value=fetch_start_datetime_dict)

        new_last_run, incident_entries_dict = fetch_incidents(last_run, first_fetch, queries_dict, max_fetch, 5)

        assert incident_entries_dict == expected_parsed_incident_entries
        assert new_last_run["last_fetch_dict"].get("X_log_type", "") == "2022-01-01 13:00:00"
        assert new_last_run["last_id_dict"].get("X_log_type", "") == {"dummy_device1": "000000002"}
        assert new_last_run["last_fetch_dict"].get("Y_log_type", "") == "2022-01-01 13:00:00"
        assert new_last_run["last_id_dict"].get("Y_log_type", "") == {"dummy_device2": "000000003"}


def test_find_largest_id_per_device(mocker):
    """
    Given:
    - list of dictionaries representing raw entries, some contain seqno and some don't, some contain device_name and
    When:
        - find_largest_id_per_device is called.
    Then:
        - return a dictionary with the largest id per device and skip entries that don't contain seqno or device_name.
    """
    raw_entries = [
        {"device_name": "dummy_device1", "seqno": "000000001"},
        {"device_name": "dummy_device1", "seqno": "000000002"},
        {"device_name": "dummy_device2", "seqno": "000000001"},
        {"device_name": "dummy_device7"},
        {"seqno": "000000008"},
    ]
    from Panorama import find_largest_id_per_device

    res = find_largest_id_per_device(raw_entries)
    assert res == {"dummy_device1": "000000002", "dummy_device2": "000000001"}


def test_filter_fetched_entries(mocker):
    """
    Given:
    - list of dictionaries representing raw entries with seqno, device_name, and time_generated fields
    - dictionary with the largest id per device (id_dict)
    - dictionary with last fetch times per log type (last_fetch_dict)

    When:
    - filter_fetched_entries is called with the new timestamp-based filtering logic

    Then:
    - return a dictionary with entries filtered based on:
      1. Logs with time_generated > last_fetch_time are always kept (no seqno comparison)
      2. Logs with time_generated == last_fetch_time are kept only if seqno > last_id
      3. Logs missing required fields (seqno, device_name, time_generated) are skipped
      4. Correlation logs are filtered by @logid instead of seqno
    """
    from Panorama import LastFetchTimes, LastIDs, filter_fetched_entries

    # Test case 1: Regular log types with timestamp-based filtering
    raw_entries = {
        "log_type1": [
            # Missing time_generated - should be skipped
            {"device_name": "dummy_device1", "seqno": "000000001"},
            # time_generated > last_fetch_time - should be kept regardless of seqno
            {"device_name": "dummy_device1", "seqno": "000000002", "time_generated": "2022-01-01 14:00:00"},
            # time_generated == last_fetch_time, seqno > last_id - should be kept
            {"device_name": "dummy_device2", "seqno": "000000002", "time_generated": "2022-01-01 13:00:00"},
            # time_generated == last_fetch_time, seqno <= last_id - should be filtered out
            {"device_name": "dummy_device2", "seqno": "000000001", "time_generated": "2022-01-01 13:00:00"},
        ],
        "log_type2": [
            # New log type with no previous fetch - should be kept
            {"device_name": "dummy_device3", "seqno": "000000004", "time_generated": "2022-01-01 12:00:00"},
            # Missing device_name - should be skipped
            {"seqno": "000000007", "time_generated": "2022-01-01 12:00:00"},
        ],
    }

    id_dict = LastIDs(
        log_type1={"dummy_device1": "000000003", "dummy_device2": "000000001"}  # type: ignore[typeddict-item]
    )

    last_fetch_dict = LastFetchTimes(
        log_type1="2022-01-01 13:00:00"  # type: ignore[typeddict-item]
    )

    res = filter_fetched_entries(raw_entries, id_dict, last_fetch_dict)

    # Expected: only entries that pass the new filtering logic
    assert res == {
        "log_type1": [
            # Entry with time_generated > last_fetch_time (14:00 > 13:00)
            {"device_name": "dummy_device1", "seqno": "000000002", "time_generated": "2022-01-01 14:00:00"},
            # Entry with time_generated == last_fetch_time and seqno (2) > last_id (1)
            {"device_name": "dummy_device2", "seqno": "000000002", "time_generated": "2022-01-01 13:00:00"},
        ],
        "log_type2": [
            # New log type - all valid entries are kept
            {"device_name": "dummy_device3", "seqno": "000000004", "time_generated": "2022-01-01 12:00:00"}
        ],
    }

    # Test case 2: Correlation log type (uses @logid instead of seqno)
    corr_entries = {
        "Correlation": [
            {"@logid": "1", "match_time": "2022-01-01 12:00:00"},
            {"@logid": "2", "match_time": "2022-01-01 13:00:00"},
            {"@logid": "3", "match_time": "2022-01-01 14:00:00"},
        ]
    }

    corr_id_dict = LastIDs(Correlation=1)  # type: ignore[typeddict-item]
    corr_last_fetch_dict = LastFetchTimes()

    corr_res = filter_fetched_entries(corr_entries, corr_id_dict, corr_last_fetch_dict)

    # Expected: only entries with @logid > 1
    assert corr_res == {
        "Correlation": [
            {"@logid": "2", "match_time": "2022-01-01 13:00:00"},
            {"@logid": "3", "match_time": "2022-01-01 14:00:00"},
        ]
    }


@pytest.mark.parametrize("name_match, name_contain, filters, expected_result", mock_rules.get_mock_rules_and_application)
def test_build_xpath_filter(name_match, name_contain, filters, expected_result):
    from Panorama import build_xpath_filter

    mock_result = build_xpath_filter(name_match, name_contain, filters)
    assert mock_result == expected_result


@pytest.mark.parametrize(
    "sample_file, expected_result_file",
    [
        ("test_data/prettify_edls_arr_sample.json", "test_data/prettify_edls_arr_expected_result.json"),
    ],
)
def test_prettify_edls_arr(sample_file, expected_result_file):
    """
    Given:
    - raw response from api represented by a dictionary

    When:
    - calling panorama_list_edls and there is only one edl in response

    Then:
    - assert that the returned value after prettify is correct
    """
    from Panorama import prettify_edls_arr

    with open(sample_file) as f:
        sample = json.loads(f.read())

    with open(expected_result_file) as f:
        expected_result = json.loads(f.read())

    mock_result = prettify_edls_arr(sample)
    assert mock_result == expected_result


def test_panorama_list_rules():
    import Panorama

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    mock_version_xml = """
    <response status="success" code="19">
        <result total-count="1" count="1">
            <entry name="hehe 2">
                <to>
                    <member>any</member>
                </to>
                <from>
                    <member>any</member>
                </from>
                <source>
                    <member>any</member>
                </source>
                <destination>
                    <member>any</member>
                </destination>
                <source-user>
                    <member>any</member>
                </source-user>
                <category>
                    <member>any</member>
                </category>
                <application>
                    <member>dns</member>
                    <member>http</member>
                </application>
                <service>
                    <member>application-default</member>
                </service>
                <hip-profiles>
                    <member>any</member>
                </hip-profiles>
                <action>allow</action>
            </entry>
        </result>
    </response>
    """
    xpath = "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry"
    query = "(application/member eq 'dns')"

    with requests_mock.Mocker() as m:
        mock_request = m.get("https://1.1.1.1:443", text=mock_version_xml, status_code=200)
        rules = Panorama.panorama_list_rules(xpath, query=query)

    assert rules["application"]["member"][0] == "dns"
    assert (
        mock_request.last_request.qs["xpath"][0]
        == "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[(application/member = 'dns')]"
    )


def test_prettify_rules():
    """
    Given:
        - rule entry.
    When:
        - Running the prettify_rules method.
    Then:
        - Ensure no errors are raised.
    """
    from Panorama import prettify_rules

    test_rule = {
        "@name": "test",
        "@uuid": "11111-111-111-11",
        "source": {"@loc": "test", "member": [{"@loc": "test", "#text": "text"}, "Failing String"]},
    }
    prettier_rules = prettify_rules(test_rule)
    assert "Failing String" in prettier_rules[0].get("Source")


@pytest.mark.parametrize("include_shared", ["No", "Yes"])
def test_panorama_list_tags(mocker, include_shared):
    """
    Given:
        - The include_shared argument.
    When:
        - Running the pan_os_list_tag_command method.
    Then:
        - Ensure the returned tags list output and HR table is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = ""
    tags_response_xml = """<response status="success" code="19"><result total-count="1" count="1">
        <tag admin="admin" dirtyId="6" time="2023/05/28 06:51:22">
            <entry name="tag1">
                <color>color13</color>
            </entry>
            <entry name="tag2">
                <color>color39</color>
            </entry>
            <entry name="tag3">
                <color>color39</color>
                <disable-override>no</disable-override>
                <comments>text text text</comments>
            </entry></tag></result></response>"""

    shared_tags_response_xml = """<response status="success" code="19"><result total-count="1" count="1">
        <tag admin="admin" dirtyId="6" time="2023/05/28 06:51:22">
            <entry name="sharedtag1">
                <color>color15</color>
            </entry>
            <entry name="sharedtag2">
                <color>color34</color>
            </entry></tag></result></response>"""

    tags_mock_response = MockedResponse(text=tags_response_xml, status_code=200)
    shared_tags_mock_response = MockedResponse(text=shared_tags_response_xml, status_code=200)

    mocker.patch.object(requests, "request", side_effect=[tags_mock_response, shared_tags_mock_response])

    expected_outputs_tags_list = [
        {"name": "tag1", "color": "color13", "location": ""},
        {"name": "tag2", "color": "color39", "location": ""},
        {"name": "tag3", "color": "color39", "location": "", "disable-override": "no", "comments": "text text text"},
    ]

    expected_hr_result = (
        "### Tags:\n|Name|Color|Comment|Location|\n|---|---|---|---|\n| tag1 | color13"
        " |  |  |\n| tag2 | color39 |  |  |\n| tag3 | color39 | text text text |  |\n"
    )

    if include_shared == "Yes":
        expected_outputs_tags_list.extend(
            [
                {"name": "sharedtag1", "color": "color15", "location": "shared"},
                {"name": "sharedtag2", "color": "color34", "location": "shared"},
            ]
        )
        expected_hr_result += "| sharedtag1 | color15 |  | shared |\n| sharedtag2 | color34 |  | shared |\n"

    command_results = Panorama.pan_os_list_tag_command({"include_shared_tags": include_shared})

    assert command_results.outputs == expected_outputs_tags_list
    assert command_results.readable_output == expected_hr_result


def test_pan_os_create_tag_command(mocker):
    """
    Given:
        - The tag name to create.
    When:
        - Running the pan_os_create_tag_command method.
    Then:
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'

    create_tag_mock_response = MockedResponse(text=expected_text_response, status_code=200)
    mocker.patch.object(requests, "request", return_value=create_tag_mock_response)

    command_results = Panorama.pan_os_create_tag_command({"name": "testtag"})

    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'The tag with name "testtag" was created successfully.'


@pytest.mark.parametrize("is_shared", [False, True])
def test_pan_os_edit_tag_command(mocker, is_shared):
    """
    Given:
        - The command arguments to edit the tag.
    When:
        1. The tag is not in a shared device group.
        2. The tag is in a shared device group.
        - Running the pan_os_edit_tag_command method.
    Then:
        - Ensure the request method call counts is according to if the tag is shared.
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    expected_first_text_response_if_shared = (
        '<response status="error" code="12"><msg><line>Edit breaks config validity</line></msg></response>'
    )
    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'
    expected_list_text_response = """<response status="success" code="19"><result total-count="1" count="1">
        <tag admin="admin" dirtyId="6" time="2023/05/28 06:51:22">
            <entry name="testtag">
                <color>color39</color>
                <disable-override>no</disable-override>
                <comments>text text text</comments>
            </entry></tag></result></response>"""
    expected_request_count = 4 if is_shared else 3

    edit_tag_mock_response = MockedResponse(text=expected_text_response, status_code=200)
    edit_tag_first_mock_response = MockedResponse(text=expected_first_text_response_if_shared, status_code=200)
    list_tag_mr = MockedResponse(text=expected_list_text_response, status_code=200)

    responses = (
        [list_tag_mr, list_tag_mr, edit_tag_first_mock_response, edit_tag_mock_response]
        if is_shared
        else [list_tag_mr, list_tag_mr, edit_tag_mock_response]
    )
    request_mocker = mocker.patch.object(requests, "request", side_effect=responses)

    command_results = Panorama.pan_os_edit_tag_command({"name": "testtag", "new_name": "newtesttag"})

    assert request_mocker.call_count == expected_request_count
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'The tag with name "testtag" was edited successfully.'


@pytest.mark.parametrize("is_shared", [False, True])
def test_pan_os_delete_tag_command(mocker, is_shared):
    """
    Given:
        - The tag name to delete.
    When:
        1. The tag is not in a shared device group.
        2. The tag is in a shared device group.
        - Running the pan_os_delete_tag_command method.
    Then:
        - Ensure the request method call counts is according to if the tag is shared.
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = "somedevice"

    expected_first_text_response_if_shared = '<response status="success" code="7"><msg>Object doesn\'t exist</msg></response>'
    expected_second_text_response_if_shared = (
        '<response status="success" code="19"><result total-count="2" count="2">'
        '<entry name="somedevice"></entry></result></response>'
    )
    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'
    expected_request_count = 3 if is_shared else 1

    delete_tag_mock_response = MockedResponse(text=expected_text_response, status_code=200)
    delete_tag_first_mock_response = MockedResponse(text=expected_first_text_response_if_shared, status_code=200)
    delete_tag_second_mock_response = MockedResponse(text=expected_second_text_response_if_shared, status_code=200)

    responses = (
        [delete_tag_first_mock_response, delete_tag_second_mock_response, delete_tag_mock_response]
        if is_shared
        else [delete_tag_mock_response]
    )
    request_mocker = mocker.patch.object(requests, "request", side_effect=responses)

    command_results = Panorama.pan_os_delete_tag_command({"name": "testtag", "new_name": "newtesttag"})

    assert request_mocker.call_count == expected_request_count
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'The tag with name "testtag" was deleted successfully.'


@pytest.mark.parametrize(
    "device_group, vsys, args, expected_response",
    [
        (
            "device_group",
            "",
            {"disable_override": True, "comment": ""},
            "<disable-override>yes</disable-override><comments></comments>",
        ),
        ("", "vsys1", {"disable_override": True, "comment": ""}, "<comments></comments>"),
    ],
)
def test_build_tag_element(mocker, device_group, vsys, args, expected_response):
    """
    Given:
     - given the disable_override argument that isn't supported with Firewall instances

    When:
     - Running the build_tag_element function

    Then:
     - Ensure that the expected response matches the actual response.
     (ignoring the disable_override argument when using Firewall instances)
    """
    from Panorama import build_tag_element

    mocker.patch("Panorama.DEVICE_GROUP", device_group)
    mocker.patch("Panorama.VSYS", vsys)
    response = build_tag_element(**args)
    assert response == expected_response


@pytest.mark.parametrize(
    (
        "element_to_change, context_element, element_value, current_objects_items, params_element, "
        "expected_exception, expected_warning, expected_warning_exit"
    ),
    [
        ("tag", "Tags", ["tag3"], ["tag3"], "<tag></tag>", False, False, False),  # Last tag
        ("tag", "Tags", ["tag2"], ["tag3", "tag2"], "<tag><member>tag3</member></tag>", False, False, False),  # Not last tag
        ("tag", "Tags", ["nonexistent_tag"], ["tag1"], "", False, True, True),  # Non-existent tag > exit
        (
            "tag",
            "Tags",
            ["nonexistent_tag", "tag1"],
            ["tag1"],
            "<tag></tag>",
            False,
            True,
            False,
        ),  # Non-existent tag & existent > warning
        ("source", "Source", ["source"], ["source"], "", True, False, False),  # raise exception
    ],
)
def test_panorama_edit_rule_items_remove(
    mocker,
    element_to_change,
    context_element,
    element_value,
    current_objects_items,
    params_element,
    expected_exception,
    expected_warning,
    expected_warning_exit,
):
    """
    Given:
     - element_to_change: The element to be changed in the rule.
     - element_value: The value(s) to be removed from the element.
     - current_objects_items: The current items present in the element.
     - params_element: The expected element value in the request body.
     - expected_exception: Flag indicating whether an exception is expected to be raised.
     - expected_warning: Flag indicating whether a warning is expected to be returned.
     - expected_warning_exit: Flag indicating whether the warning is expected to trigger an exit.

    When:
     - Running the panorama_edit_rule_items function to remove element from rule.

    Then:
     - Ensure that the expected response matches the actual response.
     - If expected_exception is True, assert that the correct exception is raised.
     - If expected_warning is True, assert that the correct warning message is returned.
     - If expected_warning_exit is True, assert that the warning triggers an exit.
     - If expected_warning_exit is False, assert the correct values in the request body, the success message,
       and the call to return_results.
    """
    from Panorama import panorama_edit_rule_items

    mocker.patch("Panorama.VSYS", "vsys1")
    mocker.patch("Panorama.DEVICE_GROUP", "")
    mocker.patch("Panorama.panorama_get_current_element", return_value=current_objects_items)
    mock_return_warning = mocker.patch("Panorama.return_warning")
    request_mock = mocker.patch("Panorama.http_request", return_value=TestPanoramaEditRuleCommand.EDIT_SUCCESS_RESPONSE)

    return_results_mock = mocker.patch("Panorama.return_results")

    if expected_exception:
        with pytest.raises(Exception, match=f"The object: {element_to_change} must have at least one item."):
            panorama_edit_rule_items("rulename", element_to_change, element_value, "remove")
    else:
        panorama_edit_rule_items("rulename", element_to_change, element_value, "remove")

        if expected_warning:
            mock_return_warning.assert_called_once_with(
                "The following tags do not exist: nonexistent_tag", exit=expected_warning_exit
            )

        if not expected_warning_exit:
            assert request_mock.call_args.kwargs["body"]["action"] == "edit"
            assert request_mock.call_args.kwargs["body"]["element"] == params_element
            assert return_results_mock.call_args[0][0]["HumanReadable"] == "Rule edited successfully."
            assert isinstance(
                return_results_mock.call_args[0][0]["EntryContext"]["Panorama.SecurityRule(val.Name == obj.Name)"][
                    context_element
                ],
                list,
            )


def test_list_device_groups_names(mocker):
    from Panorama import list_device_groups_names

    mocker.patch("Panorama.get_device_groups_names", return_value=["Test-Device", "Test-Device-2"])

    result = list_device_groups_names()

    assert result.outputs == ["Test-Device", "Test-Device-2"]
    assert result.readable_output == "### Device Group Names:\n|Group Name|\n|---|\n| Test-Device |\n| Test-Device-2 |\n"


def test_panorama_list_security_profile_group_command(mocker):
    """
    Given:
        - A Panorama instance with security profile groups.
    When:
        - Running the pan_os_list_security_profile_group_command.
    Then:
        - Ensure the returned security profile groups list output and HR table is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = ""

    tags_response_xml = """<response status="success" code="19"><result total-count="2" count="2">
    <entry name="test1" loc="">
        <virus><member>default</member></virus><spyware><member>default</member></spyware>
        <vulnerability><member>default</member></vulnerability><url-filtering><member>default</member></url-filtering>
    </entry>
    <entry name="test2" loc="">
        <virus><member>default</member></virus><spyware><member>default</member></spyware>
        <vulnerability><member>default</member></vulnerability><url-filtering><member>default</member></url-filtering>
        <wildfire-analysis><member>test wildfire analysis</member></wildfire-analysis><file-blocking>
        <member>basic file blocking</member></file-blocking><data-filtering><member>test data filtering</member></data-filtering>
    </entry>
    </result></response>"""

    mock_response = MockedResponse(text=tags_response_xml, status_code=200)
    mocker.patch.object(requests, "request", return_value=mock_response)

    expected_outputs_tags_list = [
        {
            "virus": "default",
            "spyware": "default",
            "vulnerability": "default",
            "url-filtering": "default",
            "name": "test1",
            "location": "",
        },
        {
            "virus": "default",
            "spyware": "default",
            "vulnerability": "default",
            "url-filtering": "default",
            "wildfire-analysis": "test wildfire analysis",
            "file-blocking": "basic file blocking",
            "data-filtering": "test data filtering",
            "name": "test2",
            "location": "",
        },
    ]

    expected_hr_result = (
        "### Security Profile Groups:\n|Name|Location|Antivirus Profile|Anti-Spyware Profile|Vulnerability "
        "Protection Profile|URL Filtering Profile|File Blocking Profile|Data Filtering Profile|WildFire "
        "Analysis Profile|\n|---|---|---|---|---|---|---|---|---|\n| test1 |  | default | default | default | "
        "default |  |  |  |\n| test2 |  | default | default | default | default | basic file blocking | test "
        "data filtering | test wildfire analysis |\n"
    )

    command_results = Panorama.pan_os_list_security_profile_groups_command({})

    assert command_results.outputs == expected_outputs_tags_list
    assert command_results.readable_output == expected_hr_result


def test_pan_os_create_security_profile_group_command(mocker):
    """
    Given:
        - The security profile groups name to create.
    When:
        - Running the pan_os_create_security_profile_group_command.
    Then:
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'

    mock_response = MockedResponse(text=expected_text_response, status_code=200)
    mocker.patch.object(requests, "request", return_value=mock_response)

    command_results = Panorama.pan_os_create_security_profile_group_command({"group_name": "test_spg"})

    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'Successfully created Security Profile Group: "test_spg"'


def test_pan_os_edit_security_profile_group_command(mocker):
    """
    Given:
        - The profile_to_change and the value arguments to edit in the security profile groups.
    When:
        - Running the pan_os_edit_security_profile_group_command.
    Then:
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'

    mock_response = MockedResponse(text=expected_text_response, status_code=200)
    mocker.patch.object(requests, "request", return_value=mock_response)

    command_results = Panorama.pan_os_edit_security_profile_group_command({"group_name": "test_spg"})

    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'Successfully edited Security Profile Group: "test_spg"'


def test_pan_os_delete_security_profile_group_command(mocker):
    """
    Given:
        - The security profile groups name to delete.
    When:
        - Running the pan_os_delete_security_profile_group_command.
    Then:
        - Ensure the returned response and readable outputs is as expected.
    """
    import Panorama
    import requests

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = "somedevice"

    expected_text_response = '<response status="success" code="20"><msg>command succeeded</msg></response>'

    mock_response = MockedResponse(text=expected_text_response, status_code=200)
    mocker.patch.object(requests, "request", return_value=mock_response)

    command_results = Panorama.pan_os_delete_security_profile_group_command({"group_name": "test_spg"})
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == 'Successfully deleted Security Profile Group: "test_spg"'


@pytest.mark.parametrize(
    "profile_name, profile_type, device_group, action, threat_id, expected_xpath",
    [
        # test cases for device_group
        (
            "name",
            "vulnerability",
            "device_group",
            "set",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/vulnerability/entry[@name='name']/threat-exception"
            ),
        ),
        (
            "name",
            "spyware",
            "device_group",
            "set",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/spyware/entry[@name='name']/threat-exception"
            ),
        ),
        # test case for VSYS
        (
            "name",
            "vulnerability",
            None,
            "set",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/vsys/entry[@name='vsys']"
                "/profiles/vulnerability/entry[@name='name']/threat-exception"
            ),
        ),
        (
            "name",
            "spyware",
            None,
            "set",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/vsys/entry[@name='vsys']"
                "/profiles/spyware/entry[@name='name']/threat-exception"
            ),
        ),
        # test case for EDIT action type
        (
            "name",
            "spyware",
            "device_group",
            "edit",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/spyware/entry[@name='name']/threat-exception"
                "/entry[@name='1000']"
            ),
        ),
        (
            "name",
            "vulnerability",
            "device_group",
            "edit",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/vulnerability/entry[@name='name']/threat-exception"
                "/entry[@name='1000']"
            ),
        ),
        # test case for DELETE action type
        (
            "name",
            "vulnerability",
            "device_group",
            "delete",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/vulnerability/entry[@name='name']/threat-exception"
                "/entry[@name='1000']"
            ),
        ),
        (
            "name",
            "spyware",
            "device_group",
            "delete",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/device-group/entry[@name='device_group']"
                "/profiles/spyware/entry[@name='name']/threat-exception"
                "/entry[@name='1000']"
            ),
        ),
        (
            "name",
            "spyware",
            None,
            "delete",
            "1000",
            (
                "/config/devices/entry[@name='localhost.localdomain']"
                "/vsys/entry[@name='vsys']"
                "/profiles/spyware/entry[@name='name']/threat-exception"
                "/entry[@name='1000']"
            ),
        ),
    ],
)
def test_pan_os_xpath_creation_for_exception_crud(profile_name, profile_type, device_group, action, threat_id, expected_xpath):
    """
    Given:
        - A profile name, profile type, device group name, action, and threat ID.
    When:
        - Running build_xpath_for_profile_exception_commands function to generate the XPath.
    Then:
        - Ensure the returned XPath is correctly constructed for both Vulnerability Protection and Anti Spyware profiles.
    """
    import Panorama

    Panorama.VSYS = "vsys"

    result = Panorama.build_xpath_for_profile_exception_commands(profile_name, profile_type, device_group, action, threat_id)
    assert result == expected_xpath


def test_pan_os_check_profile_type_by_given_profile_name(mocker):
    """
    Given:
        - A profile name that could exist in either 'Vulnerability Protection Profile' or 'Anti Spyware Profile'.
    When:
        - Checking the profile type by the given profile name.
    Then:
        - Ensure the correct profile type is returned or an appropriate exception is raised.
    """
    import Panorama

    mocker.patch(
        "Panorama.get_all_profile_names_from_profile_type",
        side_effect=[
            ["profile_1", "profile_2"],
            ["profile_3", "profile_4"],
            [],
            ["profile_3"],
            ["profile_5"],
            ["profile_5"],
            [],
            [],
        ],
    )

    result = Panorama.check_profile_type_by_given_profile_name("profile_1", "device_group")
    assert result == "vulnerability"

    result = Panorama.check_profile_type_by_given_profile_name("profile_3", None)
    assert result == "spyware"

    with pytest.raises(
        DemistoException,
        match="Profile name was found both in Vulnerability Protection Profiles "
        "and in Anti Spyware Profiles. Please specify profile_type.",
    ):
        Panorama.check_profile_type_by_given_profile_name("profile_5", "device_group")

    with pytest.raises(
        DemistoException, match="Profile name was not found in Vulnerability Protection Profiles or in Anti Spyware Profiles."
    ):
        Panorama.check_profile_type_by_given_profile_name("profile_6", "device_group")


def test_pan_os_get_threat_id_from_predefined_threats(mocker):
    """
    Given:
        - A threat name that may match a threat name, ID, or CVE in the predefined threats list.
    When:
        - Searching for the threat ID using the provided threat name.
    Then:
        - Ensure the correct threat ID, name, and CVEs are returned, or an appropriate exception is raised.
    """
    import Panorama

    mock_predefined_threats = [
        {"@name": "10003", "threatname": "Test Threat 1", "cve": {"member": ["CVE-2023-1234"]}},
        {"@name": "10004", "threatname": "Test Threat 2", "cve": {"member": ["CVE-2023-5678"]}},
        {"@name": "10005", "threatname": "Test Threat 3", "cve": {"member": ["CVE-2023-9012"]}},
    ]

    mocker.patch.object(Panorama, "get_predefined_threats_list", return_value=mock_predefined_threats)

    result = Panorama.get_threat_id_from_predefined_threats("Test Threat 1")
    assert result == ("10003", "Test Threat 1", ["CVE-2023-1234"])

    result = Panorama.get_threat_id_from_predefined_threats("10004")
    assert result == ("10004", "Test Threat 2", ["CVE-2023-5678"])

    result = Panorama.get_threat_id_from_predefined_threats("CVE-2023-9012")
    assert result == ("10005", "Test Threat 3", ["CVE-2023-9012"])

    with pytest.raises(DemistoException, match="Threat was not found."):
        Panorama.get_threat_id_from_predefined_threats("Nonexistent Threat")


def test_pan_os_add_profile_exception(mocker):
    """
    Given:
        - A profile name, profile type, threat name, and device group.
    When:
        - Running the `pan_os_add_profile_exception_command` to add an exception to a security profile.
    Then:
        - Ensure the returned response indicates the successful creation of the exception with the correct threat name and ID.
    """
    import Panorama

    mock_response = {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    mocker.patch.object(Panorama, "http_request", return_value=mock_response)
    mocker.patch.object(Panorama, "get_threat_id_from_predefined_threats", return_value=("1000", "threatname", "cve"))

    command_results = Panorama.pan_os_add_profile_exception_command(
        args={
            "profile_name": "test_spg",
            "threat_name": "1000",
            "profile_type": "Vulnerability Protection Profile",
            "device_group": "device_group",
        }
    )
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == (
        'Successfully created exception "threatname" with threat ID 1000 in the "test_spg" profile of type "vulnerability".'
    )


def test_pan_os_edit_profile_exception(mocker):
    """
    Given:
        - A profile name, profile type, threat name, and device group.
    When:
        - Running the `pan_os_edit_profile_exception_command` to edit an exception in a security profile.
    Then:
        - Ensure the returned response indicates the successful editing of the exception with the correct threat name and ID.
    """
    import Panorama

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = "device_group"

    mock_response = {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    mocker.patch.object(Panorama, "http_request", return_value=mock_response)
    mocker.patch.object(Panorama, "get_threat_id_from_predefined_threats", return_value=("1000", "threatname", "cve"))
    command_results = Panorama.pan_os_edit_profile_exception_command(
        args={
            "profile_name": "test_spg",
            "threat_name": "1000",
            "profile_type": "Vulnerability Protection Profile",
            "device_group": "device_group",
        }
    )
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == (
        'Successfully edited exception "threatname" with threat ID 1000 in the "test_spg" profile of type "vulnerability".'
    )


def test_pan_os_delete_profile_exception(mocker):
    """
    Given:
        - A profile name, profile type, threat name, and device group.
    When:
        - Running the `pan_os_delete_profile_exception_command` to delete an exception from a security profile.
    Then:
        - Ensure the returned response indicates the successful deletion of the exception with the correct threat name and ID.
    """
    import Panorama

    mock_response = {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    mocker.patch.object(Panorama, "http_request", return_value=mock_response)
    mocker.patch.object(Panorama, "get_threat_id_from_predefined_threats", return_value=("1000", "threatname", "cve"))

    command_results = Panorama.pan_os_delete_profile_exception_command(
        args={
            "profile_name": "test_spg",
            "threat_name": "1000",
            "profile_type": "Vulnerability Protection Profile",
            "device_group": "device_group",
        }
    )
    assert command_results.raw_response == {"response": {"@status": "success", "@code": "20", "msg": "command succeeded"}}
    assert command_results.readable_output == (
        'Successfully deleted exception "threatname" with threat ID 1000 in the "test_spg" profile of type "vulnerability".'
    )


def test_pan_os_list_profile_exception(mocker):
    """
    Given:
        - A profile name and profile type.
    When:
        - Running the `pan_os_list_profile_exception_command` to list exceptions in a security profile.
    Then:
        - Ensure the returned response is of type `CommandResults` and the readable output lists the correct profile exceptions.
    """
    import Panorama

    Panorama.URL = "https://1.1.1.1:443/"
    Panorama.API_KEY = "thisisabogusAPIKEY!"
    Panorama.DEVICE_GROUP = "device_group"
    mocker.patch.object(
        Panorama,
        "profile_exception_crud_requests",
        return_value=(
            {
                "raw_response": {
                    "response": {
                        "result": {
                            "threat-exception": {
                                "entry": [
                                    {
                                        "@name": "10003",
                                        "action": {"block": {}},
                                        "exempt-ip": {"entry": {"@name": "192.168.1.1"}},
                                        "packet-capture": "yes",
                                        "@admin": "admin1",
                                        "@dirtyId": "dirty1",
                                        "@time": "2024-08-14T12:00:00",
                                    },
                                    {
                                        "@name": "10002",
                                        "action": {"allow": {}},
                                        "packet-capture": "no",
                                        "@admin": "admin2",
                                        "@dirtyId": "dirty2",
                                        "@time": "2024-08-14T12:00:00",
                                    },
                                ]
                            }
                        }
                    },
                },
                "exception_id": "id",
                "exception_name": "name",
                "profile_type": "vulnerability",
            }
        ),
    )

    mocker.patch.object(Panorama, "get_threat_id_from_predefined_threats", return_value=("test", "threatname", "cve"))

    args = {"profile_name": "test_profile", "profile_type": "Vulnerability Protection Profile"}
    result = Panorama.pan_os_list_profile_exception_command(args)

    assert isinstance(result, CommandResults)

    expected_hr = [
        {
            "ID": "10003",
            "Name": "threatname",
            "CVE": "cve",
            "Action": "block",
            "Exempt IP": "192.168.1.1",
            "Packet Capture": "yes",
        },
        {
            "ID": "10002",
            "Name": "threatname",
            "CVE": "cve",
            "Action": "allow",
            "Exempt IP": "",
            "Packet Capture": "no",
        },
    ]

    expected_output = {
        "Name": "test_profile",
        "Exception": [
            {
                "id": "10003",
                "name": "threatname",
                "CVE": "cve",
                "action": "block",
                "packet-capture": "yes",
                "exempt-ip": "192.168.1.1",
            },
            {
                "id": "10002",
                "name": "threatname",
                "CVE": "cve",
                "action": "allow",
                "packet-capture": "no",
            },
        ],
    }

    assert "Profile Exceptions" in result.readable_output

    for hr_entry in expected_hr:
        for _, value in hr_entry.items():
            assert value in result.readable_output

    assert result.outputs == expected_output
    assert result.outputs_prefix == "Panorama.Vulnerability"
    assert result.outputs_key_field == "Name"


def test_fetch_incidents_correlation(mocker: MockerFixture):
    """
    Given:
        -
    When:
        -
    Then:
        -
    """
    from Panorama import LastFetchTimes, LastIDs, LastRun, MaxFetch, QueryMap, fetch_incidents

    corr_logs = load_json("test_data/corr_logs.json")
    mock_get_query_entries = mocker.patch("Panorama.get_query_entries")

    last_fetch_dict = LastFetchTimes(Correlation="2024/04/08 07:22:54")
    last_id_dict = LastIDs(Correlation=0)
    max_fetch_dict = MaxFetch(Correlation=10)
    last_run = LastRun(last_fetch_dict=last_fetch_dict, last_id_dict=last_id_dict, max_fetch_dict=max_fetch_dict)

    # assert duplicates are removed:

    mock_get_query_entries.return_value = corr_logs[:5]
    _, entries = fetch_incidents(last_run, "2024/04/08 07:22:54", QueryMap(Correlation="query"), max_fetch_dict, 1)

    assert entries[0]["name"] == "Correlation 1"
    assert "CORRELATION" in entries[0]["rawJSON"]
    assert mock_get_query_entries.call_args_list[0].args == (
        "Correlation",
        "(query) and (match_time geq '2024/04/08 07:22:54')",
        10,
        1,
        0,
    )  # asserting that "match_time" is used instead of "time_generated".
    assert last_fetch_dict == LastFetchTimes(Correlation="2024-04-09 07:22:54")  # the max date
    assert last_id_dict == LastIDs(Correlation=4)
    assert max_fetch_dict == MaxFetch(Correlation=10)

    # test with dict from older versions

    last_id_dict["Correlation"] = cast(int, {})

    _, entries = fetch_incidents(last_run, "2024/04/08 07:22:54", QueryMap(Correlation="query"), max_fetch_dict, 1)
    assert entries[0]["name"] == "Correlation 1"


def test_fetch_incidents_offset(mocker: MockerFixture):
    """
    Given: Panorama incidents.

    When: Using fetch-incidents command using offset to get the next incidents.

    Then: Assert the correct amount of incidents were fetched and the correct offset value was stored.

    """
    from Panorama import LastFetchTimes, LastIDs, LastRun, MaxFetch, Offset, QueryMap, fetch_incidents

    corr_logs = load_json("test_data/corr_logs_time_dif.json")
    mock_get_query_entries = mocker.patch("Panorama.get_query_entries")

    last_fetch_dict = LastFetchTimes(Correlation="2024/04/08 07:22:54")
    last_id_dict = LastIDs()
    max_fetch_dict = MaxFetch(Correlation=5)
    offset_dict = Offset(Correlation=0)

    last_run = LastRun(
        last_fetch_dict=last_fetch_dict, last_id_dict=last_id_dict, max_fetch_dict=max_fetch_dict, offset_dict=offset_dict
    )

    # assert duplicates are removed:

    mock_get_query_entries.return_value = corr_logs[:5]
    new_last_run, entries = fetch_incidents(last_run, "2024/04/08 07:22:54", QueryMap(Correlation="query"), max_fetch_dict, 1)

    assert entries[0]["name"] == "Correlation 1"
    assert "CORRELATION" in entries[0]["rawJSON"]
    assert mock_get_query_entries.call_args_list[0].args == (
        "Correlation",
        "(query) and (match_time geq '2024/04/08 07:22:54')",
        5,
        1,
        0,
    )  # asserting that "match_time" is used instead of "time_generated".
    assert last_fetch_dict == LastFetchTimes(Correlation="2024-04-08 07:22:54")  # the max date
    assert last_id_dict == LastIDs(Correlation=5)
    assert max_fetch_dict == MaxFetch(Correlation=5)
    assert offset_dict == Offset(Correlation=5)

    mock_get_query_entries.return_value = corr_logs[5:]
    new_last_run, entries = fetch_incidents(new_last_run, "2024/04/08 07:22:54", QueryMap(Correlation="query"), max_fetch_dict, 1)

    assert entries[0]["name"] == "Correlation 6"
    assert "CORRELATION" in entries[0]["rawJSON"]
    assert mock_get_query_entries.call_args_list[1].args == (
        "Correlation",
        "(query) and (match_time geq '2024/04/08 07:22:54')",
        5,
        1,
        5,
    )  # asserting that "match_time" is used instead of "time_generated".
    assert last_fetch_dict == LastFetchTimes(Correlation="2024-04-08 07:22:55")  # the max date
    assert last_id_dict == LastIDs(Correlation=10)
    assert max_fetch_dict == MaxFetch(Correlation=5)
    assert offset_dict == Offset(Correlation=2)


def test_build_master_key_create_or_update_cmd():
    """
    Given:
        - Command arguments for updating Panorama / PAN-OS master key

    When:
        - Calling build_master_key_create_or_update_cmd.

    Assert:
        - Correct XML command string.
    """
    from Panorama import build_master_key_create_or_update_cmd

    # Set
    args = {
        "current_master_key": "MyFakeMasterKey1",
        "new_master_key": "MyFakeMasterKey2",
        "lifetime_in_hours": "2160",
        "reminder_in_hours": "1992",
    }
    # Arrange
    cmd = build_master_key_create_or_update_cmd(args, action="update")

    # Assert
    assert cmd == (
        "<request><master-key><lifetime>2160</lifetime><reminder>1992</reminder>"
        "<new-master-key>MyFakeMasterKey2</new-master-key>"
        "<current-master-key>MyFakeMasterKey1</current-master-key>"
        "<on-hsm>no</on-hsm></master-key></request>"
    )


def test_pan_os_create_master_key_command(requests_mock: RequestsMock):
    """
    Given:
        - Command arguments for creating Panorama / PAN-OS master key

    When:
        - Calling pan_os_create_master_key_command.

    Assert:
        - Correct human readable output and raw response.
    """
    import Panorama
    from Panorama import pan_os_create_master_key_command, xml2json

    # Set
    args = {"master_key": "MyFakeMasterKey1", "lifetime_in_hours": "2160", "reminder_in_hours": "1992"}
    Panorama.URL = "https://1.1.1.1:443/api/"

    xml_root = load_xml_root_from_test_file("test_data/create_master_key.xml")
    response_result = xml_root.find("result").text

    xml_response_text = ElementTree.tostring(xml_root, encoding="unicode")
    requests_mock.get(Panorama.URL, text=xml_response_text)

    # Arrange
    command_results: CommandResults = pan_os_create_master_key_command(args)

    # Assert
    assert command_results.readable_output == (
        f"{response_result}. \n\n⚠️ The current API key is no longer valid! (by design) "
        "Generate a new API key and update it in the integration instance configuration to keep using the integration."
    )
    assert command_results.raw_response == json.loads(xml2json(xml_response_text))


def test_pan_os_get_master_key_details_command(mocker: MockerFixture, requests_mock: RequestsMock):
    """
    When:
        - Calling pan_os_get_master_key_command.

    Assert:
        - Correct human readable, context output, and raw response.
    """
    import Panorama
    from Panorama import pan_os_get_master_key_details_command, xml2json

    # Set
    Panorama.URL = "https://1.1.1.1:443/api/"

    xml_root = load_xml_root_from_test_file("test_data/get_master_key.xml")
    xml_response_text = ElementTree.tostring(xml_root, encoding="unicode")
    requests_mock.get(Panorama.URL, text=xml_response_text)

    table_to_markdown = mocker.patch("Panorama.tableToMarkdown")

    # Arrange
    command_results: CommandResults = pan_os_get_master_key_details_command()
    table_name: str = table_to_markdown.call_args[0][0]
    table_data: dict = table_to_markdown.call_args[0][1]
    raw_response: dict = json.loads(xml2json(xml_response_text))

    # Assert
    assert table_name == "Master Key Details"
    assert table_data == raw_response["response"]["result"]
    assert command_results.outputs == raw_response["response"]["result"]
    assert command_results.raw_response == raw_response


@patch("Panorama.run_op_command")
def test_show_jobs_id_not_found(patched_run_op_command):
    """
    Given:
        - A specific job_id (23)

    When:
        - running show_jobs function

    Then:
        - Ensure DemistoException is thrown with ann informative message (since the given ID does not exist in all devices)
    """
    from Panorama import UniversalCommand

    patched_run_op_command.side_effect = panos.errors.PanDeviceXapiError("job 23 not found")
    MockTopology = type("MockTopology", (), {"all": lambda *x, **y: [Panorama(hostname="123")]})

    with pytest.raises(DemistoException, match="The given ID 23 is not found in all devices of the topology."):
        UniversalCommand.show_jobs(topology=MockTopology(), id=23)


@patch("Panorama.TemplateStack.refreshall")
@patch("Panorama.Template.refreshall")
@patch("Panorama.run_op_command")
def test_pan_os_get_certificate_info_command(
    patched_run_op_command, template_refreshall, template_stack_refreshall, mocker: MockerFixture, requests_mock: RequestsMock
):
    """
    Test pan-os-get-certificate-expiration command.

    This test validates that:
    1. The command correctly fetches certificates from Panorama devices
    2. The command correctly fetches certificates from Firewall devices
    3. The command correctly fetches predefined certificates
    4. The command returns appropriate output
    """

    from Panorama import pan_os_get_certificate_info_command

    def side_effect_func(device, cmd, **kwargs):
        if cmd == "request certificate show":
            return load_xml_root_from_test_file("test_data/request_certificate_show.xml")
        elif cmd == "show config running":
            return load_xml_root_from_test_file("test_data/show_running_config.xml")
        elif cmd == "show config pushed-template":
            return load_xml_root_from_test_file("test_data/show_config_pushed_template.xml")
        else:
            return None

    mock_template = MagicMock(spec=Template)
    mock_template.devices = []
    template_refreshall.return_value = [mock_template]

    mock_template_stack = MagicMock(spec=TemplateStack)
    mock_template_stack.devices = [MOCK_FIREWALL_1_SERIAL]
    template_stack_refreshall.return_value = [mock_template_stack]

    patched_run_op_command.side_effect = side_effect_func

    # # Mock Panorama device
    mock_panorama = MagicMock(spec=Panorama)
    mock_panorama.hostname = "panorama.test"
    mock_panorama_devices = [mock_panorama]

    # # Mock Firewall device
    mock_firewall = MagicMock(spec=Firewall)
    mock_firewall.serial = MOCK_FIREWALL_1_SERIAL
    mock_firewall.parent = MagicMock(get=lambda x: "panorama.test" if x == "hostname" else None)
    mock_firewall_devices = [mock_firewall]

    # Mock empty topology
    mock_topology = MagicMock()
    mock_topology.panorama_devices.return_value = mock_panorama_devices
    mock_topology.firewall_devices.return_value = mock_firewall_devices

    Panorama.URL = "https://1.1.1.1:443/api/"

    # Define output of http request to "/config/predefined/certificate"
    with open("test_data/config_predefined_certificate.txt") as f:
        requests_mock.get(Panorama.URL, text=f.read())

    result = pan_os_get_certificate_info_command(topology=mock_topology, args={"show_expired_only": False})

    assert isinstance(result, CommandResults)

    assert result.outputs == load_json("test_data/get_certificate_info_command.json")


class TestDynamicUpdateCommands:
    @pytest.mark.parametrize(
        "update_type_str, expected_api_call",
        [
            ("APP_THREAT", "<request><content><upgrade><download><latest/></download></upgrade></content></request>"),
            ("ANTIVIRUS", "<request><anti-virus><upgrade><download><latest/></download></upgrade></anti-virus></request>"),
            ("WILDFIRE", "<request><wildfire><upgrade><download><latest/></download></upgrade></wildfire></request>"),
            (
                "GP",
                "<request><global-protect-clientless-vpn><upgrade><download><latest/></download></upgrade></global-protect-clientless-vpn></request>",
            ),
        ],
    )
    def test_download_latest_dynamic_update_content(self, update_type_str, expected_api_call, mocker):
        """Test that for each dynamic update type, the correct API call is made.

        Args:
            update_type_str (str): Type of dynamic update to download.
            expected_api_call (str): Expected API call to download the given update type.
        """
        from Panorama import panorama_download_latest_dynamic_update_content
        from Panorama import DynamicUpdateType

        update_type = getattr(DynamicUpdateType, update_type_str)
        mock_request = mocker.patch("Panorama.http_request", return_value={})
        panorama_download_latest_dynamic_update_content(update_type=update_type, target="1337")
        request_call_args = mock_request.call_args[1]
        assert request_call_args["body"]["cmd"] == expected_api_call

    def test_panorama_check_latest_dynamic_update_command(self, mocker):
        """test_panorama_check_latest_dynamic_update_command Test function to check current status of dynamic updates.

        Test the function that gathers information on latest available and currently installed dynamic updates using
        sample PAN-OS API responses to ensure the data is processed and the function returns the expected reaponse.
        """
        from Panorama import panorama_check_latest_dynamic_update_command

        # Side-effect function to return the proper NGFW API response for the requested dynamic update type
        def side_effect_function(update_type, target):
            if update_type.name == "APP_THREAT":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_app-threat.json")

            elif update_type.name == "ANTIVIRUS":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_antivirus.json")

            elif update_type.name == "WILDFIRE":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_wildfire.json")

            elif update_type.name == "GP":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_gp.json")

            else:
                return None

        mock_api_call = mocker.patch("Panorama.panorama_check_latest_dynamic_update_content")
        mock_api_call.side_effect = side_effect_function

        mock_return_results = mocker.patch("Panorama.return_results")

        panorama_check_latest_dynamic_update_command({"target": "1337"})

        # Prepare results for comparison
        returned_commandresults: CommandResults = mock_return_results.call_args[0][0]
        expected_returned_output = load_json("test_data/pan-os-check-latest-dynamic-update-status_expected-returned-outputs.json")
        expected_returned_readable = (
            "### Dynamic Update Status Summary\n|Update Type|Is Up To Date|Latest Available "
            "Version|Currently Installed Version|\n|---|---|---|---|\n| Content | True | 8987-9481 | 8987-9481 |\n| "
            "AntiVirus | False | 5212-5732 | 5211-5731 |\n| WildFire | False | 986250-990242 | 986026-990018 |\n| GP | "
            "True | 98-260 | 98-260 |\n\n\n**Total Content Types Outdated: 2**"
        )

        assert returned_commandresults.outputs == expected_returned_output
        assert returned_commandresults.readable_output == expected_returned_readable

    def test_panorama_check_latest_dynamic_update_panorama_instance(self, mocker):
        """
        Given:
            - A panorama instance (VSYS instance parameter is set to '').
            - target argument is not specified (set to '').

        When:
            - Calling the panorama_check_latest_dynamic_update_command.

        Then:
            - Verify that an exception is raised askung the user to specify a target firewall
            when running from a Panorama instance.

        """
        from Panorama import panorama_check_latest_dynamic_update_command

        # VSYS global variable is set to '' by default, which means we are simulating a run from a Panorama instance.

        mocker.patch("Panorama.VSYS", "")

        with pytest.raises(DemistoException) as e:
            panorama_check_latest_dynamic_update_command({"target": ""})
        assert "When running from a Panorama instance, you must specify the target argument." in str(e.value)

    def test_panorama_check_latest_dynamic_update_command_empty_response(self, mocker):
        """
        Tests the scenario were the response from the 'panorama_check_latest_dynamic_update_content' api call, includes no
        entries - which means that the Firewall probably doesn't have any App/Threat or Antivirus, or Wildfire or GP
        installed.

        Given:
            - a Panorama instance.
            - Mock response for the 'panorama_check_latest_dynamic_update_content' api call, with no entries.

        When:
            - Calling the panorama_check_latest_dynamic_update_command.

        Then:
            - Verify that the outputs include no versions (all versions set to N/A).
            - Verify that the expected debug log is created.

        """
        from Panorama import panorama_check_latest_dynamic_update_command

        mocker.patch(
            "Panorama.panorama_check_latest_dynamic_update_content",
            return_value={
                "response": {
                    "@status": "success",
                    "result": {"content-updates": {"@last-updated-at": "2025/06/11 13:40:16 PDT", "entry": []}},
                }
            },
        )

        mock_return_results = mocker.patch("Panorama.return_results")
        mock_debug = mocker.patch.object(demisto, "debug")

        panorama_check_latest_dynamic_update_command({"target": "1337"})

        # Prepare results for comparison
        returned_commandresults: CommandResults = mock_return_results.call_args[0][0]
        expected_returned_output = {
            "Content": {"LatestAvailable": {}, "CurrentlyInstalled": {}, "IsUpToDate": False},
            "AntiVirus": {"LatestAvailable": {}, "CurrentlyInstalled": {}, "IsUpToDate": False},
            "WildFire": {"LatestAvailable": {}, "CurrentlyInstalled": {}, "IsUpToDate": False},
            "GP": {"LatestAvailable": {}, "CurrentlyInstalled": {}, "IsUpToDate": False},
            "ContentTypesOutOfDate": {"Count": 4},
        }
        expected_returned_readable = (
            "### Dynamic Update Status Summary\n|Update Type|Is Up To Date|Latest Available "
            "Version|Currently Installed Version|\n|---|---|---|---|\n| Content | False | N/A | N/A |"
            "\n| AntiVirus | False | N/A | N/A |\n| WildFire | False | N/A | N/A |\n| GP | False | N/A "
            "| N/A |\n\n\n**Total Content Types Outdated: 4**"
        )

        assert mock_debug.call_args[0][0] == (
            "No available updates " "(Firewall probably doesn't have any global-protect-clientless-vpn installed)."
        )
        assert returned_commandresults.outputs == expected_returned_output
        assert returned_commandresults.readable_output == expected_returned_readable

    def test_panorama_check_latest_dynamic_update_command_no_gp_license(self, mocker):
        """
        Tests the scenario were there is no Global Protect license on the Firewall.

        Given:
            - a Panorama instance.
            - Mock response for the 'panorama_check_latest_dynamic_update_content' api call, with an exception regfarding the
            GP missing license.

        When:
            - Calling the panorama_check_latest_dynamic_update_command.

        Then:
            - Verify that the outputs include the versions of the content, wildfire and antivirus, and the error message
              egarding the gp missing license.
            - Verify that the human readable output includes the versions of the versions of the content, wildfire and
              antivirus, and the error message regarding the gp missing license.
        """

        from Panorama import panorama_check_latest_dynamic_update_command

        # Side-effect function to return the proper NGFW API response for the requested dynamic update type
        def side_effect_function(update_type, target):
            if update_type.name == "APP_THREAT":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_app-threat.json")

            elif update_type.name == "ANTIVIRUS":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_antivirus.json")

            elif update_type.name == "WILDFIRE":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_wildfire.json")

            elif update_type.name == "GP":
                raise Exception("There is no Global Protext Gateway license on the box")

            else:
                return None

        mock_api_call = mocker.patch("Panorama.panorama_check_latest_dynamic_update_content")
        mock_api_call.side_effect = side_effect_function

        mock_return_results = mocker.patch("Panorama.return_results")

        panorama_check_latest_dynamic_update_command({"target": "1337"})

        # Prepare results for comparison
        returned_commandresults: CommandResults = mock_return_results.call_args[0][0]
        expected_returned_output = load_json(
            "test_data/pan-os-check-latest-dynamic-update-status_expected-returned-outputs-no-gp" "-license.json"
        )
        expected_returned_readable = (
            "### Dynamic Update Status Summary\n|Update Type|Is Up To Date|Latest Available "
            "Version|Currently Installed Version|\n|---|---|---|---|\n| Content | True | 8987-9481 | 8987-9481 |\n| "
            "AntiVirus | False | 5212-5732 | 5211-5731 |\n| WildFire | False | 986250-990242 | 986026-990018 |\n| GP | "
            "False | An Error received from Panorama API: 'There is no Global Protect Gateway license on the box.' |"
            " N/A |\n\n\n**Total Content Types Outdated: 2**"
        )

        assert returned_commandresults.outputs == expected_returned_output
        assert returned_commandresults.readable_output == expected_returned_readable

    def test_panorama_check_latest_dynamic_update_command_no_wildfire_license(self, mocker):
        """
        Tests the scenario were there is no WildFire license on the Firewall.

        Given:
            - a Panorama instance.
            - Mock response for the 'panorama_check_latest_dynamic_update_content' api call, with an exception regarding the
            WildFire missing license.

        When:
            - Calling the panorama_check_latest_dynamic_update_command.

        Then:
            - Verify that the outputs include the versions of the content, antivirus and the error message regarding
              the WildFire missing license.
            - Verify that the human-readable output includes the versions of the versions of the content, antivirus and
              the error message regarding the WildFire missing license.
        """

        from Panorama import panorama_check_latest_dynamic_update_command

        # Side-effect function to return the proper NGFW API response for the requested dynamic update type
        def side_effect_function(update_type, target):
            if update_type.name == "APP_THREAT":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_app-threat.json")

            elif update_type.name == "ANTIVIRUS":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_antivirus.json")

            elif update_type.name == "GP":
                return load_json("test_data/pan-os-check-latest-dynamic-update-status_apiresponse_gp.json")

            elif update_type.name == "WILDFIRE":
                raise Exception("There is not wildfire license on the box")

            else:
                return None

        mock_api_call = mocker.patch("Panorama.panorama_check_latest_dynamic_update_content")
        mock_api_call.side_effect = side_effect_function

        mock_return_results = mocker.patch("Panorama.return_results")

        panorama_check_latest_dynamic_update_command({"target": "1337"})

        # Prepare results for comparison
        returned_commandresults: CommandResults = mock_return_results.call_args[0][0]
        expected_returned_output = load_json(
            "test_data/pan-os-check-latest-dynamic-update-status_expected-returned-outputs-no-wildfire" "-license.json"
        )
        expected_returned_readable = (
            "### Dynamic Update Status Summary\n|Update Type|Is Up To Date|Latest Available "
            "Version|Currently Installed Version|\n|---|---|---|---|\n|"
            " Content | True | 8987-9481 | 8987-9481 |\n| "
            "AntiVirus | False | 5212-5732 | 5211-5731 |\n|"
            " WILDFIRE | False | An Error received from Panorama API: 'There is not wildfire license on the box.' | N/A |\n|"
            " GP | True | 98-260 | 98-260 |\n\n\n"
            "**Total Content Types Outdated: 1**"
        )

        assert returned_commandresults.outputs == expected_returned_output
        assert returned_commandresults.readable_output == expected_returned_readable

    @pytest.mark.parametrize(
        "update_phase, job_id, api_response_payload",
        [
            (
                "start-no-polling",
                "",
                {
                    "response": {
                        "@status": "success",
                        "@code": "19",
                        "result": {"msg": {"line": "Download job enqueued with jobid 1309"}, "job": "1309"},
                    },
                },
            ),
            (
                "start-with-polling",
                "",
                {
                    "response": {
                        "@status": "success",
                        "@code": "19",
                        "result": {"msg": {"line": "Download job enqueued with jobid 1309"}, "job": "1309"},
                    },
                },
            ),
            (
                "check",
                "1309",
                {
                    "response": {
                        "@status": "success",
                        "result": {
                            "job": {
                                "tenq": "2025/06/15 10:55:53",
                                "tdeq": "(null)",
                                "id": "1311",
                                "user": None,
                                "type": "Downld",
                                "status": "PEND",
                                "queued": "YES",
                                "stoppable": "yes",
                                "result": "PEND",
                                "tfin": None,
                                "description": None,
                                "positionInQ": "0",
                                "progress": "0",
                                "details": None,
                                "warnings": None,
                            }
                        },
                    },
                },
            ),
            (
                "finished",
                "1309",
                {
                    "response": {
                        "@status": "success",
                        "result": {
                            "job": {
                                "tenq": "2025/06/15 10:34:51",
                                "tdeq": "10:34:51",
                                "id": "1309",
                                "user": None,
                                "type": "Downld",
                                "status": "FIN",
                                "queued": "NO",
                                "stoppable": "no",
                                "result": "OK",
                                "tfin": "2025/06/15 10:34:53",
                                "description": None,
                                "positionInQ": "0",
                                "progress": "2025/06/15 10:34:53",
                                "details": {
                                    "line": [
                                        "File successfully downloaded",
                                        "Successfully downloaded",
                                        "Applications and Threats version: 8988-9483",
                                    ]
                                },
                                "warnings": None,
                            },
                        },
                    },
                },
            ),
        ],
    )
    def test_panorama_download_latest_dynamic_update_command(self, update_phase, job_id, api_response_payload, mocker):
        from Panorama import DynamicUpdateType
        from Panorama import panorama_download_latest_dynamic_update_command
        from CommonServerPython import ScheduledCommand

        mocker.patch("Panorama.panorama_download_latest_dynamic_update_content", return_value=api_response_payload)
        mocker.patch("Panorama.panorama_content_update_download_status", return_value=api_response_payload)

        mock_command_return = mocker.patch("Panorama.return_results")

        if update_phase == "start-no-polling":
            """
            Run the command for the first time, with an API response indicating the job has been enqueued.
            Verify that the response contains job info with no subsquent scheduled command.
            """
            panorama_download_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337", "polling": "false"})
            returned_results = mock_command_return.call_args[0][0]
            assert returned_results.scheduled_command is None
            assert returned_results.readable_output == "Content download JobID 1309 started on device 1337."

        elif update_phase == "start-with-polling":
            """
            Run the command for the first time, with an API response indicating the job has been enqueued.
            Verify that the response contains a ScheduledCommand object to poll for job status and that the
            default polling timeout/interval are applied when not provided.
            """
            panorama_download_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337"})
            returned_results = mock_command_return.call_args[0][0]
            assert isinstance(returned_results.scheduled_command, ScheduledCommand)
            assert returned_results.scheduled_command._timeout == "3600"
            assert returned_results.scheduled_command._next_run == "30"

        elif update_phase == "check":
            """
            Run the command as if a download has been started and check for the status of it.
            Verify that when the API response shows that the job is still pending that a ScheduledCommand
            object is returned to continue to poll for the download to complete, honoring custom
            timeout_in_seconds/interval_in_seconds when provided.
            """
            panorama_download_latest_dynamic_update_command(
                DynamicUpdateType.ANTIVIRUS,
                {
                    "target": "1337",
                    "job_id": job_id,
                    "timeout_in_seconds": "1200",
                    "interval_in_seconds": "45",
                },
            )
            returned_results = mock_command_return.call_args[0][0]
            assert isinstance(returned_results.scheduled_command, ScheduledCommand)
            assert returned_results.scheduled_command._timeout == "1200"
            assert returned_results.scheduled_command._next_run == "45"

        elif update_phase == "finished":
            """
            Run the command as if a download has been completed.
            Verify that when the API response shows the job is finished that the function returns a CommandResult
            that includes the expected outputs and does not continue to poll.
            """
            panorama_download_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337", "job_id": job_id})
            returned_results = mock_command_return.call_args[0][0]

            assert isinstance(returned_results, dict)
            assert returned_results["Contents"] == {
                "response": {
                    "@status": "success",
                    "result": {
                        "job": {
                            "tenq": "2025/06/15 10:34:51",
                            "tdeq": "10:34:51",
                            "id": "1309",
                            "user": None,
                            "type": "Downld",
                            "status": "FIN",
                            "queued": "NO",
                            "stoppable": "no",
                            "result": "OK",
                            "tfin": "2025/06/15 10:34:53",
                            "description": None,
                            "positionInQ": "0",
                            "progress": "2025/06/15 10:34:53",
                            "details": {
                                "line": [
                                    "File successfully downloaded",
                                    "Successfully downloaded",
                                    "Applications and Threats version: 8988-9483",
                                ]
                            },
                            "warnings": None,
                        }
                    },
                }
            }
            assert (
                returned_results["HumanReadable"]
                == "### AntiVirus update download status:\n|JobID|Status|Details|\n|---|---|---|\n| 1309 | Completed |"
                " tenq: 2025/06/15 10:34:51<br>tdeq: 10:34:51<br>id: 1309<br>user: null<br>type: Downld<br>status: "
                "FIN<br>queued: NO<br>stoppable: no<br>result: OK<br>tfin: 2025/06/15 10:34:53<br>description: "
                'null<br>positionInQ: 0<br>progress: 2025/06/15 10:34:53<br>details: {"line": '
                '["File successfully downloaded", "Successfully downloaded", "Applications and '
                'Threats version: 8988-9483"]}<br>warnings: null |\n'
            )
            assert returned_results["EntryContext"] == {
                "Panorama.AntiVirus.Download(val.JobID == obj.JobID)": {
                    "JobID": "1309",
                    "Status": "Completed",
                    "Details": {
                        "tenq": "2025/06/15 10:34:51",
                        "tdeq": "10:34:51",
                        "id": "1309",
                        "user": None,
                        "type": "Downld",
                        "status": "FIN",
                        "queued": "NO",
                        "stoppable": "no",
                        "result": "OK",
                        "tfin": "2025/06/15 10:34:53",
                        "description": None,
                        "positionInQ": "0",
                        "progress": "2025/06/15 10:34:53",
                        "details": {
                            "line": [
                                "File successfully downloaded",
                                "Successfully downloaded",
                                "Applications and Threats version: 8988-9483",
                            ]
                        },
                        "warnings": None,
                    },
                }
            }

    @pytest.mark.parametrize(
        "install_phase, job_id, api_response_payload",
        [
            (
                "start-no-polling",
                "",
                {
                    "response": {
                        "@status": "success",
                        "@code": "19",
                        "result": {"msg": {"line": "Content install job enqueued with jobid 1318"}, "job": "1318"},
                    }
                },
            ),
            (
                "start-with-polling",
                "",
                {
                    "response": {
                        "@status": "success",
                        "@code": "19",
                        "result": {"msg": {"line": "Content install job enqueued with jobid 1318"}, "job": "1318"},
                    }
                },
            ),
            (
                "check",
                "1318",
                {
                    "response": {
                        "@status": "success",
                        "result": {
                            "job": {
                                "tenq": "2025/06/15 12:43:29",
                                "tdeq": "12:43:29",
                                "id": "1318",
                                "user": None,
                                "type": "Antivirus",
                                "status": "ACT",
                                "queued": "NO",
                                "stoppable": "no",
                                "result": "PEND",
                                "tfin": None,
                                "description": None,
                                "positionInQ": "0",
                                "progress": "20",
                                "warnings": None,
                                "details": None,
                            }
                        },
                    }
                },
            ),
            (
                "finished",
                "1318",
                {
                    "response": {
                        "@status": "success",
                        "result": {
                            "job": {
                                "tenq": "2025/06/15 12:43:29",
                                "tdeq": "12:43:29",
                                "id": "1318",
                                "user": "admin",
                                "type": "Antivirus",
                                "status": "FIN",
                                "queued": "NO",
                                "stoppable": "no",
                                "result": "OK",
                                "tfin": "2025/06/15 12:44:21",
                                "description": None,
                                "positionInQ": "0",
                                "progress": "2025/06/15 12:44:21",
                                "details": {"line": "Job completed successfully"},
                                "warnings": None,
                            }
                        },
                    }
                },
            ),
        ],
    )
    def test_panorama_install_latest_dynamic_update_command(self, install_phase, job_id, api_response_payload, mocker):
        from Panorama import DynamicUpdateType
        from Panorama import panorama_install_latest_dynamic_update_command
        from CommonServerPython import ScheduledCommand

        mocker.patch("Panorama.panorama_install_latest_dynamic_update", return_value=api_response_payload)
        mocker.patch("Panorama.panorama_content_update_install_status", return_value=api_response_payload)

        mock_command_return = mocker.patch("Panorama.return_results")

        if install_phase == "start-no-polling":
            """
            Run the command for the first time, with an API response indicating the job has been enqueued.
            Verify that the response contains job info with no subsequent scheduled command.
            """
            panorama_install_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337", "polling": "false"})
            returned_results = mock_command_return.call_args[0][0]
            assert returned_results.scheduled_command is None
            assert returned_results.readable_output == "Content install JobID 1318 started on device 1337."

        elif install_phase == "start-with-polling":
            """
            Run the command for the first time, with an API response indicating the job has been enqueued.
            Verify that the response contains a ScheduledCommand object to poll for job status and that the
            default polling timeout/interval are applied when not provided.
            """
            panorama_install_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337"})
            returned_results = mock_command_return.call_args[0][0]
            assert isinstance(returned_results.scheduled_command, ScheduledCommand)
            assert returned_results.scheduled_command._timeout == "3600"
            assert returned_results.scheduled_command._next_run == "30"

        elif install_phase == "check":
            """
            Run the command as if an install has been started and check for the status of it.
            Verify that when the API response shows that the job is still pending that a ScheduledCommand
            object is returned to continue to poll for the install to complete, honoring custom
            timeout_in_seconds/interval_in_seconds when provided.
            """
            panorama_install_latest_dynamic_update_command(
                DynamicUpdateType.ANTIVIRUS,
                {
                    "target": "1337",
                    "job_id": job_id,
                    "timeout_in_seconds": "1200",
                    "interval_in_seconds": "45",
                },
            )
            returned_results = mock_command_return.call_args[0][0]
            assert isinstance(returned_results.scheduled_command, ScheduledCommand)
            assert returned_results.scheduled_command._timeout == "1200"
            assert returned_results.scheduled_command._next_run == "45"

        elif install_phase == "finished":
            """
            Run the command as if an install has been completed.
            Verify that when the API response shows the job is finished that the function returns a CommandResult
            that includes the expected outputs and does not continue to poll.
            """
            panorama_install_latest_dynamic_update_command(DynamicUpdateType.ANTIVIRUS, {"target": "1337", "job_id": job_id})
            returned_results = mock_command_return.call_args[0][0]

            assert isinstance(returned_results, dict)
            assert returned_results["Contents"] == {
                "response": {
                    "@status": "success",
                    "result": {
                        "job": {
                            "tenq": "2025/06/15 12:43:29",
                            "tdeq": "12:43:29",
                            "id": "1318",
                            "user": "admin",
                            "type": "Antivirus",
                            "status": "FIN",
                            "queued": "NO",
                            "stoppable": "no",
                            "result": "OK",
                            "tfin": "2025/06/15 12:44:21",
                            "description": None,
                            "positionInQ": "0",
                            "progress": "2025/06/15 12:44:21",
                            "details": {"line": "Job completed successfully"},
                            "warnings": None,
                        }
                    },
                }
            }
            assert (
                returned_results["HumanReadable"]
                == "### AntiVirus update install status:\n|JobID|Status|Details|\n|---|---|---|\n| 1318 | Completed | "
                "tenq: 2025/06/15 12:43:29<br>tdeq: 12:43:29<br>id: 1318<br>user: admin<br>type: Antivirus<br>"
                "status: FIN<br>queued: NO<br>stoppable: no<br>result: OK<br>tfin: 2025/06/15 12:44:21<br>"
                'description: null<br>positionInQ: 0<br>progress: 2025/06/15 12:44:21<br>details: {"line": '
                '"Job completed successfully"}<br>warnings: null |\n'
            )
            assert returned_results["EntryContext"] == {
                "Panorama.AntiVirus.Install(val.JobID == obj.JobID)": {
                    "JobID": "1318",
                    "Status": "Completed",
                    "Details": {
                        "tenq": "2025/06/15 12:43:29",
                        "tdeq": "12:43:29",
                        "id": "1318",
                        "user": "admin",
                        "type": "Antivirus",
                        "status": "FIN",
                        "queued": "NO",
                        "stoppable": "no",
                        "result": "OK",
                        "tfin": "2025/06/15 12:44:21",
                        "description": None,
                        "positionInQ": "0",
                        "progress": "2025/06/15 12:44:21",
                        "details": {"line": "Job completed successfully"},
                        "warnings": None,
                    },
                }
            }


@pytest.mark.parametrize(
    ("vsys", "rules", "rulebase", "expected_cmd"),
    [
        (
            "vsys1",
            "Rule1",
            "qos",
            b'<show><rule-hit-count><vsys><vsys-name><entry name="vsys1"><rule-base><entry name="qos"><rules>'
            b"<list><member>Rule1</member></list></rules></entry></rule-base></entry></vsys-name></vsys>"
            b"</rule-hit-count></show>",
        ),
        (
            "vsys3",
            "Rule1,Rule2,Rule3",
            "security",
            b'<show><rule-hit-count><vsys><vsys-name><entry name="vsys3"><rule-base><entry name="security"><rules><list>'
            b"<member>Rule1</member><member>Rule2</member><member>Rule3</member>"
            b"</list></rules></entry></rule-base></entry></vsys-name></vsys></rule-hit-count></show>",
        ),
    ],
)
def test_build_rule_hit_count_xml(vsys, rules, rulebase, expected_cmd):
    """Test the build_rule_hit_count_xml return value.

    Args:
        vsys: Virtual system name or "all" for all virtual systems
        rules: Rule names (single rule, multiple comma-separated rules, or "all")
        rulebase: The firewall rulebase to check
        expected_cmd: Expected XML command that should be generated
        mock_topology: Mocked topology fixture
        mocker: Pytest mocker fixture

    """
    from Panorama import FirewallCommand
    import xml.etree.ElementTree as ET

    xml_root = FirewallCommand.build_rule_hit_count_xml(vsys, rulebase, rules)
    cmd = ET.tostring(xml_root, encoding="unicode").encode()

    assert cmd == expected_cmd


@freeze_time("2025-06-26 13:00:00 UTC")
@pytest.mark.parametrize(
    ("rulebase_type", "unused_only", "no_new_hits_since_dt", "length_expected"),
    [
        ("security", "false", None, 10),  # Should result in 10 returned records.
        ("security", "true", None, 6),  # Should result in 6 returned records.
        (
            "security",
            "false",
            datetime.strptime("2025/06/26 00:00:00Z", "%Y/%m/%d %H:%M:%SZ"),
            8,
        ),  # Should result in 8 returned records.
    ],
)
def test_get_hitcounts(rulebase_type, unused_only, no_new_hits_since_dt, length_expected, mock_topology, mocker):
    """Test the FirewallCommand.get_hitcounts method with various filter parameters.  Verify it returns the correct
        number of rules from the sample data based on the filter conditions.

    Args:
        rulebase_type: The type of rulebase to query (e.g., "security")
        unused_only: Whether to filter for unused rules only ("true"/"false")
        no_new_hits_since_dt: Optional datetime to filter rules with no new hits since this time
        length_expected: Expected number of rules in the result
        mock_topology: Mocked topology fixture
        mocker: Pytest mocker fixture
    """
    from Panorama import FirewallCommand

    rule_hitcounts_data = "test_data/get_rule_hitcounts_rule_hits.xml"
    pushed_policy_data = "test_data/get_rule_hitcounts_pushed_policy.xml"
    mocker.patch(
        "Panorama.run_op_command",
        side_effect=[load_xml_root_from_test_file(pushed_policy_data), load_xml_root_from_test_file(rule_hitcounts_data)],
    )

    mocker.patch("Panorama.demisto.callingContext", return_value={"context": {"IntegrationInstance": "Panorama_test_instance"}})

    result = FirewallCommand.get_hitcounts(
        topology=mock_topology,
        rulebase_type=rulebase_type,
        vsys_arg="vsys1",
        rules_arg="all",
        no_new_hits_since=no_new_hits_since_dt,
        device_filter_string=None,
        target=None,
        unused_only=unused_only,
    )
    assert result is not None
    assert len(result) == length_expected


@pytest.mark.parametrize(
    "unused_only, no_new_hits_since, expected_count",
    [
        # Case 1: unused_only=False, no filter -> all rules returned
        ("false", None, 4),
        # Case 2: unused_only=True -> only rules with hit_count=0
        ("true", None, 2),
        # Case 3: unused_only=False, no_new_hits_since recent -> only rules older than filter
        ("false", dateparser.parse("12 hours ago", settings={"TIMEZONE": "UTC"}), 4),
        # Case 4: unused_only=True, no_new_hits_since recent -> only old unused rules
        ("true", dateparser.parse("12 hours ago", settings={"TIMEZONE": "UTC"}), 2),
    ],
)
def test_get_hitcounts_filters_param(unused_only, no_new_hits_since, expected_count, mocker):
    """Test get_hitcounts with different unused_only and no_new_hits_since values."""

    import xml.etree.ElementTree as ET
    from Panorama import FirewallCommand

    # ----------------------------
    # Mock topology and firewall
    # ----------------------------
    mock_firewall = mocker.Mock()
    mock_firewall.id = "FW1"

    mock_topology = mocker.Mock()
    mock_topology.firewalls.return_value = [mock_firewall]
    mock_topology.panorama_objects = []

    vsys_list = ["vsys1", "vsys2"]
    mocker.patch.object(FirewallCommand, "get_vsys_list", return_value=vsys_list)

    # ----------------------------
    # Fake run_op_command returns 2 rules per VSYS
    # ----------------------------
    def fake_run_op(firewall, cmd, cmd_xml=False):
        xml_root = ET.Element("show")
        rhc = ET.SubElement(xml_root, "rule-hit-count")
        vsys_elem = ET.SubElement(rhc, "vsys")
        vsys_name_elem = ET.SubElement(vsys_elem, "vsys-name")
        entry = ET.SubElement(vsys_name_elem, "entry", name="vsys_placeholder")
        rb = ET.SubElement(entry, "rule-base")
        rb_entry = ET.SubElement(rb, "entry", name="security")
        rules_elem = ET.SubElement(rb_entry, "rules")

        # Helper to add rule entry with all required fields
        def add_rule(name, hit_count, last_hit_timestamp):
            rule = ET.SubElement(rules_elem, "entry", name=name)
            ET.SubElement(rule, "hit_count").text = str(hit_count)
            ET.SubElement(rule, "last_hit_timestamp").text = last_hit_timestamp
            # Add required extra fields for ShowRuleHitCountResult
            ET.SubElement(rule, "latest").text = "false"
            ET.SubElement(rule, "last_reset_timestamp").text = "0"
            ET.SubElement(rule, "first_hit_timestamp").text = "0"
            ET.SubElement(rule, "rule_creation_timestamp").text = "0"
            ET.SubElement(rule, "rule_modification_timestamp").text = "0"

        # Rule1: hit_count=0, old timestamp (1970)
        add_rule("Rule1", 0, "0")

        # Rule2: hit_count=5, recent timestamp '2025-03-20T14:52:04Z'
        add_rule("Rule2", 5, "1742482324")

        return xml_root

    mocker.patch("Panorama.run_op_command", side_effect=fake_run_op)

    # Patch demisto
    mocker.patch("Panorama.demisto.debug")
    mocker.patch("Panorama.demisto.callingContext", new={"context": {"IntegrationInstance": "test_instance"}})

    # Patch build_rule_hit_count_xml to use original
    original_build_xml = FirewallCommand.build_rule_hit_count_xml
    mocker.patch.object(
        FirewallCommand,
        "build_rule_hit_count_xml",
        side_effect=lambda vsys_name, rb, rules: original_build_xml(vsys_name, rb, rules),
    )

    # ----------------------------
    # Call function
    # ----------------------------
    results = FirewallCommand.get_hitcounts(
        mock_topology,
        "security",
        "all",
        "Rule1,Rule2",
        no_new_hits_since=no_new_hits_since,
        device_filter_string=None,
        target=None,
        unused_only=unused_only,
    )

    # ----------------------------
    # Assert total rules returned
    # ----------------------------
    assert len(results) == expected_count

    # Optional: check that returned rules match filters
    for r in results:
        if unused_only == "true":
            assert r.hit_count == 0
        if no_new_hits_since is not None:
            last_hit_dt = datetime.strptime(r.last_hit_timestamp, "%Y-%m-%dT%H:%M:%SZ")
            assert last_hit_dt <= no_new_hits_since


@pytest.mark.parametrize(
    "pre_post, expected_names",
    [
        # No filter -> every rule comes back: pre-pushed, post-pushed and local.
        (None, {"PreRule", "PostRule", "LocalRule"}),
        # pre_rulebase -> only the Panorama-pushed pre-rulebase rule.
        ("pre_rulebase", {"PreRule"}),
        # post_rulebase -> only the Panorama-pushed post-rulebase rule.
        ("post_rulebase", {"PostRule"}),
    ],
)
def test_get_hitcounts_pre_post_filter(pre_post, expected_names, mocker):
    """Validate the new ``pre_post`` filter in ``FirewallCommand.get_hitcounts``.

    Given:
        - A topology with one firewall and one vsys.
        - The Panorama-pushed policy enrichment returns one rule in ``pre-rulebase`` ("PreRule")
          and one rule in ``post-rulebase`` ("PostRule"); these populate ``result.position``
          with the underscore form ("pre_rulebase" / "post_rulebase").
        - The ``show rule-hit-count`` response contains those two pushed rules plus a local
          firewall rule ("LocalRule") that is NOT present in the pushed-policy map.

    When:
        - Calling ``get_hitcounts`` with the ``pre_post`` kwarg unset / "pre_rulebase" / "post_rulebase".

    Then:
        - With no filter, all three rules are returned.
        - With a ``pre_post`` value set, only the matching Panorama-pushed rule is returned;
          the rule from the other position AND the local rule are both excluded.
    """
    import xml.etree.ElementTree as ET
    from Panorama import FirewallCommand, PushedSharedPolicy

    # Topology with a single firewall and a single vsys.
    mock_firewall = mocker.Mock()
    mock_firewall.id = "FW1"
    mock_firewall.serial = "111111111111111"
    mock_firewall.hostname = None

    mock_topology = mocker.Mock()
    mock_topology.firewalls.return_value = [mock_firewall]
    mock_topology.panorama_objects = []

    mocker.patch.object(FirewallCommand, "get_vsys_list", return_value=["vsys1"])

    # Stub the pushed-policy lookup directly with the underscore-position form that the real
    # code stores on ``result.position``. This avoids re-asserting XML-parsing behavior and
    # isolates the test to the pre_post filter logic.
    pushed_map = {
        "PreRule": PushedSharedPolicy(hostid="FW1", name="PreRule", policy_type="security", position="pre_rulebase", loc="DG-1"),
        "PostRule": PushedSharedPolicy(
            hostid="FW1", name="PostRule", policy_type="security", position="post_rulebase", loc="DG-1"
        ),
    }
    mocker.patch.object(FirewallCommand, "get_pushed_shared_policy_rules", return_value=pushed_map)

    # Fake ``show rule-hit-count`` response containing all three rules (pre, post, local).
    def fake_run_op(firewall, cmd, cmd_xml=False):
        xml_root = ET.Element("show")
        rhc = ET.SubElement(xml_root, "rule-hit-count")
        vsys_elem = ET.SubElement(rhc, "vsys")
        vsys_name_elem = ET.SubElement(vsys_elem, "vsys-name")
        entry = ET.SubElement(vsys_name_elem, "entry", name="vsys1")
        rb = ET.SubElement(entry, "rule-base")
        rb_entry = ET.SubElement(rb, "entry", name="security")
        rules_elem = ET.SubElement(rb_entry, "rules")

        for name in ("PreRule", "PostRule", "LocalRule"):
            rule = ET.SubElement(rules_elem, "entry", name=name)
            ET.SubElement(rule, "hit_count").text = "10"
            ET.SubElement(rule, "last_hit_timestamp").text = "1742482324"
            ET.SubElement(rule, "latest").text = "false"
            ET.SubElement(rule, "last_reset_timestamp").text = "0"
            ET.SubElement(rule, "first_hit_timestamp").text = "0"
            ET.SubElement(rule, "rule_creation_timestamp").text = "0"
            ET.SubElement(rule, "rule_modification_timestamp").text = "0"

        return xml_root

    mocker.patch("Panorama.run_op_command", side_effect=fake_run_op)
    mocker.patch("Panorama.demisto.debug")
    mocker.patch("Panorama.demisto.callingContext", new={"context": {"IntegrationInstance": "test_instance"}})

    results = FirewallCommand.get_hitcounts(
        mock_topology,
        "security",
        "vsys1",
        "all",
        no_new_hits_since=None,
        device_filter_string=None,
        target=None,
        unused_only="false",
        pre_post=pre_post,
    )

    returned_names = {r.name for r in results}
    assert returned_names == expected_names

    # When the filter is set, every returned row must come from Panorama at the requested position
    # — local rules and rules from the other rulebase must be excluded.
    if pre_post is not None:
        for r in results:
            assert r.is_from_panorama is True
            assert r.position == pre_post


def test_get_hitcounts_vsys_specific_enrichment(mocker):
    """
    Test that get_hitcounts performs per-vsys enrichment and uses the updated XPath logic.

    Given:
        - A topology with one firewall and two vsys (vsys1, vsys2).
        - Each vsys has different Panorama-pushed rules (vsys1 has "PanoramaRule_v1", vsys2 has "PanoramaRule_v2").
        - Each vsys also has a local rule that is NOT in the pushed policy data.

    When:
        - Calling get_hitcounts with vsys_arg="all".

    Then:
        - get_pushed_shared_policy_rules is called once per vsys (2 times total), each with the correct vsys_name.
        - The pushed-shared-policy command includes the vsys name in the XML.
        - Rules from vsys1 are enriched only with vsys1's pushed policy data (and vice versa).
        - The new XPath './/panorama/{position}/...' correctly finds rules in the XML response.
        - Local rules (not in pushed policy) have is_from_panorama=False.
    """
    import xml.etree.ElementTree as ET
    from Panorama import FirewallCommand

    # Mock topology and firewall
    mock_firewall = mocker.Mock()
    mock_firewall.id = "FW1"
    mock_firewall.serial = "111111111111111"
    mock_firewall.hostname = None

    mock_topology = mocker.Mock()
    mock_topology.firewalls.return_value = [mock_firewall]
    mock_topology.panorama_objects = []

    mocker.patch.object(FirewallCommand, "get_vsys_list", return_value=["vsys1", "vsys2"])

    # Build per-vsys pushed policy XML responses (new format with .//panorama/ XPath)
    def build_pushed_policy_xml(vsys_name: str) -> ET.Element:
        root = ET.Element("response", status="success")
        result_elem = ET.SubElement(root, "result")
        policy = ET.SubElement(result_elem, "policy")
        panorama_elem = ET.SubElement(policy, "panorama")

        # Pre-rulebase with a vsys-specific rule
        pre_rb = ET.SubElement(panorama_elem, "pre-rulebase")
        security = ET.SubElement(pre_rb, "security")
        rules = ET.SubElement(security, "rules")
        rule_name = f"PanoramaRule_{vsys_name.replace('vsys', 'v')}"
        dg_name = f"DG-{vsys_name}"
        entry = ET.SubElement(rules, "entry", name=rule_name, loc=dg_name)
        # Add a child element so the entry is not considered "empty" by ElementTree
        # (bool(element) returns False for elements with no children, causing dataclass_from_element to return None)
        ET.SubElement(entry, "action", loc=dg_name).text = "allow"

        return root

    pushed_policy_vsys1 = build_pushed_policy_xml("vsys1")
    pushed_policy_vsys2 = build_pushed_policy_xml("vsys2")

    # Build a hitcount response XML containing both a Panorama rule and a local rule
    def build_hitcount_xml(vsys_name: str) -> ET.Element:
        root = ET.Element("response", status="success")
        result_elem = ET.SubElement(root, "result")
        rhc = ET.SubElement(result_elem, "rule-hit-count")
        vsys_elem = ET.SubElement(rhc, "vsys")
        vsys_name_elem = ET.SubElement(vsys_elem, "vsys-name")
        entry = ET.SubElement(vsys_name_elem, "entry", name=vsys_name)
        rb = ET.SubElement(entry, "rule-base")
        rb_entry = ET.SubElement(rb, "entry", name="security")
        rules_elem = ET.SubElement(rb_entry, "rules")

        panorama_rule_name = f"PanoramaRule_{vsys_name.replace('vsys', 'v')}"
        local_rule_name = f"LocalRule_{vsys_name}"

        for rule_name in [panorama_rule_name, local_rule_name]:
            rule = ET.SubElement(rules_elem, "entry", name=rule_name)
            ET.SubElement(rule, "hit_count").text = "10"
            ET.SubElement(rule, "last_hit_timestamp").text = "1742482324"
            ET.SubElement(rule, "latest").text = "false"
            ET.SubElement(rule, "last_reset_timestamp").text = "0"
            ET.SubElement(rule, "first_hit_timestamp").text = "0"
            ET.SubElement(rule, "rule_creation_timestamp").text = "0"
            ET.SubElement(rule, "rule_modification_timestamp").text = "0"

        return root

    hitcount_vsys1 = build_hitcount_xml("vsys1")
    hitcount_vsys2 = build_hitcount_xml("vsys2")
    """
    Mock run_op_command to return vsys-specific responses
    The new code calls run_op_command in this order per vsys:
        1. pushed-shared-policy (enrichment)
        2. hitcount query
    So we need to mock 4 responses for 2 vsys: pushed1, hitcount1, pushed2, hitcount2
    """
    run_op_responses = [pushed_policy_vsys1, hitcount_vsys1, pushed_policy_vsys2, hitcount_vsys2]
    run_op_mock = mocker.patch("Panorama.run_op_command", side_effect=run_op_responses)

    mocker.patch("Panorama.demisto.debug")
    mocker.patch("Panorama.demisto.callingContext", new={"context": {"IntegrationInstance": "test_instance"}})

    results = FirewallCommand.get_hitcounts(
        topology=mock_topology,
        rulebase_type="security",
        vsys_arg="all",
        rules_arg="all",
        no_new_hits_since=None,
        device_filter_string=None,
        target=None,
        unused_only="false",
    )

    # Total of 4 rules returned (2 per vsys)
    assert len(results) == 4

    # run_op_command called 4 times total (pushed + hitcount per vsys)
    assert run_op_mock.call_count == 4

    # Verify the pushed-shared-policy commands include vsys name
    # Call 0: pushed policy for vsys1
    pushed_call_1_cmd = run_op_mock.call_args_list[0]
    assert "vsys1" in pushed_call_1_cmd.kwargs.get("cmd", pushed_call_1_cmd[1].get("cmd", ""))
    # Call 2: pushed policy for vsys2
    pushed_call_2_cmd = run_op_mock.call_args_list[2]
    assert "vsys2" in pushed_call_2_cmd.kwargs.get("cmd", pushed_call_2_cmd[1].get("cmd", ""))

    # Verify per-vsys enrichment: Panorama rules are correctly enriched
    vsys1_results = [r for r in results if r.vsys == "vsys1"]
    vsys2_results = [r for r in results if r.vsys == "vsys2"]

    assert len(vsys1_results) == 2
    assert len(vsys2_results) == 2

    # vsys1's Panorama rule should be enriched with vsys1's DG
    vsys1_panorama_rule = next(r for r in vsys1_results if r.name == "PanoramaRule_v1")
    assert vsys1_panorama_rule.is_from_panorama is True
    assert vsys1_panorama_rule.from_dg_name == "DG-vsys1"
    assert vsys1_panorama_rule.position == "pre_rulebase"

    # vsys1's local rule should NOT be enriched
    vsys1_local_rule = next(r for r in vsys1_results if r.name == "LocalRule_vsys1")
    assert vsys1_local_rule.is_from_panorama is False
    assert vsys1_local_rule.from_dg_name == ""

    # vsys2's Panorama rule should be enriched with vsys2's DG (not vsys1's)
    vsys2_panorama_rule = next(r for r in vsys2_results if r.name == "PanoramaRule_v2")
    assert vsys2_panorama_rule.is_from_panorama is True
    assert vsys2_panorama_rule.from_dg_name == "DG-vsys2"
    assert vsys2_panorama_rule.position == "pre_rulebase"

    # vsys2's local rule should NOT be enriched
    vsys2_local_rule = next(r for r in vsys2_results if r.name == "LocalRule_vsys2")
    assert vsys2_local_rule.is_from_panorama is False
    assert vsys2_local_rule.from_dg_name == ""

    # Verify no cross-vsys enrichment leakage
    # PanoramaRule_v1 should NOT appear in vsys2 results
    vsys2_rule_names = {r.name for r in vsys2_results}
    assert "PanoramaRule_v1" not in vsys2_rule_names
    # PanoramaRule_v2 should NOT appear in vsys1 results
    vsys1_rule_names = {r.name for r in vsys1_results}
    assert "PanoramaRule_v2" not in vsys1_rule_names


def test_panorama_upload_content_update_file_command_uses_basename(mocker):
    """
    Given: A file entry whose name contains directory components (e.g. 'subdir/firmware.bin').
    When: panorama_upload_content_update_file_command is called.
    Then: Only the basename is used for the local copy destination and os.remove is called for cleanup.
    """
    import Panorama

    mocker.patch.object(
        demisto,
        "getFilePath",
        return_value={"path": "/tmp/fake_path/firmware.bin", "name": "subdir/firmware.bin"},
    )
    copy_mock = mocker.patch("shutil.copy")
    mock_file = mocker.mock_open(read_data=b"fake content")
    mocker.patch("builtins.open", mock_file)
    mocker.patch(
        "Panorama.http_request",
        return_value={"response": {"@status": "success", "msg": "upload succeeded"}},
    )
    remove_mock = mocker.patch("os.remove")
    mocker.patch("os.path.isfile", return_value=True)
    mocker.patch.object(Panorama, "API_KEY", "test_key")
    mocker.patch.object(Panorama, "URL", "https://test.example.com")

    Panorama.panorama_upload_content_update_file_command({"category": "content", "entryID": "entry123"})

    # Assert shutil.copy was called with basename only (not the full subdir/firmware.bin)
    assert copy_mock.call_args[0][1] == "firmware.bin"
    # Assert os.remove was called
    remove_mock.assert_called_once_with("firmware.bin")