Signum

Signum password expiry notification.

Identity and Access Management · Signum

Details

IDSignum
ProviderSignum
CategoryIdentity and Access Management
From Version6.0.0
Docker Imagedemisto/python3:3.12.8.3296088
Supported ModulesAgentix XSIAM

README

Signum password expiry notification.

Configure Signum in Cortex

Parameter Required
Signum URL, in the format https://signmum.keyfactorsaas.com True
Username True
Password True
verify certificate False
Use system proxy 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.

signum-list-domain-users


List domain users by domain ID.

Base Command

signum-list-domain-users

Input

Argument Name Description Required
domain_id Identification of the domain for which to list the users. Default is 1. Required
simple_view If “True”, strip off prefixes, such as “{urn:.}” and “{http://.}”, from each dictionary key name. Possible values are: True, False. Default is True. Optional

Context Output

Path Type Description
Signum.ListDomainUsers unknown The result of the signum-list-domain-users command.

Configuration parameters

  • url — Signum URL, in the format https://signmum.keyfactorsaas.com (required)
  • credentials — Username (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • signum-list-domain-users

    List all the users for a given domain ID.

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

""" IMPORTS """
# Std imports
import base64
import hashlib
import os
import re
from datetime import datetime
from xml.etree import ElementTree

# 3-rd party imports
import urllib3

"""

GLOBALS/PARAMS

Attributes:
    INTEGRATION_NAME:
        Name of the integration as shown in the integration UI, for example: Microsoft Graph User.

    INTEGRATION_COMMAND_NAME:
        Command names should be written in all lower-case letters,
        and each word separated with a hyphen, for example: msgraph-user.

    INTEGRATION_CONTEXT_NAME:
        Context output names should be written in camel case, for example: MSGraphUser.
"""


INTEGRATION_NAME = "Signum"
INTEGRATION_COMMAND_NAME = "signum"
INTEGRATION_CONTEXT_NAME = "Signum"
# Disable insecure warnings
urllib3.disable_warnings()


class Client(BaseClient):
    def test_module(self, username: str, password: str) -> dict:
        """
            Performs basic request to check if the API is reachable and authentication is successful.
        Returns:
            Response dictionary
        """
        return self.list_domain_users(domain_id=1, username=username, password=password)

    def list_domain_users(self, domain_id: int, username: str, password: str) -> dict:
        """
            List domain users by domain_id.
        Args:
            self
            domain_id: Domain Identification number
            username: username
            password: password
        NOTE: The username and password are requested to be provided in the SOAP Header
        Returns:
            Response dictionary
        """
        # SOAP request URL
        nonce = os.urandom(16)
        created = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
        digest = base64.b64encode(hashlib.sha1(nonce + bytes(created, "utf-8") + bytes(password, "utf-8")).digest()).decode(
            "ascii"
        )
        userToken = f"UsernameToken-{digest}"

        # structured XML
        soapHeader = (
            f'<soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">'
            f'<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"'
            f' xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'
            f'<wsse:UsernameToken wsu:Id="{userToken}"> '
            f"<wsse:Username>{username}</wsse:Username>"
            f'<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0'
            f'#PasswordText">{password}</wsse:Password>'
            f'<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0'
            f'#Base64Binary">{nonce!s}</wsse:Nonce>'
            f"<wsu:Created>{created}</wsu:Created>"
            f"</wsse:UsernameToken>"
            f"</wsse:Security><wsa:Action>urn:evolium:redtrust:administration:ws/RTAdminService/ListDomainUsers</wsa:Action>"
            f"<wsa:To>"
            f"https://signum.fis.us.app.az.keyfactorsaas.com/RTAdminService.svc/basic</wsa:To>"
            f"</soap:Header>"
        )
        payload = (
            f'<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:urn="urn:evolium:redtrust:'
            f'administration:ws">'
            f"    {soapHeader}"
            f"    <soap:Body>"
            f"        <urn:ListDomainUsers>"
            f"            <!--Optional:-->"
            f"            <urn:domainId>{domain_id}</urn:domainId>"
            f"            <!--Optional:-->"
            f"            <urn:viewType>VIEW_ALL</urn:viewType>"
            f"            <!--Optional:-->"
            f"            <urn:filter></urn:filter>"
            f"            <!--Optional:-->"
            f"            <urn:numBlock>0</urn:numBlock>"
            f"            <!--Optional:-->"
            f"            <urn:orderColumn>ORDER_BY_NAME</urn:orderColumn>"
            f"            <!--Optional:-->"
            f"            <urn:orderType>ORDER_ASCENDING</urn:orderType>"
            f"        </urn:ListDomainUsers>"
            f"    </soap:Body>"
            f"</soap:Envelope>"
        )

        # headers
        headers = {
            "Content-Type": "application/soap+xml; charset=UTF-8; "
            'action="urn:evolium:redtrust:administration:ws/RTAdminService/ListDomainUsers"'
        }

        raw_response = self._http_request(method="POST", headers=headers, data=payload, resp_type="response")
        return raw_response


""" HELPER FUNCTIONS """


def xml_to_dict_recursive(root, simple_view: bool = True) -> dict:
    """
        Convert XML to Dictionary
    Args:
        root: XML format data
        simple_view: if "True", strip off prefixes, such as "{urn:.*}" and "{http://.*}", from each dictionary Key name.
    Returns:
        Response dictionary
    """
    if simple_view:
        if len(list(root)) == 0:
            return {re.sub("{urn:.*}", "", re.sub("{http://.*}", "", root.tag)): root.text}
        else:
            return {re.sub("{urn:.*}", "", re.sub("{http://.*}", "", root.tag)): list(map(xml_to_dict_recursive, list(root)))}
    else:
        if len(list(root)) == 0:
            return {root.tag: root.text}
        else:
            return {root.tag: list(map(xml_to_dict_recursive, list(root)))}


def dict_find_key_recursively(d: dict, target_key: str, result=None):
    """
        Find the value of specific key in dictionary
    Args:
        d: Dictionary
        target_key: The key for which the value is being searched.
        result: result of findings
    Returns:
        any: The value associated with the target_key, if found; otherwise, None.
    """
    if result is None:
        for current_key, value in d.items():
            if current_key == target_key:
                return value
            if isinstance(value, dict):
                result = dict_find_key_recursively(value, target_key, result)
            elif isinstance(value, list):
                for item in value:
                    result = dict_find_key_recursively(item, target_key, result)
    return result


def list_domain_users_ec(raw_response, simple_view: bool = True) -> tuple[list, list, dict]:
    """
        Get users info
    Args:
        raw_response: raw_response
        simple_view: if "True", strip off prefixes, such as "{urn:.*}" and "{http://.*}", from each dictionary Key name.
    Returns:
        tuple of List of users info
    """
    entry_context = []
    human_readable = []
    if raw_response_text := raw_response.text:
        xml = re.findall(r"<s:Envelope.*<\/s:Envelope>", raw_response_text)[0]
        root_xml = ElementTree.fromstring(xml)
        xml_dict = xml_to_dict_recursive(root=root_xml, simple_view=simple_view)
        result = None
        if simple_view:
            result = dict_find_key_recursively(d=xml_dict, target_key="ResultData", result=result)
        else:
            result = xml_dict
        for item in result:
            user_data = {}
            for d in item.get("LU1UserView"):
                user_data.update(d)
            entry_context.append(user_data)
            human_readable.append(user_data)
        raw_response = xml_dict

    return entry_context, human_readable, raw_response


""" COMMANDS """


@logger
def test_module_command(client: Client, username: str, password: str, *_) -> tuple[None, None, str]:
    """Performs a basic GET request to check if the API is reachable and authentication is successful.

    Args:
        client: Client object with request
        username: username
        password: password
        *_: Usually demisto.args()
    NOTE: The username and password are requested to be provided in the SOAP Header
    Returns:
        'ok' if test successful.

    Raises:
        DemistoException: If test failed.
    """
    results = client.test_module(username=username, password=password)
    if results.status_code == 200:  # type: ignore
        return None, None, "ok"
    raise DemistoException(f"Test module failed, {results}")


@logger
def list_domain_users_command(
    client: Client, domain_id: int, username: str, password: str, simple_view: bool = True
) -> tuple[str, dict, Union[list, dict]]:
    """
        List Domain Users

    Args:
        client:
        domain_id: Domain Identification number
        username: username
        password: password
        simple_view: if "True", strip off prefixes, such as "{urn:.*}" and "{http://.*}", from each dictionary Key name.
    NOTE: The username and password are requested to be provided in the SOAP Header

    Returns:
        human readable (markdown format), entry context and raw response
    """

    simple_view: bool = argToBoolean(simple_view)
    raw_response: dict = client.list_domain_users(domain_id=domain_id, username=username, password=password)
    title = f"{INTEGRATION_NAME} - list domain users command"
    entry_context, human_readable_ec, raw_response = list_domain_users_ec(raw_response=raw_response)
    context_entry: dict = {f"{INTEGRATION_CONTEXT_NAME}.ListDomainUsers": entry_context}

    human_readable = tableToMarkdown(
        name=title,
        t=human_readable_ec,
        removeNull=True,
    )
    return human_readable, context_entry, raw_response


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    params = demisto.params()
    username = params.get("credentials", {}).get("identifier")
    password = params.get("credentials", {}).get("password")
    verify_ssl = not params.get("insecure", False)
    proxy = params.get("proxy")
    client = Client(base_url=params.get("url"), verify=verify_ssl, proxy=proxy, auth=(username, password))
    command = demisto.command()
    demisto.debug(f"Command being called is {command}")
    commands = {
        "test-module": test_module_command,
        f"{INTEGRATION_COMMAND_NAME}-list-domain-users": list_domain_users_command,
    }
    try:
        readable_output, outputs, raw_response = commands[command](
            client=client, username=username, password=password, **demisto.args()
        )
        results = CommandResults(outputs=outputs, readable_output=readable_output, raw_response=raw_response)
        return_results(results)

    except Exception as e:
        err_msg = f"Error in {INTEGRATION_NAME} Integration [{e}]"
        return_error(err_msg, error=e)


if __name__ == "builtins":
    main()