Azure Storage Container

Create and Manage Azure Storage Container services.

IT Services · Azure Storage Container

Details

IDAzure Storage Container
ProviderMicrosoft
CategoryIT Services
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Azure Storage Container

Create and Manage Azure Storage Container services.
This integration was integrated and tested with version “2020-10-02” of Azure Storage Container

Configure Azure Storage Container in Cortex

Parameter Required
Storage account name True
Account SAS Token False
Use Azure Managed Identities False
Azure Managed Identities Client ID False
Shared Key False
Use system proxy settings False
Trust any certificate (not secure) False

## Shared Access Signatures Information (SAS)

  • The required SAS token for this integration should be taken at the storage account level and not at container level.

Shared Access Signatures (SAS) Permissions

In order to use the integration use-cases,
please make sure your SAS token contains the following permissions:

  1. ‘Blob’ and ‘File’ service.
  2. ‘Service’, ‘Container’ and ‘Object’ resource types.
  3. ‘Read’, ‘Write’, ‘Delete’, ‘List’, ‘Create’, ‘Add’, ‘Update’ and ‘Immutable storage’ permissions.
  4. ‘Blob versioning permissions’

Shared Key Permissions

To set the AllowSharedKeyAccess property for an Azure Storage account, a user needs to have the permissions to create and manage storage accounts.

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

azure-storage-container-list


List Containers under the specified storage account.

Base Command

azure-storage-container-list

Input

Argument Name Description Required
limit Number of Containers to retrieve. Default is 50. Default is 50. Optional
prefix Filters the results to return only Containers whose names begin with the specified prefix. Optional
page Page number. Default is 1. Default is 1. Optional

Context Output

Path Type Description
AzureStorageContainer.Container.name String Container name.

Command Example

!azure-storage-container-list prefix="xs"

Context Example

{
    "AzureStorageContainer": {
        "Container": {
            "name": "xsoar"
        }
    }
}

Human Readable Output

Containers List

Current page size: 50
Showing page 1 out others that may exist

Name
xsoar

azure-storage-container-create


Create a new Container under the specified account.

Base Command

azure-storage-container-create

Input

Argument Name Description Required
container_name The name of the Container to create. Rules for naming containers can be found here:
https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers–blobs–and-metadata
.
Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-create container_name="xsoar"

Human Readable Output

Container xsoar successfully created.

azure-storage-container-property-get


Retrieve properties for the specified Container.

Base Command

azure-storage-container-property-get

Input

Argument Name Description Required
container_name The name of the Container. Required

Context Output

Path Type Description
AzureStorageContainer.Container.Property.last_modified Date Last modified time of the container.
AzureStorageContainer.Container.Property.etag String The entity tag for the container.
AzureStorageContainer.Container.Property.lease_status String The lease status of the container.
AzureStorageContainer.Container.Property.lease_state String The lease state of the container.
AzureStorageContainer.Container.Property.has_immutability_policy String Indicates whether the container has an immutability policy set on it.
AzureStorageContainer.Container.Property.has_legal_hold String Indicates whether the container has a legal hold.
AzureStorageContainer.Container.name String Container name.

Command Example

!azure-storage-container-property-get container_name="xsoar"

Context Example

{
    "AzureStorageContainer": {
        "Container": {
            "Property": {
                "content_length": "0",
                "date": "2021-11-28T12:43:05",
                "default_encryption_scope": "$account-encryption-key",
                "deny_encryption_scope_override": "false",
                "etag": "\"0x8D9B26C9BBF026C\"",
                "has_immutability_policy": "false",
                "has_legal_hold": "false",
                "immutable_storage_with_versioning_enabled": "false",
                "last_modified": "2021-11-28T12:42:58",
                "lease_state": "available",
                "lease_status": "unlocked",
                "request_id": "00a9c6bf-f01e-007a-5255-e448bd000000",
                "server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
                "version": "2020-10-02"
            },
            "name": "xsoar"
        }
    }
}

Human Readable Output

Container xsoar Properties

Last Modified Etag Lease Status Lease State Has Immutability Policy Has Legal Hold
2021-11-28T12:42:58 “0x8D9B26C9BBF026C” unlocked available false false

azure-storage-container-delete


Marks the specified Container for deletion. The Container and any Blobs contained within it, will be deleted during garbage collection.

Base Command

azure-storage-container-delete

Input

Argument Name Description Required
container_name The name of the Container to delete. Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-delete container_name="xsoar"

Human Readable Output

Container xsoar successfully deleted.

azure-storage-container-blob-list


List Blobs under the specified container.

Base Command

azure-storage-container-blob-list

Input

Argument Name Description Required
container_name The name of the Container. Required
limit Number of blobs to retrieve. Default is 50. Default is 50. Optional
prefix Filters the results to return only blobs whose names begin with the specified prefix. Optional
page Page number. Default is 1. Default is 1. Optional

Context Output

Path Type Description
AzureStorageContainer.Container.Blob.name String Blob name.
AzureStorageContainer.Container.name String Container name.

Command Example

!azure-storage-container-blob-list container_name="xsoar"

Context Example

{
    "AzureStorageContainer": {
        "Container": {
            "Blob": [
                {
                    "name": "xsoar.txt"
                }
            ],
            "name": "xsoar"
        }
    }
}

Human Readable Output

xsoar Container Blobs List

Current page size: 50
Showing page 1 out others that may exist

Name
xsoar.txt

