IAMInitOktaUser

Generates a password and sets the password for an Okta user. Enables the account. Sends an email to the user with the account information. This script is running the `send-mail` command, make sure there is a mail sender integration configured.

python · Okta

Source

import uuid

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
from pyzipper import WZ_AES, ZIP_DEFLATED, AESZipFile

DEFAULT_PWD_GENERATION_SCRIPT = "GeneratePassword"
TEXT_FILE_NAME = "Okta_Password"  # File name for the text file (within the zip file) to use
EMAIL_ZIP_NAME = "Okta_Password"  # File name to use for the zip file when attaching it to the email


def find_file_entry_id(file_name: str) -> str:
    """
    Find the entry ID of a file in the context by its name.

    Args:
        file_name (str): The name of the file to find.

    Returns:
        str: The entry ID of the file.
    """
    file_entries = demisto.context().get("File", [])

    if isinstance(file_entries, dict):  # In case of a single entry
        file_entries = [file_entries]

    for item in file_entries:
        if item["Name"] == file_name:
            return item["EntryID"]

    raise DemistoException(f"Could not find file '{file_name}' in the context.")


def generate_password(
    password_generation_script: str,
    min_lcase: str,
    max_lcase: str,
    min_ucase: str,
    max_ucase: str,
    min_digits: str,
    max_digits: str,
    min_symbols: str,
    max_symbols: str,
) -> str:
    """
    Generate a random password using a script.

    Args:
        password_generation_script (str): The name of the script to use for generating the password.
        min_lcase (str): The minimum number of lowercase characters to use in the password.
        max_lcase (str): The maximum number of lowercase characters to use in the password.
        min_ucase (str): The minimum number of uppercase characters to use in the password.
        max_ucase (str): The maximum number of uppercase characters to use in the password.
        min_digits (str): The minimum number of digits to use in the password.
        max_digits (str): The maximum number of digits to use in the password.
        min_symbols (str): The minimum number of symbols to use in the password.
        max_symbols (str): The maximum number of symbols to use in the password.

    Returns:
        str: The generated password.
    """
    # Generate a random password
    if password_generation_script == DEFAULT_PWD_GENERATION_SCRIPT:
        script_params = {
            "min_lcase": min_lcase,
            "max_lcase": max_lcase,
            "min_ucase": min_ucase,
            "max_ucase": max_ucase,
            "min_digits": min_digits,
            "max_digits": max_digits,
            "min_symbols": min_symbols,
            "max_symbols": max_symbols,
        }

        pwd_generation_script_output = demisto.executeCommand(password_generation_script, script_params)

    else:
        pwd_generation_script_output = demisto.executeCommand(password_generation_script, {})

    if is_error(pwd_generation_script_output):
        raise Exception(
            f"An error occurred while trying to generate a new password for the user. "
            f"Error is:\n{get_error(pwd_generation_script_output)}"
        )

    else:
        password_output = pwd_generation_script_output[0]["Contents"]

        if isinstance(password_output, dict):
            return password_output["NEW_PASSWORD"]

        elif isinstance(password_output, str):
            return password_output

        else:
            raise Exception(
                f"Could not parse the generated password from {password_generation_script} outputs. "
                f"Please make sure the output of the script is a string, or a dictionary containing "
                f"a key named NEW_PASSWORD."
            )


def okta_update_user(username: str, password: str, temporary_password: str, password_generation_script: str) -> None:
    """
    Update user's password in Okta, and enable the user account in case it was disabled.

    Args:
        username (str): The username of the user to update.
        password (str): The new password.
        temporary_password (str): Whether the password is temporary or not ("true" or "false").
        password_generation_script (str): The name of the script that generates the password (used for error handling).

    Raises:
        DemistoException: In case of a known potential error.
    """
    # Update user password
    set_password_outputs = demisto.executeCommand(
        "okta-set-password",
        {
            "username": username,
            "password": password,
            "temporary_password": temporary_password,
        },
    )

    if is_error(set_password_outputs):
        error_message = get_error(set_password_outputs)
        if "400" in error_message:
            raise DemistoException(
                f"An error occurred while trying to set a new password for the user. "
                f"Please make sure that the '{password_generation_script}' script "
                f"complies with your domain's password complexity policy."
            )

        raise DemistoException(f"An error occurred while trying to set a new password for the user:\n{error_message}")

    # Enable user (in case it was disabled)
    enable_outputs = demisto.executeCommand("okta-activate-user", {"username": username})

    if is_error(enable_outputs):
        error_message = get_error(enable_outputs)

        if "the user is already active" not in error_message:
            raise DemistoException(f"An error occurred while trying to enable the user account. Error:\n{error_message}")


