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.
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.