diff --git a/docs/about/introduction.md b/docs/about/introduction.md index ed5371d8..d1b8e463 100644 --- a/docs/about/introduction.md +++ b/docs/about/introduction.md @@ -33,7 +33,7 @@ automate these tasks, ensuring consistency and efficiency. ## Getting Help -- **Documentation**: Explore the [SDK Developer Documentation](../sdk/index.md) for detailed guidance. +- **Documentation**: Explore the [Developer Documentation](../sdk/index.md) for detailed guidance. - **GitHub Repository**: Visit the [pan-scm-sdk GitHub repository](https://github.com/cdot65/pan-scm-sdk) for source code and issue tracking. diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index c9050b02..9dddd4e6 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -5,6 +5,16 @@ enhancements, and fixes in each version of the tool. --- +## Version 0.1.9 + +**Release Date:** October 17, 2024 + +### Wildfire Antivirus Security Profiles + +- **Wildfire Antivirus**: Add support for managing Wildfire Anti-Virus Security Profiles. + +--- + ## Version 0.1.8 **Release Date:** October 16, 2024 diff --git a/docs/sdk/config/security_services/wildfire_antivirus.md b/docs/sdk/config/security_services/wildfire_antivirus.md new file mode 100644 index 00000000..3bcd3ffa --- /dev/null +++ b/docs/sdk/config/security_services/wildfire_antivirus.md @@ -0,0 +1,165 @@ +# WildFire Antivirus Profile Configuration Object + +The `WildfireAntivirusProfile` class is used to manage WildFire Antivirus Profile objects in the Strata Cloud Manager. +It provides methods to create, retrieve, update, delete, and list WildFire Antivirus Profile objects. + +--- + +## Importing the WildfireAntivirusProfile Class + +```python +from scm.config.security import WildfireAntivirusProfile +``` + +## Methods + +### `create(data: Dict[str, Any]) -> WildfireAntivirusProfileResponseModel` + +Creates a new WildFire Antivirus Profile object. + +**Parameters:** + +- `data` (Dict[str, Any]): A dictionary containing the WildFire Antivirus Profile object data. + +**Example:** + +```python +profile_data = { + "name": "test_profile", + "description": "Created via pan-scm-sdk", + "folder": "Prisma Access", + "rules": [ + { + "name": "rule1", + "direction": "both", + "analysis": "public-cloud" + } + ] +} + +new_profile = wildfire_antivirus_profile.create(profile_data) +print(f"Created WildFire Antivirus Profile with ID: {new_profile.id}") +``` + +### `get(object_id: str) -> WildfireAntivirusProfileResponseModel` + +Retrieves a WildFire Antivirus Profile object by its ID. + +**Parameters:** + +- `object_id` (str): The UUID of the WildFire Antivirus Profile object. + +**Example:** + +```python +profile_id = "123e4567-e89b-12d3-a456-426655440000" +profile_object = wildfire_antivirus_profile.get(profile_id) +print(f"Profile Name: {profile_object.name}") +``` + +### `update(object_id: str, data: Dict[str, Any]) -> WildfireAntivirusProfileResponseModel` + +Updates an existing WildFire Antivirus Profile object. + +**Parameters:** + +- `object_id` (str): The UUID of the WildFire Antivirus Profile object. +- `data` (Dict[str, Any]): A dictionary containing the updated WildFire Antivirus Profile data. + +**Example:** + +```python +update_data = { + "description": "Updated description", +} + +updated_profile = wildfire_antivirus_profile.update(profile_id, update_data) +print(f"Updated WildFire Antivirus Profile with ID: {updated_profile.id}") +``` + +### `delete(object_id: str) -> None` + +Deletes a WildFire Antivirus Profile object by its ID. + +**Parameters:** + +- `object_id` (str): The UUID of the WildFire Antivirus Profile object. + +**Example:** + +```python +wildfire_antivirus_profile.delete(profile_id) +print(f"Deleted WildFire Antivirus Profile with ID: {profile_id}") +``` + +### +`list(folder: Optional[str] = None, snippet: Optional[str] = None, device: Optional[str] = None, offset: Optional[int] = None, limit: Optional[int] = None, name: Optional[str] = None, **filters) -> List[WildfireAntivirusProfileResponseModel]` + +Lists WildFire Antivirus Profile objects, optionally filtered by folder, snippet, device, or other criteria. + +**Parameters:** + +- `folder` (Optional[str]): The folder to list profiles from. +- `snippet` (Optional[str]): The snippet to list profiles from. +- `device` (Optional[str]): The device to list profiles from. +- `offset` (Optional[int]): The pagination offset. +- `limit` (Optional[int]): The pagination limit. +- `name` (Optional[str]): Filter profiles by name. +- `**filters`: Additional filters. + +**Example:** + +```python +profiles = wildfire_antivirus_profile.list(folder='Prisma Access', limit=10) + +for profile in profiles: + print(f"Profile Name: {profile.name}, ID: {profile.id}") +``` + +--- + +## Usage Example + +```python +from scm.client import Scm +from scm.config.security import WildfireAntivirusProfile + +# Initialize the SCM client +scm = Scm( + client_id="your_client_id", + client_secret="your_client_secret", + tsg_id="your_tsg_id", +) + +# Create a WildfireAntivirusProfile instance +wildfire_antivirus_profile = WildfireAntivirusProfile(scm) + +# Create a new WildFire Antivirus Profile +profile_data = { + "name": "test_profile", + "description": "Created via pan-scm-sdk", + "folder": "Prisma Access", + "rules": [ + { + "name": "rule1", + "direction": "both", + "analysis": "public-cloud" + } + ] +} + +new_profile = wildfire_antivirus_profile.create(profile_data) +print(f"Created WildFire Antivirus Profile with ID: {new_profile.id}") + +# List WildFire Antivirus Profiles +profiles = wildfire_antivirus_profile.list(folder='Prisma Access', limit=10) +for profile in profiles: + print(f"Profile Name: {profile.name}, ID: {profile.id}") +``` + +--- + +## Related Models + +- [WildfireAntivirusProfileRequestModel](models/wildfire_antivirus_profile_models.md#wildfireantivirusprofilerequest) +- [WildfireAntivirusProfileResponseModel](models/wildfire_antivirus_profile_models.md#wildfireantivirusprofileresponse) diff --git a/docs/sdk/index.md b/docs/sdk/index.md index a9914b3b..8feea6f1 100644 --- a/docs/sdk/index.md +++ b/docs/sdk/index.md @@ -14,6 +14,7 @@ configuration objects and data models used to interact with Palo Alto Networks S - [Service](config/objects/service.md) - [Security Services](config/security_services/index) - [Anti-Spyware](config/security_services/anti_spyware.md) + - [Wildfire Antivirus](config/security_services/wildfire_antivirus.md) - Data Models - [Objects](models/objects/index) - [Address Models](models/objects/address_models.md) @@ -23,6 +24,7 @@ configuration objects and data models used to interact with Palo Alto Networks S - [Service Models](models/objects/service_models.md) - [Security Services](models/security_services/index) - [Anti-Spyware](models/security_services/anti_spyware_profile_models.md) + - [Wildfire Antivirus](models/security_services/wildfire_antivirus_profile_models.md) --- diff --git a/docs/sdk/models/security_services/index.md b/docs/sdk/models/security_services/index.md index 7225810e..8066858b 100644 --- a/docs/sdk/models/security_services/index.md +++ b/docs/sdk/models/security_services/index.md @@ -17,3 +17,4 @@ For each configuration object, there are corresponding request and response mode ## Models by Configuration Object - [Anti Spyware Security Profile Models](anti_spyware_profile_models.md) +- [Wildfire Antivirus Security Profile Models](wildfire_antivirus_profile_models.md) diff --git a/docs/sdk/models/security_services/wildfire_antivirus_profile_models.md b/docs/sdk/models/security_services/wildfire_antivirus_profile_models.md new file mode 100644 index 00000000..e4432709 --- /dev/null +++ b/docs/sdk/models/security_services/wildfire_antivirus_profile_models.md @@ -0,0 +1,133 @@ +# WildFire Antivirus Profile Models + +This section covers the data models associated with the `WildfireAntivirusProfile` configuration object. + +--- + +## WildfireAntivirusProfileRequestModel + +Used when creating or updating a WildFire Antivirus Profile object. + +### Attributes + +- `name` (str): **Required.** The name of the WildFire Antivirus Profile object. +- `description` (Optional[str]): A description of the WildFire Antivirus Profile object. +- `packet_capture` (Optional[bool]): Whether packet capture is enabled. +- `mlav_exception` (Optional[List[MlavExceptionEntry]]): List of MLAV exceptions. +- `rules` (List[RuleBase]): **Required.** List of rules for the profile. +- `threat_exception` (Optional[List[ThreatExceptionEntry]]): List of threat exceptions. +- **Container Type Fields** (Exactly one must be provided): + - `folder` (Optional[str]): The folder where the profile is defined. + - `snippet` (Optional[str]): The snippet where the profile is defined. + - `device` (Optional[str]): The device where the profile is defined. + +### Example + +```python +profile_request = WildfireAntivirusProfileRequestModel( + name="test-profile", + description="Sample WildFire Antivirus Profile", + folder="Prisma Access", + rules=[ + RuleRequest( + name="rule1", + direction="both", + analysis="public-cloud" + ) + ] +) +``` + +--- + +## WildfireAntivirusProfileResponseModel + +Used when parsing WildFire Antivirus Profile objects retrieved from the API. + +### Attributes + +- `id` (str): The UUID of the WildFire Antivirus Profile object. +- `name` (str): The name of the WildFire Antivirus Profile object. +- `description` (Optional[str]): A description of the WildFire Antivirus Profile object. +- `packet_capture` (Optional[bool]): Whether packet capture is enabled. +- `mlav_exception` (Optional[List[MlavExceptionEntry]]): List of MLAV exceptions. +- `rules` (List[RuleBase]): List of rules for the profile. +- `threat_exception` (Optional[List[ThreatExceptionEntry]]): List of threat exceptions. +- **Container Type Fields**: + - `folder` (Optional[str]): The folder where the profile is defined. + - `snippet` (Optional[str]): The snippet where the profile is defined. + - `device` (Optional[str]): The device where the profile is defined. + +### Example + +```python +profile_response = WildfireAntivirusProfileResponseModel( + id="123e4567-e89b-12d3-a456-426655440000", + name="test-profile", + description="Sample WildFire Antivirus Profile", + folder="Prisma Access", + rules=[ + RuleResponse( + name="rule1", + direction="both", + analysis="public-cloud" + ) + ] +) +``` + +--- + +## RuleBase + +Base class for Rule objects used in WildFire Antivirus Profiles. + +### Attributes + +- `name` (str): **Required.** Rule name. +- `analysis` (Optional[Analysis]): Analysis type (public-cloud or private-cloud). +- `application` (List[str]): List of applications (default: ["any"]). +- `direction` (Direction): **Required.** Direction (download, upload, or both). +- `file_type` (List[str]): List of file types (default: ["any"]). + +--- + +## MlavExceptionEntry + +Represents an entry in the 'mlav_exception' list. + +### Attributes + +- `name` (str): **Required.** Exception name. +- `description` (Optional[str]): Description of the exception. +- `filename` (str): **Required.** Filename for the exception. + +--- + +## ThreatExceptionEntry + +Represents an entry in the 'threat_exception' list. + +### Attributes + +- `name` (str): **Required.** Threat exception name. +- `notes` (Optional[str]): Notes for the threat exception. + +--- + +## Enums + +### Analysis + +Enumeration of analysis types: + +- `public_cloud` +- `private_cloud` + +### Direction + +Enumeration of directions: + +- `download` +- `upload` +- `both` diff --git a/mkdocs.yml b/mkdocs.yml index 88bc5e66..e76a7049 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,10 +21,15 @@ plugins: show_source: true nav: - Home: index.md - - Introduction: about/introduction.md - - Installation: about/installation.md - - Getting Started: about/getting-started.md - - SDK Developer Documentation: + - About: + - Introduction: about/introduction.md + - Installation: about/installation.md + - Getting Started: about/getting-started.md + - Troubleshooting: about/troubleshooting.md + - Contributing: about/contributing.md + - Release Notes: about/release-notes.md + - License: about/license.md + - Developer Documentation: - Overview: sdk/index.md - Configuration: - Objects: @@ -50,9 +55,6 @@ nav: - Anti Spyware Security Profile: sdk/models/security_services/anti_spyware.md - Authentication Module: sdk/auth.md - SCM Client: sdk/client.md - - Troubleshooting: about/troubleshooting.md - - Contributing: about/contributing.md - - License: about/license.md extra_css: - css/termynal.css - css/custom.css diff --git a/pyproject.toml b/pyproject.toml index 690aa31b..7165e2bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-scm-sdk" -version = "0.1.8" +version = "0.1.9" description = "Python SDK for Palo Alto Networks Strata Cloud Manager." authors = ["Calvin Remsburg "] license = "Apache 2.0" diff --git a/scm/config/security/__init__.py b/scm/config/security/__init__.py index 844a64b6..b3ab52c2 100644 --- a/scm/config/security/__init__.py +++ b/scm/config/security/__init__.py @@ -1,3 +1,4 @@ # scm/config/security/__init__.py -from .anti_spyware_profiles import AntiSpywareProfile +from .anti_spyware_profile import AntiSpywareProfile +from .wildfire_antivirus_profile import WildfireAntivirusProfile diff --git a/scm/config/security/anti_spyware_profiles.py b/scm/config/security/anti_spyware_profile.py similarity index 98% rename from scm/config/security/anti_spyware_profiles.py rename to scm/config/security/anti_spyware_profile.py index 23439c7f..573040af 100644 --- a/scm/config/security/anti_spyware_profiles.py +++ b/scm/config/security/anti_spyware_profile.py @@ -1,4 +1,4 @@ -# scm/config/security/anti_spyware_profiles.py +# scm/config/security/anti_spyware_profile.py from typing import List, Dict, Any, Optional from scm.config import BaseObject diff --git a/scm/config/security/wildfire_antivirus_profile.py b/scm/config/security/wildfire_antivirus_profile.py new file mode 100644 index 00000000..cc2e15f1 --- /dev/null +++ b/scm/config/security/wildfire_antivirus_profile.py @@ -0,0 +1,185 @@ +# scm/config/security/wildfire_antivirus_profiles.py + +from typing import List, Dict, Any, Optional +from scm.config import BaseObject +from scm.models.security.wildfire_antivirus_profiles import ( + WildfireAntivirusProfileRequestModel, + WildfireAntivirusProfileResponseModel, +) +from scm.exceptions import ValidationError + + +class WildfireAntivirusProfile(BaseObject): + """ + Manages WildFire Antivirus Profiles in Palo Alto Networks' Strata Cloud Manager. + + This class provides methods to create, retrieve, update, delete, and list WildFire Antivirus Profiles + using the Strata Cloud Manager API. It supports operations within folders, snippets, + or devices, and allows filtering of profiles based on various criteria. + + Attributes: + ENDPOINT (str): The API endpoint for WildFire Antivirus Profile operations. + + Errors: + ValidationError: Raised when invalid container parameters are provided. + + Returns: + WildfireAntivirusProfileResponseModel: For create, get, and update methods. + List[WildfireAntivirusProfileResponseModel]: For the list method. + """ + + ENDPOINT = "/config/security/v1/wildfire-anti-virus-profiles" + + def __init__(self, api_client): + super().__init__(api_client) + + def create(self, data: Dict[str, Any]) -> WildfireAntivirusProfileResponseModel: + """ + Create a new WildFire Antivirus Profile. + + Args: + data (Dict[str, Any]): The data for the new profile. + + Returns: + WildfireAntivirusProfileResponseModel: The created profile. + """ + profile = WildfireAntivirusProfileRequestModel(**data) + payload = profile.model_dump(exclude_unset=True) + response = self.api_client.post(self.ENDPOINT, json=payload) + return WildfireAntivirusProfileResponseModel(**response) + + def get(self, object_id: str) -> WildfireAntivirusProfileResponseModel: + """ + Retrieve a WildFire Antivirus Profile by its ID. + + Args: + object_id (str): The ID of the profile to retrieve. + + Returns: + WildfireAntivirusProfileResponseModel: The retrieved profile. + """ + endpoint = f"{self.ENDPOINT}/{object_id}" + response = self.api_client.get(endpoint) + return WildfireAntivirusProfileResponseModel(**response) + + def update( + self, object_id: str, data: Dict[str, Any] + ) -> WildfireAntivirusProfileResponseModel: + """ + Update an existing WildFire Antivirus Profile. + + Args: + object_id (str): The ID of the profile to update. + data (Dict[str, Any]): The updated data for the profile. + + Returns: + WildfireAntivirusProfileResponseModel: The updated profile. + """ + profile = WildfireAntivirusProfileRequestModel(**data) + payload = profile.model_dump(exclude_unset=True) + endpoint = f"{self.ENDPOINT}/{object_id}" + response = self.api_client.put(endpoint, json=payload) + return WildfireAntivirusProfileResponseModel(**response) + + def delete(self, object_id: str) -> None: + """ + Delete a WildFire Antivirus Profile. + + Args: + object_id (str): The ID of the profile to delete. + + Returns: + None + """ + endpoint = f"{self.ENDPOINT}/{object_id}" + self.api_client.delete(endpoint) + + def list( + self, + folder: Optional[str] = None, + snippet: Optional[str] = None, + device: Optional[str] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + name: Optional[str] = None, + **filters, + ) -> List[WildfireAntivirusProfileResponseModel]: + """ + List WildFire Antivirus Profiles. + + Args: + folder (Optional[str]): The folder to filter profiles. + snippet (Optional[str]): The snippet to filter profiles. + device (Optional[str]): The device to filter profiles. + offset (Optional[int]): The pagination offset. + limit (Optional[int]): The pagination limit. + name (Optional[str]): Filter profiles by name. + **filters: Additional filters. + + Returns: + List[WildfireAntivirusProfileResponseModel]: The list of profiles. + """ + params = {} + error_messages = [] + + # Validate offset and limit + if offset is not None: + if not isinstance(offset, int) or offset < 0: + error_messages.append("Offset must be a non-negative integer") + if limit is not None: + if not isinstance(limit, int) or limit <= 0: + error_messages.append("Limit must be a positive integer") + + # If there are any validation errors, raise ValueError with all error messages + if error_messages: + raise ValueError(". ".join(error_messages)) + + # Include container type parameter + container_params = { + "folder": folder, + "snippet": snippet, + "device": device, + } + provided_containers = { + k: v for k, v in container_params.items() if v is not None + } + + if len(provided_containers) != 1: + raise ValidationError( + "Exactly one of 'folder', 'snippet', or 'device' must be provided." + ) + + params.update(provided_containers) + + # Handle pagination parameters + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + + # Handle filters + if name is not None: + params["name"] = name + + # Include any additional filters provided + params.update( + { + k: v + for k, v in filters.items() + if v is not None + and k not in container_params + and k + not in [ + "offset", + "limit", + "name", + ] + } + ) + + response = self.api_client.get(self.ENDPOINT, params=params) + profiles = [ + WildfireAntivirusProfileResponseModel(**item) + for item in response.get("data", []) + ] + return profiles diff --git a/scm/models/security/__init__.py b/scm/models/security/__init__.py index 6bd4a2ac..a8f1217d 100644 --- a/scm/models/security/__init__.py +++ b/scm/models/security/__init__.py @@ -4,3 +4,7 @@ AntiSpywareProfileRequestModel, AntiSpywareProfileResponseModel, ) +from .wildfire_antivirus_profiles import ( + WildfireAntivirusProfileResponseModel, + WildfireAntivirusProfileRequestModel, +) diff --git a/scm/models/security/anti_spyware_profiles.py b/scm/models/security/anti_spyware_profiles.py index e19dd806..544eaa49 100644 --- a/scm/models/security/anti_spyware_profiles.py +++ b/scm/models/security/anti_spyware_profiles.py @@ -1,4 +1,4 @@ -# scm/models/security/anti_spyware_profiles.py +# scm/models/security/anti_spyware_profile.py from typing import List, Optional from pydantic import ( diff --git a/scm/models/security/wildfire_antivirus_profiles.py b/scm/models/security/wildfire_antivirus_profiles.py new file mode 100644 index 00000000..b3542a9b --- /dev/null +++ b/scm/models/security/wildfire_antivirus_profiles.py @@ -0,0 +1,216 @@ +# scm/models/security/wildfire_antivirus_profiles.py + +from typing import List, Optional +from pydantic import ( + BaseModel, + Field, + model_validator, + field_validator, + ConfigDict, +) +from enum import Enum +import uuid + + +class Analysis(str, Enum): + """Enumeration of analysis types.""" + + public_cloud = "public-cloud" + private_cloud = "private-cloud" + + +class Direction(str, Enum): + """Enumeration of directions.""" + + download = "download" + upload = "upload" + both = "both" + + +class RuleBase(BaseModel): + """ + Base class for Rule. + """ + + name: str = Field( + ..., + description="Rule name", + ) + analysis: Optional[Analysis] = Field( + None, + description="Analysis type", + ) + application: List[str] = Field( + default_factory=lambda: ["any"], + description="List of applications", + ) + direction: Direction = Field( + ..., + description="Direction", + ) + file_type: List[str] = Field( + default_factory=lambda: ["any"], + description="List of file types", + ) + + +class RuleRequest(RuleBase): + pass # No additional fields needed for request-specific model + + +class RuleResponse(RuleBase): + pass # No additional fields needed for response-specific model + + +class MlavExceptionEntry(BaseModel): + """ + Represents an entry in the 'mlav_exception' list. + """ + + name: str = Field( + ..., + description="Exception name", + ) + description: Optional[str] = Field( + None, + description="Description", + ) + filename: str = Field( + ..., + description="Filename", + ) + + +class ThreatExceptionEntry(BaseModel): + """ + Represents an entry in the 'threat_exception' list. + """ + + name: str = Field( + ..., + description="Threat exception name", + ) + notes: Optional[str] = Field( + None, + description="Notes", + ) + + +class WildfireAntivirusProfileBaseModel(BaseModel): + """ + Base model for Wildfire Antivirus Profile, containing common fields. + """ + + name: str = Field( + ..., + description="Profile name", + pattern=r"^[a-zA-Z0-9._-]+$", + ) + description: Optional[str] = Field( + None, + description="Description", + ) + packet_capture: Optional[bool] = Field( + False, + description="Packet capture enabled", + ) + mlav_exception: Optional[List[MlavExceptionEntry]] = Field( + None, + description="MLAV exceptions", + ) + rules: List[RuleBase] = Field( + ..., + description="List of rules", + ) + threat_exception: Optional[List[ThreatExceptionEntry]] = Field( + None, + description="List of threat exceptions", + ) + + +class WildfireAntivirusProfileRequestModel(WildfireAntivirusProfileBaseModel): + """ + Represents a Wildfire Antivirus Profile for API requests. + """ + + folder: Optional[str] = Field( + None, + description="Folder", + max_length=64, + pattern=r"^[a-zA-Z\d\-_. ]+$", + ) + snippet: Optional[str] = Field( + None, + description="Snippet", + max_length=64, + pattern=r"^[a-zA-Z\d\-_. ]+$", + ) + device: Optional[str] = Field( + None, + description="Device", + max_length=64, + pattern=r"^[a-zA-Z\d\-_. ]+$", + ) + + model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True) + + @model_validator(mode="after") + def validate_container(self) -> "WildfireAntivirusProfileRequestModel": + container_fields = [ + "folder", + "snippet", + "device", + ] + provided_containers = [ + field for field in container_fields if getattr(self, field) is not None + ] + + if len(provided_containers) != 1: + raise ValueError( + "Exactly one of 'folder', 'snippet', or 'device' must be provided." + ) + + return self + + +class WildfireAntivirusProfileResponseModel(WildfireAntivirusProfileBaseModel): + """ + Represents a Wildfire Antivirus Profile for API responses. + """ + + id: str = Field( + ..., + description="Profile ID", + ) + folder: Optional[str] = Field( + None, + description="Folder", + ) + snippet: Optional[str] = Field( + None, + description="Snippet", + ) + device: Optional[str] = Field( + None, + description="Device", + ) + + @field_validator("id", mode="before") + @classmethod + def validate_id(cls, v): + try: + uuid.UUID(v) + except ValueError: + raise ValueError("Invalid UUID format for 'id'") + return v + + +class WildfireAntivirusProfilesResponse(BaseModel): + """ + Represents the API response containing a list of Wildfire Antivirus Profiles. + """ + + data: List[WildfireAntivirusProfileResponseModel] + offset: int + total: int + limit: int diff --git a/tests/test_anti_spyware_profiles.py b/tests/test_anti_spyware_profiles.py index bbe4465a..55c8e812 100644 --- a/tests/test_anti_spyware_profiles.py +++ b/tests/test_anti_spyware_profiles.py @@ -3,7 +3,7 @@ import pytest from unittest.mock import MagicMock -from scm.config.security.anti_spyware_profiles import AntiSpywareProfile +from scm.config.security.anti_spyware_profile import AntiSpywareProfile from scm.exceptions import ValidationError from scm.models.security.anti_spyware_profiles import ( AntiSpywareProfileRequestModel, diff --git a/tests/test_wildfire_antivirus_profiles.py b/tests/test_wildfire_antivirus_profiles.py new file mode 100644 index 00000000..f1cc0439 --- /dev/null +++ b/tests/test_wildfire_antivirus_profiles.py @@ -0,0 +1,535 @@ +# tests/test_wildfire_antivirus_profiles.py + +import pytest +from unittest.mock import MagicMock + +from scm.config.security.wildfire_antivirus_profile import WildfireAntivirusProfile +from scm.exceptions import ValidationError +from scm.models.security.wildfire_antivirus_profiles import ( + WildfireAntivirusProfileRequestModel, + WildfireAntivirusProfileResponseModel, + RuleRequest, + RuleResponse, + MlavExceptionEntry, + ThreatExceptionEntry, + Analysis, + Direction, +) +from typing import List +import uuid + + +def test_list_wildfire_antivirus_profiles(load_env, mock_scm): + """ + Test listing wildfire antivirus profiles. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "dd0f80ce-9345-4cf1-9979-bce99da27888", + "name": "web-security-default", + "folder": "All", + "snippet": "Web-Security-Default", + "rules": [ + { + "name": "default-fawkes", + "direction": "both", + "file_type": ["any"], + "application": ["any"], + } + ], + }, + { + "id": "e2a5dfc4-d8c8-489a-9661-092032796e09", + "name": "best-practice", + "folder": "All", + "snippet": "predefined-snippet", + "rules": [ + { + "name": "default", + "application": ["any"], + "file_type": ["any"], + "direction": "both", + "analysis": "public-cloud", + } + ], + "description": "Best practice antivirus and wildfire analysis security profile", + }, + ], + "offset": 0, + "total": 2, + "limit": 200, + } + + # Mock the API client's get method + mock_scm.get = MagicMock(return_value=mock_response) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the list method + profiles = wf_av_profile_client.list(folder="All") + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/wildfire-anti-virus-profiles", params={"folder": "All"} + ) + assert isinstance(profiles, list) + assert len(profiles) == 2 + assert isinstance(profiles[0], WildfireAntivirusProfileResponseModel) + assert profiles[0].name == "web-security-default" + assert profiles[0].rules[0].name == "default-fawkes" + assert profiles[0].rules[0].direction == Direction.both + assert profiles[0].rules[0].file_type == ["any"] + assert profiles[0].rules[0].application == ["any"] + assert profiles[1].description == ( + "Best practice antivirus and wildfire analysis security profile" + ) + + +def test_create_wildfire_antivirus_profile(load_env, mock_scm): + """ + Test creating a wildfire antivirus profile. + """ + # Prepare test data + test_profile_data = { + "name": "NewWFProfile", + "folder": "All", + "description": "A new test wildfire antivirus profile", + "packet_capture": True, + "rules": [ + { + "name": "NewRule", + "analysis": "public-cloud", + "direction": "both", + "application": ["any"], + "file_type": ["any"], + } + ], + "mlav_exception": [ + { + "name": "Exception1", + "description": "An exception", + "filename": "malicious.exe", + } + ], + "threat_exception": [ + { + "name": "ThreatException1", + "notes": "Some notes", + } + ], + } + + # Expected payload after model processing + expected_payload = test_profile_data.copy() + + # Mock response from the API client + mock_response = expected_payload.copy() + mock_response["id"] = "333e4567-e89b-12d3-a456-426655440002" # Mocked ID + + # Mock the API client's post method + mock_scm.post = MagicMock(return_value=mock_response) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the create method + created_profile = wf_av_profile_client.create(test_profile_data) + + # Assertions + mock_scm.post.assert_called_once_with( + "/config/security/v1/wildfire-anti-virus-profiles", + json=expected_payload, + ) + assert isinstance(created_profile, WildfireAntivirusProfileResponseModel) + assert created_profile.id == "333e4567-e89b-12d3-a456-426655440002" + assert created_profile.name == "NewWFProfile" + assert created_profile.rules[0].name == "NewRule" + assert created_profile.rules[0].analysis == Analysis.public_cloud + assert created_profile.rules[0].direction == Direction.both + assert created_profile.mlav_exception[0].name == "Exception1" + assert created_profile.threat_exception[0].name == "ThreatException1" + + +def test_get_wildfire_antivirus_profile(load_env, mock_scm): + """ + Test retrieving a wildfire antivirus profile by ID. + """ + # Mock response from the API client + profile_id = "123e4567-e89b-12d3-a456-426655440000" + mock_response = { + "id": profile_id, + "name": "TestProfile", + "folder": "All", + "description": "A test wildfire antivirus profile", + "rules": [ + { + "name": "TestRule", + "direction": "download", + "application": ["app1", "app2"], + "file_type": ["pdf", "exe"], + "analysis": "private-cloud", + } + ], + } + + # Mock the API client's get method + mock_scm.get = MagicMock(return_value=mock_response) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the get method + profile = wf_av_profile_client.get(profile_id) + + # Assertions + mock_scm.get.assert_called_once_with( + f"/config/security/v1/wildfire-anti-virus-profiles/{profile_id}" + ) + assert isinstance(profile, WildfireAntivirusProfileResponseModel) + assert profile.id == profile_id + assert profile.name == "TestProfile" + assert profile.description == "A test wildfire antivirus profile" + assert profile.rules[0].name == "TestRule" + assert profile.rules[0].direction == Direction.download + assert profile.rules[0].analysis == Analysis.private_cloud + + +def test_update_wildfire_antivirus_profile(load_env, mock_scm): + """ + Test updating a wildfire antivirus profile. + """ + # Prepare test data + profile_id = "123e4567-e89b-12d3-a456-426655440000" + update_data = { + "name": "UpdatedProfile", + "folder": "All", + "description": "An updated wildfire antivirus profile", + "rules": [ + { + "name": "UpdatedRule", + "direction": "upload", + "application": ["app3"], + "file_type": ["docx"], + } + ], + "packet_capture": False, + } + + # Expected payload after model processing + expected_payload = update_data.copy() + + # Mock response from the API client + mock_response = expected_payload.copy() + mock_response["id"] = profile_id + + # Mock the API client's put method + mock_scm.put = MagicMock(return_value=mock_response) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the update method + updated_profile = wf_av_profile_client.update(profile_id, update_data) + + # Assertions + mock_scm.put.assert_called_once_with( + f"/config/security/v1/wildfire-anti-virus-profiles/{profile_id}", + json=expected_payload, + ) + assert isinstance(updated_profile, WildfireAntivirusProfileResponseModel) + assert updated_profile.id == profile_id + assert updated_profile.name == "UpdatedProfile" + assert updated_profile.description == "An updated wildfire antivirus profile" + assert updated_profile.rules[0].name == "UpdatedRule" + assert updated_profile.rules[0].direction == Direction.upload + + +def test_delete_wildfire_antivirus_profile(load_env, mock_scm): + """ + Test deleting a wildfire antivirus profile. + """ + # Prepare test data + profile_id = "123e4567-e89b-12d3-a456-426655440000" + + # Mock the API client's delete method + mock_scm.delete = MagicMock(return_value=None) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the delete method + wf_av_profile_client.delete(profile_id) + + # Assertions + mock_scm.delete.assert_called_once_with( + f"/config/security/v1/wildfire-anti-virus-profiles/{profile_id}" + ) + + +def test_wildfire_antivirus_profile_request_model_validation_errors(): + """ + Test validation errors in WildfireAntivirusProfileRequestModel. + """ + # No container provided + data_no_container = { + "name": "InvalidProfile", + "rules": [ + { + "name": "Rule1", + "direction": "both", + } + ], + } + with pytest.raises(ValueError) as exc_info: + WildfireAntivirusProfileRequestModel(**data_no_container) + assert "Exactly one of 'folder', 'snippet', or 'device' must be provided." in str( + exc_info.value + ) + + # Multiple containers provided + data_multiple_containers = { + "name": "InvalidProfile", + "folder": "Shared", + "device": "Device1", + "rules": [ + { + "name": "Rule1", + "direction": "both", + } + ], + } + with pytest.raises(ValueError) as exc_info: + WildfireAntivirusProfileRequestModel(**data_multiple_containers) + assert "Exactly one of 'folder', 'snippet', or 'device' must be provided." in str( + exc_info.value + ) + + # Invalid direction in RuleRequest + data_invalid_direction = { + "name": "InvalidProfile", + "folder": "Shared", + "rules": [ + { + "name": "InvalidRule", + "direction": "sideways", # Invalid direction + } + ], + } + with pytest.raises(ValueError) as exc_info: + WildfireAntivirusProfileRequestModel(**data_invalid_direction) + assert "1 validation error for WildfireAntivirusProfileRequestModel" in str( + exc_info.value + ) + assert "Input should be 'download', 'upload' or 'both'" in str(exc_info.value) + + # Invalid UUID in id field (for response model) + data_invalid_id = { + "id": "invalid-uuid", + "name": "TestProfile", + "folder": "Shared", + "rules": [ + { + "name": "Rule1", + "direction": "both", + } + ], + } + with pytest.raises(ValueError) as exc_info: + WildfireAntivirusProfileResponseModel(**data_invalid_id) + assert "Invalid UUID format for 'id'" in str(exc_info.value) + + +def test_wildfire_antivirus_profile_list_validation_error(load_env, mock_scm): + """ + Test validation error when listing with multiple containers. + """ + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Attempt to call the list method with multiple containers + with pytest.raises(ValidationError) as exc_info: + wf_av_profile_client.list(folder="Shared", snippet="TestSnippet") + + # Assertions + assert "Exactly one of 'folder', 'snippet', or 'device' must be provided." in str( + exc_info.value + ) + + +def test_list_wildfire_antivirus_profiles_with_pagination(load_env, mock_scm): + """ + Test listing wildfire antivirus profiles with pagination parameters. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "dd0f80ce-9345-4cf1-9979-bce99da27888", + "name": "web-security-default", + "folder": "All", + "snippet": "Web-Security-Default", + "rules": [ + { + "name": "default-fawkes", + "direction": "both", + "file_type": ["any"], + "application": ["any"], + } + ], + }, + { + "id": "e2a5dfc4-d8c8-489a-9661-092032796e09", + "name": "best-practice", + "folder": "All", + "snippet": "predefined-snippet", + "rules": [ + { + "name": "default", + "application": ["any"], + "file_type": ["any"], + "direction": "both", + "analysis": "public-cloud", + } + ], + "description": "Best practice antivirus and wildfire analysis security profile", + }, + { + "id": "d5994c99-ee2e-4c9c-80a0-20e2eefd3f2d", + "name": "TEST123", + "folder": "All", + "description": "THIS IS A TEST", + "rules": [ + { + "name": "RULE1", + "analysis": "public-cloud", + "direction": "download", + "application": [ + "facebook-uploading", + "facebook-posting", + "facebook-downloading", + "facebook-base", + ], + "file_type": ["flash", "jar"], + } + ], + }, + ], + "offset": 1, + "total": 3, + "limit": 200, + } + + # Mock the API client's get method + mock_scm.get = MagicMock(return_value=mock_response) + + # Create an instance of AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the list method with pagination parameters + profiles = anti_spyware_profile_client.list( + folder="Prisma Access", + offset=1, + limit=1, + ) + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/wildfire-anti-virus-profiles", + params={"folder": "Prisma Access", "offset": 1, "limit": 1}, + ) + assert isinstance(profiles, list) + assert len(profiles) == 3 + assert profiles[0].name == "web-security-default" + assert profiles[0].id == "dd0f80ce-9345-4cf1-9979-bce99da27888" + + +def test_wildfire_antivirus_profile_list_with_invalid_pagination(load_env, mock_scm): + """ + Test validation error when invalid pagination parameters are provided. + """ + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Attempt to call the list method with invalid pagination parameters + with pytest.raises(ValueError) as exc_info: + wf_av_profile_client.list(folder="All", offset=-1, limit=0) + + # Assertions + assert "Offset must be a non-negative integer" in str(exc_info.value) + assert "Limit must be a positive integer" in str(exc_info.value) + + +def test_rule_request_model_validation(): + """ + Test validation in RuleRequest model. + """ + # Invalid analysis + data_invalid_analysis = { + "name": "TestRule", + "direction": "both", + "analysis": "invalid-cloud", + } + with pytest.raises(ValueError) as exc_info: + RuleRequest(**data_invalid_analysis) + assert "1 validation error for RuleRequest" in str(exc_info.value) + assert " Input should be 'public-cloud' or 'private-cloud'" in str(exc_info.value) + + # Missing required fields + data_missing_fields = { + "analysis": "public-cloud", + "direction": "both", + } + with pytest.raises(ValueError) as exc_info: + RuleRequest(**data_missing_fields) + assert "1 validation error for RuleRequest" in str(exc_info.value) + assert "name\n Field required" in str(exc_info.value) + + +def test_list_wildfire_antivirus_profiles_with_name_filter(load_env, mock_scm): + """ + Test listing wildfire antivirus profiles with name filter. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "e2a5dfc4-d8c8-489a-9661-092032796e09", + "name": "SpecificProfile", + "folder": "All", + "rules": [ + { + "name": "default", + "application": ["any"], + "file_type": ["any"], + "direction": "both", + "analysis": "public-cloud", + } + ], + } + ], + "offset": 0, + "total": 1, + "limit": 200, + } + + # Mock the API client's get method + mock_scm.get = MagicMock(return_value=mock_response) + + # Create an instance of WildfireAntivirusProfile with the mocked Scm + wf_av_profile_client = WildfireAntivirusProfile(mock_scm) + + # Call the list method with name filter + profiles = wf_av_profile_client.list(folder="All", name="SpecificProfile") + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/wildfire-anti-virus-profiles", + params={"folder": "All", "name": "SpecificProfile"}, + ) + assert isinstance(profiles, list) + assert len(profiles) == 1 + assert profiles[0].name == "SpecificProfile" + assert profiles[0].id == "e2a5dfc4-d8c8-489a-9661-092032796e09"