azure-storage-container-blob-create


Create a new Blob under the specified Container.

Base Command

azure-storage-container-blob-create

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
file_entry_id The entry ID of the file to upload as a new blob. Available from XSOAR war room while the context data contains file output. Required
blob_name The name of the Blob to create. Default is XSOAR file name. Optional

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-blob-create container_name="xsoar" file_entry_id="16488@b5e40781-86c8-4799-8f10-ace443e93234" blob_name="xsoar.txt"

Human Readable Output

Blob successfully created.

azure-storage-container-blob-update


Update the content of an existing Blob.

Base Command

azure-storage-container-blob-update

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
file_entry_id The entry ID of the file to upload as a new blob. Available from XSOAR war room while the context data contains file output. Required
blob_name The name of the Blob to update. Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-blob-update container_name="xsoar" file_entry_id="16488@b5e40781-86c8-4799-8f10-ace443e93234" blob_name="xsoar.txt"

Human Readable Output

Blob xsoar.txt successfully updated.

azure-storage-container-blob-get


Retrieve Blob from Container.

Base Command

azure-storage-container-blob-get

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the Blob to retrieve. Required

Context Output

Path Type Description
File.Size String The size of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.MD5 Unknown The MD5 hash of the file.
File.Extension String The file extension.

Command Example

!azure-storage-container-blob-get container_name="xsoar" blob_name="xsoar.txt"

Context Example

{
    "File": {
        "EntryID": "16508@b5e40781-86c8-4799-8f10-ace443e93234",
        "Extension": "txt",
        "Info": "text/plain; charset=utf-8",
        "MD5": "950eb0708854a661313dd150a643af8b",
        "Name": "xsoar.txt",
        "SHA1": "2f82d9a13f948a1ced93f9da85323d45fb2eedf8",
        "SHA256": "150296c0c1a1ca044fc132010b6049342c460f4a68386ab24de9b6a167e54765",
        "SHA512": "6d9deaacce47943767d19c8de846c9e57692543c90b4f45c3abbe4123380bd1665dc4bd9b4d0a99d09d3a0f8c67da842826181834223362f1c75f5f9e6c358ef",
        "SSDeep": "3:h8Kpl:Rpl",
        "Size": 11,
        "Type": "ASCII text, with no line terminators"
    }
}

Human Readable Output

azure-storage-container-blob-tag-get


Retrieve the tags of the specified Blob.

Base Command

azure-storage-container-blob-tag-get

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the blob. Required

Context Output

Path Type Description
AzureStorageContainer.Container.Blob.Tag.Key String Tag key.
AzureStorageContainer.Container.Blob.Tag.Value String Tag value.
AzureStorageContainer.Container.Blob.name String Blob name.
AzureStorageContainer.Container.name String Container name.

Command Example

!azure-storage-container-blob-tag-get container_name="xsoar" blob_name="xsoar.txt"

Context Example

{
    "AzureStorageContainer": {
        "Container": {
            "Blob": {
                "Tag": [
                    {
                        "Key": "tag-name-1",
                        "Value": "tag-value-1"
                    }
                ],
                "name": "xsoar.txt"
            },
            "name": "xsoar"
        }
    }
}

Human Readable Output

Blob xsoar.txt Tags

Key Value
tag-name-1 tag-value-1

azure-storage-container-blob-tag-set


Sets the tags for the specified Blob. The command replace the entire tags of the Blob and can be used to remove tags.

Base Command

azure-storage-container-blob-tag-set

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the blob. Required
tags Tags fields in JSON format: {“tag-name-1”: “tag-value-1”, “tag-name-2”: “tag-value-2”}. The tags fields may contain at most 10 tags. Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-blob-tag-set container_name="xsoar" blob_name="xsoar.txt" tags=`{ "tag-name-1": "tag-value-1" }`

Human Readable Output

xsoar.txt Tags successfully updated.

azure-storage-container-blob-delete


Marks the specified Blob for deletion. The Blob will be deleted during garbage collection.

Base Command

azure-storage-container-blob-delete

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the Blob to delete. Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-blob-delete container_name="xsoar" blob_name="xsoar.txt"

Human Readable Output

Blob xsoar.txt successfully deleted.

azure-storage-container-blob-property-get


Retrieve Blob properties.

Base Command

azure-storage-container-blob-property-get

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the blob. Required

Context Output

Path Type Description
AzureStorageContainer.Container.Blob.Property.last_modified Date Last modified time of the blob.
AzureStorageContainer.Container.Blob.Property.etag String The entity tag for the blob.
AzureStorageContainer.Container.Blob.Property.lease_status String The lease status of the blob.
AzureStorageContainer.Container.Blob.Property.lease_state String The lease state of the blob.
AzureStorageContainer.Container.Blob.Property.blob_type String The blob type.
AzureStorageContainer.Container.Blob.Property.content_length Number The size of the blob in bytes.
AzureStorageContainer.Container.Blob.Property.content_type String The content type specified for the blob. If no content type was specified, the default content type is application/octet-stream.
AzureStorageContainer.Container.Blob.Property.content-md5 String The MD5 hash of the blob content.
AzureStorageContainer.Container.Blob.Property.creation_time Date The date at which the blob was created.
AzureStorageContainer.Container.Blob.name String Blob name.

Command Example

!azure-storage-container-blob-property-get container_name="xsoar" blob_name="xsoar.txt"

Context Example