def send_email(
    username: str,
    email_recipient: str,
    email_subject: str,
    email_body: str,
    display_name: str | None,
    error_message: str | None = None,
    password: str | None = None,
    zip_file_entry_id: str | None = None,
) -> list | dict:
    """
    Email the user with the password (plain text or encrypted).
    One of 'error_message', 'password' or 'zip_file_entry_id' must be provided.

    Args:
        username (str): The username of the user.
        email_recipient (str): The email address of the recipient.
        email_subject (str): The subject of the email.
        email_body (str): The body of the email.
        display_name (str | None, optional): The display name of the user.
        error_message (str | None, optional): An error message to include in the email.
        password (str | None, optional): The password to send in the email (plain text).
        zip_file_entry_id (str | None, optional): The entry ID of the zip file to send in the email (encrypted).

    Returns:
        list | dict: The outputs of the send-mail command.
    """
    if not any((error_message, password, zip_file_entry_id)):
        raise ValueError("Either 'password' or 'zip_file_entry_id' must be provided.")

    if error_message:
        email_subject = f"'User Activation In Okta' failed for user '{display_name}'"
        email_body = (
            "Hello,\n\n"
            "This message was sent to inform you that an error occurred while trying "
            f"'to activate the user account of '{username}' in Okta.\n\n'"
            f"'The error is: '{error_message}'\n\nRegards,\nIAM Team'"
        )

    else:
        if not email_subject:
            email_subject = f"User '{display_name or username}' was successfully activated in Okta"

        email_password = "Available in the attached zip file" if zip_file_entry_id else password

        if not email_body:
            email_body = "Hello,\n\nThe following account has been activated in Okta:\n\n"
            if display_name:
                email_body += f"Name: {display_name}\n"

            email_body += f"Username: {username}\nPassword: {email_password}\n\nRegards,\nIAM Team"
        else:
            email_body += f"\nUsername: {username}\nPassword: {email_password}"

    send_email_args = {"to": email_recipient, "subject": email_subject, "body": email_body}

    if zip_file_entry_id:
        send_email_args |= {"attachIDs": zip_file_entry_id, "attachNames": f"{EMAIL_ZIP_NAME}.zip"}

    return demisto.executeCommand("send-mail", send_email_args)


@polling_function(
    name="IAMInitOktaUser",
    interval=10,
    requires_polling_arg=False,
)
def create_zip_with_password(args: dict, generated_password: str, zip_password: str) -> PollResult:
    """
    Create a zip file with a password.
    The function returns a zip file to the war room, and calls this script recursively using polling.

    Args:
        args (dict): The arguments passed to the script.
        generated_password (str): The password to encrypt.
        zip_password (str): The password to use for encrypting the zip file.

    Returns:
        PollResult: The polling result.
    """
    text_file_name = f"{TEXT_FILE_NAME}.txt"
    zip_file_name = f"{EMAIL_ZIP_NAME}_{uuid.uuid4()}.zip"

    try:
        with open(text_file_name, "w") as text_file:
            text_file.write(generated_password)

        demisto.debug(f"zipping {text_file_name=}")
        with AESZipFile(zip_file_name, mode="w", compression=ZIP_DEFLATED, encryption=WZ_AES) as zf:
            zf.pwd = bytes(zip_password, "utf-8")
            zf.write(text_file_name)

        with open(zip_file_name, "rb") as zip_file:
            zip_content = zip_file.read()

    except Exception as e:
        raise DemistoException(f"Could not generate zip file. Error:\n{e!s}")

    finally:
        for file_name in (text_file_name, zip_file_name):
            if os.path.exists(file_name):
                os.remove(file_name)

    return_results(fileResult(zip_file_name, zip_content))

    return PollResult(
        response=None,
        continue_to_poll=True,
        partial_result=CommandResults(readable_output=f"Encrypted zip file generated. File name: '{zip_file_name}'."),
        args_for_next_run={**args, "zip_file_name": zip_file_name},
    )


