KeeperSecretsManager

Use the Keeper Secrets Manager integration to manage secrets and protect sensitive data through Keeper Vault.

Authentication & Identity Management · Keeper Secrets Manager

Details

IDKeeperSecretsManager
ProviderKeeper Security Inc.
CategoryAuthentication & Identity Management
From Version6.5.0
Docker Imagedemisto/keeper-ksm:1.0.0.10221838
Supported ModulesAgentix XSIAM

README

Manage Secrets and Protect Sensitive Data through Keeper Vault.
This integration was integrated and tested with version 16.3.5 of Keeper Secrets Manager.

Configure Keeper Secrets Manager in Cortex

Parameter Description Required
KSM Configuration The KSM config to use for connection. True
Trust any certificate (not secure) When ‘trust any certificate’ is selected, the integration ignores TLS/SSL certificate validation errors. Use to test connection issues or connect to a server without a valid certificate. False
Fetches credentials Fetches credentials from login records. False
Concat username to credential object name Use to make the credential object unique in case of duplicate names in different folders/secrets. False
A comma-separated list of credential names to fetch. Partial names are not supported. If left empty, all credentials will be fetched. False

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.

ksm-find-files


Search for records by full or partial file name match.

Base Command

ksm-find-files

Input

Argument Name Description Required
file_name File name text to search for. Required
partial_match Search for partial file name match. Optional

Context Output

Path Type Description
KeeperSecretsManager.Files.record_uid String Record UID.
KeeperSecretsManager.Files.file_uid String File UID.
KeeperSecretsManager.Files.file_name String File Name.
KeeperSecretsManager.Files.file_size String File Size.

Command example

!ksm-find-files file_name="blank.txt"

Context Example

{
    "KeeperSecretsManager": {
        "Files": [
            {
                "file_name": "blank.txt",
                "file_size": 5,
                "file_uid": "Z8F-lSBHmTiMMDQrRiBjUA",
                "record_uid": "PNby7a3Mrh4OfPdkpdfFsA"
            },
            {
                "file_name": "blank.txt",
                "file_size": 5,
                "file_uid": "xdsQvfDzD-W38_alIwJnMg",
                "record_uid": "4FTOiJx-m31hDIlmief1Cg"
            }
        ]
    }
}

Human Readable Output

Records with attachments

Record Details

file_name file_size file_uid record_uid
blank.txt 5 Z8F-lSBHmTiMMDQrRiBjUA PNby7a3Mrh4OfPdkpdfFsA
blank.txt 5 xdsQvfDzD-W38_alIwJnMg 4FTOiJx-m31hDIlmief1Cg

ksm-find-records


Search for records by full or partial title match.

Base Command

ksm-find-records

Input

Argument Name Description Required
title Title text to search for. Required
partial_match Search for partial title match. Optional

Context Output

Path Type Description
KeeperSecretsManager.Records.uid String Record UID.
KeeperSecretsManager.Records.type String Record Type.
KeeperSecretsManager.Records.title String Record Title.

Command example

!ksm-find-records title="file"

Context Example

{
    "KeeperSecretsManager": {
        "Records": [
            {
                "uid": "WcizqXQGsk0Jho48Mn52MQ",
                "type": "file",
                "title": "files1"
            },
            {
                "uid": "Px5xVljXRZ1dPYMQ9Yv05Q",
                "type": "file",
                "title": "files2"
            }
        ]
    }
}

Human Readable Output

Records

Record Details

uid type tite
WcizqXQGsk0Jho48Mn52MQ file files1
Px5xVljXRZ1dPYMQ9Yv05Q file files2

ksm-get-field


Use this command to get field value from Keeper record.

Base Command

ksm-get-field

Input

Argument Name Description Required
notation Keeper KSM notation URI. Required

Context Output

Path Type Description
KeeperSecretsManager.Field.field String Extracted field value.

Command example