{
    "AzureStorageContainer": {
        "Container": {
            "Blob": {
                "Property": {
                    "accept_ranges": "bytes",
                    "access_tier": "Hot",
                    "access_tier_inferred": "true",
                    "blob_type": "BlockBlob",
                    "content_length": "11",
                    "content_md5": "lQ6wcIhUpmExPdFQpkOviw==",
                    "content_type": "application/octet-stream",
                    "creation_time": "2021-11-28T12:43:09",
                    "date": "2021-11-28T12:43:24",
                    "etag": "\"0x8D9B26CA6AD74B3\"",
                    "last_modified": "2021-11-28T12:43:17",
                    "lease_state": "available",
                    "lease_status": "unlocked",
                    "request_id": "8f64c941-501e-0097-4255-e403f0000000",
                    "server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
                    "server_encrypted": "true",
                    "version": "2020-10-02"
                },
                "name": "xsoar.txt"
            },
            "name": "xsoar"
        }
    }
}

Human Readable Output

Blob xsoar.txt Properties

Creation Time Last Modified Content Length Content Type Etag
2021-11-28T12:43:09 2021-11-28T12:43:17 11 application/octet-stream “0x8D9B26CA6AD74B3”

azure-storage-container-blob-property-set


Set Blob properties.

Base Command

azure-storage-container-blob-property-set

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
blob_name The name of the blob. Required
content_type Blob content type. Indicates the media type of the blob. Optional
content_md5 Blob MD5 hash value. Can be used by the client to check for content integrity. Optional
content_encoding Blob content encoding. Used to specify the compression algorithm of the blob content. Optional
content_language Blob content language. Describes the human languages of the blob content. Optional
content_disposition Blob content disposition. Conveys additional information about how to process the response payload, and also can be used to attach additional metadata. Optional
cache_control Modifies the cache control string for the blob. Indicates directives for caching in both requests and responses. Optional
request_id Request ID generated by the client and recorded in the analytics logs when storage analytics logging is enabled. Optional
lease_id Required if the blob has an active lease. Optional

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-blob-property-set container_name="xsoar" blob_name="xsoar.txt" content_type="text/plain"

Human Readable Output

Blob xsoar.txt properties successfully updated.

azure-storage-container-sas-create


Retrieve Blob properties.

Base Command

azure-storage-container-sas-create

Input

Argument Name Description Required
container_name The name of the Blob Container. Required
expiry_time Expiry time for sas token(hours). Required
signed_resources specifies which resources are accessible via the shared access signature. Options available c(container), b(blob), bv(blob version),bs(blob snapshot),d(directory) Required
signed_permissions The permissions that are associated with the shared access signature. The user is restricted to operations that are allowed by the permissions. Possible permission: r = Read, a=access, c=create, w=write. Also must follow the this order “racwdxltmeop”Example: r,c,a,w,rac, racw. Required
signed_ip specifies a public IP address or a range of public IP addresses from which to accept requests. Required
account_key The account key to create the SAS token with.  

Command Example

!azure-storage-container-sas-create account_key="TestAccountKey" expiry_time="1" signed_resources="test signed_permissions="test signed_ip="127.0.0.1"

azure-storage-container-block-public-access


Block public access to a container..

Base Command

azure-storage-container-block-public-access

Input

Argument Name Description Required
container_name The name of the Blob Container. Required

Context Output

There is no context output for this command.

Command Example

!azure-storage-container-block-public-access container_name="xsoar"

Human Readable Output

xsoar.txt Public access to container ‘{container_name}’ has been successfully blocked.

Configuration parameters

  • credentials — Storage account name
  • use_managed_identities — Use Azure Managed Identities
  • managed_identities_client_id
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • key — Account key
  • shared_key

Commands (15)

  • azure-storage-container-blob-create

    Create a new Blob under the specified Container.

  • azure-storage-container-blob-delete

    Marks the specified Blob for deletion. The Blob will be deleted during garbage collection.

  • azure-storage-container-blob-get

    Retrieve Blob from Container.

  • azure-storage-container-blob-list

    List Blobs under the specified container.

  • azure-storage-container-blob-property-get

    Retrieve Blob properties.

  • azure-storage-container-blob-property-set

    Set Blob properties.

  • azure-storage-container-blob-tag-get

    Retrieve the tags of the specified Blob.

  • azure-storage-container-blob-tag-set

    Sets the tags for the specified Blob. The command replace the entire tags of the Blob and can be used to remove tags.

  • azure-storage-container-blob-update

    Update the content of an existing Blob.

  • azure-storage-container-block-public-access

    Block public access to a container.

  • azure-storage-container-create

    Create a new Container under the specified account.

  • azure-storage-container-delete

    Marks the specified Container for deletion. The Container and any Blobs contained within it, will be deleted during garbage collection.

  • azure-storage-container-list

    List Containers under the specified storage account.

  • azure-storage-container-property-get

    Retrieve properties for the specified Container.

  • azure-storage-container-sas-create

    create SAS token for container.

import hashlib
import hmac
import os
import shutil
from collections.abc import Callable
from urllib import parse  # noqa: F401

import defusedxml.ElementTree as defused_ET
import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
from requests import Response

DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
account_sas_token = ""
storage_account_name = ""


