From 3bd44a2b3416be6627bf70652eeea7121019f588 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 06:17:24 -0500 Subject: [PATCH 01/11] Refactor model imports and reorganize directories Moved models into the 'objects' sub-directory and updated import paths across various files. Also reorganized the security-related files into the config/security directory for better module management. --- scm/config/objects/address.py | 2 +- scm/config/objects/address_group.py | 2 +- scm/config/objects/application.py | 2 +- scm/config/objects/application_group.py | 5 ++++- scm/config/objects/service.py | 2 +- scm/config/security/__init__.py | 3 +++ scm/config/security/anti_spyware_profiles.py | 1 + scm/models/__init__.py | 9 +-------- scm/models/objects/__init__.py | 13 +++++++++++++ scm/models/{ => objects}/address.py | 0 scm/models/{ => objects}/address_group.py | 0 scm/models/{ => objects}/application.py | 0 scm/models/{ => objects}/application_filter.py | 0 scm/models/{ => objects}/application_group.py | 0 scm/models/{ => objects}/service.py | 0 tests/factories.py | 6 +++--- tests/test_address_groups.py | 4 ++-- tests/test_addresses.py | 2 +- tests/test_application_groups.py | 5 ++++- tests/test_applications.py | 2 +- tests/test_models.py | 4 ++-- tests/test_services.py | 2 +- 22 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 scm/config/security/__init__.py create mode 100644 scm/config/security/anti_spyware_profiles.py create mode 100644 scm/models/objects/__init__.py rename scm/models/{ => objects}/address.py (100%) rename scm/models/{ => objects}/address_group.py (100%) rename scm/models/{ => objects}/application.py (100%) rename scm/models/{ => objects}/application_filter.py (100%) rename scm/models/{ => objects}/application_group.py (100%) rename scm/models/{ => objects}/service.py (100%) diff --git a/scm/config/objects/address.py b/scm/config/objects/address.py index beec5353..7a9cc983 100644 --- a/scm/config/objects/address.py +++ b/scm/config/objects/address.py @@ -2,7 +2,7 @@ from typing import List, Dict, Any, Optional from scm.config import BaseObject -from scm.models import AddressRequestModel, AddressResponseModel +from scm.models.objects import AddressRequestModel, AddressResponseModel from scm.exceptions import ValidationError diff --git a/scm/config/objects/address_group.py b/scm/config/objects/address_group.py index 984bdee1..980ef6c7 100644 --- a/scm/config/objects/address_group.py +++ b/scm/config/objects/address_group.py @@ -2,7 +2,7 @@ from typing import List, Dict, Any, Optional from scm.config import BaseObject -from scm.models import AddressGroupRequestModel, AddressGroupResponseModel +from scm.models.objects import AddressGroupRequestModel, AddressGroupResponseModel from scm.exceptions import ValidationError diff --git a/scm/config/objects/application.py b/scm/config/objects/application.py index 81ec6029..c9cfd7a2 100644 --- a/scm/config/objects/application.py +++ b/scm/config/objects/application.py @@ -2,7 +2,7 @@ from typing import List, Dict, Any, Optional from scm.config import BaseObject -from scm.models import ApplicationRequestModel, ApplicationResponseModel +from scm.models.objects import ApplicationRequestModel, ApplicationResponseModel from scm.exceptions import ValidationError diff --git a/scm/config/objects/application_group.py b/scm/config/objects/application_group.py index 64ca9abf..c796ff5f 100644 --- a/scm/config/objects/application_group.py +++ b/scm/config/objects/application_group.py @@ -3,7 +3,10 @@ from typing import List, Dict, Any, Optional from scm.config import BaseObject -from scm.models import ApplicationGroupRequestModel, ApplicationGroupResponseModel +from scm.models.objects import ( + ApplicationGroupRequestModel, + ApplicationGroupResponseModel, +) from scm.exceptions import ValidationError diff --git a/scm/config/objects/service.py b/scm/config/objects/service.py index a47fd90a..d3dbc5f1 100644 --- a/scm/config/objects/service.py +++ b/scm/config/objects/service.py @@ -3,7 +3,7 @@ from typing import List, Dict, Any, Optional from scm.config import BaseObject -from scm.models import ServiceRequestModel, ServiceResponseModel +from scm.models.objects import ServiceRequestModel, ServiceResponseModel from scm.exceptions import ValidationError diff --git a/scm/config/security/__init__.py b/scm/config/security/__init__.py new file mode 100644 index 00000000..844a64b6 --- /dev/null +++ b/scm/config/security/__init__.py @@ -0,0 +1,3 @@ +# scm/config/security/__init__.py + +from .anti_spyware_profiles import AntiSpywareProfile diff --git a/scm/config/security/anti_spyware_profiles.py b/scm/config/security/anti_spyware_profiles.py new file mode 100644 index 00000000..bd9b30fc --- /dev/null +++ b/scm/config/security/anti_spyware_profiles.py @@ -0,0 +1 @@ +# scm/config/security/anti_spyware_profiles.py diff --git a/scm/models/__init__.py b/scm/models/__init__.py index ade78791..7ea1a101 100644 --- a/scm/models/__init__.py +++ b/scm/models/__init__.py @@ -1,10 +1,3 @@ # scm/models/__init__.py -from .address import AddressRequestModel, AddressResponseModel -from .address_group import AddressGroupRequestModel, AddressGroupResponseModel -from .application import ApplicationRequestModel, ApplicationResponseModel -from .application_group import ( - ApplicationGroupRequestModel, - ApplicationGroupResponseModel, -) -from .service import ServiceRequestModel, ServiceResponseModel +from .auth import AuthRequestModel diff --git a/scm/models/objects/__init__.py b/scm/models/objects/__init__.py new file mode 100644 index 00000000..aa2b57af --- /dev/null +++ b/scm/models/objects/__init__.py @@ -0,0 +1,13 @@ +# scm/models/objects/__init__.py + +from .address import AddressRequestModel, AddressResponseModel +from .address_group import AddressGroupRequestModel, AddressGroupResponseModel +from .application import ( + ApplicationRequestModel, + ApplicationResponseModel, +) +from .application_group import ( + ApplicationGroupRequestModel, + ApplicationGroupResponseModel, +) +from .service import ServiceRequestModel, ServiceResponseModel diff --git a/scm/models/address.py b/scm/models/objects/address.py similarity index 100% rename from scm/models/address.py rename to scm/models/objects/address.py diff --git a/scm/models/address_group.py b/scm/models/objects/address_group.py similarity index 100% rename from scm/models/address_group.py rename to scm/models/objects/address_group.py diff --git a/scm/models/application.py b/scm/models/objects/application.py similarity index 100% rename from scm/models/application.py rename to scm/models/objects/application.py diff --git a/scm/models/application_filter.py b/scm/models/objects/application_filter.py similarity index 100% rename from scm/models/application_filter.py rename to scm/models/objects/application_filter.py diff --git a/scm/models/application_group.py b/scm/models/objects/application_group.py similarity index 100% rename from scm/models/application_group.py rename to scm/models/objects/application_group.py diff --git a/scm/models/service.py b/scm/models/objects/service.py similarity index 100% rename from scm/models/service.py rename to scm/models/objects/service.py diff --git a/tests/factories.py b/tests/factories.py index b69122c0..132d80c8 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -2,13 +2,13 @@ import factory -from scm.models import ( +from scm.models.objects import ( ApplicationRequestModel, ServiceRequestModel, ApplicationGroupRequestModel, ) -from scm.models.address import AddressRequestModel -from scm.models.address_group import AddressGroupRequestModel, DynamicFilter +from scm.models.objects.address import AddressRequestModel +from scm.models.objects.address_group import AddressGroupRequestModel, DynamicFilter class AddressFactory(factory.Factory): diff --git a/tests/test_address_groups.py b/tests/test_address_groups.py index f8fea947..f048f55d 100644 --- a/tests/test_address_groups.py +++ b/tests/test_address_groups.py @@ -3,8 +3,8 @@ from scm.config.objects import AddressGroup from scm.exceptions import ValidationError -from scm.models import AddressGroupResponseModel, AddressGroupRequestModel -from scm.models.address_group import DynamicFilter +from scm.models.objects import AddressGroupResponseModel, AddressGroupRequestModel +from scm.models.objects.address_group import DynamicFilter from tests.factories import AddressGroupStaticFactory, AddressGroupDynamicFactory from unittest.mock import MagicMock diff --git a/tests/test_addresses.py b/tests/test_addresses.py index 6a0f36e3..47911b98 100644 --- a/tests/test_addresses.py +++ b/tests/test_addresses.py @@ -3,7 +3,7 @@ from scm.config.objects import Address from scm.exceptions import ValidationError -from scm.models import AddressResponseModel, AddressRequestModel +from scm.models.objects import AddressResponseModel, AddressRequestModel from tests.factories import AddressFactory from unittest.mock import MagicMock diff --git a/tests/test_application_groups.py b/tests/test_application_groups.py index 333bbaac..744148db 100644 --- a/tests/test_application_groups.py +++ b/tests/test_application_groups.py @@ -4,7 +4,10 @@ from scm.config.objects import ApplicationGroup from scm.exceptions import ValidationError -from scm.models import ApplicationGroupResponseModel, ApplicationGroupRequestModel +from scm.models.objects import ( + ApplicationGroupResponseModel, + ApplicationGroupRequestModel, +) from tests.factories import ApplicationGroupFactory from unittest.mock import MagicMock diff --git a/tests/test_applications.py b/tests/test_applications.py index 2906c993..a599ab05 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -3,7 +3,7 @@ from scm.config.objects import Application from scm.exceptions import ValidationError -from scm.models import ApplicationResponseModel, ApplicationRequestModel +from scm.models.objects import ApplicationResponseModel, ApplicationRequestModel from tests.factories import ApplicationFactory from unittest.mock import MagicMock diff --git a/tests/test_models.py b/tests/test_models.py index 71cfa9ab..48ef5dad 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -2,11 +2,11 @@ import pytest -from scm.models import ( +from scm.models.objects import ( AddressRequestModel, AddressGroupRequestModel, ) -from scm.models.address_group import DynamicFilter +from scm.models.objects.address_group import DynamicFilter from pydantic import ValidationError from tests.factories import ( diff --git a/tests/test_services.py b/tests/test_services.py index 59f7a7e7..e8d75854 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -6,7 +6,7 @@ from scm.exceptions import ValidationError as SCMValidationError from scm.config.objects import Service -from scm.models import ServiceRequestModel, ServiceResponseModel +from scm.models.objects import ServiceRequestModel, ServiceResponseModel from tests.factories import ServiceFactory From ae6afb53e9fccd0a8b14681d5fbbb921eb7ff202 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 06:18:30 -0500 Subject: [PATCH 02/11] Remove redundant validator from Address model The `validate_container_type` model validator was removed from the `AddressRequestModel`. This validator was duplicative as the necessary validation is already covered by another existing validator. Removing it helps simplify and streamline the code. --- scm/models/objects/address.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scm/models/objects/address.py b/scm/models/objects/address.py index 861de25c..8b647e2e 100644 --- a/scm/models/objects/address.py +++ b/scm/models/objects/address.py @@ -273,19 +273,3 @@ def validate_address_type(self) -> "AddressRequestModel": "Exactly one of 'ip_netmask', 'ip_range', 'ip_wildcard', or 'fqdn' must be provided." ) return self - - @model_validator(mode="after") - def validate_container_type(self) -> "AddressRequestModel": - container_fields = [ - "folder", - "snippet", - "device", - ] - provided = [ - field for field in container_fields if getattr(self, field) is not None - ] - if len(provided) != 1: - raise ValueError( - "Exactly one of 'folder', 'snippet', or 'device' must be provided." - ) - return self From ad4acf2c5d10c8b554db69227820ade401c1e309 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 06:19:04 -0500 Subject: [PATCH 03/11] Remove redundant 'validate_container_type' method This method duplicate-checks a single condition that is already validated by another method. Removing it reduces the code complexity and minimizes the risk of conflicting validations. --- scm/models/objects/address_group.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scm/models/objects/address_group.py b/scm/models/objects/address_group.py index 4d2d49e6..fe4ecf74 100644 --- a/scm/models/objects/address_group.py +++ b/scm/models/objects/address_group.py @@ -255,19 +255,3 @@ def validate_address_group_type(self) -> "AddressGroupRequestModel": if len(provided) != 1: raise ValueError("Exactly one of 'static' or 'dynamic' must be provided.") return self - - @model_validator(mode="after") - def validate_container_type(self) -> "AddressGroupRequestModel": - container_fields = [ - "folder", - "snippet", - "device", - ] - provided = [ - field for field in container_fields if getattr(self, field) is not None - ] - if len(provided) != 1: - raise ValueError( - "Exactly one of 'folder', 'snippet', or 'device' must be provided." - ) - return self From 6fd949856cfde16f26de44c26302d3217a209530 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 09:25:08 -0500 Subject: [PATCH 04/11] Update import paths and add security module init file Corrected file paths in application, address group, address, application group, and service models. Also, added an `__init__.py` file to the security module for proper module initialization. --- scm/models/objects/address.py | 2 ++ scm/models/objects/address_group.py | 2 +- scm/models/objects/application.py | 2 +- scm/models/objects/application_group.py | 2 +- scm/models/objects/service.py | 2 +- scm/models/security/__init__.py | 6 ++++++ 6 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 scm/models/security/__init__.py diff --git a/scm/models/objects/address.py b/scm/models/objects/address.py index 8b647e2e..616413bb 100644 --- a/scm/models/objects/address.py +++ b/scm/models/objects/address.py @@ -1,3 +1,5 @@ +# scm/models/objects/address.py + import uuid from typing import Optional, List diff --git a/scm/models/objects/address_group.py b/scm/models/objects/address_group.py index fe4ecf74..03dc6241 100644 --- a/scm/models/objects/address_group.py +++ b/scm/models/objects/address_group.py @@ -1,4 +1,4 @@ -# scm/models/address_group.py +# scm/models/objects/address_group.py import uuid from typing import Optional, List diff --git a/scm/models/objects/application.py b/scm/models/objects/application.py index 7c7226bc..945c3776 100644 --- a/scm/models/objects/application.py +++ b/scm/models/objects/application.py @@ -1,4 +1,4 @@ -# scm/models/application.py +# scm/models/objects/application.py from typing import Optional, List from uuid import UUID diff --git a/scm/models/objects/application_group.py b/scm/models/objects/application_group.py index eae8deca..f1f20e0d 100644 --- a/scm/models/objects/application_group.py +++ b/scm/models/objects/application_group.py @@ -1,4 +1,4 @@ -# scm/models/application_group.py +# scm/models/objects/application_group.py import uuid from typing import Optional, List diff --git a/scm/models/objects/service.py b/scm/models/objects/service.py index d43c9610..21abac71 100644 --- a/scm/models/objects/service.py +++ b/scm/models/objects/service.py @@ -1,4 +1,4 @@ -# scm/models/service.py +# scm/models/objects/service.py import uuid from typing import Optional, List diff --git a/scm/models/security/__init__.py b/scm/models/security/__init__.py new file mode 100644 index 00000000..6bd4a2ac --- /dev/null +++ b/scm/models/security/__init__.py @@ -0,0 +1,6 @@ +# scm/models/security/__init__.py + +from .anti_spyware_profiles import ( + AntiSpywareProfileRequestModel, + AntiSpywareProfileResponseModel, +) From b3ca2564c505d8490f9668d1e7cbe36072977d85 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 09:26:11 -0500 Subject: [PATCH 05/11] Add anti-spyware profile models Introduce comprehensive models for anti-spyware profiles, including actions, rules, threat exceptions, and API request/response structures. These models ensure accurate representation and validation of anti-spyware settings. --- scm/models/security/anti_spyware_profiles.py | 372 +++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 scm/models/security/anti_spyware_profiles.py diff --git a/scm/models/security/anti_spyware_profiles.py b/scm/models/security/anti_spyware_profiles.py new file mode 100644 index 00000000..766c7017 --- /dev/null +++ b/scm/models/security/anti_spyware_profiles.py @@ -0,0 +1,372 @@ +# scm/models/security/anti_spyware_profiles.py + +from typing import List, Optional, Union +from pydantic import BaseModel, Field, model_validator, ConfigDict, RootModel +from enum import Enum +import uuid + + +class InlinePolicyAction(str, Enum): + """Enumeration of allowed inline policy actions.""" + + alert = "alert" + allow = "allow" + drop = "drop" + reset_both = "reset-both" + reset_client = "reset-client" + reset_server = "reset-server" + + +class MicaEngineSpywareEnabledEntry(BaseModel): + """ + Represents an entry in the 'mica_engine_spyware_enabled' list. + + Attributes: + name (str): Name of the MICA engine spyware detector. + inline_policy_action (InlinePolicyAction): Action to be taken by the inline policy. + """ + + name: str = Field(..., description="Name of the MICA engine spyware detector") + inline_policy_action: InlinePolicyAction = Field( + InlinePolicyAction.alert, + description="Inline policy action", + ) + + +class BlockIpAction(BaseModel): + """ + Represents the 'block_ip' action with additional properties. + + Attributes: + track_by (str): Method of tracking ('source-and-destination' or 'source'). + duration (int): Duration in seconds (1 to 3600). + """ + + track_by: str = Field( + ..., + description="Tracking method", + pattern="^(source-and-destination|source)$", + ) + duration: int = Field( + ..., + description="Duration in seconds", + ge=1, + le=3600, + ) + + +class Action(RootModel[dict]): + """ + Represents the 'action' field in rules and threat exceptions. + + This model uses a custom validator to ensure only one action is set. + """ + + @model_validator(mode="before") + @classmethod + def check_and_transform_action(cls, values): + if isinstance(values, str): + # Convert the string to a dict with an empty object + values = {values: {}} + elif not isinstance(values, dict): + raise ValueError("Invalid action format; must be a string or dict.") + + action_fields = [ + "allow", + "alert", + "drop", + "reset_client", + "reset_server", + "reset_both", + "block_ip", + "default", + ] + provided_actions = [field for field in action_fields if field in values] + + if len(provided_actions) != 1: + raise ValueError("Exactly one action must be provided in 'action' field.") + + return values + + def get_action_name(self) -> str: + """Utility method to get the name of the action.""" + return next(iter(self.root.keys()), "unknown") + + +class PacketCapture(str, Enum): + """Enumeration of packet capture options.""" + + disable = "disable" + single_packet = "single-packet" + extended_capture = "extended-capture" + + +class Severity(str, Enum): + """Enumeration of severity levels.""" + + critical = "critical" + high = "high" + medium = "medium" + low = "low" + informational = "informational" + any = "any" + + +class Category(str, Enum): + """Enumeration of threat categories.""" + + dns_proxy = "dns-proxy" + backdoor = "backdoor" + data_theft = "data-theft" + autogen = "autogen" + spyware = "spyware" + dns_security = "dns-security" + downloader = "downloader" + dns_phishing = "dns-phishing" + phishing_kit = "phishing-kit" + cryptominer = "cryptominer" + hacktool = "hacktool" + dns_benign = "dns-benign" + dns_wildfire = "dns-wildfire" + botnet = "botnet" + dns_grayware = "dns-grayware" + inline_cloud_c2 = "inline-cloud-c2" + keylogger = "keylogger" + p2p_communication = "p2p-communication" + domain_edl = "domain-edl" + webshell = "webshell" + command_and_control = "command-and-control" + dns_ddns = "dns-ddns" + net_worm = "net-worm" + any = "any" + tls_fingerprint = "tls-fingerprint" + dns_new_domain = "dns-new-domain" + dns = "dns" + fraud = "fraud" + dns_c2 = "dns-c2" + adware = "adware" + post_exploitation = "post-exploitation" + dns_malware = "dns-malware" + browser_hijack = "browser-hijack" + dns_parked = "dns-parked" + + +class ExemptIpEntry(BaseModel): + """ + Represents an entry in the 'exempt_ip' list within a threat exception. + + Attributes: + name (str): Name of the IP address or range to exempt. + """ + + name: str = Field( + ..., + description="Exempt IP name", + ) + + +class Rule(BaseModel): + """ + Represents a rule within an anti-spyware profile. + + Attributes: + name (str): The name of the rule. + severity (List[Severity]): List of severities associated with the rule. + category (Category): Category of the rule. + threat_name (Optional[str]): Specific threat name targeted by the rule. + packet_capture (Optional[PacketCapture]): Packet capture setting. + action (Optional[Action]): Action to be taken when the rule is matched. + """ + + name: str = Field( + ..., + description="Rule name", + ) + severity: List[Severity] = Field( + ..., + description="List of severities", + ) + category: Category = Field( + ..., + description="Category", + ) + threat_name: Optional[str] = Field( + None, + description="Threat name", + min_length=4, + ) + packet_capture: Optional[PacketCapture] = Field( + None, + description="Packet capture setting", + ) + action: Optional[Action] = Field( + None, + description="Action", + ) + + @model_validator(mode="before") + def default_threat_name(cls, values): + return values or "any" + + +class ThreatException(BaseModel): + """ + Represents a threat exception within an anti-spyware profile. + + Attributes: + name (str): The name of the threat exception. + action (Action): The action to be taken. + packet_capture (PacketCapture): Packet capture setting. + exempt_ip (Optional[List[ExemptIpEntry]]): List of exempt IP entries. + notes (Optional[str]): Additional notes. + """ + + name: str = Field( + ..., + description="Threat exception name", + ) + action: Action = Field( + ..., + description="Action", + ) + packet_capture: PacketCapture = Field( + ..., + description="Packet capture setting", + ) + exempt_ip: Optional[List[ExemptIpEntry]] = Field( + None, + description="Exempt IP list", + ) + notes: Optional[str] = Field( + None, + description="Notes", + ) + + +class AntiSpywareProfileRequestModel(BaseModel): + """ + Represents an anti-spyware profile for API requests. + + Attributes: + name (str): The name of the profile. + description (Optional[str]): Description of the profile. + folder (Optional[str]): The folder where the profile is stored. + snippet (Optional[str]): The snippet associated with the profile. + device (Optional[str]): The device where the profile is applied. + cloud_inline_analysis (Optional[bool]): Cloud inline analysis setting. + inline_exception_edl_url (Optional[List[str]]): List of exception EDL URLs. + inline_exception_ip_address (Optional[List[str]]): List of exception IP addresses. + mica_engine_spyware_enabled (Optional[List[MicaEngineSpywareEnabledEntry]]): List of MICA engine settings. + rules (List[Rule]): List of rules in the profile. + threat_exception (Optional[List[ThreatException]]): List of threat exceptions. + """ + + # Model configuration + model_config = ConfigDict( + validate_assignment=True, + arbitrary_types_allowed=True, + ) + + name: str = Field( + ..., + description="Profile name", + ) + description: Optional[str] = Field( + None, + description="Description", + ) + 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\-_. ]+$", + ) + cloud_inline_analysis: Optional[bool] = Field( + False, + description="Cloud inline analysis", + ) + inline_exception_edl_url: Optional[List[str]] = Field( + None, + description="Inline exception EDL URLs", + ) + inline_exception_ip_address: Optional[List[str]] = Field( + None, + description="Inline exception IP addresses", + ) + mica_engine_spyware_enabled: Optional[List[MicaEngineSpywareEnabledEntry]] = Field( + None, + description="List of MICA engine spyware enabled entries", + ) + rules: List[Rule] = Field(..., description="List of rules") + threat_exception: Optional[List[ThreatException]] = Field( + None, + description="List of threat exceptions", + ) + + # Custom Validators + @model_validator(mode="after") + def validate_container(self) -> "AntiSpywareProfileRequestModel": + 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 AntiSpywareProfileResponseModel(AntiSpywareProfileRequestModel): + """ + Represents an anti-spyware profile for API responses. + + Attributes: + id (str): The UUID of the profile. + """ + + id: str = Field(..., description="Profile ID") + + @model_validator(mode="before") + def validate_uuid(cls, values): + if "id" in values and values["id"] is not None: + try: + uuid.UUID(values["id"]) + except ValueError: + raise ValueError("Invalid UUID format for 'id'") + return values + + +class AntiSpywareProfilesResponse(BaseModel): + """ + Represents the API response containing a list of anti-spyware profiles. + + Attributes: + data (List[AntiSpywareProfileResponseModel]): List of anti-spyware profiles. + offset (int): Offset used in pagination. + total (int): Total number of profiles available. + limit (int): Maximum number of profiles returned. + """ + + data: List[AntiSpywareProfileResponseModel] + offset: int + total: int + limit: int From b5646d3f914f42b7522d2b2934af460cdf50a89a Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 15:19:35 -0500 Subject: [PATCH 06/11] Add support for Anti Spyware Profiles Introduced the `AntiSpywareProfile` class to manage Anti-Spyware Profiles within the SCM. Updated the relevant models and documentation to reflect the new functionality. Bumped version to 0.1.7 in `pyproject.toml`. --- docs/about/release-notes.md | 12 +- pyproject.toml | 2 +- scm/config/security/anti_spyware_profiles.py | 109 +++++++ scm/models/security/anti_spyware_profiles.py | 288 +++++++++---------- 4 files changed, 251 insertions(+), 160 deletions(-) diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index ae47ad99..e8ccdd56 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -1,10 +1,20 @@ -# Release Notes +~~# Release Notes Welcome to the release notes for the `pan-scm-sdk` tool. This document provides a detailed record of changes, enhancements, and fixes in each version of the tool. --- +## Version 0.1.7 + +**Release Date:** October 16, 2024 + +### Anti Spyware Profiles + +- **Anti Spyware Profiles**: Add the ability to support Anti Spyware Profiles. + +--- + ## Version 0.1.6 **Release Date:** October 15, 2024 diff --git a/pyproject.toml b/pyproject.toml index d3e0f793..fb7c23ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-scm-sdk" -version = "0.1.6" +version = "0.1.7" description = "Python SDK for Palo Alto Networks Strata Cloud Manager." authors = ["Calvin Remsburg "] license = "Apache 2.0" diff --git a/scm/config/security/anti_spyware_profiles.py b/scm/config/security/anti_spyware_profiles.py index bd9b30fc..e36987bf 100644 --- a/scm/config/security/anti_spyware_profiles.py +++ b/scm/config/security/anti_spyware_profiles.py @@ -1 +1,110 @@ # scm/config/security/anti_spyware_profiles.py + +from typing import List, Dict, Any, Optional +from scm.config import BaseObject +from scm.models.security import ( + AntiSpywareProfileRequestModel, + AntiSpywareProfileResponseModel, +) +from scm.exceptions import ValidationError + + +class AntiSpywareProfile(BaseObject): + """ + Manages Anti-Spyware Profiles in Palo Alto Networks' Strata Cloud Manager. + + This class provides methods to create, retrieve, update, delete, and list Anti-Spyware 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 Anti-Spyware Profile operations. + + Errors: + ValidationError: Raised when invalid container parameters are provided. + + Returns: + AntiSpywareProfileResponseModel: For create, get, and update methods. + List[AntiSpywareProfileResponseModel]: For the list method. + """ + + ENDPOINT = "/config/security/v1/anti-spyware-profiles" + + def __init__(self, api_client): + super().__init__(api_client) + + def create(self, data: Dict[str, Any]) -> AntiSpywareProfileResponseModel: + profile = AntiSpywareProfileRequestModel(**data) + payload = profile.model_dump(exclude_unset=True) + response = self.api_client.post(self.ENDPOINT, json=payload) + return AntiSpywareProfileResponseModel(**response) + + def get(self, object_id: str) -> AntiSpywareProfileResponseModel: + endpoint = f"{self.ENDPOINT}/{object_id}" + response = self.api_client.get(endpoint) + return AntiSpywareProfileResponseModel(**response) + + def update( + self, object_id: str, data: Dict[str, Any] + ) -> AntiSpywareProfileResponseModel: + profile = AntiSpywareProfileRequestModel(**data) + payload = profile.model_dump(exclude_unset=True) + endpoint = f"{self.ENDPOINT}/{object_id}" + response = self.api_client.put(endpoint, json=payload) + return AntiSpywareProfileResponseModel(**response) + + def delete(self, object_id: str) -> 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[AntiSpywareProfileResponseModel]: + params = {} + + # 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 = [ + AntiSpywareProfileResponseModel(**item) for item in response.get("data", []) + ] + return profiles diff --git a/scm/models/security/anti_spyware_profiles.py b/scm/models/security/anti_spyware_profiles.py index 766c7017..f359af97 100644 --- a/scm/models/security/anti_spyware_profiles.py +++ b/scm/models/security/anti_spyware_profiles.py @@ -1,7 +1,14 @@ # scm/models/security/anti_spyware_profiles.py -from typing import List, Optional, Union -from pydantic import BaseModel, Field, model_validator, ConfigDict, RootModel +from typing import List, Optional +from pydantic import ( + BaseModel, + Field, + model_validator, + field_validator, + ConfigDict, + RootModel, +) from enum import Enum import uuid @@ -28,8 +35,7 @@ class MicaEngineSpywareEnabledEntry(BaseModel): name: str = Field(..., description="Name of the MICA engine spyware detector") inline_policy_action: InlinePolicyAction = Field( - InlinePolicyAction.alert, - description="Inline policy action", + InlinePolicyAction.alert, description="Inline policy action" ) @@ -47,26 +53,21 @@ class BlockIpAction(BaseModel): description="Tracking method", pattern="^(source-and-destination|source)$", ) - duration: int = Field( - ..., - description="Duration in seconds", - ge=1, - le=3600, - ) + duration: int = Field(..., description="Duration in seconds", ge=1, le=3600) -class Action(RootModel[dict]): +class ActionRequest(RootModel[dict]): """ - Represents the 'action' field in rules and threat exceptions. + Represents the 'action' field in rules and threat exceptions for requests. - This model uses a custom validator to ensure only one action is set. + Enforces that exactly one action is provided. """ @model_validator(mode="before") @classmethod def check_and_transform_action(cls, values): if isinstance(values, str): - # Convert the string to a dict with an empty object + # Convert string to dict values = {values: {}} elif not isinstance(values, dict): raise ValueError("Invalid action format; must be a string or dict.") @@ -89,7 +90,44 @@ def check_and_transform_action(cls, values): return values def get_action_name(self) -> str: - """Utility method to get the name of the action.""" + return next(iter(self.root.keys()), "unknown") + + +class ActionResponse(RootModel[dict]): + """ + Represents the 'action' field in rules and threat exceptions for responses. + + Accepts empty dictionaries. + """ + + @model_validator(mode="before") + @classmethod + def check_action(cls, values): + if isinstance(values, str): + # Convert string to dict + values = {values: {}} + elif not isinstance(values, dict): + raise ValueError("Invalid action format; must be a string or dict.") + + action_fields = [ + "allow", + "alert", + "drop", + "reset_client", + "reset_server", + "reset_both", + "block_ip", + "default", + ] + provided_actions = [field for field in action_fields if field in values] + + if len(provided_actions) > 1: + raise ValueError("At most one action must be provided in 'action' field.") + + # Accept empty dicts (no action specified) + return values + + def get_action_name(self) -> str: return next(iter(self.root.keys()), "unknown") @@ -138,7 +176,6 @@ class Category(str, Enum): command_and_control = "command-and-control" dns_ddns = "dns-ddns" net_worm = "net-worm" - any = "any" tls_fingerprint = "tls-fingerprint" dns_new_domain = "dns-new-domain" dns = "dns" @@ -149,6 +186,7 @@ class Category(str, Enum): dns_malware = "dns-malware" browser_hijack = "browser-hijack" dns_parked = "dns-parked" + any = "any" class ExemptIpEntry(BaseModel): @@ -159,170 +197,100 @@ class ExemptIpEntry(BaseModel): name (str): Name of the IP address or range to exempt. """ - name: str = Field( - ..., - description="Exempt IP name", - ) + name: str = Field(..., description="Exempt IP name") -class Rule(BaseModel): +class RuleBase(BaseModel): """ - Represents a rule within an anti-spyware profile. - - Attributes: - name (str): The name of the rule. - severity (List[Severity]): List of severities associated with the rule. - category (Category): Category of the rule. - threat_name (Optional[str]): Specific threat name targeted by the rule. - packet_capture (Optional[PacketCapture]): Packet capture setting. - action (Optional[Action]): Action to be taken when the rule is matched. + Base class for Rule. """ - name: str = Field( - ..., - description="Rule name", - ) - severity: List[Severity] = Field( - ..., - description="List of severities", - ) - category: Category = Field( - ..., - description="Category", - ) - threat_name: Optional[str] = Field( - None, - description="Threat name", - min_length=4, - ) + name: str = Field(..., description="Rule name") + severity: List[Severity] = Field(..., description="List of severities") + category: Category = Field(..., description="Category") + threat_name: Optional[str] = Field(None, description="Threat name", min_length=3) packet_capture: Optional[PacketCapture] = Field( - None, - description="Packet capture setting", - ) - action: Optional[Action] = Field( - None, - description="Action", + None, description="Packet capture setting" ) - @model_validator(mode="before") - def default_threat_name(cls, values): - return values or "any" + @field_validator("threat_name", mode="before") + @classmethod + def default_threat_name(cls, v): + return v or "any" + + +class RuleRequest(RuleBase): + action: Optional[ActionRequest] = Field(None, description="Action") + + +class RuleResponse(RuleBase): + action: Optional[ActionResponse] = Field(None, description="Action") -class ThreatException(BaseModel): +class ThreatExceptionBase(BaseModel): + """ + Base class for ThreatException. """ - Represents a threat exception within an anti-spyware profile. - Attributes: - name (str): The name of the threat exception. - action (Action): The action to be taken. - packet_capture (PacketCapture): Packet capture setting. - exempt_ip (Optional[List[ExemptIpEntry]]): List of exempt IP entries. - notes (Optional[str]): Additional notes. + name: str = Field(..., description="Threat exception name") + packet_capture: PacketCapture = Field(..., description="Packet capture setting") + exempt_ip: Optional[List[ExemptIpEntry]] = Field(None, description="Exempt IP list") + notes: Optional[str] = Field(None, description="Notes") + + +class ThreatExceptionRequest(ThreatExceptionBase): + action: ActionRequest = Field(..., description="Action") + + +class ThreatExceptionResponse(ThreatExceptionBase): + action: ActionResponse = Field(..., description="Action") + + +class AntiSpywareProfileBaseModel(BaseModel): + """ + Base model for AntiSpywareProfile, containing common fields. """ - name: str = Field( - ..., - description="Threat exception name", - ) - action: Action = Field( - ..., - description="Action", + name: str = Field(..., description="Profile name") + description: Optional[str] = Field(None, description="Description") + cloud_inline_analysis: Optional[bool] = Field( + False, description="Cloud inline analysis" ) - packet_capture: PacketCapture = Field( - ..., - description="Packet capture setting", + inline_exception_edl_url: Optional[List[str]] = Field( + None, description="Inline exception EDL URLs" ) - exempt_ip: Optional[List[ExemptIpEntry]] = Field( - None, - description="Exempt IP list", + inline_exception_ip_address: Optional[List[str]] = Field( + None, description="Inline exception IP addresses" ) - notes: Optional[str] = Field( - None, - description="Notes", + mica_engine_spyware_enabled: Optional[List[MicaEngineSpywareEnabledEntry]] = Field( + None, description="List of MICA engine spyware enabled entries" ) -class AntiSpywareProfileRequestModel(BaseModel): +class AntiSpywareProfileRequestModel(AntiSpywareProfileBaseModel): """ Represents an anti-spyware profile for API requests. - - Attributes: - name (str): The name of the profile. - description (Optional[str]): Description of the profile. - folder (Optional[str]): The folder where the profile is stored. - snippet (Optional[str]): The snippet associated with the profile. - device (Optional[str]): The device where the profile is applied. - cloud_inline_analysis (Optional[bool]): Cloud inline analysis setting. - inline_exception_edl_url (Optional[List[str]]): List of exception EDL URLs. - inline_exception_ip_address (Optional[List[str]]): List of exception IP addresses. - mica_engine_spyware_enabled (Optional[List[MicaEngineSpywareEnabledEntry]]): List of MICA engine settings. - rules (List[Rule]): List of rules in the profile. - threat_exception (Optional[List[ThreatException]]): List of threat exceptions. """ - # Model configuration - model_config = ConfigDict( - validate_assignment=True, - arbitrary_types_allowed=True, - ) - - name: str = Field( - ..., - description="Profile name", - ) - description: Optional[str] = Field( - None, - description="Description", - ) folder: Optional[str] = Field( - None, - description="Folder", - max_length=64, - pattern=r"^[a-zA-Z\d\-_. ]+$", + 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\-_. ]+$", + 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\-_. ]+$", - ) - cloud_inline_analysis: Optional[bool] = Field( - False, - description="Cloud inline analysis", + None, description="Device", max_length=64, pattern=r"^[a-zA-Z\d\-_. ]+$" ) - inline_exception_edl_url: Optional[List[str]] = Field( - None, - description="Inline exception EDL URLs", - ) - inline_exception_ip_address: Optional[List[str]] = Field( - None, - description="Inline exception IP addresses", - ) - mica_engine_spyware_enabled: Optional[List[MicaEngineSpywareEnabledEntry]] = Field( - None, - description="List of MICA engine spyware enabled entries", - ) - rules: List[Rule] = Field(..., description="List of rules") - threat_exception: Optional[List[ThreatException]] = Field( - None, - description="List of threat exceptions", + rules: List[RuleRequest] = Field(..., description="List of rules") + threat_exception: Optional[List[ThreatExceptionRequest]] = Field( + None, description="List of threat exceptions" ) - # Custom Validators + model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True) + @model_validator(mode="after") def validate_container(self) -> "AntiSpywareProfileRequestModel": - container_fields = [ - "folder", - "snippet", - "device", - ] + container_fields = ["folder", "snippet", "device"] provided_containers = [ field for field in container_fields if getattr(self, field) is not None ] @@ -335,24 +303,28 @@ def validate_container(self) -> "AntiSpywareProfileRequestModel": return self -class AntiSpywareProfileResponseModel(AntiSpywareProfileRequestModel): +class AntiSpywareProfileResponseModel(AntiSpywareProfileBaseModel): """ Represents an anti-spyware profile for API responses. - - Attributes: - id (str): The UUID of the profile. """ 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") + rules: List[RuleResponse] = Field(..., description="List of rules") + threat_exception: Optional[List[ThreatExceptionResponse]] = Field( + None, description="List of threat exceptions" + ) - @model_validator(mode="before") - def validate_uuid(cls, values): - if "id" in values and values["id"] is not None: - try: - uuid.UUID(values["id"]) - except ValueError: - raise ValueError("Invalid UUID format for 'id'") - return values + @field_validator("id") + @classmethod + def validate_id(cls, v): + try: + uuid.UUID(v) + except ValueError: + raise ValueError("Invalid UUID format for 'id'") + return v class AntiSpywareProfilesResponse(BaseModel): From 5c5f1e1c43b3aa89b87cc7e1d5015a493d812696 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 20:06:12 -0500 Subject: [PATCH 07/11] Add tests for AntiSpywareProfile and AddressRequestModel Implemented comprehensive validation and functionality tests for AntiSpywareProfile, including CRUD operations and model-specific checks. Extended AddressRequestModel tests to cover various validation cases, ensuring robustness and correctness of the models. --- scm/config/security/anti_spyware_profiles.py | 13 + scm/models/security/anti_spyware_profiles.py | 163 ++++- tests/test_addresses.py | 95 +++ tests/test_anti_spyware_profiles.py | 634 +++++++++++++++++++ 4 files changed, 870 insertions(+), 35 deletions(-) create mode 100644 tests/test_anti_spyware_profiles.py diff --git a/scm/config/security/anti_spyware_profiles.py b/scm/config/security/anti_spyware_profiles.py index e36987bf..23439c7f 100644 --- a/scm/config/security/anti_spyware_profiles.py +++ b/scm/config/security/anti_spyware_profiles.py @@ -68,6 +68,19 @@ def list( **filters, ) -> List[AntiSpywareProfileResponseModel]: 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} diff --git a/scm/models/security/anti_spyware_profiles.py b/scm/models/security/anti_spyware_profiles.py index f359af97..e19dd806 100644 --- a/scm/models/security/anti_spyware_profiles.py +++ b/scm/models/security/anti_spyware_profiles.py @@ -33,9 +33,13 @@ class MicaEngineSpywareEnabledEntry(BaseModel): inline_policy_action (InlinePolicyAction): Action to be taken by the inline policy. """ - name: str = Field(..., description="Name of the MICA engine spyware detector") + name: str = Field( + ..., + description="Name of the MICA engine spyware detector", + ) inline_policy_action: InlinePolicyAction = Field( - InlinePolicyAction.alert, description="Inline policy action" + InlinePolicyAction.alert, + description="Inline policy action, defaults to 'alert'", ) @@ -53,7 +57,12 @@ class BlockIpAction(BaseModel): description="Tracking method", pattern="^(source-and-destination|source)$", ) - duration: int = Field(..., description="Duration in seconds", ge=1, le=3600) + duration: int = Field( + ..., + description="Duration in seconds", + ge=1, + le=3600, + ) class ActionRequest(RootModel[dict]): @@ -197,7 +206,10 @@ class ExemptIpEntry(BaseModel): name (str): Name of the IP address or range to exempt. """ - name: str = Field(..., description="Exempt IP name") + name: str = Field( + ..., + description="Exempt IP name", + ) class RuleBase(BaseModel): @@ -205,12 +217,26 @@ class RuleBase(BaseModel): Base class for Rule. """ - name: str = Field(..., description="Rule name") - severity: List[Severity] = Field(..., description="List of severities") - category: Category = Field(..., description="Category") - threat_name: Optional[str] = Field(None, description="Threat name", min_length=3) + name: str = Field( + ..., + description="Rule name", + ) + severity: List[Severity] = Field( + ..., + description="List of severities", + ) + category: Category = Field( + ..., + description="Category", + ) + threat_name: Optional[str] = Field( + None, + description="Threat name", + min_length=3, + ) packet_capture: Optional[PacketCapture] = Field( - None, description="Packet capture setting" + None, + description="Packet capture setting", ) @field_validator("threat_name", mode="before") @@ -220,11 +246,17 @@ def default_threat_name(cls, v): class RuleRequest(RuleBase): - action: Optional[ActionRequest] = Field(None, description="Action") + action: Optional[ActionRequest] = Field( + None, + description="Action", + ) class RuleResponse(RuleBase): - action: Optional[ActionResponse] = Field(None, description="Action") + action: Optional[ActionResponse] = Field( + None, + description="Action", + ) class ThreatExceptionBase(BaseModel): @@ -232,18 +264,36 @@ class ThreatExceptionBase(BaseModel): Base class for ThreatException. """ - name: str = Field(..., description="Threat exception name") - packet_capture: PacketCapture = Field(..., description="Packet capture setting") - exempt_ip: Optional[List[ExemptIpEntry]] = Field(None, description="Exempt IP list") - notes: Optional[str] = Field(None, description="Notes") + name: str = Field( + ..., + description="Threat exception name", + ) + packet_capture: PacketCapture = Field( + ..., + description="Packet capture setting", + ) + exempt_ip: Optional[List[ExemptIpEntry]] = Field( + None, + description="Exempt IP list", + ) + notes: Optional[str] = Field( + None, + description="Notes", + ) class ThreatExceptionRequest(ThreatExceptionBase): - action: ActionRequest = Field(..., description="Action") + action: ActionRequest = Field( + ..., + description="Action", + ) class ThreatExceptionResponse(ThreatExceptionBase): - action: ActionResponse = Field(..., description="Action") + action: ActionResponse = Field( + ..., + description="Action", + ) class AntiSpywareProfileBaseModel(BaseModel): @@ -251,19 +301,29 @@ class AntiSpywareProfileBaseModel(BaseModel): Base model for AntiSpywareProfile, containing common fields. """ - name: str = Field(..., description="Profile name") - description: Optional[str] = Field(None, description="Description") + name: str = Field( + ..., + description="Profile name", + ) + description: Optional[str] = Field( + None, + description="Description", + ) cloud_inline_analysis: Optional[bool] = Field( - False, description="Cloud inline analysis" + False, + description="Cloud inline analysis", ) inline_exception_edl_url: Optional[List[str]] = Field( - None, description="Inline exception EDL URLs" + None, + description="Inline exception EDL URLs", ) inline_exception_ip_address: Optional[List[str]] = Field( - None, description="Inline exception IP addresses" + None, + description="Inline exception IP addresses", ) mica_engine_spyware_enabled: Optional[List[MicaEngineSpywareEnabledEntry]] = Field( - None, description="List of MICA engine spyware enabled entries" + None, + description="List of MICA engine spyware enabled entries", ) @@ -273,24 +333,41 @@ class AntiSpywareProfileRequestModel(AntiSpywareProfileBaseModel): """ folder: Optional[str] = Field( - None, description="Folder", max_length=64, pattern=r"^[a-zA-Z\d\-_. ]+$" + 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\-_. ]+$" + 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\-_. ]+$" + None, + description="Device", + max_length=64, + pattern=r"^[a-zA-Z\d\-_. ]+$", + ) + rules: List[RuleRequest] = Field( + ..., + description="List of rules", ) - rules: List[RuleRequest] = Field(..., description="List of rules") threat_exception: Optional[List[ThreatExceptionRequest]] = Field( - None, description="List of threat exceptions" + None, + description="List of threat exceptions", ) model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True) @model_validator(mode="after") def validate_container(self) -> "AntiSpywareProfileRequestModel": - container_fields = ["folder", "snippet", "device"] + container_fields = [ + "folder", + "snippet", + "device", + ] provided_containers = [ field for field in container_fields if getattr(self, field) is not None ] @@ -308,13 +385,29 @@ class AntiSpywareProfileResponseModel(AntiSpywareProfileBaseModel): Represents an anti-spyware 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") - rules: List[RuleResponse] = Field(..., description="List of rules") + 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", + ) + rules: List[RuleResponse] = Field( + ..., + description="List of rules", + ) threat_exception: Optional[List[ThreatExceptionResponse]] = Field( - None, description="List of threat exceptions" + None, + description="List of threat exceptions", ) @field_validator("id") diff --git a/tests/test_addresses.py b/tests/test_addresses.py index 47911b98..9c8bcb01 100644 --- a/tests/test_addresses.py +++ b/tests/test_addresses.py @@ -1,4 +1,6 @@ # tests/test_addresses.py +import uuid + import pytest from scm.config.objects import Address @@ -388,3 +390,96 @@ def test_address_request_model_multiple_containers_with_device(): assert "Exactly one of 'folder', 'snippet', or 'device' must be provided." in str( exc_info.value ) + + +def test_address_request_model_validation(): + """ + Test validation in AddressRequestModel. + """ + # Valid input with exactly one address type + valid_data = { + "name": "TestAddress", + "ip_netmask": "192.168.1.1/24", + "folder": "Shared", + } + address = AddressRequestModel(**valid_data) + assert address.name == "TestAddress" + assert address.ip_netmask == "192.168.1.1/24" + + # No address type provided + invalid_data_no_address = { + "name": "InvalidAddress", + "folder": "Shared", + } + with pytest.raises(ValueError) as exc_info: + AddressRequestModel(**invalid_data_no_address) + assert ( + "Exactly one of 'ip_netmask', 'ip_range', 'ip_wildcard', or 'fqdn' must be provided." + in str(exc_info.value) + ) + + # Multiple address types provided + invalid_data_multiple_addresses = { + "name": "InvalidAddress", + "ip_netmask": "192.168.1.1/24", + "fqdn": "example.com", + "folder": "Shared", + } + with pytest.raises(ValueError) as exc_info: + AddressRequestModel(**invalid_data_multiple_addresses) + assert ( + "Exactly one of 'ip_netmask', 'ip_range', 'ip_wildcard', or 'fqdn' must be provided." + in str(exc_info.value) + ) + + # Test each address type individually + address_types = ["ip_netmask", "ip_range", "ip_wildcard", "fqdn"] + for address_type in address_types: + valid_data = { + "name": f"TestAddress_{address_type}", + address_type: "test_value", + "folder": "Shared", + } + address = AddressRequestModel(**valid_data) + assert getattr(address, address_type) == "test_value" + + # Test valid UUID + valid_uuid = str(uuid.uuid4()) + valid_uuid_data = { + "id": valid_uuid, + "name": "ValidUUIDAddress", + "ip_netmask": "192.168.1.1/24", + "folder": "Shared", + } + address = AddressResponseModel(**valid_uuid_data) + assert address.id == valid_uuid + + # Test invalid UUID + invalid_uuid_data = { + "id": "invalid-uuid", + "name": "InvalidUUIDAddress", + "ip_netmask": "192.168.1.1/24", + "folder": "Shared", + } + with pytest.raises(ValueError) as exc_info: + AddressRequestModel(**invalid_uuid_data) + assert "Invalid UUID format for 'id'" in str(exc_info.value) + + # Test with None UUID + none_uuid_data = { + "id": None, + "name": "NoneUUIDAddress", + "ip_netmask": "192.168.1.1/24", + "folder": "Shared", + } + address = AddressResponseModel(**none_uuid_data) + assert address.id is None + + # Test without UUID field + no_uuid_data = { + "name": "NoUUIDAddress", + "ip_netmask": "192.168.1.1/24", + "folder": "Shared", + } + address = AddressRequestModel(**no_uuid_data) + assert not hasattr(address, "id") diff --git a/tests/test_anti_spyware_profiles.py b/tests/test_anti_spyware_profiles.py new file mode 100644 index 00000000..bbe4465a --- /dev/null +++ b/tests/test_anti_spyware_profiles.py @@ -0,0 +1,634 @@ +# tests/test_anti_spyware_profiles.py + +import pytest +from unittest.mock import MagicMock + +from scm.config.security.anti_spyware_profiles import AntiSpywareProfile +from scm.exceptions import ValidationError +from scm.models.security.anti_spyware_profiles import ( + AntiSpywareProfileRequestModel, + AntiSpywareProfileResponseModel, + RuleRequest, + RuleResponse, + ThreatExceptionRequest, + ThreatExceptionResponse, + Severity, + Category, + PacketCapture, + ActionRequest, + ActionResponse, +) +from typing import List + + +def test_list_anti_spyware_profiles(load_env, mock_scm): + """ + Test listing anti-spyware profiles. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "123e4567-e89b-12d3-a456-426655440000", + "name": "TestProfile1", + "folder": "Prisma Access", + "description": "A test anti-spyware profile", + "rules": [ + { + "name": "TestRule1", + "severity": ["critical", "high"], + "category": "spyware", + "threat_name": "any", + "packet_capture": "disable", + "action": {"alert": {}}, + } + ], + "threat_exception": [ + { + "name": "TestException1", + "action": {"allow": {}}, + "packet_capture": "single-packet", + "exempt_ip": [{"name": "192.168.1.1"}], + "notes": "Test note", + } + ], + }, + { + "id": "223e4567-e89b-12d3-a456-426655440001", + "name": "TestProfile2", + "folder": "Prisma Access", + "rules": [], + "threat_exception": [], + }, + ], + "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 AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Call the list method + profiles = anti_spyware_profile_client.list(folder="Prisma Access") + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/anti-spyware-profiles", params={"folder": "Prisma Access"} + ) + assert isinstance(profiles, list) + assert len(profiles) == 2 + assert isinstance(profiles[0], AntiSpywareProfileResponseModel) + assert profiles[0].name == "TestProfile1" + assert profiles[0].description == "A test anti-spyware profile" + assert profiles[0].rules[0].name == "TestRule1" + assert profiles[0].rules[0].severity == [Severity.critical, Severity.high] + assert profiles[0].rules[0].category == Category.spyware + assert profiles[0].rules[0].action.get_action_name() == "alert" + assert profiles[0].threat_exception[0].name == "TestException1" + assert profiles[0].threat_exception[0].action.get_action_name() == "allow" + assert profiles[0].threat_exception[0].exempt_ip[0].name == "192.168.1.1" + + +def test_create_anti_spyware_profile(load_env, mock_scm): + """ + Test creating an anti-spyware profile. + """ + # Prepare test data + test_profile_data = { + "name": "NewTestProfile", + "folder": "Prisma Access", + "description": "A new test anti-spyware profile", + "rules": [ + { + "name": "NewRule", + "severity": ["medium", "low"], + "category": "adware", + "packet_capture": "single-packet", + "action": "alert", + } + ], + "threat_exception": [ + { + "name": "NewException", + "action": "allow", + "packet_capture": "disable", + "exempt_ip": [{"name": "10.0.0.1"}], + "notes": "Exception note", + } + ], + } + + # Expected payload after model processing + expected_payload = { + "name": "NewTestProfile", + "folder": "Prisma Access", + "description": "A new test anti-spyware profile", + "rules": [ + { + "name": "NewRule", + "severity": ["medium", "low"], + "category": "adware", + "packet_capture": "single-packet", + "action": {"alert": {}}, + } + ], + "threat_exception": [ + { + "name": "NewException", + "action": {"allow": {}}, + "packet_capture": "disable", + "exempt_ip": [{"name": "10.0.0.1"}], + "notes": "Exception note", + } + ], + } + + # 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 AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Call the create method + created_profile = anti_spyware_profile_client.create(test_profile_data) + + # Assertions + mock_scm.post.assert_called_once_with( + "/config/security/v1/anti-spyware-profiles", + json=expected_payload, + ) + assert isinstance(created_profile, AntiSpywareProfileResponseModel) + assert created_profile.id == "333e4567-e89b-12d3-a456-426655440002" + assert created_profile.name == "NewTestProfile" + assert created_profile.rules[0].name == "NewRule" + assert created_profile.rules[0].action.get_action_name() == "alert" + assert created_profile.threat_exception[0].name == "NewException" + + +def test_get_anti_spyware_profile(load_env, mock_scm): + """ + Test retrieving an anti-spyware profile by ID. + """ + # Mock response from the API client + profile_id = "123e4567-e89b-12d3-a456-426655440000" + mock_response = { + "id": profile_id, + "name": "TestProfile", + "folder": "Prisma Access", + "description": "A test anti-spyware profile", + "rules": [], + "threat_exception": [], + } + + # 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 = AntiSpywareProfile(mock_scm) + + # Call the get method + profile = anti_spyware_profile_client.get(profile_id) + + # Assertions + mock_scm.get.assert_called_once_with( + f"/config/security/v1/anti-spyware-profiles/{profile_id}" + ) + assert isinstance(profile, AntiSpywareProfileResponseModel) + assert profile.id == profile_id + assert profile.name == "TestProfile" + assert profile.description == "A test anti-spyware profile" + + +def test_update_anti_spyware_profile(load_env, mock_scm): + """ + Test updating an anti-spyware profile. + """ + # Prepare test data + profile_id = "123e4567-e89b-12d3-a456-426655440000" + update_data = { + "name": "UpdatedProfile", + "folder": "Prisma Access", + "description": "An updated anti-spyware profile", + "rules": [ + { + "name": "UpdatedRule", + "severity": ["high"], + "category": "botnet", + "packet_capture": "extended-capture", + "action": {"block_ip": {"track_by": "source", "duration": 3600}}, + } + ], + "threat_exception": [], + } + + # Expected payload after model processing + expected_payload = { + "name": "UpdatedProfile", + "folder": "Prisma Access", + "description": "An updated anti-spyware profile", + "rules": [ + { + "name": "UpdatedRule", + "severity": ["high"], + "category": "botnet", + "packet_capture": "extended-capture", + "action": {"block_ip": {"track_by": "source", "duration": 3600}}, + } + ], + "threat_exception": [], + } + + # 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 AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Call the update method + updated_profile = anti_spyware_profile_client.update(profile_id, update_data) + + # Assertions + mock_scm.put.assert_called_once_with( + f"/config/security/v1/anti-spyware-profiles/{profile_id}", + json=expected_payload, + ) + assert isinstance(updated_profile, AntiSpywareProfileResponseModel) + assert updated_profile.id == profile_id + assert updated_profile.name == "UpdatedProfile" + assert updated_profile.description == "An updated anti-spyware profile" + assert updated_profile.rules[0].action.get_action_name() == "block_ip" + + +def test_delete_anti_spyware_profile(load_env, mock_scm): + """ + Test deleting an anti-spyware 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 AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Call the delete method + anti_spyware_profile_client.delete(profile_id) + + # Assertions + mock_scm.delete.assert_called_once_with( + f"/config/security/v1/anti-spyware-profiles/{profile_id}" + ) + + +def test_anti_spyware_profile_list_validation_error(load_env, mock_scm): + """ + Test validation error when listing with multiple containers. + """ + # Create an instance of AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Attempt to call the list method with multiple containers + with pytest.raises(ValidationError) as exc_info: + anti_spyware_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_anti_spyware_profile_request_model_validation_errors(): + """ + Test validation errors in AntiSpywareProfileRequestModel. + """ + # No container provided + data_no_container = { + "name": "InvalidProfile", + "rules": [], + } + with pytest.raises(ValueError) as exc_info: + AntiSpywareProfileRequestModel(**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": [], + } + with pytest.raises(ValueError) as exc_info: + AntiSpywareProfileRequestModel(**data_multiple_containers) + assert "Exactly one of 'folder', 'snippet', or 'device' must be provided." in str( + exc_info.value + ) + + # Invalid action in RuleRequest + data_invalid_action = { + "name": "InvalidProfile", + "folder": "Shared", + "rules": [ + { + "name": "InvalidRule", + "severity": ["high"], + "category": "botnet", + "action": {}, # Empty action dictionary + } + ], + } + with pytest.raises(ValueError) as exc_info: + AntiSpywareProfileRequestModel(**data_invalid_action) + assert "Exactly one action must be provided in 'action' field." in str( + exc_info.value + ) + + # Invalid UUID in id field (for response model) + data_invalid_id = { + "id": "invalid-uuid", + "name": "TestProfile", + "folder": "Shared", + "rules": [], + } + with pytest.raises(ValueError) as exc_info: + AntiSpywareProfileResponseModel(**data_invalid_id) + assert "Invalid UUID format for 'id'" in str(exc_info.value) + + +def test_rule_request_model_validation(): + """ + Test validation in RuleRequest model. + """ + # Invalid severity + data_invalid_severity = { + "name": "TestRule", + "severity": ["nonexistent_severity"], + "category": "spyware", + "action": "alert", + } + with pytest.raises(ValueError) as exc_info: + RuleRequest(**data_invalid_severity) + assert "1 validation error for RuleRequest" in str(exc_info.value) + + # Missing action + data_missing_action = { + "name": "TestRule", + "severity": ["critical"], + "category": "spyware", + } + rule = RuleRequest(**data_missing_action) + assert rule.action is None + + +def test_threat_exception_request_model_validation(): + """ + Test validation in ThreatExceptionRequest model. + """ + # Invalid packet_capture + data_invalid_packet_capture = { + "name": "TestException", + "action": "alert", + "packet_capture": "invalid_option", + } + with pytest.raises(ValueError) as exc_info: + ThreatExceptionRequest(**data_invalid_packet_capture) + assert "1 validation error for ThreatExceptionRequest" in str(exc_info.value) + + # Missing action + data_missing_action = { + "name": "TestException", + "packet_capture": "disable", + } + with pytest.raises(Exception) as exc_info: + ThreatExceptionRequest(**data_missing_action) + # assert isinstance(exc_info.value, ValidationError) + assert "1 validation error for ThreatExceptionRequest" in str(exc_info.value) + assert "action\n Field required" in str(exc_info.value) + + +def test_list_anti_spyware_profiles_with_pagination(load_env, mock_scm): + """ + Test listing anti-spyware profiles with pagination parameters. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "223e4567-e89b-12d3-a456-426655440001", + "name": "TestProfile2", + "folder": "Prisma Access", + "rules": [], + "threat_exception": [], + }, + ], + "offset": 1, + "total": 2, + "limit": 1, + } + + # 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 = AntiSpywareProfile(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/anti-spyware-profiles", + params={"folder": "Prisma Access", "offset": 1, "limit": 1}, + ) + assert isinstance(profiles, list) + assert len(profiles) == 1 + assert profiles[0].name == "TestProfile2" + assert profiles[0].id == "223e4567-e89b-12d3-a456-426655440001" + + +def test_list_anti_spyware_profiles_with_name_filter(load_env, mock_scm): + """ + Test listing anti-spyware profiles with name filter. + """ + # Mock response from the API client + mock_response = { + "data": [ + { + "id": "223e4567-e89b-12d3-a456-426655440001", + "name": "SpecificProfile", + "folder": "Prisma Access", + "rules": [], + "threat_exception": [], + }, + ], + "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 AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Call the list method with name filter + profiles = anti_spyware_profile_client.list( + folder="Prisma Access", name="SpecificProfile" + ) + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/anti-spyware-profiles", + params={"folder": "Prisma Access", "name": "SpecificProfile"}, + ) + assert isinstance(profiles, list) + assert len(profiles) == 1 + assert profiles[0].name == "SpecificProfile" + assert profiles[0].id == "223e4567-e89b-12d3-a456-426655440001" + + +def test_list_anti_spyware_profiles_with_all_parameters(load_env, mock_scm): + """ + Test listing anti-spyware profiles with all optional parameters. + """ + # Mock response from the API client + mock_response = { + "data": [], + "offset": 10, + "total": 2, + "limit": 5, + } + + # 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 = AntiSpywareProfile(mock_scm) + + # Call the list method with all optional parameters + profiles = anti_spyware_profile_client.list( + folder="Prisma Access", name="TestProfile", offset=10, limit=5 + ) + + # Assertions + mock_scm.get.assert_called_once_with( + "/config/security/v1/anti-spyware-profiles", + params={ + "folder": "Prisma Access", + "name": "TestProfile", + "offset": 10, + "limit": 5, + }, + ) + assert isinstance(profiles, list) + assert len(profiles) == 0 # As per the mocked response data + + +def test_anti_spyware_profile_list_with_invalid_pagination(load_env, mock_scm): + """ + Test validation error when invalid pagination parameters are provided. + """ + # Create an instance of AntiSpywareProfile with the mocked Scm + anti_spyware_profile_client = AntiSpywareProfile(mock_scm) + + # Attempt to call the list method with invalid pagination parameters + with pytest.raises(ValueError) as exc_info: + anti_spyware_profile_client.list( + folder="Prisma Access", + 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_action_request_check_and_transform_action(): + # Test string input + action = ActionRequest("alert") + assert action.root == {"alert": {}} + + # Test dict input + action = ActionRequest({"drop": {}}) + assert action.root == {"drop": {}} + + # Test invalid input type + with pytest.raises( + ValueError, match="Invalid action format; must be a string or dict." + ): + ActionRequest(123) + + # Test multiple actions + with pytest.raises( + ValueError, match="Exactly one action must be provided in 'action' field." + ): + ActionRequest({"alert": {}, "drop": {}}) + + # Test empty dict + with pytest.raises( + ValueError, match="Exactly one action must be provided in 'action' field." + ): + ActionRequest({}) + + +def test_action_request_get_action_name(): + action = ActionRequest("alert") + assert action.get_action_name() == "alert" + + action = ActionRequest({"drop": {}}) + assert action.get_action_name() == "drop" + + +def test_action_response_check_action(): + # Test string input + action = ActionResponse("alert") + assert action.root == {"alert": {}} + + # Test dict input + action = ActionResponse({"drop": {}}) + assert action.root == {"drop": {}} + + # Test invalid input type + with pytest.raises( + ValueError, match="Invalid action format; must be a string or dict." + ): + ActionResponse(123) + + # Test multiple actions + with pytest.raises( + ValueError, match="At most one action must be provided in 'action' field." + ): + ActionResponse({"alert": {}, "drop": {}}) + + # Test empty dict (should be allowed for ActionResponse) + action = ActionResponse({}) + assert action.root == {} + + +def test_action_response_get_action_name(): + action = ActionResponse("alert") + assert action.get_action_name() == "alert" + + action = ActionResponse({"drop": {}}) + assert action.get_action_name() == "drop" + + action = ActionResponse({}) + assert action.get_action_name() == "unknown" From 992a85cf3c9330508f786e88b6d4f5fc4b1cde8d Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 20:38:51 -0500 Subject: [PATCH 08/11] Restructure documentation for better organization Reorganized the documentation to segregate configuration objects and security services for improved clarity. Deleted outdated models.md and created new directories for objects and security services each with dedicated overview files. Updated all relevant links and references accordingly. --- docs/sdk/{ => config/objects}/address.md | 4 +- .../sdk/{ => config/objects}/address_group.md | 4 +- docs/sdk/{ => config/objects}/application.md | 4 +- .../{ => config/objects}/application_group.md | 4 +- .../objects/index.md} | 2 +- docs/sdk/{ => config/objects}/service.md | 4 +- .../config/security_services/anti_spyware.md | 169 ++++++++++++++++++ docs/sdk/config/security_services/index.md | 20 +++ docs/sdk/index.md | 33 ++-- .../{ => objects}/address_group_models.md | 0 .../models/{ => objects}/address_models.md | 0 .../{ => objects}/application_group_models.md | 0 .../{ => objects}/application_models.md | 0 .../{models.md => models/objects/index.md} | 10 +- .../models/{ => objects}/service_models.md | 0 .../anti_spyware_profile_models.md | 138 ++++++++++++++ docs/sdk/models/security_services/index.md | 19 ++ mkdocs.yml | 33 ++-- 18 files changed, 403 insertions(+), 41 deletions(-) rename docs/sdk/{ => config/objects}/address.md (95%) rename docs/sdk/{ => config/objects}/address_group.md (95%) rename docs/sdk/{ => config/objects}/application.md (95%) rename docs/sdk/{ => config/objects}/application_group.md (94%) rename docs/sdk/{configuration_objects.md => config/objects/index.md} (97%) rename docs/sdk/{ => config/objects}/service.md (95%) create mode 100644 docs/sdk/config/security_services/anti_spyware.md create mode 100644 docs/sdk/config/security_services/index.md rename docs/sdk/models/{ => objects}/address_group_models.md (100%) rename docs/sdk/models/{ => objects}/address_models.md (100%) rename docs/sdk/models/{ => objects}/application_group_models.md (100%) rename docs/sdk/models/{ => objects}/application_models.md (100%) rename docs/sdk/{models.md => models/objects/index.md} (66%) rename docs/sdk/models/{ => objects}/service_models.md (100%) create mode 100644 docs/sdk/models/security_services/anti_spyware_profile_models.md create mode 100644 docs/sdk/models/security_services/index.md diff --git a/docs/sdk/address.md b/docs/sdk/config/objects/address.md similarity index 95% rename from docs/sdk/address.md rename to docs/sdk/config/objects/address.md index cff82138..8e37a9e9 100644 --- a/docs/sdk/address.md +++ b/docs/sdk/config/objects/address.md @@ -147,5 +147,5 @@ for addr in addresses: ## Related Models -- [AddressRequestModel](models/address_models.md#addressrequestmodel) -- [AddressResponseModel](models/address_models.md#addressresponsemodel) +- [AddressRequestModel](../../models/objects/address_models.md#addressrequestmodel) +- [AddressResponseModel](../../models/objects/address_models.md#addressresponsemodel) diff --git a/docs/sdk/address_group.md b/docs/sdk/config/objects/address_group.md similarity index 95% rename from docs/sdk/address_group.md rename to docs/sdk/config/objects/address_group.md index 7624d272..a10b1b94 100644 --- a/docs/sdk/address_group.md +++ b/docs/sdk/config/objects/address_group.md @@ -163,5 +163,5 @@ for group in groups: ## Related Models -- [AddressGroupRequestModel](models/address_group_models.md#addressgrouprequestmodel) -- [AddressGroupResponseModel](models/address_group_models.md#addressgroupresponsemodel) +- [AddressGroupRequestModel](../../models/objects/address_group_models.md#addressgrouprequestmodel) +- [AddressGroupResponseModel](../../models/objects/address_group_models.md#addressgroupresponsemodel) diff --git a/docs/sdk/application.md b/docs/sdk/config/objects/application.md similarity index 95% rename from docs/sdk/application.md rename to docs/sdk/config/objects/application.md index 39971e2e..600b89aa 100644 --- a/docs/sdk/application.md +++ b/docs/sdk/config/objects/application.md @@ -173,5 +173,5 @@ for app in applications: ## Related Models -- [ApplicationRequestModel](models/application_models.md#applicationrequestmodel) -- [ApplicationResponseModel](models/application_models.md#applicationresponsemodel) +- [ApplicationRequestModel](../../models/objects/application_models.md#applicationrequestmodel) +- [ApplicationResponseModel](../../models/objects/application_models.md#applicationresponsemodel) diff --git a/docs/sdk/application_group.md b/docs/sdk/config/objects/application_group.md similarity index 94% rename from docs/sdk/application_group.md rename to docs/sdk/config/objects/application_group.md index 3d0fcc88..f6eae9b2 100644 --- a/docs/sdk/application_group.md +++ b/docs/sdk/config/objects/application_group.md @@ -155,5 +155,5 @@ print(f"Created application group with ID: {new_group.id}") ## Related Models -- [ApplicationGroupRequestModel](models/application_group_models.md#ApplicationGrouprequestmodel) -- [ApplicationGroupResponseModel](models/application_group_models.md#ApplicationGroupresponsemodel) +- [ApplicationGroupRequestModel](../../models/objects/application_group_models.md#ApplicationGrouprequestmodel) +- [ApplicationGroupResponseModel](../../models/objects/application_group_models.md#ApplicationGroupresponsemodel) diff --git a/docs/sdk/configuration_objects.md b/docs/sdk/config/objects/index.md similarity index 97% rename from docs/sdk/configuration_objects.md rename to docs/sdk/config/objects/index.md index 28e3b71d..e2e6ba3a 100644 --- a/docs/sdk/configuration_objects.md +++ b/docs/sdk/config/objects/index.md @@ -1,4 +1,4 @@ -# Configuration Objects +# Objects This section covers the configuration objects provided by the `pan-scm-sdk`: diff --git a/docs/sdk/service.md b/docs/sdk/config/objects/service.md similarity index 95% rename from docs/sdk/service.md rename to docs/sdk/config/objects/service.md index 391742c0..c3b587a9 100644 --- a/docs/sdk/service.md +++ b/docs/sdk/config/objects/service.md @@ -168,7 +168,7 @@ for svc in services: ## Related Models -- [ServiceRequestModel](models/service_models.md#servicerequestmodel) -- [ServiceResponseModel](models/service_models.md#serviceresponsemodel) +- [ServiceRequestModel](../../models/objects/service_models.md#servicerequestmodel) +- [ServiceResponseModel](../../models/objects/service_models.md#serviceresponsemodel) --- diff --git a/docs/sdk/config/security_services/anti_spyware.md b/docs/sdk/config/security_services/anti_spyware.md new file mode 100644 index 00000000..d6ca6f56 --- /dev/null +++ b/docs/sdk/config/security_services/anti_spyware.md @@ -0,0 +1,169 @@ +# Anti-Spyware Profile Configuration Object + +The `AntiSpywareProfile` class is used to manage anti-spyware profile objects in the Strata Cloud Manager. It provides +methods to create, retrieve, update, delete, and list anti-spyware profile objects. + +--- + +## Importing the AntiSpywareProfile Class + +```python +from scm.config.security import AntiSpywareProfile +``` + +## Methods + +### `create(data: Dict[str, Any]) -> AntiSpywareProfileResponseModel` + +Creates a new anti-spyware profile object. + +**Parameters:** + +- `data` (Dict[str, Any]): A dictionary containing the anti-spyware profile object data. + +**Example:** + +```python +profile_data = { + "name": "test_profile", + "description": "Test anti-spyware profile", + "folder": "Prisma Access", + "rules": [ + { + "name": "rule1", + "severity": ["critical", "high"], + "category": "spyware", + "action": {"alert": {}} + } + ] +} + +new_profile = anti_spyware_profile.create(profile_data) +print(f"Created anti-spyware profile with ID: {new_profile.id}") +``` + +### `get(object_id: str) -> AntiSpywareProfileResponseModel` + +Retrieves an anti-spyware profile object by its ID. + +**Parameters:** + +- `object_id` (str): The UUID of the anti-spyware profile object. + +**Example:** + +```python +profile_id = "123e4567-e89b-12d3-a456-426655440000" +profile_object = anti_spyware_profile.get(profile_id) +print(f"Anti-Spyware Profile Name: {profile_object.name}") +``` + +### `update(object_id: str, data: Dict[str, Any]) -> AntiSpywareProfileResponseModel` + +Updates an existing anti-spyware profile object. + +**Parameters:** + +- `object_id` (str): The UUID of the anti-spyware profile object. +- `data` (Dict[str, Any]): A dictionary containing the updated anti-spyware profile data. + +**Example:** + +```python +update_data = { + "description": "Updated anti-spyware profile description", +} + +updated_profile = anti_spyware_profile.update(profile_id, update_data) +print(f"Updated anti-spyware profile with ID: {updated_profile.id}") +``` + +### `delete(object_id: str) -> None` + +Deletes an anti-spyware profile object by its ID. + +**Parameters:** + +- `object_id` (str): The UUID of the anti-spyware profile object. + +**Example:** + +```python +anti_spyware_profile.delete(profile_id) +print(f"Deleted anti-spyware 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[AntiSpywareProfileResponseModel]` + +Lists anti-spyware profile objects, optionally filtered by folder, snippet, device, or other criteria. + +**Parameters:** + +- `folder` (Optional[str]): The folder to list anti-spyware profiles from. +- `snippet` (Optional[str]): The snippet to list anti-spyware profiles from. +- `device` (Optional[str]): The device to list anti-spyware profiles from. +- `offset` (Optional[int]): The offset for pagination. +- `limit` (Optional[int]): The limit for pagination. +- `name` (Optional[str]): Filter profiles by name. +- `**filters`: Additional filters. + +**Example:** + +```python +profiles = anti_spyware_profile.list(folder='Prisma Access', limit=10) + +for profile in profiles: + print(f"Anti-Spyware Profile Name: {profile.name}, ID: {profile.id}") +``` + +--- + +## Usage Example + +```python +from scm.client import Scm +from scm.config.security import AntiSpywareProfile + +# Initialize the SCM client +scm = Scm( + client_id="your_client_id", + client_secret="your_client_secret", + tsg_id="your_tsg_id", +) + +# Create an AntiSpywareProfile instance +anti_spyware_profile = AntiSpywareProfile(scm) + +# Create a new anti-spyware profile +profile_data = { + "name": "test_profile", + "description": "Test anti-spyware profile", + "folder": "Prisma Access", + "rules": [ + { + "name": "rule1", + "severity": ["critical", "high"], + "category": "spyware", + "action": {"alert": {}} + } + ] +} + +new_profile = anti_spyware_profile.create(profile_data) +print(f"Created anti-spyware profile with ID: {new_profile.id}") + +# List anti-spyware profiles +profiles = anti_spyware_profile.list(folder='Prisma Access', limit=10) +for profile in profiles: + print(f"Anti-Spyware Profile Name: {profile.name}, ID: {profile.id}") +``` + +--- + +## Related Models + +- [AntiSpywareProfileRequestModel](../../models/security_services/anti_spyware_profile_models.md#AntiSpywareProfileRequestModel) +- [AntiSpywareProfileResponseModel](../../models/security_services/anti_spyware_profile_models.md#AntiSpywareProfileResponseModel) + diff --git a/docs/sdk/config/security_services/index.md b/docs/sdk/config/security_services/index.md new file mode 100644 index 00000000..d5a6b2be --- /dev/null +++ b/docs/sdk/config/security_services/index.md @@ -0,0 +1,20 @@ +# Security Services + +This section covers the configuration security services provided by the `pan-scm-sdk`: + +- [Anti Spyware Profile](anti_spyware.md) + +Each configuration object corresponds to a resource in the Strata Cloud Manager and provides methods for CRUD (Create, +Read, Update, Delete) operations. + +--- + +## Available Objects + +### [AntiSpywareProfile](anti_spyware.md) + +Manage individual Anti-Spyware Security Profiles. + +--- + +Select an object above to view detailed documentation, including methods, parameters, and examples. diff --git a/docs/sdk/index.md b/docs/sdk/index.md index 4da31533..06ad3a27 100644 --- a/docs/sdk/index.md +++ b/docs/sdk/index.md @@ -5,18 +5,24 @@ configuration objects and data models used to interact with Palo Alto Networks S ## Contents -- [Configuration Objects](configuration_objects.md) - - [Address](address.md) - - [Address Group](address_group.md) - - [Application](application.md) - - [Application Group](application_group.md) - - [Service](service.md) -- [Data Models](models.md) - - [Address Models](models/address_models.md) - - [Address Group Models](models/address_group_models.md) - - [Application Models](models/application_models.md) - - [Application Group Models](models/application_group_models.md) - - [Service Models](models/service_models.md) +- Configuration + - [Objects](config/objects/index) + - [Address](config/objects/address.md) + - [Address Group](config/objects/address_group.md) + - [Application](config/objects/application.md) + - [Application Group](config/objects/application_group.md) + - [Service](config/objects/service.md) + - [Security Services](config/security_services/index) + - [Anti-Spyware](config/security_services/anti_spyware.md) +- Data Models + - [Objects](models/objects/index) + - [Address Models](models/objects/address_models.md) + - [Address Group Models](models/objects/address_group_models.md) + - [Application Models](models/objects/application_models.md) + - [Application Group Models](models/objects/application_group_models.md) + - [Service Models](models/objects/service_models.md) + - [Security Services](models/security_services/index) + - [Anti-Spyware](models/security_services/anti_spyware_profile_models.md) --- @@ -25,5 +31,6 @@ configuration objects and data models used to interact with Palo Alto Networks S The `pan-scm-sdk` provides a set of classes and models to simplify interaction with the Strata Cloud Manager API. By utilizing this SDK, developers can programmatically manage configurations, ensuring consistency and efficiency. -Proceed to the [Configuration Objects](configuration_objects.md) section to learn more about the objects you can manage +Proceed to the [Configuration Objects](config/objects/index) section to learn more about the objects you can +manage using the SDK. diff --git a/docs/sdk/models/address_group_models.md b/docs/sdk/models/objects/address_group_models.md similarity index 100% rename from docs/sdk/models/address_group_models.md rename to docs/sdk/models/objects/address_group_models.md diff --git a/docs/sdk/models/address_models.md b/docs/sdk/models/objects/address_models.md similarity index 100% rename from docs/sdk/models/address_models.md rename to docs/sdk/models/objects/address_models.md diff --git a/docs/sdk/models/application_group_models.md b/docs/sdk/models/objects/application_group_models.md similarity index 100% rename from docs/sdk/models/application_group_models.md rename to docs/sdk/models/objects/application_group_models.md diff --git a/docs/sdk/models/application_models.md b/docs/sdk/models/objects/application_models.md similarity index 100% rename from docs/sdk/models/application_models.md rename to docs/sdk/models/objects/application_models.md diff --git a/docs/sdk/models.md b/docs/sdk/models/objects/index.md similarity index 66% rename from docs/sdk/models.md rename to docs/sdk/models/objects/index.md index 8018df2b..0c484a93 100644 --- a/docs/sdk/models.md +++ b/docs/sdk/models/objects/index.md @@ -16,8 +16,8 @@ For each configuration object, there are corresponding request and response mode ## Models by Configuration Object -- [Address Models](models/address_models.md) -- [Address Group Models](models/address_group_models.md) -- [Application Models](models/application_models.md) -- [Application Group Models](models/application_group_models.md) -- [Service Models](models/service_models.md) +- [Address Models](address_models.md) +- [Address Group Models](address_group_models.md) +- [Application Models](application_models.md) +- [Application Group Models](application_group_models.md) +- [Service Models](service_models.md) diff --git a/docs/sdk/models/service_models.md b/docs/sdk/models/objects/service_models.md similarity index 100% rename from docs/sdk/models/service_models.md rename to docs/sdk/models/objects/service_models.md diff --git a/docs/sdk/models/security_services/anti_spyware_profile_models.md b/docs/sdk/models/security_services/anti_spyware_profile_models.md new file mode 100644 index 00000000..42ded69e --- /dev/null +++ b/docs/sdk/models/security_services/anti_spyware_profile_models.md @@ -0,0 +1,138 @@ +# Anti-Spyware Profile Models + +This section covers the data models associated with the `AntiSpywareProfile` configuration object. + +--- + +## AntiSpywareProfileRequestModel + +Used when creating or updating an anti-spyware profile object. + +### Attributes + +- `name` (str): **Required.** The name of the anti-spyware profile. +- `description` (Optional[str]): A description of the anti-spyware profile. +- `cloud_inline_analysis` (Optional[bool]): Enable or disable cloud inline analysis. Defaults to False. +- `inline_exception_edl_url` (Optional[List[str]]): List of inline exception EDL URLs. +- `inline_exception_ip_address` (Optional[List[str]]): List of inline exception IP addresses. +- `mica_engine_spyware_enabled` (Optional[List[MicaEngineSpywareEnabledEntry]]): List of MICA engine spyware enabled + entries. +- **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. +- `rules` (List[RuleRequest]): **Required.** List of rules for the profile. +- `threat_exception` (Optional[List[ThreatExceptionRequest]]): List of threat exceptions for the profile. + +### Example + +```python +anti_spyware_profile_request = AntiSpywareProfileRequestModel( + name="test_profile", + description="Test anti-spyware profile", + folder="Prisma Access", + rules=[ + RuleRequest( + name="rule1", + severity=["critical", "high"], + category="spyware", + action=ActionRequest(root={"alert": {}}) + ) + ] +) +``` + +--- + +## AntiSpywareProfileResponseModel + +Used when parsing anti-spyware profile objects retrieved from the API. + +### Attributes + +- `id` (str): The UUID of the anti-spyware profile object. +- `name` (str): The name of the anti-spyware profile. +- `description` (Optional[str]): A description of the anti-spyware profile. +- `cloud_inline_analysis` (Optional[bool]): Cloud inline analysis setting. +- `inline_exception_edl_url` (Optional[List[str]]): List of inline exception EDL URLs. +- `inline_exception_ip_address` (Optional[List[str]]): List of inline exception IP addresses. +- `mica_engine_spyware_enabled` (Optional[List[MicaEngineSpywareEnabledEntry]]): List of MICA engine spyware enabled + entries. +- **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. +- `rules` (List[RuleResponse]): List of rules for the profile. +- `threat_exception` (Optional[List[ThreatExceptionResponse]]): List of threat exceptions for the profile. + +### Example + +```python +anti_spyware_profile_response = AntiSpywareProfileResponseModel( + id="123e4567-e89b-12d3-a456-426655440000", + name="test_profile", + description="Test anti-spyware profile", + folder="Prisma Access", + rules=[ + RuleResponse( + name="rule1", + severity=["critical", "high"], + category="spyware", + action=ActionResponse(root={"alert": {}}) + ) + ] +) +``` + +--- + +## Additional Models + +### MicaEngineSpywareEnabledEntry + +Represents an entry in the 'mica_engine_spyware_enabled' list. + +#### Attributes + +- `name` (str): Name of the MICA engine spyware detector. +- `inline_policy_action` (InlinePolicyAction): Action to be taken by the inline policy. + +### RuleRequest and RuleResponse + +Represents a rule in the anti-spyware profile. + +#### Attributes + +- `name` (str): Rule name. +- `severity` (List[Severity]): List of severities. +- `category` (Category): Category of the rule. +- `threat_name` (Optional[str]): Threat name. +- `packet_capture` (Optional[PacketCapture]): Packet capture setting. +- `action` (ActionRequest or ActionResponse): Action to be taken. + +### ThreatExceptionRequest and ThreatExceptionResponse + +Represents a threat exception in the anti-spyware profile. + +#### Attributes + +- `name` (str): Threat exception name. +- `packet_capture` (PacketCapture): Packet capture setting. +- `exempt_ip` (Optional[List[ExemptIpEntry]]): Exempt IP list. +- `notes` (Optional[str]): Notes. +- `action` (ActionRequest or ActionResponse): Action to be taken. + +### ActionRequest and ActionResponse + +Represents the 'action' field in rules and threat exceptions. + +#### Methods + +- `get_action_name() -> str`: Returns the name of the action. + +### Enums + +- `InlinePolicyAction`: Enumeration of allowed inline policy actions. +- `PacketCapture`: Enumeration of packet capture options. +- `Severity`: Enumeration of severity levels. +- `Category`: Enumeration of threat categories. diff --git a/docs/sdk/models/security_services/index.md b/docs/sdk/models/security_services/index.md new file mode 100644 index 00000000..7225810e --- /dev/null +++ b/docs/sdk/models/security_services/index.md @@ -0,0 +1,19 @@ +# Data Models + +The `pan-scm-sdk` utilizes Pydantic models for data validation and serialization. This ensures that the data being sent +to and received from the Strata Cloud Manager API adheres to the expected structure and constraints. + +--- + +## Overview + +For each configuration object, there are corresponding request and response models: + +- **Request Models**: Used when creating or updating resources. +- **Response Models**: Used when parsing data retrieved from the API. + +--- + +## Models by Configuration Object + +- [Anti Spyware Security Profile Models](anti_spyware_profile_models.md) diff --git a/mkdocs.yml b/mkdocs.yml index a270cdb2..7ec0b1c7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,19 +26,28 @@ nav: - Getting Started: about/getting-started.md - SDK Developer Documentation: - Overview: sdk/index.md - - Configuration Objects: - - Address: sdk/address.md - - Address Group: sdk/address_group.md - - Application: sdk/application.md - - Application Group: sdk/application_group.md - - Service: sdk/service.md + - Configuration: + - Objects: + - Overview: sdk/config/objects/index.md + - Address: sdk/config/objects/address.md + - Address Group: sdk/config/objects/address_group.md + - Application: sdk/config/objects/application.md + - Application Group: sdk/config/objects/application_group.md + - Service: sdk/config/objects/service.md + - Security Services: + - Overview: sdk/config/security_services/index.md + - Address: sdk/config/security_services/anti_spyware.md - Data Models: - - Overview: sdk/models.md - - Address Models: sdk/models/address_models.md - - Address Group Models: sdk/models/address_group_models.md - - Application Models: sdk/models/application_models.md - - Application Group Models: sdk/models/application_group_models.md - - Service Models: sdk/models/service_models.md + - Objects: + - Overview: sdk/models/objects/index.md + - Address Models: sdk/models/objects/address_models.md + - Address Group Models: sdk/models/objects/address_group_models.md + - Application Models: sdk/models/objects/application_models.md + - Application Group Models: sdk/models/objects/application_group_models.md + - Service Models: sdk/models/objects/service_models.md + - Security Services: + - Overview: sdk/models/security_services/index.md + - Address: sdk/models/security_services/anti_spyware.md - Authentication Module: sdk/auth.md - SCM Client: sdk/client.md - Troubleshooting: about/troubleshooting.md From 56106940a078c954865f823b8808c277ea8bbcc0 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 20:43:37 -0500 Subject: [PATCH 09/11] Simplify Mermaid class diagram and update SDK documentation Trimmed down class definitions in the Mermaid diagram for clarity and enhanced SDK documentation to include information about data model structures in Python dictionaries. --- docs/mermaid/sdk.mmd | 488 +++++++++++++++---------------------------- docs/sdk/index.md | 6 +- 2 files changed, 168 insertions(+), 326 deletions(-) diff --git a/docs/mermaid/sdk.mmd b/docs/mermaid/sdk.mmd index dbd4efb0..63c4fee6 100644 --- a/docs/mermaid/sdk.mmd +++ b/docs/mermaid/sdk.mmd @@ -1,330 +1,170 @@ classDiagram direction BT - class BaseException { - args - __cause__ - __context__ - __suppress_context__ - __traceback__ - __init__(self, *args: object) - __setstate__(self, __state: dict[str, Any] | None) - with_traceback(self, __tb: TracebackType | None) - } + class BaseException class Exception - class object { - __doc__ - __dict__ - __module__ - __annotations__ - __class__(self) - __class__(self, __type: type[object]) - __init__(self) - __new__(cls) - __setattr__(self, __name: str, __value: Any) - __delattr__(self, __name: str) - __eq__(self, __value: object) - __ne__(self, __value: object) - __str__(self) - __repr__(self) - __hash__(self) - __format__(self, __format_spec: str) - __getattribute__(self, __name: str) - __sizeof__(self) - __reduce__(self) - __reduce_ex__(self, __protocol: SupportsIndex) - __dir__(self) - __init_subclass__(cls) - __subclasshook__(cls, __subclass: type) - } - class node3 { - auth_request - signing_key - session - __init__(self, auth_request: AuthRequest) - _create_session(self) - _get_signing_key(self) - decode_token(self) - is_expired(self) - refresh_token(self) - } - class node6 { - session - api_base_url - oauth_client - __init__( - self, - client_id: str, - client_secret: str, - tsg_id: str, - api_base_url: str = "https://api.strata.paloaltonetworks.com", - ) - request(self, method: str, endpoint: str, **kwargs) - get(self, endpoint: str, **kwargs) - post(self, endpoint: str, **kwargs) - put(self, endpoint: str, **kwargs) - delete(self, endpoint: str, **kwargs) - } - class node1 { - client_id - client_secret - tsg_id - scope - token_url - construct_scope(cls, values) - } - class node5 { - parameters_str - combined_parameters - parent_namespace - __pydantic_generic_metadata__ - config_wrapper - original_model_post_init - parent_parameters - BaseModel - types_namespace - class_vars - error_message - __pydantic_decorators__ - model_computed_fields - base_private_attributes - private_attributes - base_field_names - cls - mro - generic_type_label - __pydantic_complete__ - __pydantic_post_init__ - missing_parameters - bases_str - __pydantic_custom_init__ - parameters - __pydantic_parent_namespace__ - __new__( - mcs, - cls_name: str, - bases: tuple[type[Any], ...], - namespace: dict[str, Any], - __pydantic_generic_metadata__: PydanticGenericMetadata | None = None, - __pydantic_reset_parent_namespace__: bool = True, - _create_model_module: str | None = None, - **kwargs: Any, - ) - __getattr__(self, item: str) - __prepare__(cls, *args: Any, **kwargs: Any) - __instancecheck__(self, instance: Any) - _collect_bases_data(bases: tuple[type[Any], ...]) - __fields__(self) - __dir__(self) - } - class node4 { - __pydantic_parent_namespace__ - model_config - model_fields - model_computed_fields - __class_vars__ - __private_attributes__ - __signature__ - __pydantic_complete__ - __pydantic_core_schema__ - __pydantic_custom_init__ - __pydantic_decorators__ - __pydantic_generic_metadata__ - __pydantic_parent_namespace__ - __pydantic_post_init__ - __pydantic_root_model__ - __pydantic_serializer__ - __pydantic_validator__ - __pydantic_extra__ - __pydantic_fields_set__ - __pydantic_private__ - __pydantic_core_schema__ - __pydantic_validator__ - __pydantic_serializer__ - __slots__ - __pydantic_base_init__ - __repr_name__ - __repr_str__ - __pretty__ - __rich_repr__ - __init__(self, /, **data: Any) - model_extra(self) - model_fields_set(self) - model_construct(cls, _fields_set: set[str] | None = None, **values: Any) - model_copy(self, *, update: dict[str, Any] | None = None, deep: bool = False) - model_dump( - self, - *, - mode: Literal['json', 'python'] | str = 'python', - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - serialize_as_any: bool = False, - ) - model_dump_json( - self, - *, - indent: int | None = None, - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - serialize_as_any: bool = False, - ) - model_json_schema( - cls, - by_alias: bool = True, - ref_template: str = DEFAULT_REF_TEMPLATE, - schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, - mode: JsonSchemaMode = 'validation', - ) - model_parametrized_name(cls, params: tuple[type[Any], ...]) - model_post_init(self, __context: Any) - model_rebuild( - cls, - *, - force: bool = False, - raise_errors: bool = True, - _parent_namespace_depth: int = 2, - _types_namespace: dict[str, Any] | None = None, - ) - model_validate( - cls, - obj: Any, - *, - strict: bool | None = None, - from_attributes: bool | None = None, - context: Any | None = None, - ) - model_validate_json( - cls, - json_data: str | bytes | bytearray, - *, - strict: bool | None = None, - context: Any | None = None, - ) - model_validate_strings( - cls, - obj: Any, - *, - strict: bool | None = None, - context: Any | None = None, - ) - __get_pydantic_core_schema__(cls, source: type[BaseModel], handler: GetCoreSchemaHandler, /) - __get_pydantic_json_schema__( - cls, - core_schema: CoreSchema, - handler: GetJsonSchemaHandler, - /, - ) - __pydantic_init_subclass__(cls, **kwargs: Any) - __class_getitem__( - cls, typevar_values: type[Any] | tuple[type[Any], ...] - ) - __copy__(self) - __deepcopy__(self, memo: dict[int, Any] | None = None) - __getattr__(self, item: str) - __setattr__(self, name: str, value: Any) - __delattr__(self, item: str) - _check_frozen(self, name: str, value: Any) - __getstate__(self) - __setstate__(self, state: dict[Any, Any]) - __eq__(self, other: Any) - __init_subclass__(cls, **kwargs: Unpack[ConfigDict]) - __iter__(self) - __repr__(self) - __repr_args__(self) - __str__(self) - __fields__(self) - __fields_set__(self) - dict(# noqa: D102 - self, - *, - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) - json(# noqa: D102 - self, - *, - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - encoder: Callable[[Any], Any] | None = PydanticUndefined, # type: ignore[assignment] - models_as_dict: bool = PydanticUndefined, # type: ignore[assignment] - **dumps_kwargs: Any, - ) - parse_obj(cls, obj: Any) - parse_raw(# noqa: D102 - cls, - b: str | bytes, - *, - content_type: str | None = None, - encoding: str = 'utf8', - proto: DeprecatedParseProtocol | None = None, - allow_pickle: bool = False, - ) - parse_file(# noqa: D102 - cls, - path: str | Path, - *, - content_type: str | None = None, - encoding: str = 'utf8', - proto: DeprecatedParseProtocol | None = None, - allow_pickle: bool = False, - ) - from_orm(cls, obj: Any) - construct(cls, _fields_set: set[str] | None = None, **values: Any) - copy( - self, - *, - include: AbstractSetIntStr | MappingIntStrAny | None = None, - exclude: AbstractSetIntStr | MappingIntStrAny | None = None, - update: Dict[str, Any] | None = None, # noqa UP006 - deep: bool = False, - ) - schema(# noqa: D102 - cls, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE - ) - schema_json(# noqa: D102 - cls, *, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, **dumps_kwargs: Any - ) - validate(cls, value: Any) - update_forward_refs(cls, **localns: Any) - _iter(self, *args: Any, **kwargs: Any) - _copy_and_set_values(self, *args: Any, **kwargs: Any) - _get_value(cls, *args: Any, **kwargs: Any) - _calculate_keys(self, *args: Any, **kwargs: Any) - } - class node2 { - __hash__(self) - } - class node7 { - __iter__(self) - } + class node68 + class node29 + class object + class node36 + class node21 + class node44 + class node75 + class node78 + class node35 + class node6 + class node69 + class node67 + class node62 + class node70 + class node32 + class node13 + class node28 + class node79 + class node5 + class node25 + class node45 + class node66 + class node46 + class node10 + class node59 + class node14 + class node60 + class node9 + class node73 + class node34 + class node41 + class node56 + class node20 + class node47 + class node33 + class node54 + class node49 + class node52 + class node51 + class node42 + class node57 + class node63 + class node8 + class node30 + class node2 + class node1 + class node22 + class node50 + class node55 + class node11 + class node15 + class node26 + class node64 + class node72 + class node4 + class node65 + class node27 + class node18 + class node77 + class node76 + class node39 + class node40 + class node37 + class node71 + class node24 + class node12 + class node23 + class node53 + class node38 + class node74 + class str + class node31 + class node58 + class node61 + class node7 + class node43 + class node16 + class node3 object --> BaseException BaseException --> Exception - node2 ..> object - object --> node3 - object --> node6 - node4 --> node1 - object --> node4 - node5 "isinstanceof" ..> node4 - node7 ..> node4 + node29 "isinstanceof" ..> node68 + object --> node68 + node61 ..> node68 + node7 ..> node29 + node43 ..> node29 + node3 ..> node29 + node61 ..> object + object --> node21 + node36 "isinstanceof" ..> node21 + node7 ..> node21 + node21 --> node44 + object --> node75 + object --> node78 + object --> node35 + node35 --> node6 + node35 --> node69 + node35 --> node67 + node35 --> node62 + node35 --> node70 + node35 --> node32 + Exception --> node13 + node59 --> node28 + node13 --> node79 + node13 --> node5 + node13 --> node25 + node13 --> node45 + node25 --> node66 + node25 --> node46 + node25 --> node10 + node13 --> node59 + node25 --> node14 + node13 --> node60 + node45 --> node9 + node25 --> node73 + node45 --> node34 + node13 --> node41 + node79 --> node56 + node13 --> node20 + node13 --> node47 + node13 --> node33 + node21 --> node54 + node21 --> node49 + node21 --> node52 + node21 --> node51 + node21 --> node42 + node21 --> node57 + node21 --> node63 + node21 --> node8 + node21 --> node30 + node21 --> node2 + node21 --> node1 + node21 --> node22 + node21 --> node50 + node21 --> node55 + node21 --> node11 + node21 --> node15 + node44 --> node26 + node44 --> node64 + node21 --> node72 + node72 --> node4 + node72 --> node65 + node21 --> node27 + node21 --> node18 + node68 --> node77 + str --> node77 + node21 --> node76 + node68 --> node39 + str --> node39 + node21 --> node40 + node68 --> node37 + str --> node37 + node21 --> node71 + node71 --> node24 + node71 --> node12 + node68 --> node23 + str --> node23 + node21 --> node53 + node53 --> node38 + node53 --> node74 + node61 ..> str + node43 ..> str + node3 --> str + node58 --> node31 + node7 --> node31 + node7 --> node16 diff --git a/docs/sdk/index.md b/docs/sdk/index.md index 06ad3a27..a9914b3b 100644 --- a/docs/sdk/index.md +++ b/docs/sdk/index.md @@ -32,5 +32,7 @@ The `pan-scm-sdk` provides a set of classes and models to simplify interaction w utilizing this SDK, developers can programmatically manage configurations, ensuring consistency and efficiency. Proceed to the [Configuration Objects](config/objects/index) section to learn more about the objects you can -manage -using the SDK. +manage using the SDK. + +Proceed to the [Data Models](config/objects/index) section to learn more about how the Python dictionaries that are +passed into the SDK are structured. From 3d166d3116913b00df908618c1dda20e2f53559a Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 20:44:44 -0500 Subject: [PATCH 10/11] Update version to 0.1.8 and add release notes Bump the version in pyproject.toml to 0.1.8. Add release notes for version 0.1.8, including the addition of pytests to support Anti Spyware Profiles. --- docs/about/release-notes.md | 10 ++++++++++ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index e8ccdd56..c9050b02 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.8 + +**Release Date:** October 16, 2024 + +### Anti Spyware Profiles + +- **Pytests**: Add the pytests to support Anti Spyware Profiles. + +--- + ## Version 0.1.7 **Release Date:** October 16, 2024 diff --git a/pyproject.toml b/pyproject.toml index fb7c23ad..690aa31b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-scm-sdk" -version = "0.1.7" +version = "0.1.8" description = "Python SDK for Palo Alto Networks Strata Cloud Manager." authors = ["Calvin Remsburg "] license = "Apache 2.0" From 744d0a46ffcb3e854709e4db4e65f44bffef3858 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Wed, 16 Oct 2024 20:50:42 -0500 Subject: [PATCH 11/11] Rename security service file titles to improve clarity Updated the titles for Anti Spyware Security Profile in both configuration and model sections. This change enhances readability and provides a clearer understanding of the documentation structure. --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 7ec0b1c7..88bc5e66 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -36,7 +36,7 @@ nav: - Service: sdk/config/objects/service.md - Security Services: - Overview: sdk/config/security_services/index.md - - Address: sdk/config/security_services/anti_spyware.md + - Anti Spyware Security Profile: sdk/config/security_services/anti_spyware.md - Data Models: - Objects: - Overview: sdk/models/objects/index.md @@ -47,7 +47,7 @@ nav: - Service Models: sdk/models/objects/service_models.md - Security Services: - Overview: sdk/models/security_services/index.md - - Address: sdk/models/security_services/anti_spyware.md + - 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