!ksm-get-field notation="keeper://6LJgiVzzD4ZJuxQYj_wN9A/field/login"

Context Example

{
    "KeeperSecretsManager": {
        "Field": "admin"
    }
}

Human Readable Output

admin

ksm-get-file


Use this command to fetch the file attachment as a File.

Base Command

ksm-get-file

Input

Argument Name Description Required
file_uid File UID to search for. Required
record_uid Record UID to search for files. Search all records if empty. Optional

Context Output

There is no context output for this command.

Command example

!ksm-get-file file_uid="bZs6l8Hx9zkrRPYVFyuYLA"

Context Example

{
    "File": {
        "EntryID": "36@21232f297a57a5a743894a0e4a801fc3$&$9b10a24b-f008-42e0-8554-d24397e91996",
        "Extension": "txt",
        "Info": "text/plain; charset=utf-8",
        "MD5": "8e15625d6c158ec48f374efb77bd2714",
        "Name": "blank.txt",
        "SHA1": "6184d6847d594ec75c4c07514d4bb490d5e166df",
        "SHA256": "ff71cf74abb3ccb005b8b64371725db15edc42c1ad33413bbe561b2da3c85ef9",
        "SHA512": "c7503ab487c392e8cbbe756fd7340bd83214c351dfd48a2c597285267621976a5e321fa88923917b8a2fb6895727da0a42123233258b4da485b0de7c91ba8610",
        "SSDeep": "3:wO:wO",
        "Size": 5,
        "Type": "ASCII text, with no line terminators"
    }
}

Human Readable Output

ksm-get-infofile


Use this command to fetch the file attachment as an Info File.

Base Command

ksm-get-infofile

Input

Argument Name Description Required
file_uid File UID to search for. Required
record_uid Record UID to search for files. Search all records if empty. Optional

Context Output

There is no context output for this command.

Command example

!ksm-get-infofile file_uid="bZs6l8Hx9zkrRPYVFyuYLA"

Context Example

{
    "InfoFile": {
        "EntryID": "40@21232f297a57a5a743894a0e4a801fc3$&$9b10a24b-f008-42e0-8554-d24397e91996",
        "Extension": "txt",
        "Info": "text/plain; charset=utf-8",
        "Name": "blank.txt",
        "Size": 5,
        "Type": "ASCII text, with no line terminators"
    }
}

Human Readable Output

ksm-list-credentials


Use this command to list all credentials in your Keeper Vault that are shared to the KSM application.

Base Command

ksm-list-credentials

Input

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

Context Output

Path Type Description
KeeperSecretsManager.Creds.uid String Record UID.
KeeperSecretsManager.Creds.title String Record Title.
KeeperSecretsManager.Creds.name String Username.

Command example


#### Context Example

```json
{
    "KeeperSecretsManager": {
        "Creds": [
            {
                "name": "IIS Admin",
                "uid": "6LJgiVzzD4ZJuxQYj_wN9A",
                "user": "admin"
            },
            {
                "name": "nginx Admin",
                "uid": "7W6exgzq_OeVF6Xh1EJ29g",
                "user": "admin"
            }
        ]
    }
}

Human Readable Output

Credentials

Credential Details

name uid user
IIS Admin 6LJgiVzzD4ZJuxQYj_wN9A admin
nginx Admin 7W6exgzq_OeVF6Xh1EJ29g admin

ksm-list-files


Use this command to list all records that have file attachments.

Base Command

ksm-list-files

Input

Argument Name Description Required
record_uids A comma-separated list of record UIDs to search. If left empty all records with file attachments will be listed. Optional

Context Output

Path Type Description
KeeperSecretsManager.Files.record_uid String Record UID.
KeeperSecretsManager.Files.file_uid String File UID.
KeeperSecretsManager.Files.file_name String File Name.
KeeperSecretsManager.Files.file_size String File Size.

Command example


#### Context Example

```json
{
    "KeeperSecretsManager": {
        "Files": [
            {
                "file_name": "blank.txt",
                "file_size": 5,
                "file_uid": "bZs6l8Hx9zkrRPYVFyuYLA",
                "record_uid": "RXd1m_fKO2XnAWzeUawM5A"
            },
            {
                "file_name": "blank.txt",
                "file_size": 5,
                "file_uid": "xdsQvfDzD-W38_alIwJnMg",
                "record_uid": "4FTOiJx-m31hDIlmief1Cg"
            }
        ]
    }
}