class Client:
    """
    API Client
    """

    def __init__(
        self,
        server_url,
        verify,
        proxy,
        account_sas_token,
        storage_account_name,
        api_version,
        managed_identities_client_id: Optional[str] = None,
    ):
        self.ms_client = MicrosoftStorageClient(
            server_url, verify, proxy, account_sas_token, storage_account_name, api_version, managed_identities_client_id
        )

    def list_containers_request(self, limit: str = None, prefix: str = None, marker: str = None) -> str:
        """
        List Containers under the specified storage account.

        Args:
            limit (str): Number of Containers to retrieve.
            prefix (str): Filters the results to return only Containers whose name begins with the specified prefix.
            marker (str): Identifies the portion of the list to be returned.

        Returns:
            str: API response from Azure.

        """
        params = assign_params(maxresults=limit, prefix=prefix, comp="list", marker=marker)

        response = self.ms_client.http_request(method="GET", url_suffix="", params=params, resp_type="text")

        return response

    def create_container_request(self, container_name: str) -> Response:
        """
        Create a new Container under the specified account.

        Args:
            container_name (str): Container name.

        Returns:
            Response: API response from Azure.

        """
        params = assign_params(restype="container")

        response = self.ms_client.http_request(
            method="PUT", url_suffix=f"{container_name}", params=params, return_empty_response=True
        )

        return response

    def get_base_url(self):
        return self.ms_client._base_url

    def get_api_version(self):
        return self.ms_client._api_version

    def block_public_access(self, url, headers):
        return self.ms_client.http_request(method="PUT", headers=headers, full_url=url, return_empty_response=True)

    def get_container_properties_request(self, container_name: str) -> Response:
        """
        Retrieve properties for the specified Container.

        Args:
            container_name (str): Container name.

        Returns:
            Response: API response from Azure.

        """
        params = assign_params(restype="container")

        response = self.ms_client.http_request(
            method="GET", url_suffix=f"{container_name}", params=params, return_empty_response=True
        )

        return response

    def delete_container_request(self, container_name: str) -> Response:
        """
        Delete Container under the specified account.

        Args:
            container_name (str): Container name.

        Returns:
            Response: API response from Azure.

        """
        params = assign_params(restype="container")

        response = self.ms_client.http_request(
            method="DELETE", url_suffix=f"{container_name}", params=params, return_empty_response=True
        )

        return response

    def list_blobs_request(self, container_name: str, limit: str = None, prefix: str = None, marker: str = None) -> str:
        """
        List Blobs under the specified container.

        Args:
            container_name (str): Container name.
            limit (str): Number of Blob to retrieve.
            prefix (str): Filters the results to return only Blob whose name begins with the specified prefix.
            marker (str): Identifies the portion of the list to be returned.

        Returns:
            str: API response from Azure.

        """
        params = assign_params(
            container_name=container_name, maxresults=limit, prefix=prefix, restype="container", comp="list", marker=marker
        )

        response = self.ms_client.http_request(method="GET", url_suffix=f"{container_name}", params=params, resp_type="text")

        return response

    def put_blob_request(self, container_name: str, file_entry_id: str, file_name: str = None) -> Response:
        """
        Create or update Blob under the specified Container.

        Args:
            container_name (str): Container name.
            file_entry_id (str): File War room Entry ID.
            file_name (str): File name. Default is XSOAR file name.

        Returns:
            Response: API response from Azure.

        """

        xsoar_file_data = demisto.getFilePath(file_entry_id)  # Retrieve XSOAR system file path and name, given file entry ID.
        xsoar_system_file_path = xsoar_file_data["path"]
        blob_name = file_name if file_name else os.path.basename(xsoar_file_data["name"])

        headers = {"x-ms-blob-type": "BlockBlob"}

        try:
            shutil.copy(xsoar_system_file_path, blob_name)
        except FileNotFoundError:
            raise Exception(
                "Failed to prepare file for upload. The process of importing and copying the file data from XSOAR failed."
            )

        try:
            with open(blob_name, "rb") as file:
                response = self.ms_client.http_request(
                    method="PUT",
                    url_suffix=f"{container_name}/{blob_name}",
                    headers=headers,
                    return_empty_response=True,
                    data=file,
                )

        finally:
            os.remove(blob_name)

        return response

    def get_blob_request(self, container_name: str, blob_name: str) -> Response:
        """
        Retrieve Blob from Container.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.

        Returns:
            Response: API response from Azure.

        """
        response = self.ms_client.http_request(method="GET", url_suffix=f"{container_name}/{blob_name}", resp_type="response")

        return response

    def get_blob_tags_request(self, container_name: str, blob_name: str) -> str:
        """
        Retrieve the tags of the specified Blob.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.

        Returns:
            str: API response from Azure.

        """
        params = assign_params(comp="tags")

        response = self.ms_client.http_request(
            method="GET", url_suffix=f"{container_name}/{blob_name}", params=params, resp_type="text"
        )

        return response

    def set_blob_tags_request(self, container_name: str, blob_name: str, tags: str) -> Response:
        """
        Set the tags for the specified Blob.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.
            tags (str): XML tags data.

        Returns:
            Response: API response from Azure.

        """
        params = assign_params(comp="tags")

        response = self.ms_client.http_request(
            method="PUT", url_suffix=f"{container_name}/{blob_name}", params=params, return_empty_response=True, data=tags
        )

        return response

    def delete_blob_request(self, container_name: str, blob_name: str) -> Response:
        """
        Delete Blob from Container.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.

        Returns:
            Response: API response from Azure.

        """

        response = self.ms_client.http_request(
            method="DELETE", url_suffix=f"{container_name}/{blob_name}", return_empty_response=True
        )

        return response

    def get_blob_properties_request(self, container_name: str, blob_name: str) -> Response:
        """
        Retrieve Blob properties.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.

        Returns:
            Response: API response from Azure.

        """

        response = self.ms_client.http_request(method="HEAD", url_suffix=f"{container_name}/{blob_name}", resp_type="response")

        return response

    def set_blob_properties_request(self, container_name: str, blob_name: str, headers: dict) -> Response:
        """
        Set Blob properties.

        Args:
            container_name (str): Container name.
            blob_name (str): Blob name.
            headers (dict): Request Headers.

        Returns:
            Response: API response from Azure.

        """

        params = assign_params(comp="properties")
        response = self.ms_client.http_request(
            method="PUT", url_suffix=f"{container_name}/{blob_name}", params=params, headers=headers, return_empty_response=True
        )

        return response