def main():
    args = demisto.args()
    password_generation_script = args.get("pwdGenerationScript", DEFAULT_PWD_GENERATION_SCRIPT)
    username = args.get("username")
    display_name = args.get("displayname")
    email_recipient = args.get("to_email")
    email_subject = args.get("email_subject")
    email_body = args.get("email_body")
    zip_password = args.get("ZipProtectWithPassword")
    temporary_password = args.get("temporary_password", "false")
    zip_file_name = args.get("zip_file_name")

    generated_password: str | None = None
    file_entry_id: str | None = None
    context_outputs: dict[str, str] = {"success": "true"}
    error_message: str | None = None

    if not zip_password:
        return_warning(
            "It is highly recommended to run the script using the 'ZipProtectWithPassword' argument,"
            "as sending a plain text password in an email is an insecure practice."
        )

    # If zip file is already generated and this is the second iteration, we skip this section
    if not (zip_password and zip_file_name):
        try:
            generated_password = generate_password(
                password_generation_script=password_generation_script,
                min_lcase=args.get("min_lcase", "0"),
                max_lcase=args.get("max_lcase", "10"),
                min_ucase=args.get("min_ucase", "0"),
                max_ucase=args.get("max_ucase", "10"),
                min_digits=args.get("min_digits", "0"),
                max_digits=args.get("max_digits", "10"),
                min_symbols=args.get("min_symbols", "0"),
                max_symbols=args.get("max_symbols", "10"),
            )

            okta_update_user(
                username=username,
                password=generated_password,
                temporary_password=temporary_password,
                password_generation_script=password_generation_script,
            )

            if zip_password:
                # Rerun the script using polling (with a 'zip_file_name' value)
                return_results(
                    create_zip_with_password(args=args, generated_password=generated_password, zip_password=zip_password)
                )
                return

        except Exception as e:
            context_outputs["success"] = "false"
            error_message = str(e)
            context_outputs["errorDetails"] = error_message
            demisto.error(traceback.format_exc())

    else:
        file_entry_id = find_file_entry_id(zip_file_name)

    try:
        send_mail_outputs = send_email(
            display_name=display_name,
            username=username,
            error_message=error_message,
            email_recipient=email_recipient,
            email_subject=email_subject,
            email_body=email_body,
            password=generated_password,
            zip_file_entry_id=file_entry_id,
        )

        if is_error(send_mail_outputs):
            raise DemistoException(f"An error occurred while trying to send mail:\n{get_error(send_mail_outputs)}")

        context_outputs["sentMail"] = "true"

    except Exception as e:
        demisto.error(traceback.format_exc())
        context_outputs["sentMail"] = "false"
        context_outputs["sendMailError"] = str(e)

    if context_outputs["success"] and context_outputs["sentMail"]:
        readable_output = f"Successfully activated user {username}. An email with the user details was sent to {email_recipient}."
    else:
        readable_output = ""

        if error_message:
            readable_output += f"{error_message}\n"

        if context_outputs.get("sendMailError"):
            readable_output += context_outputs["sendMailError"]

    return_results(CommandResults(outputs_prefix="IAM.InitOktaUser", outputs=context_outputs, readable_output=readable_output))


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

README

Generates a password and sets the password for an Okta user.
Enables the account.
Sends an email to the user with the account information.
This script is running the send-mail command, make sure there is a mail sender integration configured.

Script Data


Name Description
Script Type python3
Tags IAM, Utility, Okta
Cortex XSOAR Version 6.5.0

Inputs


Argument Name Description
pwdGenerationScript The password generator script.
username The Okta username of the user.
displayname The display name of the employee.
temporary_password When true, you’ll need to change the password in the next login.
to_email The email address that the password will be sent to.
inc_id The incident ID.
email_subject The subject of the email sent to IT.
email_body The body of the email sent to IT.
ZipProtectWithPassword The password to protect the zip file that contains the generated password. if not provided, the generated password will be send in the body of the email.
min_lcase Minimum number of lower case characters to include in password. Used with the GeneratePassword script (leave empty if using another script).
max_lcase Maximum number of lower case characters to include in password. Used with the GeneratePassword script (leave empty if using another script).
min_ucase Minimum number of upper case characters to include in password. Used with the GeneratePassword script (leave empty if using another script).
max_ucase Maximum number of upper case characters to include in password. Used with the GeneratePassword script (leave empty if using another script).
min_digits Minimum number of digits to include in password. Used with the GeneratePassword script (leave empty if using another script).
max_digits Maximum number of digits to include in password. Used with the GeneratePassword script (leave empty if using another script).
min_symbols Minimum number of symbols to include in password. Used with the GeneratePassword script (leave empty if using another script).
max_symbols Maximum number of symbols to include in password. Used with the GeneratePassword script (leave empty if using another script).

Outputs


Path Description Type
IAM.InitOktaUser.success True if the Okta user was successfully activated, false otherwise. Boolean
IAM.InitOktaUser.sentMail True if the mail containing the information about the user activation and its auto-generated password was successfully sent to IT, false otherwise. Boolean
IAM.InitOktaUser.errorDetails The error details, if exists. String
IAM.InitOktaUser.sendMailError The error received from send-mail command, if exists. String