diff --git a/packages/tipcommon/TIPCommon/pyproject.toml b/packages/tipcommon/TIPCommon/pyproject.toml index 0073b519d..a08fc1adb 100644 --- a/packages/tipcommon/TIPCommon/pyproject.toml +++ b/packages/tipcommon/TIPCommon/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "TIPCommon" -version = "2.3.3" +version = "2.3.6" description = "General Purpose CLI tool for Google SecOps Marketplace" readme = "README.md" authors = [ diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/data_models.py b/packages/tipcommon/TIPCommon/src/TIPCommon/data_models.py index c63dec297..91e8f254d 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/data_models.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/data_models.py @@ -1173,14 +1173,18 @@ def __init__( instance: SingleJson, identifier: str, integration_identifier: str, + instance_description: str, environment_identifier: str, instance_name: str, + is_configured: bool ) -> None: self.instance = instance self.identifier = identifier self.integration_identifier = integration_identifier + self.instance_description = instance_description self.environment_identifier = environment_identifier self.instance_name = instance_name + self.is_configured = is_configured @classmethod def from_json( @@ -1200,8 +1204,9 @@ def from_json( """ return cls( instance=integration_env_json, - identifier=integration_env_json["identifier"], - integration_identifier=integration_env_json["integrationIdentifier"], + identifier=integration_env_json.get("identifier", ""), + integration_identifier=integration_env_json.get("integrationIdentifier", ""), + instance_description=integration_env_json.get("instanceDescription", ""), environment_identifier=( integration_env_json.get("environmentIdentifier") or integration_env_json.get("environment") @@ -1209,6 +1214,10 @@ def from_json( instance_name=( integration_env_json.get("instanceName") or integration_env_json.get("displayName") ), + is_configured=integration_env_json.get( + "isConfigured", + integration_env_json.get("configured", False) + ), ) def to_json(self) -> SingleJson: @@ -1363,10 +1372,13 @@ class DynamicParameter: @classmethod def from_json(cls, json_data: SingleJson) -> DynamicParameter: return cls( - key=json_data["key"], - value=json_data["value"], + key=json_data.get("key", json_data.get("dynamicParameterId", "")), + value=json_data.get("value", ""), ) + def to_json(self) -> SingleJson: + return {"key": self.key, "value": self.value} + @dataclasses.dataclass(slots=True) class EnvironmentData: @@ -2015,6 +2027,912 @@ def to_json(self) -> SingleJson: @dataclasses.dataclass(slots=True) +class Environment: + name: str | None + display_name: str | None + description: str | None + contact_name: str | None + contact_emails: str | None + contact_phone: str | None + remediation_duration_in_days: int | None + allow_remediation_actions: bool | None + notify_on_remediation_actions: bool | None + retention_duration_in_months: int | None + retention_duration_internal: int | None + base64_image: str | None + for_db_migration: bool | None + environment_allowed_for_all_users: bool | None + dynamic_parameters: list[SingleJson] + properties: SingleJson + is_system: bool | None + aliases: list[str] + instance_url: str | None + data_access_scopes: list[Any] + identifier: int | None + weight: int | None + + @classmethod + def from_json(cls, data: dict) -> "Environment": + aliases = data.get("aliases") + if aliases is None: + try: + aliases = json.loads(data.get("aliasesJson") or "[]") + except json.JSONDecodeError: + aliases = [] + + scopes = data.get("dataAccessScopes") + if scopes is None: + try: + scopes = json.loads(data.get("dataAccessScopesJson") or "[]") + except Exception: + scopes = [] + + return cls( + name=data.get("name") or data.get("displayName"), + display_name=data.get("displayName"), + description=data.get("description"), + contact_name=data.get("contactName", data.get("contact")), + contact_emails=data.get("contactEmails"), + contact_phone=data.get("contactPhone"), + remediation_duration_in_days=data.get("remediationDurationInDays", 0), + allow_remediation_actions=data.get("shouldAllowRemediationActions", 0), + notify_on_remediation_actions=data.get( + "shouldNotifyOnRemediationActions", + 0 + ), + retention_duration_in_months=data.get( + "retentionDurationInMonths", + data.get("retentionDuration", 0), + ), + retention_duration_internal=data.get("retentionDurationInMonthsInternal"), + base64_image=data.get("base64Image"), + for_db_migration=data.get("forDBMigration", 0), + environment_allowed_for_all_users=data.get( + "environmentAllowedForAllUsers", + 0 + ), + dynamic_parameters=list(data.get("dynamicParameters") or []), + properties=data.get("properties") or {}, + is_system=data.get("isSystem", data.get("system")), + aliases=aliases, + instance_url=data.get("instanceUrl", data.get("instanceUri")), + data_access_scopes=scopes, + identifier=data.get("id"), + weight=data.get("weight"), + ) + + def to_legacy(self) -> dict: + return { + "id": self.identifier, + "name": self.display_name or self.name, + "description": self.description, + "contactName": self.contact_name, + "contactEmails": self.contact_emails, + "contactEmailsList": None, + "contactPhone": self.contact_phone, + "allowedActions": None, + "remediationDurationInDays": self.remediation_duration_in_days, + "shouldAllowRemediationActions": self.allow_remediation_actions, + "shouldNotifyOnRemediationActions": self.notify_on_remediation_actions, + "retentionDurationInMonths": self.retention_duration_in_months, + "retentionDurationInMonthsInternal": self.retention_duration_internal, + "base64Image": self.base64_image, + "forDBMigration": self.for_db_migration, + "environmentAllowedForAllUsers": self.environment_allowed_for_all_users, + "dynamicParameters": self.dynamic_parameters, + "properties": self.properties, + "isSystem": self.is_system, + "aliases": self.aliases, + "instanceUrl": self.instance_url, + "dataAccessScopes": self.data_access_scopes, + } + + def to_1p(self) -> dict: + weight_value = ( + self.weight + if isinstance(self.weight, int) and 1 <= self.weight <= 10 + else 1 + ) + + if not self.name: + raise ValueError("1P environment name is required") + + return { + "name": self.name, + "id": self.identifier, + "system": bool(self.is_system), + "displayName": self.display_name or self.name, + "description": self.description or "", + "contact": self.contact_name or "N/A", + "contactEmails": self.contact_emails or "example@example.com", + "contactPhone": self.contact_phone or "0000000000", + "retentionDuration": int(self.retention_duration_in_months or 0), + "dynamicParameters": self.dynamic_parameters or [], + "aliasesJson": json.dumps(self.aliases or []), + "dataAccessScopesJson": json.dumps(self.data_access_scopes or []), + "instanceUri": self.instance_url or "", + "weight": weight_value, + } + + +@dataclasses.dataclass(slots=True) +class IntegrationSetting: + """Represents a single setting from an integration instance.""" + display_name: str + property_name: str + value: Any + property_type: str + _id: int + property_description: str + is_password: bool + is_mandatory: bool + + @classmethod + def from_json(cls, data: SingleJson) -> IntegrationSetting: + """Parses a setting JSON object.""" + is_password = data.get("propertyType") == 3 or data.get("type") == "Password" + + return cls( + display_name=data.get("displayName", data.get("propertyDisplayName", "")), + property_name=data.get("propertyName", ""), + value=data.get("value"), + property_type=data.get("type", data.get("propertyType", "")), + _id=data.get("id"), + property_description=data.get( + "propertyDescription", + data.get("description", ""), + ), + is_password=is_password, + is_mandatory=data.get("mandatory", data.get("isMandatory", False)), + ) + + + +@dataclasses.dataclass(slots=True) +class VisualFamily: + _id: Any + family: str + description: str = "" + image_base64: str = "" + is_custom: bool = False + rules: list[dict] = dataclasses.field(default_factory=list) + creation_time: int = 0 + modification_time: int = 0 + + @classmethod + def from_json(cls, data: SingleJson) -> VisualFamily: + return cls( + _id=data.get("id"), + family=data.get("family"), + description=data.get("description", ""), + image_base64=data.get("imageBase64", ""), + is_custom=data.get("custom", data.get("isCustom", False)), + rules=data.get("modelingRules", data.get("rules", [])), + creation_time=data.get("creationTimeUnixTimeInMs", 0), + modification_time=data.get("modificationTimeUnixTimeInMs", 0), + ) + + def to_json(self) -> SingleJson: + return { + "id": self._id, + "family": self.family, + "description": self.description, + "imageBase64": self.image_base64, + "isCustom": self.is_custom, + "rules": self.rules, + "creationTimeUnixTimeInMs": self.creation_time, + "modificationTimeUnixTimeInMs": self.modification_time, + } + + +@dataclasses.dataclass(slots=True) +class OntologyRecord: + _id: Any + source: str + family_name: str = "" + family_id: int | None = None + product: str | None = None + event_name: str | None = None + mapping_status: str | None = None + change_source: Any = None + example_event_fields: list[dict] = dataclasses.field(default_factory=list) + + @classmethod + def from_json(cls, data: SingleJson) -> OntologyRecord: + return cls( + _id=data.get("id"), + source=data.get("source"), + family_name=data.get("visualFamily", data.get("familyName", "")), + family_id=data.get("familyId"), + product=data.get("product"), + event_name=data.get("eventName"), + mapping_status=data.get("mappingStatus"), + change_source=data.get("changeSource"), + example_event_fields=data.get("exampleEventFields", []) + ) + + + def to_json(self) -> SingleJson: + return { + "id": self._id, + "source": self.source, + "familyName": self.family_name, + "familyId": self.family_id, + "product": self.product, + "eventName": self.event_name, + "mappingStatus": self.mapping_status, + "changeSource": self.change_source, + "exampleEventFields": self.example_event_fields, + } + +@dataclasses.dataclass(slots=True) +class CaseTag: + """Represents a single case tag.""" + value: str + name: str + property_name: str + priority: int + is_potential_case_name: bool + environments: list[Any] + kind: int + compare_type: int + identifier: int + creation_time_unix_time_in_ms: int + modification_time_unix_time_in_ms: int + + @classmethod + def from_json(cls, data: SingleJson) -> "CaseTag": + """Parses a case tag JSON object and guarantees non-empty value.""" + + name = data.get("displayName") or data.get("name") or "" + + value = data.get("value") + if not value: + value = name + + return cls( + value=value, + name=name, + property_name=data.get("propertyName", ""), + priority=int(data.get("priority", 0)), + is_potential_case_name=data.get( + "isPotentialCaseName", + data.get("canBeCaseTitle", False), + ), + environments=data.get("environments", []), + kind=data.get("type", data.get("matchCriteria")), + compare_type=data.get( + "compareType", + data.get("comparisonType"), + ), + identifier=data.get("id"), + creation_time_unix_time_in_ms=data.get( + "creationTimeUnixTimeInMs" + ), + modification_time_unix_time_in_ms=data.get( + "modificationTimeUnixTimeInMs" + ), + ) + + def to_json(self) -> SingleJson: + """Converts the CaseTag object to a JSON-serializable dictionary.""" + return { + "value": self.value, + "name": self.name, + "propertyName": self.property_name, + "priority": self.priority, + "isPotentialCaseName": self.is_potential_case_name, + "environments": self.environments, + "type": self.kind, + "compareType": self.compare_type, + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_json_1p(self) -> SingleJson: + return { + "displayName": self.name, + "matchCriteria": self.kind, + "value": self.value, + "propertyName": self.property_name, + "comparisonType": self.compare_type, + "priority": self.priority, + "canBeCaseTitle": self.is_potential_case_name, + } + + +@dataclasses.dataclass(slots=True) +class Domain: + domain: str + alias: str | None + environments: list[Any] + identifier: int + creation_time_unix_time_in_ms: int + modification_time_unix_time_in_ms: int + + @classmethod + def from_legacy_or_1p(cls, data: SingleJson) -> "Domain": + if "environments" in data: + envs = data.get("environments") or [] + else: + raw = data.get("environmentsJson", "[]") or "[]" + try: + envs = json.loads(raw) + except Exception: + envs = [] + + return cls( + domain=data.get("displayName") or data.get("domain") or "", + alias=data.get("alias"), + environments=envs, + identifier=data.get("id"), + creation_time_unix_time_in_ms=data.get("creationTimeUnixTimeInMs", 0), + modification_time_unix_time_in_ms=data.get("modificationTimeUnixTimeInMs", 0), + ) + + def to_legacy(self) -> SingleJson: + return { + "domain": self.domain, + "alias": self.alias, + "environments": self.environments, + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_1p(self) -> SingleJson: + return { + "displayName": self.domain, + "environmentsJson": self.environments, + } + + +@dataclasses.dataclass(slots=True) +class CaseStage: + name: str + order: int + identifier: int + creation_time_unix_time_in_ms: int + modification_time_unix_time_in_ms: int + + @classmethod + def from_legacy_or_1p(cls, data: SingleJson) -> "CaseStage": + return cls( + name=data.get("displayName") or data.get("name") or "", + order=data.get("order", 0), + identifier=data.get("id"), + creation_time_unix_time_in_ms=data.get("creationTimeUnixTimeInMs"), + modification_time_unix_time_in_ms=data.get("modificationTimeUnixTimeInMs"), + ) + + def to_legacy(self) -> SingleJson: + return { + "name": self.name, + "order": self.order, + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_1p(self) -> SingleJson: + return { + "displayName": self.name, + "order": self.order, + } + +@dataclasses.dataclass(slots=True) +class CaseCloseReasons: + identifier: int + root_cause: str + close_reason: str | None + creation_time_unix_time_in_ms: int | None + modification_time_unix_time_in_ms: int | None + + @classmethod + def from_legacy_or_1p(cls, data: dict) -> "CaseCloseReasons": + return cls( + identifier=data.get("id"), + root_cause=data.get("rootCause", ""), + close_reason=data.get("forCloseReason") or data.get("closeReason"), + creation_time_unix_time_in_ms=data.get("creationTimeUnixTimeInMs"), + modification_time_unix_time_in_ms=data.get("modificationTimeUnixTimeInMs"), + ) + + def to_legacy(self) -> dict: + return { + "rootCause": self.root_cause, + "forCloseReason": self.close_reason, + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_1p(self) -> dict: + return { + "id": self.identifier, + "rootCause": self.root_cause, + "closeReason": self.close_reason, + } + +@dataclasses.dataclass(slots=True) +class CustomList: + entity_identifier: str + category: str + for_db_migration: bool | None + environments: list[str] + identifier: int + creation_time_unix_time_in_ms: int + modification_time_unix_time_in_ms: int + + @classmethod + def from_legacy_or_1p(cls, data): + envs = data.get("environments") + if envs is None: + envs_json = data.get("environmentsJson") + if isinstance(envs_json, str): + try: + envs = json.loads(envs_json) + except Exception: + envs = [] + else: + envs = [] + + return cls( + entity_identifier=data.get("entityIdentifier", ""), + category=data.get("category", ""), + for_db_migration=data.get("forDBMigration"), + environments=envs, + identifier=data.get("id"), + creation_time_unix_time_in_ms=data.get("creationTimeUnixTimeInMs"), + modification_time_unix_time_in_ms=data.get("modificationTimeUnixTimeInMs"), + ) + + def to_legacy(self): + return { + "entityIdentifier": self.entity_identifier, + "category": self.category, + "forDBMigration": self.for_db_migration, + "environments": self.environments, # list + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_1p(self): + return { + "category": self.category, + "entityIdentifier": self.entity_identifier, + "environments": self.environments, + } + + +@dataclasses.dataclass(slots=True) +class Blacklist: + entity_identifier: str + entity_type: str + action: int + environments: list[Any] + scope: int | None + identifier: int + + @classmethod + def from_legacy_or_1p(cls, data: SingleJson) -> "Blacklist": + return cls( + entity_identifier=data.get("entityIdentifier", ""), + entity_type=data.get("entityType", ""), + action=data.get("elementType", data.get("action", 0)), + environments=data.get("environments", []), + scope=data.get("scope"), + identifier=data.get("id"), + ) + + def to_legacy(self) -> SingleJson: + return { + "id": self.identifier, + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "elementType": self.action, + "scope": self.scope, + "environments": self.environments, + } + + def to_1p(self) -> SingleJson: + return { + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "action": self.action, + "environmentsJson": ( + ",".join(map(str, self.environments)) if self.environments else "" + ) + } + + +@dataclasses.dataclass(slots=True) +class Network: + name: str + address: str + priority: int + environments: list[Any] + identifier: int + creation_time_unix_time_in_ms: int | None + modification_time_unix_time_in_ms: int | None + + @classmethod + def from_legacy_or_1p(cls, data: dict) -> "Network": + # environments can come from legacy (list) or 1p (string → JSON list) + envs = data.get("environments") + if envs is None: + envs_json = data.get("environmentsJson") + if isinstance(envs_json, str): + try: + envs = json.loads(envs_json) + except Exception: + envs = [] + else: + envs = [] + + return cls( + name=data.get("displayName") or data.get("name") or "", + address=data.get("address", ""), + priority=data.get("priority", 0), + environments=envs, + identifier=data.get("id"), + creation_time_unix_time_in_ms=data.get("creationTimeUnixTimeInMs"), + modification_time_unix_time_in_ms=data.get("modificationTimeUnixTimeInMs"), + ) + + def to_legacy(self) -> dict: + return { + "address": self.address, + "name": self.name, + "priority": self.priority, + "environments": self.environments, + "id": self.identifier, + "creationTimeUnixTimeInMs": self.creation_time_unix_time_in_ms, + "modificationTimeUnixTimeInMs": self.modification_time_unix_time_in_ms, + } + + def to_1p(self) -> dict: + return { + "name": ( + "projects/project/locations/location/instances/" + f"instance/internalNetworks/{self.identifier}" + ), + "id": self.identifier, + "displayName": self.name, + "address": self.address, + "environmentsJson": json.dumps(self.environments), + "priority": self.priority, + } + + +@dataclasses.dataclass(slots=True) +class SlaDefinition: + identifier: int | None + sla_type: int + alert_type: int + sla_type_value: str + sla_period: float + sla_period_time_unit: int + critical_sla_period: float + critical_sla_period_time_unit: int + environments: list[Any] + values: list[str] + + @classmethod + def from_legacy_or_1p(cls, data: dict) -> "SlaDefinition": + SLA_TYPE_MAP = { + 0: "AlertRuleGenerator", + 1: "AlertType", + 2: "CaseStage", + 3: "CasePriority", + 4: "AlertPriority", + } + SLA_TYPE_REVERSE_MAP = {v.lower(): k for k, v in SLA_TYPE_MAP.items()} + + def safe_int(value: Any, default: int = 0) -> int: + try: + if value is None: + return default + if isinstance(value, str): + value = value.strip() + if not value.isdigit(): + return default + return int(value) + except Exception: + return default + + def normalize_sla_type(raw: Any) -> int: + """ + Normalize slaType / valueType to numeric enum. + Supports: + - int: 1,2,3,4 + - numeric string: "1" + - enum string: "CaseStage" + """ + if raw is None: + return 0 + + # Fast path → numeric + numeric = safe_int(raw, None) + if numeric in SLA_TYPE_MAP: + return numeric + + # Enum string → mapped int + if isinstance(raw, str): + return SLA_TYPE_REVERSE_MAP.get(raw.strip().lower(), 0) + + return 0 + + alert_type = safe_int(data.get("alertType"), 0) + + raw_value = data.get("value", data.get("slaTypeValue", "")) + if isinstance(raw_value, str): + raw_value = raw_value.strip() + + values_list = list(data.get("values") or []) + + if raw_value: + normalized_value = raw_value + elif values_list: + cleaned = [v for v in values_list if isinstance(v, str) and v.strip()] + normalized_value = json.dumps(cleaned) if cleaned else "ALL" + else: + normalized_value = "ALL" + + sla_type_raw = data.get("valueType", data.get("slaType")) + + return cls( + identifier=data.get("id"), + sla_type=normalize_sla_type(sla_type_raw), + alert_type=alert_type, + sla_type_value=normalized_value, + sla_period=float(data.get("slaPeriod", 0.0) or 0.0), + sla_period_time_unit=safe_int( + data.get("slaPeriodType", data.get("slaPeriodTimeUnit")), 0 + ), + critical_sla_period=float( + data.get("criticalPeriod", data.get("criticalSlaPeriod", 0.0)) or 0.0 + ), + critical_sla_period_time_unit=safe_int( + data.get( + "criticalPeriodType", + data.get("criticalSlaPeriodTimeUnit"), + ), + 0, + ), + environments=list(data.get("environments") or []), + values=list(data.get("values") or []), + ) + + def to_legacy(self) -> dict: + return { + "id": self.identifier, + "valueType": self.sla_type, + "value": self.sla_type_value, + "slaPeriodType": self.sla_period_time_unit, + "slaPeriod": self.sla_period, + "criticalPeriodType": self.critical_sla_period_time_unit, + "criticalPeriod": self.critical_sla_period, + "environments": self.environments, + "alertType": self.alert_type, + "values": self.values, + } + + def to_1p(self) -> dict: + sla_type_map = { + 0: "AlertRuleGenerator", + 1: "AlertType", + 2: "CaseStage", + 3: "CasePriority", + 4: "AlertPriority", + } + + return { + "name": ( + "projects/project/locations/location/instances/instance/" + f"slaDefinitions/{self.identifier}" + ), + "id": self.identifier, + "slaType": sla_type_map.get(self.sla_type, self.sla_type), + "alertType": self.alert_type, + "slaTypeValue": self.sla_type_value, + "slaPeriod": self.sla_period, + "slaPeriodTimeUnit": self.sla_period_time_unit, + "criticalSlaPeriod": self.critical_sla_period, + "criticalSlaPeriodTimeUnit": self.critical_sla_period_time_unit, + "environments": self.environments, + } + + +@dataclasses.dataclass(slots=True) +class SoarBlockEntity: + """Represents a single soar block entity""" + entity_identifier: str + entity_type: str + action: str + environments: list[Any] + scope: int | None + identifier: int + + @classmethod + def from_legacy_or_1p(cls, data: dict) -> "SoarBlockEntity": + """Creates a SoarBlockEntity object from a JSON dictionary. + + Args: + data (dict): A dictionary containing the soar block entity data. + Returns: + A SoarBlockEntity object. + """ + envs = data.get("environments") + if envs is None: + envs_json = data.get("environmentsJson") + if isinstance(envs_json, str): + try: + envs = json.loads(envs_json) + except Exception: + envs = [] + else: + envs = [] + + action_value = data.get("action") + if action_value is None: + element_type = data.get("elementType", 0) + action_value = "DoNotGroupAlerts" if element_type == 0 else "Unknown" + + return cls( + entity_identifier=data.get("entityIdentifier", ""), + entity_type=data.get("entityType", ""), + action=action_value, + environments=envs, + scope=data.get("scope"), + identifier=data.get("id"), + ) + + def to_legacy(self) -> dict: + """Converts the SoarBlockEntity object to a JSON-serializable dictionary.""" + return { + "id": self.identifier, + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "elementType": 0 if self.action == "DoNotGroupAlerts" else 1, + "scope": self.scope, + "environments": self.environments, + } + + def to_1p(self) -> dict: + """Converts the SoarBlockEntity object to a 1P-serializable dictionary.""" + return { + "name": f"projects//locations//instances//entitiesBlocklists/{self.identifier}", + "id": self.identifier, + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "action": self.action, + "environmentsJson": json.dumps(self.environments), + } + + +@dataclasses.dataclass(slots=True) +class SimulatedCases: + cases: list[dict] + type: int | None + connector_identifier: str | None + debug_output: Any + + @classmethod + def from_legacy_or_1p(cls, data: dict) -> "SimulatedCases": + cases = list(data.get("cases") or []) + + for idx, case in enumerate(cases): + # ----------------------------- + # ticketId MUST NOT be empty + # ----------------------------- + ticket_id = case.get("ticketId") + if not isinstance(ticket_id, str) or not ticket_id.strip(): + fallback = ( + case.get("displayId") + or case.get("name") + or f"AUTO-TICKET-{idx + 1}" + ) + case["ticketId"] = str(fallback) + + # ----------------------------- + # Normalize baseEventIds + # ----------------------------- + for event in case.get("events", []): + fields = event.get("_fields", {}) + base_event_ids = fields.get("baseEventIds") + + if isinstance(base_event_ids, str): + try: + parsed = json.loads(base_event_ids) + if isinstance(parsed, list): + fields["baseEventIds"] = parsed + else: + fields["baseEventIds"] = [] + except Exception: + fields["baseEventIds"] = [] + + elif base_event_ids is None: + fields["baseEventIds"] = [] + + return cls( + cases=cases, + type=data.get("type"), + connector_identifier=data.get("connectorIdentifier"), + debug_output=data.get("debugOutput"), + ) + + def to_legacy(self) -> dict: + return { + "cases": self.cases, + "type": self.type, + "connectorIdentifier": self.connector_identifier, + "debugOutput": self.debug_output, + } + + def to_1p(self) -> dict: + return { + "cases": self.cases, + "type": self.type, + "connectorIdentifier": self.connector_identifier, + "debugOutput": self.debug_output, + } + + +@dataclasses.dataclass(slots=True) +class BlockRecord: + identifier: int + entity_identifier: str + entity_type: str + element_type: int + scope: int + environments: list[str] + + @classmethod + def from_legacy_or_1p(cls, data: SingleJson) -> "Entity": + """ + Factory method to create an Entity instance from a dictionary. + Handles cases where environments might be a JSON string or a list. + """ + envs = data.get("environments") + if isinstance(envs, str): + try: + envs = json.loads(envs) + except json.JSONDecodeError: + envs = [] + + return cls( + identifier=data.get("id", 0), + entity_identifier=data.get("entityIdentifier", ""), + entity_type=data.get("entityType", ""), + element_type=data.get("elementType", 0), + scope=data.get("scope", 0), + environments=envs if isinstance(envs, list) else [], + ) + + def to_legacy(self) -> SingleJson: + """Serializes the object back to the source JSON format.""" + return { + "id": self.identifier, + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "elementType": self.element_type, + "scope": self.scope, + "environments": self.environments, + } + + def to_1p(self) -> SingleJson: + """Serializes the object back to the source JSON format.""" + return { + "id": self.identifier, + "entityIdentifier": self.entity_identifier, + "entityType": self.entity_type, + "elementType": self.element_type, + "scope": self.scope, + "environments": self.environments, + } class CaseCloseComment: comment: str diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_api.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_api.py index 85649f9d4..84317e0b8 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_api.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_api.py @@ -32,11 +32,16 @@ CustomField, CustomFieldValue, EmailTemplate, + Environment, EntityCard, EventCard, Insight, + InternalDomain, + IntegrationSetting, InstalledIntegrationInstance, + OntologyRecord, UserDetails, + VisualFamily, ) from ..exceptions import InternalJSONDecoderError from ..types import ChronicleSOAR, SingleJson @@ -427,9 +432,6 @@ def get_alert_events( "alertIdentifier": alert_identifier, } - chronicle_soar.LOGGER.info( - f"Calling endpoint {endpoint} to user profile cards", - ) response = chronicle_soar.session.post(url, json=payload) validate_response(response) @@ -491,8 +493,11 @@ def get_integration_full_details( api_client.params.integration_identifier = integration_identifier response = api_client.get_integration_full_details() - validate_response(response, validate_json=False) - return response.json() + try: + validate_response(response, validate_json=False) + return response.json() + except InternalJSONDecoderError: + return [] def get_integration_instance_details_by_id( @@ -1651,7 +1656,6 @@ def get_case_activities( """ api_client = get_soar_client(chronicle_soar) - api_client.params.case_id = case_id api_client.params.query_params = query_params response = api_client.get_case_activities() @@ -1688,6 +1692,1183 @@ def get_cases_by_timestamp_filter( return response +def get_email_templates(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get email templates + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: A list of email templates + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_email_template() + + try: + validate_response(response, validate_json=True) + if isinstance(response.json(), dict): + return response.json().get("email_templates", []) + + except InternalJSONDecoderError: + return [] + + return response.json() + + +def get_system_version(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get System Version""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_system_version() + return response.json() + + +def get_environment_group_names(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get environment group names""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_environment_group_names() + return response.json() + + +def get_env_dynamic_parameters(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get environment dynamic parameters. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + + Returns: + list[SingleJson]: A list of environment dynamic parameters. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_env_dynamic_parameters() + try: + validate_response(response, validate_json=True) + response_data = response.json() + if isinstance(response_data, dict) and "dynamicParameters" in response_data: + return response_data["dynamicParameters"] + except InternalJSONDecoderError: + return [] + + return response_data + +# def add_dynamic_env_param( +# chronicle_soar: ChronicleSOAR, +# param: SingleJson, +# ) -> SingleJson: +# """Add / Update environment dynamic parameters""" +# api_client = get_soar_client(chronicle_soar) +# api_client.params.id = param.get("id") +# api_client.params.name = param.get("name") +# api_client.params.type = param.get("type", 0) +# api_client.params.default_value = param.get("defaultValue") +# api_client.params.optional_json = param.get("optionalValues", []) + +# response = api_client.add_dynamic_env_param() +# return response.json() + +#QA fixes +def add_dynamic_env_param( + chronicle_soar: ChronicleSOAR, + param: SingleJson, +) -> SingleJson: + """Add / Update environment dynamic parameters""" + api_client = get_soar_client(chronicle_soar) + api_client.params.id = param.get("id") + api_client.params.name = param.get("name") + api_client.params.display_name = param.get("displayName") + api_client.params.parameter_type = param.get("parameterType", 0) + api_client.params.default_value = param.get("defaultValue") + api_client.params.optional_json = param.get("optionalValuesJson", []) + + response = api_client.add_dynamic_env_param() + return response.json() + + +def install_integration( + chronicle_soar: ChronicleSOAR, + integration_identifier: str, + integration_name: str = "", + version: str = "", + is_certified: str = "true", + override_mapping: bool = True, + stage: bool = False, +) -> SingleJson: + """Install integration""" + api_client = get_soar_client(chronicle_soar) + api_client.params.integration_identifier = integration_identifier + api_client.params.integration_name = integration_name + api_client.params.version = version + api_client.params.is_certified = is_certified + api_client.params.override_mapping = override_mapping + api_client.params.stage = stage + + response = api_client.install_integration() + return response.json() + + +def export_package( + chronicle_soar: ChronicleSOAR, + integration_identifier: str, +) -> bytes: + """Export package""" + api_client = get_soar_client(chronicle_soar) + api_client.params.integration_identifier = integration_identifier + + response = api_client.export_package() + return response + + +def get_integration_instance_settings( + chronicle_soar: ChronicleSOAR, + instance_id: str, + integration_identifier: str, +) -> list[IntegrationSetting]: + """Get integration instance settings. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + instance_id (str): The instance id. + integration_identifier (str): The integration identifier. + + Returns: + list[IntegrationSetting]: A list of IntegrationSetting objects. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.instance_id = instance_id + api_client.params.integration_identifier = integration_identifier + response = api_client.get_integration_instance_settings() + validate_response(response) + response_data = response.json() + + if isinstance(response_data, dict) and "parameters" in response_data: + parameters = response_data.get("parameters", []) + return [IntegrationSetting.from_json(param) for param in parameters] + + return [IntegrationSetting.from_json(setting) for setting in response_data] + + +def create_integrations_instance( + chronicle_soar: ChronicleSOAR, + integration_identifier: str, + environment: str, +) -> SingleJson: + """Create integrations instance""" + api_client = get_soar_client(chronicle_soar) + api_client.params.integration_identifier = integration_identifier + api_client.params.environment = environment + response = api_client.create_integrations_instance() + return response.json() + + +def get_domains(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get domains. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + + Returns: + list[SingleJson]: .A list of domain JSON objects. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_domains() + return [InternalDomain.from_json(res).to_json() for res in response] + + +def update_domain( + chronicle_soar: ChronicleSOAR, + domain_data: SingleJson, +) -> bool: + """Update domain. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + domain_data (SingleJson): The domain data to update. + + Returns: + bool: True if the domain was updated successfully, False otherwise. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.domain_data = domain_data + response = api_client.update_domain() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def get_environment_names( + chronicle_soar: ChronicleSOAR, + search_term: str = "", + requested_page: int = 0, + page_size: int = 100, +) -> SingleJson: + """Get environment names""" + api_client = get_soar_client(chronicle_soar) + api_client.params.searchTerm = search_term + api_client.params.requestedPage = requested_page + api_client.params.pageSize = page_size + return api_client.get_environment_names() + + +def get_environments( + chronicle_soar: ChronicleSOAR, + search_term: str = "", + requested_page: int = 0, + page_size: int = 100, +) -> list[Environment]: + """Get environments""" + api_client = get_soar_client(chronicle_soar) + api_client.params.searchTerm = search_term + api_client.params.requestedPage = requested_page + api_client.params.pageSize = page_size + response = api_client.get_environments() + return [Environment.from_json(res) for res in response] + + +def import_environment( + chronicle_soar: ChronicleSOAR, + environment_data: SingleJson, +) -> bool: + """Import environment. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + environment_data (SingleJson): The environment data to update. + + Returns: + bool: True if the environment was import successfully, False otherwise. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.environment_data = environment_data + response = api_client.import_environment() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def save_integration_instance_settings( + chronicle_soar: ChronicleSOAR, + identifier: str, + environment: str, + integration_data: SingleJson, +) -> bool: + """Create integrations instance""" + api_client = get_soar_client(chronicle_soar) + api_client.params.identifier = identifier + api_client.params.environment = environment + api_client.params.integration_data = integration_data + response = api_client.save_integration_instance_settings() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def import_simulated_case( + chronicle_soar: ChronicleSOAR, + case_data: SingleJson, +) -> bool: + """Import simulated case""" + api_client = get_soar_client(chronicle_soar) + api_client.params.case_data = case_data + response = api_client.import_simulated_case() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def add_case_tag( + chronicle_soar: ChronicleSOAR, + case_tag: SingleJson, +) -> bool: + """Import simulated case""" + api_client = get_soar_client(chronicle_soar) + api_client.params.case_tag = case_tag + response = api_client.add_case_tag() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def add_case_stage( + chronicle_soar: ChronicleSOAR, + case_stage: SingleJson, +) -> bool: + """Import simulated case""" + api_client = get_soar_client(chronicle_soar) + api_client.params.case_stage = case_stage + response = api_client.add_case_stage() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def get_case_alert(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get case alert""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_case_alert() + validate_response(response, validate_json=True) + return response.json() + + +def add_close_reason( + chronicle_soar: ChronicleSOAR, + close_reason: SingleJson, +) -> SingleJson: + """Add close reason""" + api_client = get_soar_client(chronicle_soar) + api_client.params.close_reason = close_reason + response = api_client.add_close_reason() + try: + validate_response(response, validate_json=False) + return response.json() + except (HTTPError, InternalJSONDecoderError): + return {} + + +def get_networks(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get Networks, + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + + Returns: + list[SingleJson]: A list of networks. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_networks() + try: + if isinstance(response, dict) and "networks" in response: + return response["networks"] + except InternalJSONDecoderError: + return [] + return response + + +def update_network( + chronicle_soar: ChronicleSOAR, + network_data: SingleJson, +) -> bool: + """Update network. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + network_data (SingleJson): The network data to update. + + Returns: + bool: True if the network was updated successfully, False otherwise. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.network_data = network_data + response = api_client.update_network() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def get_custom_lists(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get custom lists. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + + Returns: + list[SingleJson]: A list of custom lists. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_custom_lists() + try: + validate_response(response, validate_json=True) + raw_data = response.json() + if isinstance(raw_data, dict) and "custom_lists" in raw_data: + return raw_data["custom_lists"] + except InternalJSONDecoderError: + return [] + + return raw_data + + +def update_custom_list( + chronicle_soar: ChronicleSOAR, + tracking_list: SingleJson, + tracking_id: str | None, +) -> bool: + """Update custom lists. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object. + tracking_list (SingleJson): The custom lists data to update. + tracking_id (str | None): The tracking id. + + + Returns: + bool: True if the custom lists was updated successfully, False otherwise. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.tracking_list = tracking_list + api_client.params.tracking_id = tracking_id + response = api_client.update_custom_list() + try: + validate_response(response, validate_json=False) + return True + except (HTTPError, InternalJSONDecoderError): + return False + + +def update_blocklist( + chronicle_soar: ChronicleSOAR, + blocklist_data: SingleJson, +) -> SingleJson: + """Update blocklist""" + api_client = get_soar_client(chronicle_soar) + api_client.params.blocklist_data = blocklist_data + response = api_client.update_blocklist() + try: + response = validate_response(response, validate_json=False) + return response + except (HTTPError, InternalJSONDecoderError): + return {} + + +def update_sla_record( + chronicle_soar: ChronicleSOAR, + sla_data: SingleJson, +) -> SingleJson: + """Update SLA record""" + api_client = get_soar_client(chronicle_soar) + api_client.params.sla_data = sla_data + response = api_client.update_sla_record() + validate_response(response, validate_json=False) + if ( + response is None + or getattr(response, "status_code", None) == 204 + or not getattr(response, "text", "").strip() + ): + return {} + try: + return safe_json_for_204(response, default_for_204={}) + except (ValueError, InternalJSONDecoderError): + return {} + + +def save_playbook( + chronicle_soar: ChronicleSOAR, + playbook_data: SingleJson, +) -> SingleJson: + """Save playbook""" + api_client = get_soar_client(chronicle_soar) + api_client.params.playbook_data = playbook_data + response = api_client.save_playbook() + return response.json() + + +def get_playbooks_workflow_menu_cards( + chronicle_soar: ChronicleSOAR, + api_payload: list[int, int], +) -> list[SingleJson]: + """Gets playbooks workflow menu cards. + + Args: + chronicle_soar: A ChronicleSOAR SDK object that can make API requests. + api_payload: A list containing two integers. + + Returns: + list[SingleJson]: A list of playbook workflow menu cards. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.api_payload = api_payload + response = api_client.get_playbooks_workflow_menu_cards() + validate_response(response) + return response.json() + + +def get_playbooks_workflow_menu_cards_with_env( + chronicle_soar: ChronicleSOAR, + api_payload: list[int, int], +) -> list[SingleJson]: + """Get playbooks workflow menu cards with environment filter. + Args: + chronicle_soar: A ChronicleSOAR SDK object that can make API requests. + api_payload: A list containing two integers. + + Returns: + list[SingleJson]: A list of playbook workflow menu cards. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.api_payload = api_payload + response = api_client.get_playbooks_workflow_menu_cards_with_env() + validate_response(response) + return response.json() + + +def get_playbook_workflow_menu_cards_by_identifier( + chronicle_soar: ChronicleSOAR, + playbook_identifier: str, +) -> SingleJson: + """Get playbook workflow menu cards by identifier. + Args: + chronicle_soar: A ChronicleSOAR SDK object that can make API requests. + playbook_identifier: The identifier of the playbook. + + Returns: + SingleJson: Playbook workflow menu cards. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.playbook_identifier = playbook_identifier + response = api_client.get_playbook_workflow_menu_cards_by_identifier() + validate_response(response) + return response.json() + + +def get_playbook_workflow_menu_cards_by_identifier_with_env( + chronicle_soar: ChronicleSOAR, + playbook_identifier: str, +) -> SingleJson: + """Get playbook workflow menu cards by identifier with environment filter. + Args: + chronicle_soar: A ChronicleSOAR SDK object that can make API requests. + playbook_identifier: The identifier of the playbook. + + Returns: + SingleJson: Playbook workflow menu cards. + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.playbook_identifier = playbook_identifier + response = api_client.get_playbook_workflow_menu_cards_by_identifier_with_env() + validate_response(response) + return response.json() + + +def get_installed_connectors( + chronicle_soar: ChronicleSOAR, + connector_instance_id: int | None = None, +) -> list[SingleJson] | SingleJson: + """Get installed connectors. + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + connector_instance_id (int | None): Connector instance ID + + Returns: + list[SingleJson] | SingleJson: Response JSON. + """ + api_clinet = get_soar_client(chronicle_soar) + api_clinet.params.connector_instance_id = connector_instance_id + response = api_clinet.get_installed_connectors() + return response + + +def get_visual_families( + chronicle_soar: ChronicleSOAR, + include_default_vfs: bool = False, +) -> list[SingleJson]: + """Get visual families from either legacy or 1P, normalized. + + Args: + chronicle_soar (ChronicleSOAR): Chronicle SOAR SDK object. + include_default_vfs (bool): Whether to include default visual families. + + Returns: + list[SingleJson]: Normalized response JSON (legacy-compatible shape). + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_visual_families() + validate_response(response, validate_json=True) + data = response.json() + families: list[VisualFamily] = [] + if isinstance(data, dict) and "visual_families" in data: + families = [VisualFamily.from_json(vf) for vf in data["visual_families"]] + else: + families = [VisualFamily.from_json(vf) for vf in data] + + if not include_default_vfs: + families = [f for f in families if f.is_custom] + + return [f.to_json() for f in families] + + +def get_visual_family_by_id( + chronicle_soar: ChronicleSOAR, + family_id: int, +) -> SingleJson: + """Get custom visual family by ID. + + Args: + chronicle_soar (ChronicleSOAR): Chronicle SOAR SDK object. + family_id (int): Visual family ID. + + Returns: + SingleJson: Normalized response JSON (legacy-compatible shape). + """ + api_client = get_soar_client(chronicle_soar) + api_client.params.family_id = family_id + response = api_client.get_visual_family_by_id() + validate_response(response, validate_json=True) + family: VisualFamily = VisualFamily.from_json(response.json()) + return family.to_json() + + +def get_ontology_records(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get ontology records. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: Response JSON.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_ontology_records() + if not response: + return [] + if isinstance(response, list): + records = response + elif isinstance(response.json(), dict): + records = response.json().get("ontology_records", []) + else: + records = [] + return [OntologyRecord.from_json(item).to_json() for item in records] + + +def get_case_tags(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get case tags. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: Response JSON. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_case_tags() + + if isinstance(response, list): + records = response + elif isinstance(response.json(), dict): + return response.json().get("case_tag_definitions", []) + else: + records = [] + return records + +def get_case_stages(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get case stages. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: Response JSON. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_case_stages() + try: + raw_data = response + if not raw_data: + return [] + if isinstance(response, list): + return response + elif isinstance(raw_data.json(), dict): + return raw_data.json().get("case_stage_definitions", []) + except InternalJSONDecoderError: + return [] + + return raw_data.json() + +def get_case_close_reasons(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get case close reasons. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: Response JSON. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_case_close_reasons() + validate_response(response, validate_json=True) + + raw_data = response.json() + if isinstance(raw_data, dict): + return raw_data.get("case_close_definitions", []) + + return raw_data + + +def get_block_lists_details(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get block lists details. + + Args: + chronicle_soar (ChronicleSOAR): A chronicle soar SDK object + + Returns: + list[SingleJson]: Response JSON. + """ + api_client = get_soar_client(chronicle_soar) + response = api_client.get_block_lists_details() + validate_response(response, validate_json=True) + + raw_data = response.json() + if isinstance(raw_data, dict): + return raw_data.get("soar_block_entities", []) + + return raw_data + + +def get_sla_records(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get sla records.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_sla_records().json() + + try: + if response is None: + return [] + + if isinstance(response, list): + return response + + if isinstance(response, dict): + return response.get("sla_definitions", []) or [] + + if hasattr(response, "json"): + try: + parsed = response.json() or {} + return parsed.get("sla_definitions", []) or [] + except (ValueError, InternalJSONDecoderError): + return [] + except (ValueError, InternalJSONDecoderError): + return [] + + return [] + + +def get_all_model_block_records(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get all model block records.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_all_model_block_records() + try: + validate_response(response, validate_json=True) + return response.json() + except InternalJSONDecoderError: + return [] + +def get_company_logo(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get company logo.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_company_logo() + try: + validate_response(response, validate_json=True) + return response.json() + except InternalJSONDecoderError: + return {} + +def get_case_title_settings(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get case title settings.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_case_title_settings() + try: + validate_response(response, validate_json=True) + return response.json() + except InternalJSONDecoderError: + return {} + +def save_case_title_settings( + chronicle_soar: ChronicleSOAR, + name: str, + display_name: str, + value: str, + type_: int, +) -> SingleJson: + """Save case title settings.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.name = name + api_client.params.display_name = display_name + api_client.params.value = value + api_client.params.type = type_ + + response = api_client.save_case_title_settings() + validate_response(response, validate_json=False) + return response.json() + +def add_or_update_company_logo( + chronicle_soar: ChronicleSOAR, + company_logo: SingleJson, +) -> SingleJson: + """Add or update company logo.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.company_logo = company_logo + + response = api_client.add_or_update_company_logo() + validate_response(response, validate_json=False) + return response.json() + + +def attache_workflow_to_case( + chronicle_soar: ChronicleSOAR, + case_id: int, + alert_group_identifier: str, + alert_identifier: str, + wf_name: str, + original_wf_identifier: str, +) -> SingleJson: + """Attache workflow to case.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.case_id = case_id + api_client.params.alert_group_identifier = alert_group_identifier + api_client.params.alert_identifier = alert_identifier + api_client.params.wf_name = wf_name + api_client.params.original_wf_identifier = original_wf_identifier + + response = api_client.attache_workflow_to_case() + validate_response(response, validate_json=False) + return response.json() + +def import_custom_case( + chronicle_soar: ChronicleSOAR, + case_data: SingleJson, +) -> SingleJson: + """Import custom case.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.case_data = case_data + + response = api_client.import_custom_case() + validate_response(response, validate_json=False) + return response.json() + +def case_search_everything( + chronicle_soar: ChronicleSOAR, + search_data: SingleJson, +) -> SingleJson: + """Case search everything.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.search_data = search_data + + response = api_client.case_search_everything() + validate_response(response, validate_json=False) + return response.json() + +def get_environment_action_definition( + chronicle_soar: ChronicleSOAR, + environment_action_data: list[str] | SingleJson, +) -> SingleJson: + """Get environment action definition.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.environment_action_data = environment_action_data + + response = api_client.get_environment_action_definition() + validate_response(response, validate_json=False) + return response.json() + + +def export_simulated_case( + chronicle_soar: ChronicleSOAR, + name: str, +) -> SingleJson: + """Export simulated case""" + api_client = get_soar_client(chronicle_soar) + api_client.params.name = name + + response = api_client.export_simulated_case() + validate_response(response, validate_json=False) + response = safe_json_for_204(response, default_for_204={}) + return response + + +def get_case_insights_comment_evidence( + chronicle_soar: ChronicleSOAR, + case_id: int, +) -> SingleJson: + """Get case attachments. + + Args: + chronicle_soar (ChronicleSoar): A chronicle soar SDK object + case_id (int): Chronicle SOAR case ID + + """ + api_client = get_soar_client(chronicle_soar) + + api_client.params.case_id = case_id + response = api_client.get_case_insights() + try: + validate_response(response, validate_json=True) + except InternalJSONDecoderError: + return {"items": []} + + result_data = response.json() + if isinstance(result_data, list): + return {"items": result_data} + + return result_data + + +def get_bearer_token( + chronicle_soar: ChronicleSOAR, smp_password, smp_username +) -> str: + """Get bearer token.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.smp_password = smp_password + api_client.params.smp_username = smp_username + response = api_client.get_bearer_token() + validate_response(response, validate_json=False) + return f"Bearer {response.text}" + + +def update_api_record( + chronicle_soar: ChronicleSOAR, api_record: SingleJson +) -> None: + """Update api record.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.api_record = api_record + response = api_client.update_api_record() + validate_response(response, validate_json=False) + + +def get_store_data(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get store data (integrations + powerups combined).""" + api_client = get_soar_client(chronicle_soar) + + response = api_client.get_store_data() + return response.get("integrations", response.get("marketplaceIntegrations", [])) + + +def import_package( + chronicle_soar: ChronicleSOAR, integration_name: str, b64_blob: str +) -> SingleJson: + """Import package.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.integration_name = integration_name + api_client.params.b64_blob = b64_blob + response = api_client.import_package() + validate_response(response, validate_json=False) + return response.content + + +def update_ide_item( + chronicle_soar: ChronicleSOAR, input_json: SingleJson +) -> SingleJson: + """Update ide item.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.input_json = input_json + response = api_client.update_ide_item() + validate_response(response, validate_json=True) + return response.json() + + +def get_ide_cards( + chronicle_soar: ChronicleSOAR, + identifier: str | None = None, + include_staging: bool = False +) -> list[SingleJson]: + """Get ide cards.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.integration_name = identifier + response = api_client.get_ide_cards() + validate_response(response, validate_json=True) + if include_staging: + return response.json() + return [x for x in response.json() if not x.get("productionIntegrationIdentifier")] + + +def get_ide_item( + chronicle_soar: ChronicleSOAR, item_id: str, item_type: str +) -> SingleJson: + """Get ide item.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.item_id = item_id + api_client.params.item_type = item_type + response = api_client.get_ide_item() + validate_response(response, validate_json=True) + return response.json() + + +def add_custom_family( + chronicle_soar: ChronicleSOAR, visual_family: SingleJson +) -> SingleJson: + """Add custom family.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.visual_family = visual_family + response = api_client.add_custom_family() + validate_response(response, validate_json=False) + return response.content + + +def get_mapping_rules( + chronicle_soar: ChronicleSOAR, + source: str = None, + mr_id: int = None, + product: str = None, + event_name: str = None, +) -> SingleJson: + """Get mapping rules.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.source = source + api_client.params.product = product + api_client.params.event_name = event_name + api_client.params.mr_id = mr_id + + response = api_client.get_mapping_rules() + return safe_json_for_204(response, default_for_204={}) + + +def add_mapping_rules( + chronicle_soar: ChronicleSOAR, mapping_rule: SingleJson +) -> SingleJson: + """Add mapping rules.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.mapping_rule = mapping_rule + response = api_client.add_mapping_rules() + validate_response(response, validate_json=False) + return response.content + + +def set_mappings_visual_family( + chronicle_soar: ChronicleSOAR, + source: str, + product: str, + event_name: str, + visual_family: str, +) -> bool: + """Set mappings visual family.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.source = source + api_client.params.product = product + api_client.params.event_name = event_name + api_client.params.visual_family = visual_family + response = api_client.set_mappings_visual_family() + validate_response(response, validate_json=False) + return True + + +def export_playbooks( + chronicle_soar: ChronicleSOAR, definitions: list[str] +) -> SingleJson: + """Export playbooks.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.definitions = definitions + response = api_client.export_playbooks() + validate_response(response, validate_json=False) + return response.content + + +def import_playbooks( + chronicle_soar: ChronicleSOAR, playbooks: SingleJson +) -> SingleJson: + """Import playbooks.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.playbooks = playbooks + response = api_client.import_playbooks() + validate_response(response, validate_json=False) + return response.content + + +def create_playbook_category(chronicle_soar: ChronicleSOAR, name: str) -> SingleJson: + """Create playbook category.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.name = name + response = api_client.create_playbook_category() + validate_response(response, validate_json=True) + return response.json() + + +def get_playbook_categories(chronicle_soar: ChronicleSOAR) -> SingleJson: + """Get playbook categories.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_playbook_categories() + validate_response(response, validate_json=True) + return response.json() + + +# def update_connector( +# chronicle_soar: ChronicleSOAR, connector_data: SingleJson +# ) -> SingleJson: +# """Update connector.""" +# api_client = get_soar_client(chronicle_soar) +# api_client.params.connector_data = connector_data +# response = api_client.update_connector() +# validate_response(response, validate_json=False) +# return response + +#QA fixes +def update_connector( + chronicle_soar: ChronicleSOAR, connector_data: SingleJson +) -> SingleJson: + """Update connector.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.connector_data = connector_data + api_client.params.integration_name = connector_data.get("integration") + api_client.params.connector_id = connector_data.get("connectorId") + + response = api_client.update_connector() + validate_response(response, validate_json=False) + return response + + +# def add_job(chronicle_soar: ChronicleSOAR, job: SingleJson) -> SingleJson: +# """Add job.""" +# api_client = get_soar_client(chronicle_soar) +# api_client.params.job = job +# response = api_client.add_job() +# validate_response(response, validate_json=False) +# return response.content + +#QA fixes +def add_job(chronicle_soar: ChronicleSOAR, job: SingleJson) -> SingleJson: + """Add job.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.job = job + api_client.params.integration_name = job.get("integration") + api_client.params.job_name = job.get("name") + response = api_client.add_job() + validate_response(response, validate_json=False) + return response.content + + +def add_email_template( + chronicle_soar: ChronicleSOAR, template: SingleJson +) -> bool: + """Add email template.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.template = template + response = api_client.add_email_template() + validate_response(response, validate_json=False) + return True + + +def get_denylists( + chronicle_soar: ChronicleSOAR, + is_expand: bool = False, +) -> SingleJson: + """Get denylists.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.is_expand = is_expand + response = api_client.get_denylists() + return safe_json_for_204(response, default_for_204={}) + + +def get_simulated_cases( + chronicle_soar: ChronicleSOAR, + is_expand: bool = False, +) -> SingleJson: + """Get simulated cases.""" + api_client = get_soar_client(chronicle_soar) + api_client.params.is_expand = is_expand + response = api_client.get_simulated_cases() + return safe_json_for_204(response, default_for_204={}) + + +def get_installed_integrations(chronicle_soar: ChronicleSOAR) -> list[SingleJson]: + """Get installed integration.""" + api_client = get_soar_client(chronicle_soar) + response = api_client.get_installed_integrations() + return response def get_case_close_comment( chronicle_soar: ChronicleSOAR, case_id: str | int, diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/api_client_factory.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/api_client_factory.py index 1f1bdb6f8..c285e6e3c 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/api_client_factory.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/api_client_factory.py @@ -209,6 +209,224 @@ def get_case_activities(self) -> requests.Response: def get_cases_by_timestamp_filter(self) -> requests.Response: """Get cases by timestamp filter""" + def get_email_templates(self) -> requests.Response: + """Get email templates""" + + def get_system_version(self) -> requests.Response: + """Get domain alias""" + + def get_environment_group_names(self) -> requests.Response: + """Get environment group names""" + + def get_env_dynamic_parameters(self) -> requests.Response: + """Get env dynamic parameters""" + + def add_dynamic_env_param(self) -> requests.Response: + """Add dynamic env param""" + + def install_integration(self) -> requests.Response: + """Install integration""" + + def export_package(self) -> requests.Response: + """Export package""" + + def get_integration_instance_settings(self) -> requests.Response: + """Get integration instance settings""" + + def create_integrations_instance(self) -> requests.Response: + """Create integrations instance""" + + def get_domains(self) -> requests.Response: + """Get domains""" + + def update_domain(self) -> requests.Response: + """Update domain""" + + def get_environment_names(self) -> requests.Response: + """Get environment names""" + + def get_environments(self) -> requests.Response: + """Get environments""" + + def import_environment(self) -> requests.Response: + """Import environment""" + + def save_integration_instance_settings(self) -> requests.Response: + """Save integration instance settings""" + + def import_simulated_case(self) -> requests.Response: + """Import simulated case""" + + def add_case_tag(self) -> requests.Response: + """Add case tag""" + + def add_case_stage(self) -> requests.Response: + """Add case stage""" + + def get_case_alert(self) -> requests.Response: + """Get case alert""" + + def add_close_reason(self) -> requests.Response: + """Add close reason""" + + def get_networks(self) -> requests.Response: + """Get networks""" + + def update_network(self) -> requests.Response: + """Update network""" + + def get_custom_lists(self) -> requests.Response: + """Get custom lists""" + + def update_custom_list(self) -> requests.Response: + """Update custom list""" + + def update_blocklist(self) -> requests.Response: + """Update blocklist""" + + def update_sla_record(self) -> requests.Response: + """Update sla record""" + + def save_playbook(self) -> requests.Response: + """Save playbook""" + + def get_playbooks_workflow_menu_cards(self) -> requests.Response: + """Get playbooks workflow menu cards""" + + def get_playbooks_workflow_menu_cards_with_env(self) -> requests.Response: + """Get playbooks workflow menu cards with environment filter.""" + + def get_playbook_workflow_menu_cards_by_identifier(self) -> requests.Response: + """Get playbook workflow menu cards by identifier.""" + + def get_playbook_workflow_menu_cards_by_identifier_with_env( + self, + ) -> requests.Response: + """Get playbook workflow menu cards by identifier with environment filter.""" + + def get_installed_connectors(self) -> requests.Response: + """Get installed connectors.""" + + def get_visual_families(self) -> requests.Response: + """Get custom visual families.""" + + def get_visual_family_by_id(self) -> requests.Response: + """Get custom visual family by ID.""" + + def get_ontology_records(self) -> requests.Response: + """Get ontology records""" + + def get_case_tags(self) -> requests.Response: + """Get case tags""" + + def get_case_stages(self) -> requests.Response: + """Get case stages""" + + def get_case_close_reasons(self) -> requests.Response: + """Get case close reasons""" + + def get_block_lists_details(self) -> requests.Response: + """Get block lists details""" + + def get_sla_records(self) -> requests.Response: + """Get sla records""" + + def add_tags_to_case_in_bulk(self) -> requests.Response: + """Add tags to case in bulk""" + + def get_all_model_block_records(self) -> requests.Response: + """Get all model block records""" + + def get_company_logo(self) -> requests.Response: + """Get company logo""" + + def get_case_title_settings(self) -> requests.Response: + """Get case title settings""" + + def save_case_title_settings(self) -> requests.Response: + """Save case title settings""" + + def add_or_update_company_logo(self) -> requests.Response: + """Add or update company logo""" + + def attache_workflow_to_case(self) -> requests.Response: + """AttacheWorkflowToCase""" + + def import_custom_case(self) -> requests.Response: + """Import custom case""" + + def get_environment_action_definition(self) -> requests.Response: + """Get environment action definition""" + + def export_simulated_case(self) -> requests.Response: + """Export simulated cases""" + + def get_case_insights_comment_evidence(self) -> requests.Response: + """Get case insights using 1P API.""" + + def get_bearer_token(self) -> requests.Response: + """Get bearer token.""" + + def update_api_record(self) -> requests.Response: + """Update api record.""" + + def get_store_data(self) -> requests.Response: + """Get store data.""" + + def import_package(self) -> requests.Response: + """Import package.""" + + def update_ide_item(self) -> requests.Response: + """Update ide item.""" + + def get_ide_cards(self) -> requests.Response: + """Get ide cards.""" + + def get_ide_item(self) -> requests.Response: + """Get ide item.""" + + def add_custom_family(self) -> requests.Response: + """Add custom family.""" + + def get_mapping_rules(self) -> requests.Response: + """Get mapping rules.""" + + def add_mapping_rules(self) -> requests.Response: + """Add mapping rules.""" + + def set_mappings_visual_family(self) -> requests.Response: + """Set mappings visual family.""" + + def export_playbooks(self) -> requests.Response: + """Export playbooks.""" + + def import_playbooks(self) -> requests.Response: + """Import playbooks.""" + + def create_playbook_category(self) -> requests.Response: + """Create playbook category.""" + + def get_playbook_categories(self) -> requests.Response: + """Get playbook categories.""" + + def update_connector(self) -> requests.Response: + """Update connector.""" + + def add_job(self) -> requests.Response: + """Add job.""" + + def add_email_template(self) -> requests.Response: + """Add email template.""" + + def get_denylists(self) -> requests.Response: + """Get denylists.""" + + def get_simulated_cases(self) -> requests.Response: + """Get simulated cases.""" + + def get_installed_integrations(self) -> requests.Response: + """Get installed integration""" + def get_case_close_comment(self, case_id: str | int) -> requests.Response: """Get case closure comment diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/base_soar_api.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/base_soar_api.py index 093fee972..98dfe2a50 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/base_soar_api.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/base_soar_api.py @@ -16,6 +16,9 @@ from __future__ import annotations +import re +import requests +from enum import Enum from typing import TYPE_CHECKING from TIPCommon.data_models import Container @@ -50,6 +53,11 @@ def _make_request( headers: dict[str, str] | None = None, ) -> requests.Response: url = f"{get_sdk_api_uri(self.chronicle_soar)}{endpoint}" + url = re.sub( + r'(https?://[^/]+)/(v1alpha/.*?)/download/(integrations/.*)', + r'\1/download/\2/\3', + url + ) self.chronicle_soar.LOGGER.info(f"Calling API endpoint: {method.value} {url}") request_kwargs = { "params": params, diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/legacy_soar_api.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/legacy_soar_api.py index aa2ebcbc9..4653f8648 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/legacy_soar_api.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/legacy_soar_api.py @@ -38,7 +38,7 @@ class LegacySoarApi(BaseSoarApi): def save_attachment_to_case_wall(self) -> requests.Response: """Save an attachment to the case wall using legacy API.""" - endpoint = "/cases/AddEvidence/" + endpoint: str = "/cases/AddEvidence/" payload = { "CaseIdentifier": self.params.case_id, "Base64Blob": self.params.base64_blob, @@ -52,7 +52,7 @@ def save_attachment_to_case_wall(self) -> requests.Response: @temporarily_remove_header(DATAPLANE_1P_HEADER) def get_entity_data(self) -> requests.Response: """Get entity data using legacy API.""" - endpoint = "/entities/GetEntityData" + endpoint: str = "/entities/GetEntityData" payload = { "entityIdentifier": self.params.entity_identifier, "entityType": self.params.entity_type, @@ -76,7 +76,7 @@ def get_case_insights(self) -> requests.Response: def get_installed_integrations_of_environment(self) -> requests.Response: """Get installed integrations of environment using legacy API.""" - endpoint = "/integrations/GetEnvironmentInstalledIntegrations" + endpoint: str = "/integrations/GetEnvironmentInstalledIntegrations" payload = { "name": ( "*" if self.params.environment == "Shared Instances" else self.params.environment @@ -86,20 +86,20 @@ def get_installed_integrations_of_environment(self) -> requests.Response: def get_connector_cards(self) -> requests.Response: """Get connector cards using legacy API""" - endpoint = "/connectors/cards" + endpoint: str = "/connectors/cards" query_params = {"format": "snake"} return self._make_request(HttpMethod.GET, endpoint, params=query_params) def get_federation_cases(self) -> requests.Response: """Get federation cases using legacy API""" - endpoint = "/federation/cases" + endpoint: str = "/federation/cases" params = {"continuationToken": self.params.continuation_token} return self._make_request(HttpMethod.GET, endpoint, params=params) def patch_federation_cases(self) -> requests.Response: """Get federation cases using legacy API""" - endpoint = "/federation/cases/batch-patch" + endpoint: str = "/federation/cases/batch-patch" headers = {"AppKey": self.params.api_key} if self.params.api_key else None payload = {"cases": self.params.cases_payload} return self._make_request( @@ -111,7 +111,7 @@ def patch_federation_cases(self) -> requests.Response: def get_workflow_instance_card(self) -> requests.Response: """Get workflow instance card using legacy API""" - endpoint = "/cases/GetWorkflowInstancesCards" + endpoint: str = "/cases/GetWorkflowInstancesCards" payload = { "caseId": self.params.case_id, "alertIdentifier": self.params.alert_identifier, @@ -120,7 +120,7 @@ def get_workflow_instance_card(self) -> requests.Response: def pause_alert_sla(self) -> requests.Response: """Pause alert sla""" - endpoint = "/cases/PauseAlertSla" + endpoint: str = "/cases/PauseAlertSla" payload = { "caseId": self.params.case_id, "alertIdentifier": self.params.alert_identifier, @@ -130,7 +130,7 @@ def pause_alert_sla(self) -> requests.Response: def resume_alert_sla(self) -> requests.Response: """Resume alert sla""" - endpoint = "/cases/ResumeAlertSla" + endpoint: str = "/cases/ResumeAlertSla" payload = { "caseId": self.params.case_id, "alertIdentifier": self.params.alert_identifier, @@ -146,7 +146,7 @@ def get_case_overview_details(self) -> requests.Response: def remove_case_tag(self) -> requests.Response: """Remove case tag""" - endpoint = "/cases/RemoveCaseTag" + endpoint: str = "/cases/RemoveCaseTag" payload = { "caseId": self.params.case_id, "tag": self.params.tag, @@ -156,7 +156,7 @@ def remove_case_tag(self) -> requests.Response: def change_case_description(self) -> requests.Response: """Change case description""" - endpoint = "/cases/ChangeCaseDescription?format=snake" + endpoint: str = "/cases/ChangeCaseDescription?format=snake" payload = { "case_id": self.params.case_id, "description": self.params.description, @@ -165,7 +165,7 @@ def change_case_description(self) -> requests.Response: def set_alert_priority(self) -> requests.Response: """Set alert priority""" - endpoint = "/sdk/UpdateAlertPriority" + endpoint: str = "/sdk/UpdateAlertPriority" payload = { "caseId": self.params.case_id, "alertIdentifier": self.params.alert_identifier, @@ -176,7 +176,7 @@ def set_alert_priority(self) -> requests.Response: def set_case_score_bulk(self) -> requests.Response: """Set case score bulk""" - endpoint = "/sdk/cases/score" + endpoint: str = "/sdk/cases/score" payload = { "caseScores": [ { @@ -189,7 +189,7 @@ def set_case_score_bulk(self) -> requests.Response: def get_integration_full_details(self) -> requests.Response: """Get integration full details""" - endpoint = "/store/GetIntegrationFullDetails" + endpoint: str = "/store/GetIntegrationFullDetails" payload = { "integrationIdentifier": self.params.integration_identifier, } @@ -199,7 +199,7 @@ def _get_all_integration_instances(self) -> list[SingleJson]: """Private helper method to fetch all integration instances from the API. This encapsulates the common API call logic. """ - endpoint = "/integrations/GetOptionalIntegrationInstances" + endpoint: str = "/integrations/GetOptionalIntegrationInstances" payload = { "environments": self.params.environments, "integrationIdentifier": self.params.integration_identifier, @@ -216,7 +216,7 @@ def get_integration_instance_details_by_name(self) -> requests.Response: def get_users_profile(self) -> requests.Response: """Get users profile""" - endpoint = "/settings/GetUserProfiles" + endpoint: str = "/settings/GetUserProfiles" payload = { "searchTerm": self.params.search_term, "filterRole": self.params.filter_by_role, @@ -234,24 +234,24 @@ def get_investigator_data(self) -> requests.Response: def remove_entities_from_custom_list(self) -> requests.Response: """Remove entities from custom list""" - endpoint = "/sdk/RemoveEntitiesFromCustomList" + endpoint: str = "/sdk/RemoveEntitiesFromCustomList" payload = self.params.list_entities_data return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) def add_entities_to_custom_list(self) -> requests.Response: """Add entities to custom list""" - endpoint = "/sdk/AddEntitiesToCustomList" + endpoint: str = "/sdk/AddEntitiesToCustomList" payload = self.params.list_entities_data return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) def get_traking_list_record(self) -> requests.Response: """Get traking list record""" - endpoint = "/settings/GetTrackingListRecords" + endpoint: str = "/settings/GetTrackingListRecords" return self._make_request(HttpMethod.GET, endpoint) def get_traking_list_records_filtered(self) -> requests.Response: """Get traking list records filtered""" - endpoint = "/settings/GetTrackingListRecordsFiltered" + endpoint: str = "/settings/GetTrackingListRecordsFiltered" payload = { "environments": [self.chronicle_soar.environment], } @@ -259,14 +259,14 @@ def get_traking_list_records_filtered(self) -> requests.Response: def execute_bulk_assign(self) -> requests.Response: """Execute bulk assign""" - endpoint = "/cases/ExecuteBulkAssign" + endpoint: str = "/cases/ExecuteBulkAssign" payload = {"casesIds": self.params.case_ids, "userName": self.params.user_name} return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) @temporarily_remove_header(DATAPLANE_1P_HEADER) def execute_bulk_close_case(self) -> requests.Response: """Execute bulk close case""" - endpoint = "/cases/ExecuteBulkCloseCase" + endpoint: str = "/cases/ExecuteBulkCloseCase" payload = { "casesIds": self.params.case_ids, "closeReason": self.params.close_reason, @@ -277,7 +277,7 @@ def execute_bulk_close_case(self) -> requests.Response: def get_users_profile_cards(self) -> requests.Response: """Get users profile cards.""" - endpoint = "/settings/GetUserProfileCards" + endpoint: str = "/settings/GetUserProfileCards" payload = { "searchTerm": self.params.search_term, "requestedPage": self.params.requested_page, @@ -306,7 +306,7 @@ def resume_case_sla(self, case_id: int) -> requests.Response: def rename_case(self) -> requests.Response: """Rename case""" - endpoint = "/cases/RenameCase" + endpoint: str = "/cases/RenameCase" payload = { "caseId": self.params.case_id, "title": self.params.case_title, @@ -315,7 +315,7 @@ def rename_case(self) -> requests.Response: def add_comment_to_entity(self) -> requests.Response: """Add comment to entity""" - endpoint = "/entities/AddNote?format=camel" + endpoint: str = "/entities/AddNote?format=camel" payload = { "author": self.params.author, "content": self.params.content, @@ -327,7 +327,7 @@ def add_comment_to_entity(self) -> requests.Response: def assign_case_to_user(self) -> requests.Response: """Assign case to user""" - endpoint = "/cases/AssignUserToCase" + endpoint: str = "/cases/AssignUserToCase" payload = { "caseId": self.params.case_id, "alertIdentifier": self.params.alert_identifier, @@ -337,12 +337,12 @@ def assign_case_to_user(self) -> requests.Response: def get_email_template(self) -> requests.Response: """Get email template""" - endpoint = "/settings/GetEmailTemplateRecords?format=camel" + endpoint: str = "/settings/GetEmailTemplateRecords?format=camel" return self._make_request(HttpMethod.GET, endpoint) def get_siemplify_user_details(self) -> requests.Response: """Get siemplify user details""" - endpoint = "/settings/GetUserProfiles" + endpoint: str = "/settings/GetUserProfiles" payload = { "searchTerm": self.params.search_term, "filterRole": self.params.filter_by_role, @@ -354,13 +354,13 @@ def get_siemplify_user_details(self) -> requests.Response: def get_domain_alias(self) -> requests.Response: """Get domain alias""" - endpoint = "/settings/GetDomainAliases?format=camel" + endpoint: str = "/settings/GetDomainAliases?format=camel" payload = {"searchTerm": "", "requestedPage": self.params.page_count, "pageSize": 100} return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) def add_tags_to_case_in_bulk(self) -> requests.Response: """Add tags to case in bulk""" - endpoint = "/cases/ExecuteBulkAddCaseTag" + endpoint: str = "/cases/ExecuteBulkAddCaseTag" payload = {"casesIds": self.params.case_ids, "tags": self.params.tags} return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) @@ -495,7 +495,7 @@ def get_cases_by_timestamp_filter(self) -> list[SingleJson]: end_dt_object = datetime.fromtimestamp(end_time_s, tz=UTC) end_time_iso = end_dt_object.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" while True: - endpoint = "/search/CaseSearchEverything?format=camel" + endpoint: str = "/search/CaseSearchEverything?format=camel" payload = { "pageSize": page_size, "startTime": start_time_iso, @@ -517,6 +517,505 @@ def get_cases_by_timestamp_filter(self) -> list[SingleJson]: return all_cases + def get_bearer_token(self) -> requests.Response: + """Get bearer token.""" + endpoint: str = "/auth/login" + payload = { + "password": self.params.smp_password, + "username": self.params.smp_username, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_api_record(self) -> requests.Response: + """Update api record.""" + endpoint: str = "/settings/addOrUpdateAPIKeyRecord" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.api_record + ) + + def get_store_data(self) -> SingleJson: + """Get store data.""" + endpoint_integrations = "/store/GetIntegrationsStoreData" + endpoint_powerups = "/store/GetPowerUpsStoreData" + + integrations_data = self._make_request( + HttpMethod.GET, + endpoint_integrations + ).json() + + powerups_data = self._make_request( + HttpMethod.GET, + endpoint_powerups + ).json() + + combined_response = { + "integrations": ( + integrations_data.get("integrations", []) + + powerups_data.get("integrations", []) + ) + } + + return combined_response + + def import_package(self) -> requests.Response: + """Import package.""" + endpoint: str = "/ide/ImportPackage" + data = { + "data": self.params.b64_blob, + "integrationIdentifier": self.params.integration_name, + "isCustom": True, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=data) + + def update_ide_item(self) -> requests.Response: + """Update ide item.""" + endpoint: str = "/ide/AddOrUpdateItem" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.input_json + ) + + def get_ide_cards(self) -> requests.Response: + """Get ide cards.""" + endpoint: str = "/ide/GetIdeItemCards" + return self._make_request(HttpMethod.GET, endpoint) + + def get_ide_item(self) -> requests.Response: + """Get ide item.""" + endpoint: str = "/ide/GetIdeItem" + query = { + "itemId": self.params.item_id, + "ideItemType": self.params.item_type, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=query) + + def add_custom_family(self) -> requests.Response: + """Add custom family.""" + endpoint: str = "/ontology/AddOrUpdateVisualFamily" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.visual_family + ) + + def get_mapping_rules(self) -> requests.Response: + """Get mapping rules.""" + endpoint: str = "/ontology/GetMappingRulesForSettings" + payload = { + "source": self.params.source, + "product": self.params.product, + "eventName": self.params.event_name, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_mapping_rules(self) -> requests.Response: + """Add mapping rules.""" + endpoint: str = "/ontology/AddOrUpdateMappingRules" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.mapping_rule + ) + + def set_mappings_visual_family(self) -> requests.Response: + """Set mappings visual family.""" + endpoint: str = "/ontology/AddOrUpdateProductToVisualizationFamilyRecord" + payload = { + "source": self.params.source, + "product": self.params.product or "", + "eventName": self.params.event_name, + "visualFamily": self.params.visual_family, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def export_playbooks(self) -> requests.Response: + """Export playbooks.""" + endpoint: str = "/playbooks/ExportDefinitions" + payload = {"identifiers": self.params.definitions} + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_playbooks(self) -> requests.Response: + """Import playbooks.""" + endpoint: str = "/playbooks/ImportDefinitions" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.playbooks + ) + + def create_playbook_category(self) -> requests.Response: + """Create playbook category.""" + endpoint: str = "/playbooks/AddOrUpdatePlaybookCategory" + req = { + "categoryState": 0, + "id": 0, + "isDefaultCategory": False, + "name": self.params.name, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=req) + + def get_playbook_categories(self) -> requests.Response: + """Get playbook categories.""" + endpoint: str = "/playbooks/GetWorkflowCategories" + return self._make_request(HttpMethod.GET, endpoint) + + def update_connector(self) -> requests.Response: + """Update connector.""" + endpoint: str = "/connectors/AddOrUpdateConnector" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.connector_data + ) + + def add_job(self) -> requests.Response: + """Add job.""" + endpoint: str = "/jobs/SaveOrUpdateJobData" + return self._make_request(HttpMethod.POST, endpoint, json_payload=self.params.job) + + def add_email_template(self) -> requests.Response: + """Add email template.""" + endpoint: str = "/settings/AddEmailTemplateRecords" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.template + ) + + def get_denylists(self) -> requests.Response: + """Get denylists.""" + endpoint: str = "/settings/GetAllModelBlockRecords" + return self._make_request(HttpMethod.GET, endpoint) + + def get_simulated_cases(self) -> requests.Response: + """Get simulated cases.""" + endpoint: str = "/attackssimulator/GetCustomCases" + return self._make_request(HttpMethod.GET, endpoint) + + def export_simulated_case(self) -> requests.Response: + """Export simulated cases""" + name = self.params.name + endpoint = f"/attackssimulator/ExportCustomCase/{name}" + return self._make_request(HttpMethod.GET, endpoint) + + def get_case_insights_comment_evidence(self) -> requests.Response: + """Get case insights using legacy API.""" + endpoint = f"/cases/insights/{self.params.case_id}" + return self._make_request(HttpMethod.GET, endpoint) + + def save_case_title_settings(self) -> requests.Response: + """Save case title settings.""" + endpoint: str = "/settings/SaveCaseTitleSettings" + payload = [{ + "value": self.params.value, + "order": 0, + }] + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_or_update_company_logo(self) -> requests.Response: + """Add or update company logo.""" + endpoint: str = "/settings/AddOrUpdateCompanyLogo" + payload = self.params.logo_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def attache_workflow_to_case(self) -> requests.Response: + """Attache workflow to case""" + endpoint: str = "/playbooks/AttachWorkflowToCase" + payload = { + "cyberCaseId": self.params.case_id, + "alertGroupIdentifier": self.params.alert_group_identifier, + "alertIdentifier": self.params.alert_identifier, + "wfName": self.params.wf_name, + "shouldRunAutomatic": True, + "originalWorkflowDefinitionIdentifier": self.params.original_wf_identifier, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_custom_case(self) -> requests.Response: + """Import custom case""" + endpoint: str = "/attackssimulator/ImportCustomCase" + payload = self.params.case_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def case_search_everything(self) -> requests.Response: + """Case search everything""" + endpoint: str = "/search/CaseSearchEverything" + payload = self.params.search_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_environment_action_definition(self) -> requests.Response: + """Get environment action definition""" + endpoint: str = "/settings/GetEnvironmentActionDefinitions" + payload = self.params.environment_action_data + return self._make_request(HttpMethod.GET, endpoint, json_payload=payload) + + def get_all_model_block_records(self) -> requests.Response: + """Get all model block records.""" + endpoint: str = "settings/GetAllModelBlockRecords" + return self.get_page_results(endpoint) + + def get_company_logo(self) -> requests.Response: + """Get company logo.""" + endpoint: str = "settings/GetCompanyLogo" + return self.get_page_results(endpoint) + + def get_case_title_settings(self) -> requests.Response: + """Get case title settings.""" + endpoint: str = "/settings/GetCaseTitleSettings" + return self._make_request(HttpMethod.GET, endpoint) + + def get_system_version(self) -> requests.Response: + """Get system version""" + endpoint: str = "/settings/GetSystemVersion" + return self._make_request(HttpMethod.GET, endpoint) + + def get_environment_group_names(self) -> requests.Response: + """Get environment group names""" + endpoint: str = "/environment-groups" + return self._make_request(HttpMethod.GET, endpoint) + + def get_env_dynamic_parameters(self) -> requests.Response: + """Get environment dynamic parameters""" + endpoint: str = "/settings/GetDynamicParameters" + return self._make_request(HttpMethod.GET, endpoint) + + def add_dynamic_env_param(self) -> requests.Response: + """Add dynamic environment parameter""" + endpoint: str = "/settings/AddOrUpdateDynamicParameters" + payload = { + "id": self.params.id, + "name": self.params.name, + "type": self.params.type, + "defaultValue": self.params.default_value, + "optionalValues": self.params.optional_json, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def install_integration(self) -> requests.Response: + """Install integration""" + endpoint: str = "/store/DownloadAndInstallIntegrationFromLocalStore" + payload = { + "name": self.params.integration_name, + "identifier": self.params.integration_identifier, + "version": self.params.version, + "isCertified": self.params.is_certified, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def export_package(self) -> requests.Response: + """Export package""" + endpoint = ( + f"/ide/ExportPackage/{self.params.integration_identifier}" + ) + return self._make_request(HttpMethod.GET, endpoint).content + + def get_integration_instance_settings(self) -> requests.Response: + """Get integration instance settings""" + endpoint = ( + "/integrations/GetIntegrationInstanceSettings/" + f"{self.params.instance_id}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def create_integrations_instance(self) -> requests.Response: + """Create integrations instance""" + endpoint: str = "/integrations/CreateIntegrationInstance" + payload = { + "environment": self.params.environment, + "integrationIdentifier": self.params.integration_identifier, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_page_results(self, url): + payload = {"searchTerm": "", "requestedPage": 0, "pageSize": 100} + res = self._make_request(HttpMethod.POST, url, json_payload=payload) + results = res.json()["objectsList"] + if res.json()["metadata"]["totalNumberOfPages"] > 1: + for page in range(res.json()["metadata"]["totalNumberOfPages"] - 1): + payload["requestedPage"] = page + 1 + res = self._make_request(HttpMethod.POST, url, json_payload=payload) + results.extend(res.json()["objectsList"]) + + return results + + def get_domains(self) -> requests.Response: + """Get domains""" + return self.get_page_results("/settings/GetDomainAliases") + + def update_domain(self) -> requests.Response: + """Update domain""" + endpoint: str = "/settings/AddOrUpdateDomainAliasesRecords" + payload = self.params.domain_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_environment_names(self) -> requests.Response: + """Get environment names""" + return self.get_page_results("/settings/GetEnvironmentNames") + + def get_environments(self) -> requests.Response: + """Get environments""" + return self.get_page_results("/settings/GetEnvironments") + + def import_environment(self) -> requests.Response: + """Import environment""" + endpoint: str = "/settings/AddOrUpdateEnvironmentRecords" + payload = self.params.environment_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def save_integration_instance_settings(self) -> requests.Response: + """Save integration instance settings""" + endpoint: str = "/store/SaveIntegrationConfigurationProperties" + payload = { + "instanceIdentifier": self.params.identifier, + **self.params.integration_data, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_simulated_case(self) -> requests.Response: + """Update domain""" + endpoint: str = "/attackssimulator/ImportCustomCase" + payload = self.params.case_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_case_tag(self) -> requests.Response: + """Add case tag""" + endpoint: str = "/settings/AddTagDefinitionsRecords" + payload = self.params.case_tag + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_case_stage(self) -> requests.Response: + """Add case stage""" + endpoint: str = "/settings/AddCaseStageDefinitionRecord" + payload = self.params.case_stage + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_case_alert(self) -> requests.Response: + """Get case alert""" + endpoint: str = "/settings/GetRootCauseCloseRecords" + return self._make_request(HttpMethod.GET, endpoint) + + def add_close_reason(self) -> requests.Response: + """Add close reason""" + endpoint: str = "/settings/AddOrUpdateRootCauseClose" + payload = self.params.close_reason + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_networks(self) -> requests.Response: + """Get networks""" + return self.get_page_results("/settings/GetNetworkDetails") + + def update_network(self) -> requests.Response: + """Update network""" + endpoint: str = "/settings/AddOrUpdateNetworkDetailsRecords" + payload = self.params.network_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_custom_lists(self) -> requests.Response: + """Get custom lists""" + endpoint: str = "/settings/GetTrackingListRecords" + return self._make_request(HttpMethod.GET, endpoint) + + def update_custom_list(self) -> requests.Response: + """Update custom list""" + endpoint: str = "/settings/AddorUpdateTrackingListRecords" + payload = self.params.tracking_list + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_blocklist(self) -> requests.Response: + """Update blocklist""" + endpoint: str = "/settings/AddOrUpdateModelBlockRecords" + payload = self.params.blocklist_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_sla_record(self) -> requests.Response: + """Update sla record""" + endpoint: str = "/settings/AddSlaDefinitionsRecord" + payload = self.params.sla_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def save_playbook(self) -> requests.Response: + """Save playbook""" + endpoint: str = "/playbooks/SaveWorkflowDefinitions" + payload = self.params.playbook_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbooks_workflow_menu_cards(self) -> requests.Response: + """Get playbooks workflow menu cards.""" + endpoint: str = "/playbooks/GetWorkflowMenuCards" + payload: list[int] = self.params.api_payload + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbooks_workflow_menu_cards_with_env(self) -> requests.Response: + """Get playbooks workflow menu cards with environment filter.""" + endpoint: str = "/playbooks/GetWorkflowMenuCardsWithEnvFilter" + payload: list[int] = self.params.api_payload + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbook_workflow_menu_cards_by_identifier(self) -> requests.Response: + """Get playbook workflow menu cards by identifier.""" + endpoint: str = ( + "/playbooks/GetWorkflowFullInfoByIdentifier/" + f"{self.params.playbook_identifier}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def get_playbook_workflow_menu_cards_by_identifier_with_env( + self, + ) -> requests.Response: + """Get playbook workflow menu cards by identifier with environment filter.""" + endpoint: str = ( + "/playbooks/GetWorkflowFullInfoWithEnvFilterByIdentifier/" + f"{self.params.playbook_identifier}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def get_installed_jobs(self) -> requests.Response: + """Get installed jobs.""" + endpoint: str = "/jobs/GetInstalledJobs" + return self._make_request(HttpMethod.GET, endpoint) + + def get_installed_connectors(self) -> requests.Response: + """Get installed connectors.""" + endpoint: str = "/connectors/GetConnectorsData" + return self._make_request( + HttpMethod.GET, + endpoint + ).json()["installedConnectors"] + + def get_visual_families(self) -> requests.Response: + """Get custom visual families.""" + endpoint: str = "/ontology/GetVisualFamilies" + return self._make_request(HttpMethod.GET, endpoint) + + def get_visual_family_by_id(self) -> requests.Response: + """Get custom visual family by ID.""" + endpoint: str = ( + f"/ontology/GetFamilyData/{self.params.family_id}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def get_ontology_records(self) -> requests.Response: + """Get ontology records""" + endpoint: str = "/ontology/GetOntologyStatusRecords" + return self.get_page_results(endpoint) + + def get_case_tags(self) -> requests.Response: + """Get case tags""" + endpoint: str = "/settings/GetTagDefinitionsRecords" + return self.get_page_results(endpoint) + + def get_case_stages(self) -> requests.Response: + """Get case stages""" + endpoint: str = "/settings/GetCaseStageDefinitionRecords" + return self.get_page_results(endpoint) + + def get_case_close_reasons(self) -> requests.Response: + """Get case close reasons""" + return self.get_case_alert() + + def get_block_lists_details(self) -> requests.Response: + """Get block lists details""" + endpoint: str = "/settings/GetBlockListDetails" + return self.get_page_results(endpoint) + + def get_sla_records(self) -> requests.Response: + """Get sla records""" + endpoint = "/settings/GetSlaDefinitionsRecords" + return self._make_request(HttpMethod.GET, endpoint) + + def get_installed_integrations(self) -> requests.Response: + """Get installed jobs.""" + endpoint: str = "/ide/GetIdeItemCards" + return self._make_request(HttpMethod.GET, endpoint).json() def get_case_close_comment(self, case_id: str | int) -> requests.Response: """Get case closure comment diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py index cc8ab21af..fc53f8312 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py @@ -16,6 +16,7 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING from TIPCommon.rest.custom_types import HttpMethod @@ -499,6 +500,7 @@ def assign_case_to_user(self) -> requests.Response: payload = {"casesIds": [self.params.case_id], "userName": self.params.assign_to} return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + @temporarily_remove_header(DATAPLANE_1P_HEADER) def get_email_template(self) -> requests.Response: """Get email template""" endpoint = "/system/settings/emailTemplates" @@ -748,6 +750,705 @@ def get_cases_by_timestamp_filter(self) -> list[SingleJson]: ) return self._paginate_results(initial_endpoint=initial_endpoint, root_response_key="cases") + def get_system_version(self) -> requests.Response: + """Get system version""" + endpoint = "/legacySystem:legacyGetSystemVersion" + return self._make_request(HttpMethod.GET, endpoint) + + def get_environment_group_names(self) -> requests.Response: + """Get environment group names""" + endpoint = "/environmentGroups" + return self._make_request(HttpMethod.GET, endpoint) + + def get_env_dynamic_parameters(self) -> requests.Response: + """Get env dynamic parameters""" + endpoint = "/settings/dynamicParameters" + return self._make_request(HttpMethod.GET, endpoint) + + # def add_dynamic_env_param(self) -> requests.Response: + # """Add dynamic env param""" + # endpoint = f"/settings/dynamicParameters/{self.params.id}" + # return self._make_request(HttpMethod.GET, endpoint) + + # QA Fixes + def add_dynamic_env_param(self) -> requests.Response: + """Add dynamic env param""" + endpoint = "/settings/dynamicParameters" + payload = { + "name": self.params.name, + "displayName": self.params.display_name, + "parameterType": self.params.parameter_type, + "defaultValue": self.params.default_value, + "optionalValuesJson": str(self.params.optional_json), + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_tags_to_case_in_bulks(self) -> requests.Response: + """Add tags to case in bulk""" + endpoint = "/cases:executeBulkAddTag" + payload = { + "propertiesStatus": { + "additionalProp1": 0, + "additionalProp2": 0, + "additionalProp3": 0, + }, + "displayName": self.params.name, + "parameterType": self.params.type, + "defaultValue": self.params.default_value, + "optionalValuesJson": str(self.params.optional_json), + } + return self._make_request(HttpMethod.PATCH, endpoint, json_payload=payload) + + def install_integration(self) -> requests.Response: + """Install integration""" + endpoint = f"/marketplaceIntegrations/{self.params.integration_identifier}:install" + payload = { + "overrideMapping": self.params.override_mapping, + "staging": self.params.stage, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def export_package(self) -> requests.Response: + """Export package""" + endpoint = ( + f"/download/integrations/{self.params.integration_identifier}:" + "export?alt=media" + ) + return self._make_request(HttpMethod.GET, endpoint).content + + def get_integration_instance_settings(self) -> requests.Response: + """Get integration instance settings""" + endpoint = ( + f"/integrations/{self.params.integration_identifier}/integrationInstances/" + f"{self.params.instance_id}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def create_integrations_instance(self) -> requests.Response: + """Create integrations instance""" + endpoint = ( + f"/integrations/{self.params.integration_identifier}/integrationInstances" + ) + payload = {"environment": self.params.environment} + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_domains(self) -> requests.Response: + """Create integrations instance""" + endpoint = "/system/settings/domains" + response = self._make_request(HttpMethod.GET, endpoint) + raw = response.text.strip() + if not raw: + return [] + try: + data = response.json() + except json.JSONDecodeError: + return [] + return data.get("domains", []) + + def update_domain(self) -> requests.Response: + """Update domain""" + endpoint = "/system/settings/domains" + payload = self.params.domain_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_environment_names(self) -> requests.Response: + """Get environment names""" + endpoint = "/system/settings/environments" + response = self._make_request(HttpMethod.GET, endpoint) + return [ + evn_name.get("displayName") + for evn_name in response.json().get("environments", []) + ] + + def get_environments(self) -> requests.Response: + """Get environments""" + endpoint = ( + "/system/settings/environments" + ) + return self._make_request(HttpMethod.GET, endpoint).json()["environments"] + + def import_environment(self) -> requests.Response: + """Import environment""" + endpoint = ( + "/system/settings/environments" + ) + payload = self.params.environment_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def save_integration_instance_settings(self) -> requests.Response: + """Save integration instance settings""" + endpoint = f"/integrations/{self.params.identifier}/integrationInstances" + payload = {"environment": self.params.environment} + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_simulated_case(self) -> requests.Response: + """Update domain""" + endpoint = "/legacyCases:importCustomCase" + payload = self.params.case_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_case_tag(self) -> requests.Response: + """Add case tag""" + endpoint = "/system/settings/caseTagDefinitions" + payload = self.params.case_tag + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def add_case_stage(self) -> requests.Response: + """Add case stage""" + endpoint = "/system/settings/caseStageDefinitions" + payload = self.params.case_stage + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + @temporarily_remove_header(DATAPLANE_1P_HEADER) + def get_case_alert(self) -> requests.Response: + """Get case alert""" + endpoint = "/system/settings/caseCloseDefinitions" + return self._make_request(HttpMethod.GET, endpoint) + + def add_close_reason(self) -> requests.Response: + """Add close reason""" + endpoint = "/system/settings/caseCloseDefinitions" + payload = self.params.close_reason + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_networks(self) -> requests.Response: + """Get networks""" + endpoint = "/system/settings/networks" + response = self._make_request(HttpMethod.GET, endpoint) + raw = response.text.strip() + if not raw: + return [] + try: + data = response.json() + except Exception: + return [] + return response.json() + + def update_network(self) -> requests.Response: + """Update network""" + endpoint = "/system/settings/networks" + payload = self.params.network_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_custom_lists(self) -> requests.Response: + """Get custom lists""" + endpoint = "/system/settings/customLists" + return self._make_request(HttpMethod.GET, endpoint) + + def update_custom_list(self) -> requests.Response: + """Update custom list""" + endpoint = f"/system/settings/customLists" + payload = self.params.tracking_list + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_blocklist(self) -> requests.Response: + """Update blocklist""" + endpoint = "/system/settings/soar-block-entities" + payload = self.params.blocklist_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_sla_record(self) -> requests.Response: + """Update sla record""" + endpoint = "/system/settings/slaDefinitions" + payload = self.params.sla_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def save_playbook(self) -> requests.Response: + """Save playbook""" + endpoint = "/legacyPlaybooks:legacySaveWorkflowDefinitions" + payload = self.params.playbook_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbooks_workflow_menu_cards(self) -> requests.Response: + """Get playbooks workflow menu cards.""" + endpoint: str = "/legacyPlaybooks:legacyGetWorkflowMenuCards" + payload: list[int] = self.params.api_payload + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbooks_workflow_menu_cards_with_env(self) -> requests.Response: + """Get playbooks workflow menu cards with environment filter.""" + endpoint: str = "/legacyPlaybooks:legacyGetWorkflowMenuCardsWithEnvFilter" + payload: list[int] = self.params.api_payload + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_playbook_workflow_menu_cards_by_identifier(self) -> requests.Response: + """Get playbook workflow menu cards by identifier.""" + endpoint: str = ( + "/legacyPlaybooks:legacyGetWorkflowFullInfoByIdentifier" + f"?WorkflowIdentifier={self.params.playbook_identifier}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def get_playbook_workflow_menu_cards_by_identifier_with_env( + self, + ) -> requests.Response: + """Get playbook workflow menu cards by identifier with environment filter.""" + endpoint: str = ( + "/legacyPlaybooks:legacyGetWorkflowFullInfoWithEnvFilterByIdentifier?" + f"WorkflowIdentifier={self.params.playbook_identifier}" + ) + return self._make_request(HttpMethod.GET, endpoint) + + def get_installed_jobs(self) -> requests.Response: + """Get installed jobs.""" + endpoint: str = "/integrations/-/jobs/-/jobInstances" + if self.params.job_instance_id: + endpoint += f"/{self.params.job_instance_id}" + return self._make_request(HttpMethod.GET, endpoint) + + def _enrich_connector_instances_with_params( + self, + response_json: SingleJson + ) -> list[SingleJson]: + """Helper to add parameters to a list of connector instances.""" + instances = ( + response_json.get("connector_instances") + or response_json.get("connectorInstances", []) + ) + connector_names = [item['name'] for item in instances] + + detailed_data_list = [] + for name in connector_names: + prefix = "projects/project/locations/location/instances/instance/" + clean_path = name.replace(prefix, "") + detail_response = self._make_request(HttpMethod.GET, f"/{clean_path}") + if detail_response.status_code == 200: + data = detail_response.json() + data["params"] = data["parameters"] + detailed_data_list.append(data) + else: + print(f"Failed to fetch details for {name}") + + return detailed_data_list + + def get_installed_connectors(self) -> requests.Response: + """Get installed connectors.""" + instance_id: str = self.params.connector_instance_id + endpoint: str = "/integrations/-/connectors/-/connectorInstances" + if instance_id: + endpoint += f"/{instance_id}" + return self._make_request(HttpMethod.GET, endpoint) + + response = self._make_request(HttpMethod.GET, endpoint).json() + return self._enrich_connector_instances_with_params(response) + + def get_connector_params(self) -> requests.Response: + """Get connector cards using legacy API""" + endpoint = ( + f"/integrations/{self.params.integration_name}" + "/connectors/-/connectorInstances" + ) + response = self._make_request(HttpMethod.GET, endpoint) + response.raise_for_status() + response_json = response.json() + + instances = ( + response_json.get("connector_instances") + or response_json.get("items") + or response_json.get("connectorInstances", []) + ) + + for instance in instances: + instance_name = instance.get("name") + if instance_name: + try: + details_response = self._make_request( + HttpMethod.GET, f"/{instance_name}" + ) + if details_response.status_code == 200: + details = details_response.json() + instance["parameters"] = details.get("parameters", []) + else: + instance["parameters"] = [] + except Exception: + instance["parameters"] = [] + + new_response = requests.Response() + new_response.status_code = 200 + new_response.headers["Content-Type"] = "application/json" + new_response._content = json.dumps(response_json).encode("utf-8") + return new_response + + @temporarily_remove_header(DATAPLANE_1P_HEADER) + def get_visual_families(self) -> requests.Response: + """Get custom visual families.""" + endpoint = "/ontologyRecords/-/visualFamilies" + return self._make_request(HttpMethod.GET, endpoint) + + def get_visual_family_by_id(self) -> requests.Response: + """Get custom visual family by ID.""" + endpoint = f"/ontologyRecords/-/visualFamilies/{self.params.family_id}" + return self._make_request(HttpMethod.GET, endpoint) + + def get_ontology_records(self) -> requests.Response: + """Get ontology records""" + endpoint = "/ontologyRecords" + return self._make_request(HttpMethod.GET, endpoint) + + def get_case_tags(self) -> requests.Response: + """Get case tags""" + endpoint = "/system/settings/caseTagDefinitions" + return self._make_request(HttpMethod.GET, endpoint) + + @temporarily_remove_header(DATAPLANE_1P_HEADER) + def get_case_stages(self) -> requests.Response: + """Get case stages""" + endpoint = "/system/settings/caseStageDefinitions" + return self._make_request(HttpMethod.GET, endpoint) + + def get_case_close_reasons(self) -> requests.Response: + """Get case close reasons""" + return self.get_case_alert() + + def get_block_lists_details(self) -> requests.Response: + """Get block lists details""" + endpoint = "/system/settings/soar-block-entities" + return self._make_request(HttpMethod.GET, endpoint) + + @temporarily_remove_header(DATAPLANE_1P_HEADER) + def get_sla_records(self) -> requests.Response: + """Get sla records""" + endpoint = "/system/settings/slaDefinitions" + return self._make_request(HttpMethod.GET, endpoint) + + def get_all_model_block_records(self) -> requests.Response: + """Get all model block records.""" + endpoint: str = "/entitiesBlocklists" + return self._make_request(HttpMethod.GET, endpoint) + + def get_company_logo(self) -> requests.Response: + """Get company logo.""" + endpoint: str = "/moduleSettings/CompanySetting/properties" + return self._make_request(HttpMethod.GET, endpoint) + + def get_case_title_settings(self) -> requests.Response: + """Get case title settings.""" + endpoint: str = "/moduleSettings/CaseTitleSettings/properties/" + return self._make_request(HttpMethod.GET, endpoint) + + def save_case_title_settings(self) -> requests.Response: + """Save case title settings.""" + endpoint: str = ( + f"/moduleSettings/CaseTitleSettings/properties/{self.params.display_name}" + ) + payload = { + "name": self.params.name, + "displayName": self.params.display_name, + "type": self.params.type, + "value": self.params.value, + } + return self._make_request(HttpMethod.PATCH, endpoint, json_payload=payload) + + def add_or_update_company_logo(self) -> requests.Response: + """Add or update company logo.""" + endpoint: str = "/moduleSettings/CompanySetting/properties/CompanyLogo" + payload = self.params.logo_data + return self._make_request(HttpMethod.PATCH, endpoint, json_payload=payload) + + def attache_workflow_to_case(self) -> requests.Response: + """Attache workflow to case.""" + endpoint: str = "/legacyPlaybooks:legacyAttachWorkflowToCase" + payload = { + "cyberCaseId": self.params.case_id, + "alertGroupIdentifier": self.params.alert_group_identifier, + "alertIdentifier": self.params.alert_identifier, + "wfName": self.params.wf_name, + "shouldRunAutomatic": True, + "originalWorkflowDefinitionIdentifier": self.params.original_wf_identifier, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_custom_case(self) -> requests.Response: + """Import custom case.""" + endpoint: str = "/legacyCases:importCustomCase" + payload = self.params.case_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def case_search_everything(self) -> requests.Response: + """Case search everything.""" + endpoint: str = "/legacySearches:legacyCaseSearchEverything" + payload = self.params.search_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def get_environment_action_definition(self) -> requests.Response: + """Get environment action definition.""" + endpoint: str = "/legacySoarSettings:legacyGetEnvironmentActionDefinitions" + payload = self.params.environment_action_data + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def export_simulated_case(self) -> requests.Response: + """Export simulated cases""" + name = self.params.name + endpoint = f"/legacySoarCases:exportCustomCase/{name}" + return self._make_request(HttpMethod.GET, endpoint) + + def get_bearer_token(self) -> requests.Response: + """Get bearer token.""" + endpoint = "/auth/login" + payload = { + "password": self.params.smp_password, + "username": self.params.smp_username, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def update_api_record(self) -> requests.Response: + """Update api record.""" + endpoint = "/settings/addOrUpdateAPIKeyRecord" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.api_record + ) + + # def get_store_data(self) -> SingleJson: + # """Get store data.""" + # endpoint = "/marketplaceIntegrations" + # return self._make_request(HttpMethod.GET, endpoint).json() + + #QA fixes + + def get_store_data(self) -> SingleJson: + """Get all store data and return it in the expected dictionary format.""" + endpoint = "/marketplaceIntegrations" + all_integrations = [] + next_page_token = None + + while True: + params = {} + if next_page_token: + params["pageToken"] = next_page_token + + response_json = self._make_request(HttpMethod.GET, endpoint, params=params).json() + + page_items = response_json.get("marketplaceIntegrations", []) + all_integrations.extend(page_items) + + next_page_token = response_json.get("nextPageToken") + + if not next_page_token: + break + + return { + "marketplaceIntegrations": all_integrations, + "nextPageToken": None + } + + def import_package(self) -> requests.Response: + """Import package.""" + endpoint = "/ide/ImportPackage" + data = { + "data": self.params.b64_blob, + "integrationIdentifier": self.params.integration_name, + "isCustom": True, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=data) + + def update_ide_item(self) -> requests.Response: + """Update ide item.""" + endpoint = "/ide/AddOrUpdateItem" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.input_json + ) + + def get_ide_cards(self) -> requests.Response: + """Get ide cards (1P compatible).""" + + class _DictResponse: + def __init__(self, payload: dict): + self._payload = payload + self.status_code = 200 + + def json(self): + """ + Must ALWAYS return: list[dict] + so this keeps working safely: + for x in response.json(): + x.get(...) + """ + if not self._payload: + return [] + + cards = self._payload.get("cards", []) + if isinstance(cards, list): + return cards + + return [] + + def raise_for_status(self): + return None + + connectors_endpoint = "/integrations/{identifier}/connectors" + actions_endpoint = "/integrations/{identifier}/actions" + jobs_endpoint = "/integrations/{identifier}/jobs" + managers_endpoint = "/integrations/{identifier}/managers" + + cards: list[dict] = [] + + connectors_response = self._make_request( + HttpMethod.GET, + connectors_endpoint.format(identifier=self.params.integration_name), + ) + connectors_data = safe_json_for_204(connectors_response, default_for_204={}) + cards.extend(connectors_data.get("connectors", [])) + + actions_response = self._make_request( + HttpMethod.GET, + actions_endpoint.format(identifier=self.params.integration_name), + ) + actions_data = safe_json_for_204(actions_response, default_for_204={}) + cards.extend(actions_data.get("actions", [])) + + jobs_response = self._make_request( + HttpMethod.GET, + jobs_endpoint.format(identifier=self.params.integration_name), + ) + jobs_data = safe_json_for_204(jobs_response, default_for_204={}) + cards.extend(jobs_data.get("jobs", [])) + + managers_response = self._make_request( + HttpMethod.GET, + managers_endpoint.format(identifier=self.params.integration_name), + ) + managers_data = safe_json_for_204(managers_response, default_for_204={}) + cards.extend(managers_data.get("managers", [])) + + payload = { + "cards": [ + { + "identifier": self.params.integration_name, + "cards": cards, + } + ] + } + + return _DictResponse(payload) + + def get_ide_item(self) -> requests.Response: + """Get ide item.""" + endpoint = "/ide/GetIdeItem" + query = { + "itemId": self.params.item_id, + "ideItemType": self.params.item_type, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=query) + + def add_custom_family(self) -> requests.Response: + """Add custom family.""" + endpoint = "/ontologyRecords/*/visualFamilies" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.visual_family + ) + + def get_mapping_rules(self) -> requests.Response: + """Get mapping rules.""" + endpoint = f"/ontologyRecords/{self.params.mr_id}/mappingRules" + return self._make_request(HttpMethod.GET, endpoint) + + def add_mapping_rules(self) -> requests.Response: + """Add mapping rules.""" + endpoint = "/ontologyRecords/*/mappingRules:save" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.mapping_rule + ) + + def set_mappings_visual_family(self) -> requests.Response: + """Set mappings visual family.""" + endpoint = "/ontologyRecords/*/mappingRules:family" + payload = { + "source": self.params.source, + "product": self.params.product or "", + "eventName": self.params.event_name, + "visualFamily": self.params.visual_family, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def export_playbooks(self) -> requests.Response: + """Export playbooks.""" + endpoint = "/legacyPlaybooks:legacyExportDefinitions" + payload = {"identifiers": self.params.definitions} + return self._make_request(HttpMethod.POST, endpoint, json_payload=payload) + + def import_playbooks(self) -> requests.Response: + """Import playbooks.""" + endpoint = "/legacyPlaybooks:legacyImportDefinitions" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.playbooks + ) + + def create_playbook_category(self) -> requests.Response: + """Create playbook category.""" + endpoint = "/legacyPlaybooks:legacyAddOrUpdatePlaybookCategory" + req = { + "categoryState": 0, + "id": 0, + "isDefaultCategory": False, + "name": self.params.name, + } + return self._make_request(HttpMethod.POST, endpoint, json_payload=req) + + def get_playbook_categories(self) -> requests.Response: + """Get playbook categories.""" + endpoint = "/legacyPlaybooks:legacyGetWorkflowCategories" + return self._make_request(HttpMethod.GET, endpoint) + + # def update_connector(self) -> requests.Response: + # """Update connector.""" + # endpoint = "/connectors/AddOrUpdateConnector" + # #integrations/{integration}/connectors + # return self._make_request( + # HttpMethod.POST, endpoint, json_payload=self.params.connector_data + # ) + + # QA Fixes + def update_connector(self) -> requests.Response: + """Update connector.""" + name = self.params.integration_name + connector = self.params.connector_id + endpoint = f"/integrations/{name}/connectors/{connector}/connectorInstances/" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.connector_data + ) + + # def add_job(self) -> requests.Response: + # """Add job.""" + # endpoint = "/jobs/SaveOrUpdateJobData" + # #integrations/{integration}/jobs/{job}/jonInstances/{jobInstance} + # return self._make_request(HttpMethod.POST, endpoint, json_payload=self.params.job) + + #QA fixes + def add_job(self) -> requests.Response: + """Add job.""" + integration = self.params.integration_name + job_full_name = self.params.job_name + try: + job_id = job_full_name.split("jobs/")[1].split("/")[0] + except (IndexError, AttributeError): + job_id = job_full_name + + endpoint = f"/integrations/{integration}/jobs/{job_id}/jobInstances/" + return self._make_request(HttpMethod.POST, endpoint, json_payload=self.params.job) + + def add_email_template(self) -> requests.Response: + """Add email template.""" + endpoint = "/system/settings/emailTemplates" + return self._make_request( + HttpMethod.POST, endpoint, json_payload=self.params.template + ) + + def get_denylists(self) -> requests.Response: + """Get denylists.""" + endpoint = "/system/settings/soarBlockEntities" + params = {"expand": "*"} if self.params.is_expand else None + return self._make_request(HttpMethod.GET, endpoint, params=params) + + @temporarily_remove_header(DATAPLANE_1P_HEADER) + def get_simulated_cases(self) -> requests.Response: + """Get simulated cases.""" + endpoint = "/legacyCases:getCustomCases" + return self._make_request(HttpMethod.GET, endpoint) + + def get_installed_integrations(self) -> requests.Response: + """Get installed integrations.""" + endpoint: str = "/integrations" + return self._make_request(HttpMethod.GET, endpoint).json()["integrations"] def get_case_close_comment(self, case_id: str | int) -> requests.Response: """Get case closure comment diff --git a/packages/tipcommon/TIPCommon/uv.lock b/packages/tipcommon/TIPCommon/uv.lock index 63c241b45..dbe3adc08 100644 --- a/packages/tipcommon/TIPCommon/uv.lock +++ b/packages/tipcommon/TIPCommon/uv.lock @@ -1,27 +1,27 @@ version = 1 -revision = 3 +revision = 2 requires-python = "==3.11.*" [[package]] name = "anyio" -version = "4.12.1" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -50,79 +50,77 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", size = 3475444, upload-time = "2026-03-25T23:34:38.944Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", size = 4218227, upload-time = "2026-03-25T23:34:40.871Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", size = 4381399, upload-time = "2026-03-25T23:34:42.609Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", size = 4217595, upload-time = "2026-03-25T23:34:44.205Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", size = 4380912, upload-time = "2026-03-25T23:34:46.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", size = 3409955, upload-time = "2026-03-25T23:34:48.465Z" }, ] [[package]] name = "google-api-core" -version = "2.29.0" +version = "2.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -131,9 +129,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/10/05572d33273292bac49c2d1785925f7bc3ff2fe50e3044cf1062c1dde32e/google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7", size = 177828, upload-time = "2026-01-08T22:21:39.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/0b/b6e296aff70bef900766934cf4e83eaacc3f244adb61936b66d24b204080/google_api_core-2.30.1.tar.gz", hash = "sha256:7304ef3bd7e77fd26320a36eeb75868f9339532bfea21694964f4765b37574ee", size = 176742, upload-time = "2026-03-30T22:50:52.637Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/b6/85c4d21067220b9a78cfb81f516f9725ea6befc1544ec9bd2c1acd97c324/google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9", size = 173906, upload-time = "2026-01-08T22:21:36.093Z" }, + { url = "https://files.pythonhosted.org/packages/43/86/a00ea4596780ef3f0721c1f073c0c5ae992da4f35cf12f0d8c92d19267a6/google_api_core-2.30.1-py3-none-any.whl", hash = "sha256:3be893babbb54a89c6807b598383ddf212112130e3d24d06c681b5d18f082e08", size = 173238, upload-time = "2026-03-30T22:48:50.586Z" }, ] [[package]] @@ -180,14 +178,14 @@ wheels = [ [[package]] name = "googleapis-common-protos" -version = "1.72.0" +version = "1.73.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506, upload-time = "2026-03-26T22:17:38.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556, upload-time = "2026-03-26T22:15:58.455Z" }, ] [[package]] @@ -214,14 +212,14 @@ wheels = [ [[package]] name = "httplib2" -version = "0.31.1" +version = "0.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/df/6eb1d485a513776bbdbb1c919b72e59b5acc51c5e7ef28ad1cd444e252a3/httplib2-0.31.1.tar.gz", hash = "sha256:21591655ac54953624c6ab8d587c71675e379e31e2cfe3147c83c11e9ef41f92", size = 250746, upload-time = "2026-01-13T12:14:14.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/d8/1b05076441c2f01e4b64f59e5255edc2f0384a711b6d618845c023dc269b/httplib2-0.31.1-py3-none-any.whl", hash = "sha256:d520d22fa7e50c746a7ed856bac298c4300105d01bc2d8c2580a9b57fb9ed617", size = 91101, upload-time = "2026-01-13T12:14:12.676Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, ] [[package]] @@ -250,38 +248,38 @@ wheels = [ [[package]] name = "proto-plus" -version = "1.27.0" +version = "1.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, ] [[package]] name = "protobuf" -version = "6.33.4" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, - { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, - { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, - { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] name = "pyasn1" -version = "0.6.2" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] [[package]] @@ -387,33 +385,32 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" +version = "0.15.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]] name = "tipcommon" -version = "2.3.3" +version = "2.3.6" source = { editable = "." } dependencies = [ { name = "google-api-python-client" }, @@ -452,26 +449,26 @@ dev = [ [[package]] name = "ty" -version = "0.0.13" +version = "0.0.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/dc/b607f00916f5a7c52860b84a66dc17bc6988e8445e96b1d6e175a3837397/ty-0.0.13.tar.gz", hash = "sha256:7a1d135a400ca076407ea30012d1f75419634160ed3b9cad96607bf2956b23b3", size = 4999183, upload-time = "2026-01-21T13:21:16.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/94/4879b81f8681117ccaf31544579304f6dc2ddcc0c67f872afb35869643a2/ty-0.0.26.tar.gz", hash = "sha256:0496b62405d62de7b954d6d677dc1cc5d3046197215d7a0a7fef37745d7b6d29", size = 5393643, upload-time = "2026-03-26T16:27:11.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/df/3632f1918f4c0a33184f107efc5d436ab6da147fd3d3b94b3af6461efbf4/ty-0.0.13-py3-none-linux_armv6l.whl", hash = "sha256:1b2b8e02697c3a94c722957d712a0615bcc317c9b9497be116ef746615d892f2", size = 9993501, upload-time = "2026-01-21T13:21:26.628Z" }, - { url = "https://files.pythonhosted.org/packages/92/87/6a473ced5ac280c6ce5b1627c71a8a695c64481b99aabc798718376a441e/ty-0.0.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f15cdb8e233e2b5adfce673bb21f4c5e8eaf3334842f7eea3c70ac6fda8c1de5", size = 9860986, upload-time = "2026-01-21T13:21:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9b/d89ae375cf0a7cd9360e1164ce017f8c753759be63b6a11ed4c944abe8c6/ty-0.0.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0819e89ac9f0d8af7a062837ce197f0461fee2fc14fd07e2c368780d3a397b73", size = 9350748, upload-time = "2026-01-21T13:21:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/9ad58518056fab344b20c0bb2c1911936ebe195318e8acc3bc45ac1c6b6b/ty-0.0.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de79f481084b7cc7a202ba0d7a75e10970d10ffa4f025b23f2e6b7324b74886", size = 9849884, upload-time = "2026-01-21T13:21:21.886Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c3/8add69095fa179f523d9e9afcc15a00818af0a37f2b237a9b59bc0046c34/ty-0.0.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4fb2154cff7c6e95d46bfaba283c60642616f20d73e5f96d0c89c269f3e1bcec", size = 9822975, upload-time = "2026-01-21T13:21:14.292Z" }, - { url = "https://files.pythonhosted.org/packages/a4/05/4c0927c68a0a6d43fb02f3f0b6c19c64e3461dc8ed6c404dde0efb8058f7/ty-0.0.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00be58d89337c27968a20d58ca553458608c5b634170e2bec82824c2e4cf4d96", size = 10294045, upload-time = "2026-01-21T13:21:30.505Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/6dc190838aba967557fe0bfd494c595d00b5081315a98aaf60c0e632aaeb/ty-0.0.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72435eade1fa58c6218abb4340f43a6c3ff856ae2dc5722a247d3a6dd32e9737", size = 10916460, upload-time = "2026-01-21T13:21:07.788Z" }, - { url = "https://files.pythonhosted.org/packages/04/40/9ead96b7c122e1109dfcd11671184c3506996bf6a649306ec427e81d9544/ty-0.0.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77a548742ee8f621d718159e7027c3b555051d096a49bb580249a6c5fc86c271", size = 10597154, upload-time = "2026-01-21T13:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7d/e832a2c081d2be845dc6972d0c7998914d168ccbc0b9c86794419ab7376e/ty-0.0.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da067c57c289b7cf914669704b552b6207c2cc7f50da4118c3e12388642e6b3f", size = 10410710, upload-time = "2026-01-21T13:21:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/31/e3/898be3a96237a32f05c4c29b43594dc3b46e0eedfe8243058e46153b324f/ty-0.0.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d1b50a01fffa140417fca5a24b658fbe0734074a095d5b6f0552484724474343", size = 9826299, upload-time = "2026-01-21T13:21:00.845Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/db2d852ce0ed742505ff18ee10d7d252f3acfd6fc60eca7e9c7a0288a6d8/ty-0.0.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f33c46f52e5e9378378eca0d8059f026f3c8073ace02f7f2e8d079ddfe5207e", size = 9831610, upload-time = "2026-01-21T13:21:05.842Z" }, - { url = "https://files.pythonhosted.org/packages/9e/61/149f59c8abaddcbcbb0bd13b89c7741ae1c637823c5cf92ed2c644fcadef/ty-0.0.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:168eda24d9a0b202cf3758c2962cc295878842042b7eca9ed2965259f59ce9f2", size = 9978885, upload-time = "2026-01-21T13:21:10.306Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cd/026d4e4af60a80918a8d73d2c42b8262dd43ab2fa7b28d9743004cb88d57/ty-0.0.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d4917678b95dc8cb399cc459fab568ba8d5f0f33b7a94bf840d9733043c43f29", size = 10506453, upload-time = "2026-01-21T13:20:56.633Z" }, - { url = "https://files.pythonhosted.org/packages/63/06/8932833a4eca2df49c997a29afb26721612de8078ae79074c8fe87e17516/ty-0.0.13-py3-none-win32.whl", hash = "sha256:c1f2ec40daa405508b053e5b8e440fbae5fdb85c69c9ab0ee078f8bc00eeec3d", size = 9433482, upload-time = "2026-01-21T13:20:58.717Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fd/e8d972d1a69df25c2cecb20ea50e49ad5f27a06f55f1f5f399a563e71645/ty-0.0.13-py3-none-win_amd64.whl", hash = "sha256:8b7b1ab9f187affbceff89d51076038363b14113be29bda2ddfa17116de1d476", size = 10319156, upload-time = "2026-01-21T13:21:03.266Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, + { url = "https://files.pythonhosted.org/packages/83/24/99fe33ecd7e16d23c53b0d4244778c6d1b6eb1663b091236dcba22882d67/ty-0.0.26-py3-none-linux_armv6l.whl", hash = "sha256:35beaa56cf59725fd59ab35d8445bbd40b97fe76db39b052b1fcb31f9bf8adf7", size = 10521856, upload-time = "2026-03-26T16:27:06.335Z" }, + { url = "https://files.pythonhosted.org/packages/55/97/1b5e939e2ff69b9bb279ab680bfa8f677d886309a1ac8d9588fd6ce58146/ty-0.0.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:487a0be58ab0eb02e31ba71eb6953812a0f88e50633469b0c0ce3fb795fe0fa1", size = 10320958, upload-time = "2026-03-26T16:27:13.849Z" }, + { url = "https://files.pythonhosted.org/packages/71/25/37081461e13d38a190e5646948d7bc42084f7bd1c6b44f12550be3923e7e/ty-0.0.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a01b7de5693379646d423b68f119719a1338a20017ba48a93eefaff1ee56f97b", size = 9799905, upload-time = "2026-03-26T16:26:55.805Z" }, + { url = "https://files.pythonhosted.org/packages/a1/1c/295d8f55a7b0e037dfc3a5ec4bdda3ab3cbca6f492f725bf269f96a4d841/ty-0.0.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:628c3ee869d113dd2bd249925662fd39d9d0305a6cb38f640ddaa7436b74a1ef", size = 10317507, upload-time = "2026-03-26T16:27:31.887Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/48b3875c5d2f48fe017468d4bbdde1164c76a8184374f1d5e6162cf7d9b8/ty-0.0.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63d04f35f5370cbc91c0b9675dc83e0c53678125a7b629c9c95769e86f123e65", size = 10319821, upload-time = "2026-03-26T16:27:29.647Z" }, + { url = "https://files.pythonhosted.org/packages/ff/28/cfb2d495046d5bf42d532325cea7412fa1189912d549dbfae417a24fd794/ty-0.0.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a53c4e6f6a91927f8b90e584a4b12bcde05b0c1870ddff8d17462168ad7947a", size = 10831757, upload-time = "2026-03-26T16:27:37.441Z" }, + { url = "https://files.pythonhosted.org/packages/26/bf/dbc3e42f448a2d862651de070b4108028c543ca18cab096b38d7de449915/ty-0.0.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:caf2ced0e58d898d5e3ba5cb843e0ebd377c8a461464748586049afbd9321f51", size = 11369556, upload-time = "2026-03-26T16:26:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/92/4c/6d2f8f34bc6d502ab778c9345a4a936a72ae113de11329c1764bb1f204f6/ty-0.0.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:384807bbcb7d7ce9b97ee5aaa6417a8ae03ccfb426c52b08018ca62cf60f5430", size = 11085679, upload-time = "2026-03-26T16:27:21.746Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f4/f3f61c203bc980dd9bba0ba7ed3c6e81ddfd36b286330f9487c2c7d041aa/ty-0.0.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2c766a94d79b4f82995d41229702caf2d76e5c440ec7e543d05c70e98bf8ab", size = 10900581, upload-time = "2026-03-26T16:27:24.39Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fd/3ca1b4e4bdd129829e9ce78677e0f8e0f1038a7702dccecfa52f037c6046/ty-0.0.26-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f41ac45a0f8e3e8e181508d863a0a62156341db0f624ffd004b97ee550a9de80", size = 10294401, upload-time = "2026-03-26T16:27:03.999Z" }, + { url = "https://files.pythonhosted.org/packages/de/20/4ee3d8c3f90e008843795c765cb8bb245f188c23e5e5cc612c7697406fba/ty-0.0.26-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:73eb8327a34d529438dfe4db46796946c4e825167cbee434dc148569892e435f", size = 10351469, upload-time = "2026-03-26T16:27:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b1/9fb154ade65906d4148f0b999c4a8257c2a34253cb72e15d84c1f04a064e/ty-0.0.26-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4bb53a79259516535a1b55f613ba1619e9c666854946474ca8418c35a5c4fd60", size = 10529488, upload-time = "2026-03-26T16:27:01.378Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b02b03b1862e27b64143db65946d68b138160a5b6bfea193bee0b8bbc34/ty-0.0.26-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f0e75edc1aeb1b4b84af516c7891f631254a4ca3dcd15e848fa1e061e1fe9da", size = 10999015, upload-time = "2026-03-26T16:27:34.636Z" }, + { url = "https://files.pythonhosted.org/packages/21/16/0a56b8667296e2989b9d48095472d98ebf57a0006c71f2a101bbc62a142d/ty-0.0.26-py3-none-win32.whl", hash = "sha256:943c998c5523ed6b519c899c0c39b26b4c751a9759e460fb964765a44cde226f", size = 9912378, upload-time = "2026-03-26T16:27:08.999Z" }, + { url = "https://files.pythonhosted.org/packages/60/c2/fef0d4bba9cd89a82d725b3b1a66efb1b36629ecf0fb1d8e916cb75b8829/ty-0.0.26-py3-none-win_amd64.whl", hash = "sha256:19c856d343efeb1ecad8ee220848f5d2c424daf7b2feda357763ad3036e2172f", size = 10863737, upload-time = "2026-03-26T16:27:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/888ebcb3c4d3b6b72d5d3241fddd299142caa3c516e6d26a9cd887dfed3b/ty-0.0.26-py3-none-win_arm64.whl", hash = "sha256:2cde58ccffa046db1223dc28f3e7d4f2c7da8267e97cc5cd186af6fe85f1758a", size = 10285408, upload-time = "2026-03-26T16:27:16.432Z" }, ] [[package]] diff --git a/packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl b/packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl new file mode 100644 index 000000000..6e4de8d87 Binary files /dev/null and b/packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl differ