def get_pagination_next_marker_element(limit: str, page: int, client_request: Callable, params: dict) -> str:
    """
    Get next marker element for request pagination.
    'marker' is a string value that identifies the portion of the list to be returned with the next list operation.
    The operation returns a NextMarker element within the response body if the list returned was not complete.
    This value may then be used as a query parameter in a subsequent call to request the next portion of the list items.
    Args:
        limit (str): Number of elements to retrieve.
        page (str): Page number.
        client_request (Callable): Client request function.
        params (dict): Request params.

    Returns:
        str: Next marker.

    """
    offset = int(limit) * (page - 1)
    response = client_request(limit=str(offset), **params)
    tree = ET.ElementTree(defused_ET.fromstring(response))
    root = tree.getroot()

    return root.findtext("NextMarker")  # type: ignore


def list_containers_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    List Containers under the specified storage account.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    limit = args.get("limit") or "50"
    prefix = args.get("prefix")
    page = arg_to_number(args.get("page") or "1")

    marker = ""
    readable_message = f"Containers List:\n Current page size: {limit}\n Showing page {page} out others that may exist"

    if page > 1:  # type: ignore
        marker = get_pagination_next_marker_element(
            limit=limit,
            page=page,  # type: ignore
            client_request=client.list_containers_request,
            params={"prefix": prefix},
        )

        if not marker:
            return CommandResults(
                readable_output=readable_message, outputs_prefix="AzureStorageContainer.Container", outputs=[], raw_response=[]
            )

    response = client.list_containers_request(limit, prefix, marker)

    tree = ET.ElementTree(defused_ET.fromstring(response))
    root = tree.getroot()

    raw_response = []
    outputs = []

    for element in root.iter("Container"):
        outputs.append({"name": element.findtext("Name")})
        data = {"Name": element.findtext("Name")}
        properties = {}
        for container_property in element.findall("Properties"):
            for attribute in container_property:
                properties[attribute.tag] = attribute.text

        data["Property"] = properties  # type: ignore
        raw_response.append(data)

    readable_output = tableToMarkdown(readable_message, outputs, headers=["name"], headerTransform=string_to_table_header)

    command_results = CommandResults(
        readable_output=readable_output,
        outputs_prefix="AzureStorageContainer.Container",
        outputs_key_field="name",
        outputs=outputs,
        raw_response=raw_response,
    )

    return command_results


def create_container_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Create a new Container under the specified account.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]

    container_name_regex = "^[a-z0-9](?!.*--)[a-z0-9-]{1,61}[a-z0-9]$"
    # Rules for naming containers can be found here:
    # https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata

    if not re.search(container_name_regex, container_name):
        raise Exception("The specified container name is invalid.")

    client.create_container_request(container_name)

    command_results = CommandResults(
        readable_output=f"Container {container_name} successfully created.",
    )

    return command_results


def convert_dict_time_format(data: dict, keys: list):
    """
    Convert dictionary data values time format.
    Args:
        data (dict): Data.
        keys (list): Keys list to convert

    """
    for key in keys:
        if data.get(key):
            time_value = datetime.strptime(data.get(key), DATE_FORMAT)  # type: ignore
            iso_time = FormatIso8601(time_value)
            data[key] = iso_time


def get_container_properties_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Retrieve properties for the specified Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]

    response = client.get_container_properties_request(container_name)

    raw_response = response.headers
    raw_response = dict(raw_response)  # Convert raw_response from 'CaseInsensitiveDict' to 'dict'

    response_headers = list(raw_response.keys())
    outputs = {}

    properties = transform_response_to_context_format(raw_response, response_headers)

    outputs["name"] = container_name
    outputs["Property"] = properties

    convert_dict_time_format(outputs["Property"], ["last_modified", "date"])

    readable_output = tableToMarkdown(
        f"Container {container_name} Properties:",
        outputs.get("Property"),
        headers=["last_modified", "etag", "lease_status", "lease_state", "has_immutability_policy", "has_legal_hold"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="AzureStorageContainer.Container",
        outputs_key_field="name",
        outputs=outputs,
        raw_response=raw_response,
    )


def delete_container_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Delete Container under the specified account.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]

    client.delete_container_request(container_name)

    command_results = CommandResults(
        readable_output=f"Container {container_name} successfully deleted.",
    )

    return command_results