Human Readable Output

Records with attachments

Record Details

file_name file_size file_uid record_uid
blank.txt 5 bZs6l8Hx9zkrRPYVFyuYLA RXd1m_fKO2XnAWzeUawM5A
blank.txt 5 xdsQvfDzD-W38_alIwJnMg 4FTOiJx-m31hDIlmief1Cg

ksm-list-records


Use this command to list all records from your Keeper Vault that are shared to the application.

Base Command

ksm-list-records

Input

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

Context Output

Path Type Description
KeeperSecretsManager.Records.uid String Record UID.
KeeperSecretsManager.Records.type String Record Type.
KeeperSecretsManager.Records.title String Record Title.

Command example


#### Context Example

```json
{
    "KeeperSecretsManager": {
        "Records": [
            {
                "title": "files2",
                "type": "file",
                "uid": "RXd1m_fKO2XnAWzeUawM5A"
            },
            {
                "title": "files1",
                "type": "file",
                "uid": "4FTOiJx-m31hDIlmief1Cg"
            },
            {
                "title": "IIS Admin",
                "type": "login",
                "uid": "6LJgiVzzD4ZJuxQYj_wN9A"
            },
            {
                "title": "nginx Admin",
                "type": "login",
                "uid": "7W6exgzq_OeVF6Xh1EJ29g"
            }
        ]
    }
}

Human Readable Output

Records

Record Details

title type uid
files2 file RXd1m_fKO2XnAWzeUawM5A
files1 file 4FTOiJx-m31hDIlmief1Cg
IIS Admin login 6LJgiVzzD4ZJuxQYj_wN9A
nginx Admin login 7W6exgzq_OeVF6Xh1EJ29g

Configuration parameters

  • credentials — KSM Configuration (required)
  • insecure — Trust any certificate (not secure)
  • isFetchCredentials — Fetches credentials
  • concat_username_to_cred_name — Concat username to credential object name
  • credential_names — A comma-separated list of credential names to fetch.

Commands (8)

  • ksm-find-files

    Search for records by full or partial file name match.

  • ksm-find-records

    Search for records by full or partial title match.

  • ksm-get-field

    Use this command to get field value from Keeper record.

  • ksm-get-file

    Use this command to fetch the file attachment as a File.

  • ksm-get-infofile

    Use this command to fetch the file attachment as an Info File.

  • ksm-list-credentials

    Use this command to list all credentials in your Keeper Vault that are shared to the KSM application.

  • ksm-list-files

    Use this command to list all records that have file attachments.

  • ksm-list-records

    Use this command to list all records from your Keeper Vault that are shared to the application.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

"""Keeper Secrets Manager Integration for Cortex XSOAR (aka Demisto)

Manage Secrets and Protect Sensitive Data through Keeper Vault.

"""

from CommonServerUserPython import *  # noqa

import urllib3
import traceback
from typing import Any
from keeper_secrets_manager_core import SecretsManager
from keeper_secrets_manager_core.storage import InMemoryKeyValueStorage


# Disable insecure warnings
urllib3.disable_warnings()  # pylint: disable=no-member


""" CONSTANTS """

APP_NAME = "keeper-secrets-manager"
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR

metadata_collector = YMLMetadataCollector(
    integration_name="KeeperSecretsManager",
    description="Use the Keeper Secrets Manager integration to manage secrets and protect"
    " sensitive data through Keeper Vault.",
    detailed_description="<p>Keeper Secrets Manager API is available for administrators"
    " and developers to integrate Keeper Vault data into their applications."
    " This integration fetches credentials. For more information,"
    ' see <a href="https://xsoar.pan.dev/docs/reference/articles/managing-credentials">'
    "Managing Credentials</a>.</p>"
    " <p>This integration was integrated and tested with"
    " version 16.3.5 of Keeper Secrets Manager.</p>",
    display="Keeper Secrets Manager",
    category="Authentication",
    default_classifier=None,
    default_mapper_in=None,
    deprecated=None,
    docker_image="demisto/keeper-ksm:1.0.0.33394",
    fromversion="6.5.0",
    is_runonce=False,
    integration_subtype="python3",
    integration_type="python",
    is_feed=False,
    is_fetch=False,
    long_running=False,
    long_running_port=False,
    tests=["No tests"],
    conf=[
        ConfKey(
            name="credentials",
            display="KSM Configuration",
            additional_info="The KSM config to use for connection.",
            required=True,
            key_type=ParameterTypes.TEXT_AREA_ENCRYPTED,
        ),
        ConfKey(
            name="insecure",
            display="Trust any certificate (not secure)",
            additional_info="When 'trust any certificate' is selected,"
            " the integration ignores TLS/SSL certificate validation errors."
            " Use to test connection issues or connect to a server without a valid"
            " certificate.",
            required=False,
            key_type=ParameterTypes.BOOLEAN,
        ),
        ConfKey(
            name="isFetchCredentials",
            display="Fetches credentials",
            additional_info="Fetches credentials from login records.",
            default_value=False,
            required=False,
            key_type=ParameterTypes.BOOLEAN,
        ),
        ConfKey(
            name="concat_username_to_cred_name",
            display="Concat username to credential object name",
            additional_info="Use to make the credential object unique"
            " in case of duplicate names in different folders/secrets.",
            required=False,
            key_type=ParameterTypes.BOOLEAN,
        ),
        ConfKey(
            name="credential_names",
            display="A comma-separated list of credential names to fetch.",
            additional_info="Partial names are not supported. If left empty, all credentials will be fetched.",
            required=False,
            key_type=ParameterTypes.TEXT_AREA,
        ),
        # ConfKey(name="proxy",
        #         display="Use system proxy settings",
        #         required=False,
        #         key_type=ParameterTypes.BOOLEAN),
    ],
)

""" CLIENT CLASS """


class Client:
    """Client class to interact with the service API"""

    def __init__(self, credentials: str, insecure: bool):
        self.credentials = credentials
        self.verify_ssl_certs = not insecure
        try:
            config = InMemoryKeyValueStorage(credentials)
        except Exception:
            demisto.debug("Failed to initialize KSM configuration. Invalid credentials.")
            raise ValueError("Failed to initialize KSM configuration. Invalid credentials.")
        try:
            self.secrets_manager = SecretsManager(config=config, verify_ssl_certs=self.verify_ssl_certs)
        except Exception as e:
            demisto.debug("Failed to initialize KSM Client. " + str(e))
            raise

    def ksm_get_field(self, notation: str) -> str:
        """Returns a simple python dict with the information provided
        in the input (notation).

        :type notation: ``str``
        :param notation: Keeper URI notation to select desired field

        :return: field value
        :rtype: ``dict``
        """

        result = self.secrets_manager.get_notation(notation)

        return result or ""

    def ksm_list_records(self) -> list[dict[str, str]]:
        result = []
        records = self.secrets_manager.get_secrets() or []
        for r in records:
            result.append({"uid": r.uid, "type": r.type, "title": r.title})
        return result

    def ksm_find_records(self, title: str, partial_match: bool = True) -> list[dict[str, str]]:
        result = []
        title = title.lower()
        records = self.secrets_manager.get_secrets() or []
        for r in records:
            record_title: str = str(r.title).lower() if r.title else ""
            if record_title == title or (partial_match and title and record_title.__contains__(title)):
                result.append({"uid": r.uid, "type": r.type, "title": r.title})
        return result

    def ksm_fetch_credentials(self) -> list[dict[str, str]]:
        result = []
        records = self.secrets_manager.get_secrets() or []
        for r in records:
            if r.type == "login":
                result.append({"user": r.get_standard_field_value("login", True), "password": r.password, "name": r.title})
        return result

    def list_credentials(self) -> list[dict[str, str]]:
        result = []
        records = self.secrets_manager.get_secrets() or []
        for r in records:
            if r.type == "login":
                result.append({"name": r.title, "user": r.get_standard_field_value("login", True), "uid": r.uid})
        return result

    def ksm_list_files(self, record_uids: list[str]) -> list[dict[str, str]]:
        result = []
        records = self.secrets_manager.get_secrets(record_uids) or []
        for r in records:
            files = r.files or []
            for f in files:
                result.append(
                    {"record_uid": r.uid, "file_uid": f.f.get("fileUid", ""), "file_name": f.name or f.title, "file_size": f.size}
                )
        return result

    def ksm_find_files(self, file_name: str, partial_match: bool = True) -> list[dict[str, str]]:
        result = []
        file_name = file_name.lower()
        records = self.secrets_manager.get_secrets() or []
        for r in records:
            files = r.files or []
            for f in files:
                fname: str = str(f.name).lower() if f.name else ""
                if fname == file_name or (partial_match and file_name and fname.__contains__(file_name)):
                    result.append(
                        {"record_uid": r.uid, "file_uid": f.f.get("fileUid", ""), "file_name": f.name, "file_size": f.size}
                    )
        return result

    def ksm_get_file(self, record_uid: str, file_uid: str) -> list[tuple[str, bytes]]:
        result = []
        record_filter = [record_uid] if record_uid else []
        records = self.secrets_manager.get_secrets(record_filter) or []
        for r in records:
            files = r.files or []
            for f in files:
                if file_uid == f.f.get("fileUid", ""):
                    result.append((f.name or f.title, f.get_file_data()))
        return result


""" HELPER FUNCTIONS """


""" COMMAND FUNCTIONS """


def fetch_credentials(client: Client):
    params: dict = demisto.params()
    args: dict = demisto.args()
    credentials_str = params.get("credential_names")
    credentials_names_from_configuration = argToList(credentials_str)
    credentials_name = args.get("identifier")
    concat_username_to_cred_name = argToBoolean(params.get("concat_username_to_cred_name") or "false")
    demisto.debug("Name of credential used: ", credentials_name)
    demisto.debug("Name of credentials used: ", credentials_names_from_configuration)
    credentials = []

    try:
        credentials = client.ksm_fetch_credentials()
    except Exception as e:
        demisto.debug(f"Could not fetch credentials. Error: {e}")
        credentials = []

    if concat_username_to_cred_name:
        for i in range(len(credentials)):
            credentials[i]["name"] = f"{credentials[i].get('name', '')}_{credentials[i].get('user', '')}"

    if credentials_name:
        credentials = list(filter(lambda c: c.get("name", "") == credentials_name, credentials))
        if len(credentials) > 1:
            credentials = credentials[:1]
        # Important - always return a list containing up to one set of credentials.
        # If no list or a list with more than one element will be returned,
        # the credentials tab will fail to load.
    elif credentials_names_from_configuration:
        credentials = list(filter(lambda c: c.get("name", "") in credentials_names_from_configuration, credentials))

    demisto.credentials(credentials)


LIST_CREDENTIALS_OUTPUTS = [
    OutputArgument(name="uid", output_type=str, description="Record UID."),
    OutputArgument(name="title", output_type=str, description="Record Title."),
    OutputArgument(name="name", output_type=str, description="Username."),
]


@metadata_collector.command(
    command_name="ksm-list-credentials",
    outputs_prefix="KeeperSecretsManager.Creds",
    outputs_list=LIST_CREDENTIALS_OUTPUTS,
    restore=True,
    description="Use this command to list all credentials in your Keeper Vault that are shared to the KSM application.",
)
def list_credentials_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """Lists all credentials available to the KSM application.
    :param client: the client object with the given params
    :return: the credentials info without the explicit password
    """
    demisto.debug("list_credentials_command - command_name: " + str(kwargs.get("command_name", "")))
    creds_list = client.list_credentials()
    markdown = "### Credentials\n"
    markdown += tableToMarkdown("Credential Details", creds_list)

    results = CommandResults(
        readable_output=markdown,
        outputs_prefix="KeeperSecretsManager.Creds",
        outputs_key_field="name",
        outputs=creds_list,
        raw_response=creds_list,
    )
    return results


@metadata_collector.command(
    command_name="ksm-get-field",
    outputs_prefix="KeeperSecretsManager.Field",
    inputs_list=[InputArgument(name="notation", required=True, description="Keeper KSM notation URI.")],
    description="Use this command to get field value from Keeper record.",
)
def get_field_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """Get field command - returns field value.

    Args:
        client (Client): KeeperSecretsManager client to use.
        notation (str):  The Keeper notation URI used to find the field in the record. required.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the field value.

    Context Outputs:
        field (str): Extracted field value.
    """

    demisto.debug("get_field_command - command_name: " + str(kwargs.get("command_name", "")))
    notation = args.get("notation", None)
    if not notation:
        raise ValueError("notation URI not specified")

    result = client.ksm_get_field(notation)
    readable_output = f"## {result}"

    results = CommandResults(
        readable_output=readable_output, outputs_prefix="KeeperSecretsManager.Field", outputs_key_field="", outputs=result
    )
    return results


GET_RECORDS_OUTPUTS = [
    OutputArgument(name="uid", output_type=str, description="Record UID."),
    OutputArgument(name="type", output_type=str, description="Record Type."),
    OutputArgument(name="title", output_type=str, description="Record Title."),
]


@metadata_collector.command(
    command_name="ksm-list-records",
    outputs_prefix="KeeperSecretsManager.Records",
    outputs_list=GET_RECORDS_OUTPUTS,
    restore=True,
    description="Use this command to list all records from your Keeper Vault that are shared to the application.",
)
def list_records_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """List records command - returns list of record info.

    Args:
        client (Client): KeeperSecretsManager client to use.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records info.

    :rtype: ``CommandResults``
    """

    demisto.debug("list_records_command - command_name: " + str(kwargs.get("command_name", "")))
    records = client.ksm_list_records()
    markdown = "### Records\n"
    markdown += tableToMarkdown("Record Details", records)

    results = CommandResults(
        readable_output=markdown, outputs_prefix="KeeperSecretsManager.Records", outputs_key_field="uid", outputs=records
    )
    return results


@metadata_collector.command(
    command_name="ksm-find-records",
    outputs_prefix="KeeperSecretsManager.Records",
    outputs_list=GET_RECORDS_OUTPUTS,
    restore=True,
    inputs_list=[
        InputArgument(name="title", required=True, description="Title text to search for."),
        InputArgument(name="partial_match", required=False, default=False, description="Search for partial title match."),
    ],
    description="Search for records by full or partial title match.",
)
def find_records_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """Find records command - returns list of record info.

    Args:
        client (Client): KeeperSecretsManager client to use.
        title (str): Title text to search for.
        partial_match (bool): Flag for partial title match.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records info.

    :rtype: ``CommandResults``
    """

    demisto.debug("find_records_command - command_name: " + str(kwargs.get("command_name", "")))

    title = args.get("title") or ""
    partial_match = args.get("partial_match", None)
    partial_match = bool(partial_match)
    demisto.debug(f"Find records with title={title} and partial_match={partial_match}")

    records = client.ksm_find_records(title, partial_match)
    markdown = "### Records\n"
    markdown += tableToMarkdown("Record Details", records)

    results = CommandResults(
        readable_output=markdown, outputs_prefix="KeeperSecretsManager.Records", outputs_key_field="uid", outputs=records
    )
    return results


GET_FILES_OUTPUTS = [
    OutputArgument(name="record_uid", output_type=str, description="Record UID."),
    OutputArgument(name="file_uid", output_type=str, description="File UID."),
    OutputArgument(name="file_name", output_type=str, description="File Name."),
    OutputArgument(name="file_size", output_type=str, description="File Size."),
]


@metadata_collector.command(
    command_name="ksm-list-files",
    outputs_prefix="KeeperSecretsManager.Files",
    outputs_list=GET_FILES_OUTPUTS,
    restore=True,
    inputs_list=[
        InputArgument(
            name="record_uids",
            required=False,
            default="",
            description="A comma-separated list of record UIDs to search."
            " If left empty all records with file attachments will be listed.",
        )
    ],
    description="Use this command to list all records that have file attachments.",
)
def list_files_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """List files command - returns list of record and file info.

    Args:
        client (Client): KeeperSecretsManager client to use.
        record_uids (str):  The CSV list of record UIDs to search for files.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records and files info.

    :rtype: ``CommandResults``
    """

    demisto.debug("list_files_command - command_name: " + str(kwargs.get("command_name", "")))
    record_uids_str = args.get("record_uids") or ""
    record_uids = argToList(record_uids_str)
    demisto.debug("List files for record UIDs: ", record_uids_str)

    records = client.ksm_list_files(record_uids)
    markdown = "### Records with attachments\n"
    markdown += tableToMarkdown("Record Details", records)

    results = CommandResults(
        readable_output=markdown, outputs_prefix="KeeperSecretsManager.Files", outputs_key_field="file_uid", outputs=records
    )
    return results


@metadata_collector.command(
    command_name="ksm-find-files",
    outputs_prefix="KeeperSecretsManager.Files",
    outputs_list=GET_FILES_OUTPUTS,
    restore=True,
    inputs_list=[
        InputArgument(name="file_name", required=True, description="File name text to search for."),
        InputArgument(name="partial_match", required=False, default=False, description="Search for partial file name match."),
    ],
    description="Search for records by full or partial file name match.",
)
def find_files_command(client: Client, args: dict[str, Any], **kwargs) -> CommandResults:
    """Find files command - returns list of record and file info.

    Args:
        client (Client): KeeperSecretsManager client to use.
        file_name (str): The file name text to search for.
        partial_match (bool): Flag for partial file name match.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records and files info.

    :rtype: ``CommandResults``
    """

    demisto.debug("find_files_command - command_name: " + str(kwargs.get("command_name", "")))

    file_name = args.get("file_name") or ""
    partial_match = args.get("partial_match", None)
    partial_match = bool(partial_match)
    demisto.debug(f"Find records with file_name={file_name} and partial_match={partial_match}")

    records = client.ksm_find_files(file_name, partial_match)
    markdown = "### Records with attachments\n"
    markdown += tableToMarkdown("Record Details", records)

    results = CommandResults(
        readable_output=markdown, outputs_prefix="KeeperSecretsManager.Files", outputs_key_field="file_uid", outputs=records
    )
    return results


@metadata_collector.command(
    command_name="ksm-get-file",
    inputs_list=[
        InputArgument(name="file_uid", required=True, description="File UID to search for."),
        InputArgument(
            name="record_uid",
            required=False,
            default="",
            description="Record UID to search for files. Search all records if empty.",
        ),
    ],
    description="Use this command to fetch the file attachment as a File.",
)
def get_file_command(client: Client, args: dict[str, Any], **kwargs) -> dict | None:
    """Get file command - returns the file attachment as a File.

    Args:
        client (Client): KeeperSecretsManager client to use.
        file_uid (str):  The file UID to search for. required.
        record_uid (str):  The record UID to search for files. Searches all records if record UID is empty.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records and files info.

    :rtype: ``CommandResults``
    """

    demisto.debug("get_file_command - command_name: " + str(kwargs.get("command_name", "")))
    record_uid = args.get("record_uid") or ""
    file_uid = args.get("file_uid", None)
    if not file_uid:
        raise ValueError("file_uid not specified")
    demisto.debug(f'Get file UID: "{file_uid}" from record UID:"{record_uid}"')

    files = client.ksm_get_file(record_uid, file_uid)
    if files:
        file_result = fileResult(filename=files[0][0], data=files[0][1])
        # file_result["Contents"] = files[0][1]
        return file_result
    return None


@metadata_collector.command(
    command_name="ksm-get-infofile",
    inputs_list=[
        InputArgument(name="file_uid", required=True, description="File UID to search for."),
        InputArgument(
            name="record_uid",
            required=False,
            default="",
            description="Record UID to search for files. Search all records if empty.",
        ),
    ],
    description="Use this command to fetch the file attachment as an Info File.",
)
def get_infofile_command(client: Client, args: dict[str, Any], **kwargs) -> dict | None:
    """Get info file command - returns the file attachment as an Info File.

    Args:
        client (Client): KeeperSecretsManager client to use.
        file_uid (str):  The file UID to search for. required.
        record_uid (str):  The record UID to search for files. Searches all records if record UID is empty.

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the list with records and files info.

    :rtype: ``CommandResults``
    """

    demisto.debug("get_infofile_command - command_name: " + str(kwargs.get("command_name", "")))
    record_uid = args.get("record_uid") or ""
    file_uid = args.get("file_uid", None)
    if not file_uid:
        raise ValueError("file_uid not specified")
    demisto.debug(f'Get file UID: "{file_uid}" from record UID:"{record_uid}"')

    files = client.ksm_get_file(record_uid, file_uid)
    if files:
        file_result = fileResult(filename=files[0][0], data=files[0][1], file_type=EntryType.ENTRY_INFO_FILE)
        # file_result["Contents"] = files[0][1]
        return file_result
    return None


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param Client: client to use

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    message: str = ""
    try:
        # Tests connectivity and authentication to KSM service.
        # This validates all the inputs given in the integration configuration panel,
        # either manually or by using an API that uses them.
        client.secrets_manager.get_secrets(["AAAAAAAAAAAAAAAAAAAAA"])
        message = "ok"
    except Exception as e:
        message = str(e)
    return message


""" MAIN FUNCTION """


def main() -> None:  # pragma: no cover
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    credentials = demisto.params().get("credentials")
    insecure = demisto.params().get("insecure", False)
    command = demisto.command()

    try:
        demisto.debug(f"Command being called in KSM is: {command}")
        client = Client(credentials=credentials, insecure=insecure)

        if command == "test-module":
            return_results(test_module(client))
        elif command == "fetch-credentials":
            fetch_credentials(client)
        elif command == "ksm-list-credentials":
            return_results(list_credentials_command(client, demisto.args()))
        elif command == "ksm-list-records":
            return_results(list_records_command(client, demisto.args()))
        elif command == "ksm-get-field":
            return_results(get_field_command(client, demisto.args()))
        elif command == "ksm-list-files":
            return_results(list_files_command(client, demisto.args()))
        elif command == "ksm-get-file":
            return_results(get_file_command(client, demisto.args()))
        elif command == "ksm-get-infofile":
            return_results(get_infofile_command(client, demisto.args()))
        elif command == "ksm-find-records":
            return_results(find_records_command(client, demisto.args()))
        elif command == "ksm-find-files":
            return_results(find_files_command(client, demisto.args()))

    except Exception as e:
        demisto.error(traceback.format_exc())
        return_error(f"Failed to execute {command} command.\nError:\n{e!s}")


""" ENTRY POINT """


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()