def list_blobs_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    List Blobs under the specified container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    limit = args.get("limit") or "50"
    prefix = args.get("prefix")
    page = arg_to_number(args.get("page") or "1")

    marker = ""
    readable_message = (
        f"{container_name} Container Blobs List:\n Current page size: {limit}\n Showing page {page} out others that may exist"
    )
    if page > 1:  # type: ignore
        marker = get_pagination_next_marker_element(
            limit=limit,
            page=page,  # type: ignore
            client_request=client.list_blobs_request,
            params={"container_name": container_name, "prefix": prefix},
        )  # type: ignore

        if not marker:
            return CommandResults(
                readable_output=readable_message, outputs_prefix="AzureStorageContainer.Container", outputs=[], raw_response=[]
            )

    response = client.list_blobs_request(container_name, limit, prefix, marker)

    tree = ET.ElementTree(defused_ET.fromstring(response))
    root = tree.getroot()

    raw_response = []
    blobs = []

    for element in root.iter("Blob"):
        data = {"name": element.findtext("Name")}
        blobs.append(dict(data))
        properties = {}
        for blob_property in element.findall("Properties"):
            for attribute in blob_property:
                properties[attribute.tag] = attribute.text

        data["Property"] = properties  # type: ignore
        raw_response.append(data)

    outputs = {"name": container_name, "Blob": blobs}
    readable_output = tableToMarkdown(
        readable_message, outputs.get("Blob"), headers="name", headerTransform=string_to_table_header
    )

    command_results = CommandResults(
        readable_output=readable_output,
        outputs_prefix="AzureStorageContainer.Container",
        outputs_key_field="name",
        outputs=outputs,
        raw_response=raw_response,
    )

    return command_results


def create_blob_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Create a new Blob under the specified Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    file_entry_id = args["file_entry_id"]
    blob_name = args.get("blob_name")

    client.put_blob_request(container_name, file_entry_id, blob_name)

    command_results = CommandResults(
        readable_output="Blob successfully created.",
    )

    return command_results


def update_blob_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Update Blob under the specified Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    file_entry_id = args["file_entry_id"]
    blob_name = args["blob_name"]

    client.put_blob_request(container_name, file_entry_id, blob_name)

    command_results = CommandResults(
        readable_output=f"Blob {blob_name} successfully updated.",
    )

    return command_results


def get_blob_command(client: Client, args: Dict[str, Any]) -> fileResult:  # type: ignore
    """
    Retrieve Blob from Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        fileResult: XSOAR File Result.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]

    response = client.get_blob_request(container_name, blob_name)

    return fileResult(filename=blob_name, data=response.content)


def get_blob_tags_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Retrieve the tags of the specified Blob.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]

    response = client.get_blob_tags_request(container_name, blob_name)

    tree = ET.ElementTree(defused_ET.fromstring(response))
    root = tree.getroot()

    raw_response = []
    outputs = {"name": container_name, "Blob": {"name": blob_name}}

    for element in root.iter("Tag"):
        tag = {"Key": element.findtext("Key"), "Value": element.findtext("Value")}
        raw_response.append(dict(tag))

    outputs["Blob"]["Tag"] = raw_response

    readable_output = tableToMarkdown(
        f"Blob {blob_name} Tags:", outputs["Blob"]["Tag"], headers=["Key", "Value"], headerTransform=pascalToSpace
    )

    command_results = CommandResults(
        readable_output=readable_output,
        outputs_prefix="AzureStorageContainer.Container",
        outputs_key_field="name",
        outputs=outputs,
        raw_response=raw_response,
    )

    return command_results


def create_set_tags_request_body(tags: dict) -> str:
    """
    Create XML request body for set blob tags.
    Args:
        tags (dict): Tags data. Key represents tag name , and value represents tag Value.

    Returns:
        str: Set tags request body.

    """
    top = ET.Element("Tags")

    tag_set = ET.SubElement(top, "TagSet")

    for key, value in tags.items():
        tag = ET.SubElement(tag_set, "Tag")
        tag_key = ET.SubElement(tag, "Key")
        tag_key.text = key

        tag_value = ET.SubElement(tag, "Value")
        tag_value.text = value

    return ET.tostring(top, encoding="unicode")


def set_blob_tags_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Sets the tags for the specified Blob.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]
    tags = args["tags"]

    try:
        tags = json.loads(tags)
    except ValueError:
        raise ValueError("Failed to parse tags argument. Please provide valid JSON format tags data.")

    xml_data = create_set_tags_request_body(tags)

    client.set_blob_tags_request(container_name, blob_name, xml_data)

    command_results = CommandResults(
        readable_output=f"{blob_name} Tags successfully updated.",
    )

    return command_results


def delete_blob_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Delete Blob from Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]

    client.delete_blob_request(container_name, blob_name)

    command_results = CommandResults(
        readable_output=f"Blob {blob_name} successfully deleted.",
    )

    return command_results


def transform_response_to_context_format(data: dict, keys: list) -> dict:
    """
    Transform API response data to suitable XSOAR context data.
    Remove 'x-ms' prefix and replace '-' to '_' for more readable and conventional variables.
    Args:
        data (dict): Data to exchange.
        keys (list): Keys to filter.

    Returns:
        dict: Processed data.

    """
    return {key.replace("x-ms-", "").replace("-", "_").lower(): value for key, value in data.items() if key in keys}


def get_blob_properties_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Retrieve Blob properties.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]

    response = client.get_blob_properties_request(container_name, blob_name)

    raw_response = response.headers
    raw_response = dict(raw_response)  # Convert raw_response from 'CaseInsensitiveDict' to 'dict'

    response_headers = list(raw_response.keys())
    outputs = {}

    properties = transform_response_to_context_format(raw_response, response_headers)

    outputs["name"] = container_name
    outputs["Blob"] = {"name": blob_name, "Property": properties}

    convert_dict_time_format(outputs["Blob"]["Property"], ["creation_time", "last_modified", "date"])

    readable_output = tableToMarkdown(
        f"Blob {blob_name} Properties:",
        outputs.get("Blob").get("Property"),  # type: ignore
        headers=["creation_time", "last_modified", "content_length", "content_type", "etag"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="AzureStorageContainer.Container",
        outputs_key_field="name",
        outputs=outputs,
        raw_response=raw_response,
    )


def set_blob_properties_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """
    Set Blob properties.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """
    container_name = args["container_name"]
    blob_name = args["blob_name"]
    content_type = args.get("content_type")
    content_md5 = args.get("content_md5")
    content_encoding = args.get("content_encoding")
    content_language = args.get("content_language")
    content_disposition = args.get("content_disposition")
    cache_control = args.get("cache_control")
    request_id = args.get("request_id")
    lease_id = args.get("lease_id")

    headers = remove_empty_elements(
        {
            "x-ms-blob-cache-control": cache_control,
            "x-ms-blob-content-type": content_type,
            "x-ms-blob-content-md5": content_md5,
            "x-ms-blob-content-encoding": content_encoding,
            "x-ms-blob-content-language": content_language,
            "x-ms-blob-content-disposition": content_disposition,
            "x-ms-client-request-id": request_id,
            "x-ms-lease-id": lease_id,
        }
    )

    client.set_blob_properties_request(container_name, blob_name, headers)

    command_results = CommandResults(
        readable_output=f"Blob {blob_name} properties successfully updated.",
    )

    return command_results


# generate signature helper function
def generate_sas_signature(
    account_key: str, cr: str, sp: str, signedstart: str, expiry: str, sr: str, api_version: str, sip: str = ""
) -> str:
    """
    Generate sas token for Container

    Args:
        account_key: account key of conatiner.
        cr: canonicalizedResource.
        sp:SignedPermissions.
        signedstart: start time for sas token.
        expiry : Expiry time for sas token.
    Returns:
        sas token

    """
    if sip is None:
        sip = ""
    string_to_sign = (
        sp
        + "\n"  # noqa: W504
        + signedstart
        + "\n"
        + expiry
        + "\n"
        + cr
        + "\n"
        + ""
        + "\n"
        + sip
        + "\n"
        + "https"
        + "\n"
        + api_version
        + "\n"
        + sr
        + "\n"
        + ""
        + "\n"
        + ""
        + "\n"
        + ""
        + "\n"
        + ""
        + "\n"
        + ""
        + "\n"
        + ""
    ).encode("UTF-8")
    signed_hmac_sha256 = hmac.new(base64.b64decode(account_key), string_to_sign, hashlib.sha256)
    sig = base64.b64encode(signed_hmac_sha256.digest())

    token = {"sp": sp, "st": signedstart, "se": expiry, "sip": sip, "spr": "https", "sv": api_version, "sr": sr, "sig": sig}

    sas_token = urllib.parse.urlencode(token)
    return sas_token


def check_valid_permission(valid_permissions: str, input_permissions: str) -> bool:
    """
    Check the permissions follows valid permission order.

    Args:
        valid_permissions : valid permissions order
        input_permissions : permissions given

    Returns:
        bool

    """
    permissions_length = len(input_permissions)
    if len(valid_permissions) < permissions_length:
        return False
    for i in range(permissions_length - 1):
        last = valid_permissions.rindex(input_permissions[i])
        first = valid_permissions.index(input_permissions[i + 1])
        if last == -1 or first == -1 or last > first:
            return False
    return True


def generate_sas_token_command(client: Client, args: dict) -> CommandResults:  # type: ignore # pragma: no cover
    """
    Generate sas url for Container.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs and raw response for XSOAR.

    """
    api_version = client.get_api_version()
    container_name = args.get("container_name")
    signed_resource = args.get("signed_resources")
    signed_permissions = args.get("signed_permissions")
    valid_permissions = "racwdxltmeop"
    signed_ip = args.get("signed_ip")
    # Check Permissions
    if check_valid_permission(valid_permissions, signed_permissions):  # type: ignore
        # Set start time
        signed_start = str((datetime.utcnow() - timedelta(minutes=2)).strftime("%Y-%m-%dT%H:%M:%SZ"))
        account_key = demisto.params().get("key") or args.get("account_key")

        if not account_key:
            raise DemistoException("An account key must be given to generate the SAS token.")

        time_taken = int(args.get("expiry_time"))  # type: ignore
        signed_expiry = str((datetime.utcnow() + timedelta(hours=time_taken)).strftime("%Y-%m-%dT%H:%M:%SZ"))
        url_suffix = f"{container_name}"
        canonicalized_resource = f"/blob/{storage_account_name}/{container_name}"
        url = client.get_base_url() + url_suffix
        sas_token = generate_sas_signature(
            account_key,
            canonicalized_resource,
            signed_permissions,  # type: ignore # noqa
            signed_start,  # type: ignore # noqa
            signed_expiry,
            signed_resource,  # type: ignore # noqa
            api_version,
            signed_ip,  # type: ignore # noqa
        )  # type: ignore
        sas_url = f"{url}?{sas_token}"
        res_data = sas_url
        markdown = tableToMarkdown("Azure storage container SAS url", res_data, headers=[container_name])
        result = CommandResults(
            readable_output=markdown,
            outputs_prefix="AzureStorageContainer.Container",
            outputs_key_field=container_name,
            outputs=res_data,
        )
        return result
    else:
        raise DemistoException("Permissions are invalid or in wrong order. Correct order for permissions are 'racwdl'")


def block_public_access_command(client: Client, args: Dict[str, Any]):
    """
    Block container's public access.

    Args:
        client (Client): Azure Blob Storage API client.
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs and raw response for XSOAR.

    """

    account_key = demisto.params().get("shared_key", {}).get("password")
    if not account_key:
        raise KeyError("The 'shared_key' parameter must be provided.")
    else:
        account_name = demisto.params().get("credentials", {}).get("identifier")
        container_name = args.get("container_name")
        api_version = client.get_api_version()
        request_url = f"https://{account_name}.blob.core.windows.net/{container_name}?restype=container&comp=acl"
        request_date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")

        # string for API signature
        string_to_sign = (
            f"PUT\n"  # HTTP Verb
            f"\n"  # Content-Encoding
            f"\n"  # Content-Language
            f"\n"  # Content-Length
            f"\n"  # Content-MD5
            f"\n"  # Content-Type
            f"\n"  # Date
            f"\n"  # If-Modified-Since
            f"\n"  # If-Match
            f"\n"  # If-None-Match
            f"\n"  # If-Unmodified-Since
            f"\n"  # Range
            f"x-ms-date:{request_date}\n"
            f"x-ms-version:{api_version}\n"
            f"/{account_name}/{container_name}\n"
            "comp:acl\n"
            "restype:container"
        )

        # create signature token for API auth
        try:
            decoded_key = base64.b64decode(account_key)
            signature = hmac.new(decoded_key, string_to_sign.encode("utf-8"), hashlib.sha256).digest()
            encoded_signature = base64.b64encode(signature).decode("utf-8")
        except ValueError:
            raise ValueError("Incorrect shared key provided")
        authorization_header = f"SharedKey {account_name}:{encoded_signature}"
        headers = {
            "x-ms-date": request_date,
            "Authorization": authorization_header,
            "x-ms-version": api_version,
        }
        response = client.block_public_access(request_url, headers)
        demisto.debug(f"Response from block public access API:- {response}")
        command_results = CommandResults(
            readable_output=f"Public access to container '{container_name}' has been successfully blocked",
        )
        return command_results


def test_module(client: Client) -> None:
    """
    Tests API connectivity and authentication.
    Args:
        client (Client): Azure Blob Storage API client.
    Returns:
        str : 'ok' if test passed, anything else will fail the test.
    """
    try:
        client.list_containers_request()
    except Exception as exception:
        if "Error in API call" in str(exception):
            return return_results("Authorization Error: make sure API Credentials are correctly set")

        if "Error Type" in str(exception):
            return return_results(
                "Verify that the storage account name is correct and that you have access to the server from your host."
            )

        raise exception

    return_results("ok")
    return None


def main() -> None:  # pragma: no cover
    """
    Main function
    """
    params: Dict[str, Any] = demisto.params()
    args: Dict[str, Any] = demisto.args()
    verify_certificate: bool = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    global account_sas_token
    global storage_account_name
    account_sas_token = params.get("credentials", {}).get("password")
    storage_account_name = params["credentials"]["identifier"]
    managed_identities_client_id = get_azure_managed_identities_client_id(params)
    api_version = "2020-10-02"
    base_url = f"https://{storage_account_name}.blob.core.windows.net/"
    # supported api versions can be found here:
    # https://learn.microsoft.com/en-us/rest/api/storageservices/previous-azure-storage-service-versions
    command = demisto.command()
    demisto.debug(f"Command being called is {command}")

    try:
        client: Client = Client(
            base_url,
            verify_certificate,
            proxy,
            account_sas_token,
            storage_account_name,
            api_version,
            managed_identities_client_id,
        )

        commands = {
            "azure-storage-container-list": list_containers_command,
            "azure-storage-container-create": create_container_command,
            "azure-storage-container-property-get": get_container_properties_command,
            "azure-storage-container-delete": delete_container_command,
            "azure-storage-container-blob-list": list_blobs_command,
            "azure-storage-container-blob-create": create_blob_command,
            "azure-storage-container-blob-update": update_blob_command,
            "azure-storage-container-blob-get": get_blob_command,
            "azure-storage-container-blob-tag-get": get_blob_tags_command,
            "azure-storage-container-blob-tag-set": set_blob_tags_command,
            "azure-storage-container-blob-delete": delete_blob_command,
            "azure-storage-container-blob-property-get": get_blob_properties_command,
            "azure-storage-container-blob-property-set": set_blob_properties_command,
            "azure-storage-container-sas-create": generate_sas_token_command,
            "azure-storage-container-block-public-access": block_public_access_command,
        }

        if command == "test-module":
            test_module(client)
        elif command in commands:
            return_results(commands[command](client, args))
        else:
            raise NotImplementedError(f"{command} command is not implemented.")

    except Exception as e:
        return_error(str(e))


from MicrosoftAzureStorageApiModule import *  # noqa: E402

if __name__ in ["__main__", "builtin", "builtins"]:
    main()