Okta IAM

Integrate with Okta's Identity Access Management service to execute CRUD operations to employee lifecycle processes.

Authentication & Identity Management · Okta

Details

IDOkta IAM
ProviderOkta
CategoryAuthentication & Identity Management
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud

README

Note: This integration should be used along with our ILM premium pack. For further details, visit our ILM pack documentation.

Integrate with Okta’s Identity Access Management service to execute CRUD operations to employee lifecycle processes.
This integration was integrated and tested with version v1 of the Okta integration.
For more information, refer to the Identity Lifecycle Management article.

Configure Okta IAM in Cortex

Parameter Description Required
url Okta URL (https://<domain>.okta.com) True
apitoken API Token (see Detailed Instructions) True
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False
create-user-enabled Allow creating users False
update-user-enabled Allow updating users False
enable-user-enabled Allow enabling users False
disable-user-enabled Allow disabling users False
create-if-not-exists Automatically create user if not found in update command False
mapper-in Incoming Mapper True
mapper-out Outgoing Mapper True
max_fetch Fetch Limit (recommended less than 200) False
isFetch Fetch incidents False
incidentFetchInterval Incidents Fetch Interval False
incidentType Incident type False
auto_generate_query_filter Query only application events configured in the IAM Configuration False
fetch_query_filter Fetch Query Filter (Okta system log events) True
first_fetch First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) False
  • To allow the integration to access the mapper from within the code, as required by the ILM pack, both mappers have to be configured in their proper respective fields and not in the “Mapper (outgoing)” dropdown list selector.

Fetch incidents using an “IAM - Configuration” incident

When the “Query only application events configured in IAM Configuration” checkbox is selected, add or remove event types for the applications you configured in the IAM Configuration incident are retrieved. You must have at least one application configured in XSOAR to fetch incidents from Okta.

Fetch incidents using a manual query filter expression

Note: Cortex XSOAR recommends you use the Query only application events configured in IAM Configuration option to generate the fetch-incidents query filter. The following following method should be used primarily for debugging purposes.
Clear the “Query only application events configured in IAM Configuration” checkbox to use a custom fetch query filter expression. The expression must be in SCIM syntax, and include the add and remove event types, as well as the application ID.
For example: (eventType eq "application.user_membership.add" or eventType eq "application.user_membership.remove") and target.id eq "0oar418fvkm67MWGd0h7"
You may also use the advanced search in Okta’s System Logs to generate the filter expression.
For more details, visit Okta API reference.

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.

iam-create-user


Creates a user.

Base Command

iam-create-user

Input

Argument Name Description Required
user-profile User Profile indicator details. Required
allow-enable When set to true, after the command execution the status of the user in the 3rd-party integration will be active. Optional

Context Output

Path Type Description
IAM.Vendor.active Boolean When true, indicates that the employee’s status is active in the 3rd-party integration.
IAM.Vendor.brand String Name of the integration.
IAM.Vendor.details string Provides the raw data from the 3rd-party integration.
IAM.Vendor.email String The employee’s email address.
IAM.Vendor.errorCode Number HTTP error response code.
IAM.Vendor.errorMessage String Reason why the API failed.
IAM.Vendor.id String The employee’s user ID in the app.
IAM.Vendor.instanceName string Name of the integration instance.
IAM.Vendor.success Boolean When true, indicates that the command was executed successfully.
IAM.Vendor.username String The employee’s username in the app.

Command Example

!iam-create-user user-profile={\"email\":\"testdemisto2@paloaltonetworks.com\", \"surname\":\"Test\",\"givenname\":\"Demisto\"}

Human Readable Output

Create User Results (Okta IAM)
brand instanceName success active id username email details
Okta IAM Okta IAM_instance_1 true true 00uujxnbh3uJw4tWA0h7 testdemisto2@paloaltonetworks.com testdemisto2@paloaltonetworks.com id: 00uujxnbh3uJw4tWA0h7
status: PROVISIONED
created: 2020-10-18T17:54:30.000Z
activated: 2020-10-18T17:54:30.000Z
statusChanged: 2020-10-18T17:54:30.000Z
lastLogin: null
lastUpdated: 2020-10-18T17:54:30.000Z
passwordChanged: null
type: {“id”: “oty8zfz6plq7b0r830h7”}
profile: {“firstName”: “Demisto”, “lastName”: “Test”, “mobilePhone”: null, “secondEmail”: null, “login”: “testdemisto2@paloaltonetworks.com”, “email”: “testdemisto44@paloaltonetworks.com”}
credentials: {“provider”: {“type”: “OKTA”, “name”: “OKTA”}}
_links: {“suspend”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/suspend”, “method”: “POST”}, “schema”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/schemas/user/osc8zfz6plq7b0r830h7”}, “resetPassword”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reset_password”, “method”: “POST”}, “reactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reactivate”, “method”: “POST”}, “self”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7”}, “type”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/types/user/oty8zfz6plq7b0r830h7”}, “deactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/deactivate”, “method”: “POST”}}

iam-update-user


Updates an existing user with the data passed in the user-profile argument.

Base Command

iam-update-user

Input

Argument Name Description Required
user-profile A User Profile indicator. Required
allow-enable When set to true, after the command execution the status of the user in the 3rd-party integration will be active. Optional

Context Output

Path Type Description
IAM.Vendor.active Boolean When true, indicates that the employee’s status is active in the 3rd-party integration.
IAM.Vendor.brand String Name of the integration.
IAM.Vendor.details string Provides the raw data from the 3rd-party integration.
IAM.Vendor.email String The employee’s email address.
IAM.Vendor.errorCode Number HTTP error response code.
IAM.Vendor.errorMessage String Reason why the API failed.
IAM.Vendor.id String The employee’s user ID in the app.
IAM.Vendor.instanceName string Name of the integration instance.
IAM.Vendor.success Boolean When true, indicates that the command was executed successfully.
IAM.Vendor.username String The employee’s username in the app.

Command Example

!iam-update-user user-profile={\"email\":\"testdemisto2@paloaltonetworks.com\", \"givenname\":\"Demisto-Test\"}

Human Readable Output

Update User Results (Okta IAM)
brand instanceName success active id username email details
Okta IAM Okta IAM_instance_1 true true 00uujxnbh3uJw4tWA0h7 testdemisto2@paloaltonetworks.com testdemisto2@paloaltonetworks.com id: 00uujxnbh3uJw4tWA0h7
status: PROVISIONED
created: 2020-10-18T17:54:30.000Z
activated: 2020-10-18T17:54:30.000Z
statusChanged: 2020-10-18T17:54:30.000Z
lastLogin: null
lastUpdated: 2020-10-18T17:56:53.000Z
passwordChanged: null
type: {“id”: “oty8zfz6plq7b0r830h7”}
profile: {“firstName”: “Demisto-Test”, “lastName”: “Test”, “mobilePhone”: null, “secondEmail”: null, “login”: “testdemisto2@paloaltonetworks.com”, “email”: “testdemisto2@paloaltonetworks.com”}
credentials: {“provider”: {“type”: “OKTA”, “name”: “OKTA”}}
_links: {“suspend”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/suspend”, “method”: “POST”}, “schema”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/schemas/user/osc8zfz6plq7b0r830h7”}, “resetPassword”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reset_password”, “method”: “POST”}, “reactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reactivate”, “method”: “POST”}, “self”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7”}, “type”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/types/user/oty8zfz6plq7b0r830h7”}, “deactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/deactivate”, “method”: “POST”}}

iam-get-user


Retrieves a single user resource.

Base Command

iam-get-user

Input

Argument Name Description Required
user-profile A User Profile indicator. Required

Context Output

Path Type Description
IAM.Vendor.active Boolean When true, indicates that the employee’s status is active in the 3rd-party integration.
IAM.Vendor.brand String Name of the integration.
IAM.Vendor.details string Provides the raw data from the 3rd-party integration.
IAM.Vendor.email String The employee’s email address.
IAM.Vendor.errorCode Number HTTP error response code.
IAM.Vendor.errorMessage String Reason why the API failed.
IAM.Vendor.id String The employee’s user ID in the app.
IAM.Vendor.instanceName string Name of the integration instance.
IAM.Vendor.success Boolean When true, indicates that the command was executed successfully.
IAM.Vendor.username String The employee’s username in the app.

Command Example

!iam-get-user user-profile={\"email\":\"testdemisto2@paloaltonetworks.com\"}

Human Readable Output

Get User Results (Okta IAM)
brand instanceName success active id username email details
Okta IAM Okta IAM_instance_1 true true 00uujxnbh3uJw4tWA0h7 testdemisto2@paloaltonetworks.com testdemisto2@paloaltonetworks.com id: 00uujxnbh3uJw4tWA0h7
status: PROVISIONED
created: 2020-10-18T17:54:30.000Z
activated: 2020-10-18T17:54:30.000Z
statusChanged: 2020-10-18T17:54:30.000Z
lastLogin: null
lastUpdated: 2020-10-18T17:56:53.000Z
passwordChanged: null
type: {“id”: “oty8zfz6plq7b0r830h7”}
profile: {“firstName”: “Demisto-Test”, “lastName”: “Test”, “mobilePhone”: null, “secondEmail”: null, “login”: “testdemisto2@paloaltonetworks.com”, “email”: “testdemisto2@paloaltonetworks.com”}
credentials: {“provider”: {“type”: “OKTA”, “name”: “OKTA”}}
_links: {“suspend”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/suspend”, “method”: “POST”}, “schema”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/schemas/user/osc8zfz6plq7b0r830h7”}, “resetPassword”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reset_password”, “method”: “POST”}, “reactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/reactivate”, “method”: “POST”}, “self”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7”}, “type”: {“href”: “https://panw-test.oktapreview.com/api/v1/meta/types/user/oty8zfz6plq7b0r830h7”}, “deactivate”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7/lifecycle/deactivate”, “method”: “POST”}}

iam-disable-user


Disable an active user.

Base Command

iam-disable-user

Input

Argument Name Description Required
user-profile A User Profile indicator. Required

Context Output

Path Type Description
IAM.Vendor.active Boolean When true, indicates that the employee’s status is active in the 3rd-party integration.
IAM.Vendor.brand String Name of the integration.
IAM.Vendor.details string Provides the raw data from the 3rd-party integration.
IAM.Vendor.email String The employee’s email address.
IAM.Vendor.errorCode Number HTTP error response code.
IAM.Vendor.errorMessage String Reason why the API failed.
IAM.Vendor.id String The employee’s user ID in the app.
IAM.Vendor.instanceName string Name of the integration instance.
IAM.Vendor.success Boolean When true, indicates that the command was executed successfully.
IAM.Vendor.username String The employee’s username in the app.

Command Example

!iam-disable-user user-profile={\"email\":\"testdemisto2@paloaltonetworks.com\"}

Human Readable Output

Disable User Results (Okta IAM)
brand instanceName success active id username email details
Okta IAM Okta IAM_instance_1 true false 00uujxnbh3uJw4tWA0h7 testdemisto2@paloaltonetworks.com testdemisto2@paloaltonetworks.com id: 00uujxnbh3uJw4tWA0h7
status: PROVISIONED
created: 2020-10-18T17:54:30.000Z
activated: 2020-10-18T17:54:30.000Z
statusChanged: 2020-10-18T17:54:30.000Z
lastLogin: null
lastUpdated: 2020-10-18T17:56:53.000Z
passwordChanged: null
type: {“id”: “oty8zfz6plq7b0r830h7”}
profile: {“firstName”: “Demisto-Test”, “lastName”: “Test”, “mobilePhone”: null, “secondEmail”: null, “login”: “testdemisto2@paloaltonetworks.com”, “email”: “testdemisto2@paloaltonetworks.com”}
credentials: {“provider”: {“type”: “OKTA”, “name”: “OKTA”}}
_links: {“self”: {“href”: “https://panw-test.oktapreview.com/api/v1/users/00uujxnbh3uJw4tWA0h7”}}

okta-get-assigned-user-for-app


Gets a specific user assignment for an application by id.

Base Command

okta-get-app-user-assignment

Input

Argument Name Description Required
user_id ID of the user for which to get information. Required
application_id ID of the application for which to get information. Required

Context Output

Path Type Description
Okta.AppUserAssignment.UserID string ID of the user.
Okta.AppUserAssignment.AppID string ID of the application.
Okta.AppUserAssignment.IsAssigned boolean When True, indicates that the user is assigned to the application.
Okta.AppUserAssignment.ProfileInApp unknown The user profile data in the application.

Command Example

!okta-get-app-user-assignment user_id=00uuv6y8t1iy8YXm94h7 application_id=0oae3ioe51sQ64Aui2h7

Human Readable Output

App User Assignment
App ID Is Assigned User ID
0oae3ioe51sQ64Aui2h7 true 00uuv6y8t1iy8YXm94h7

okta-list-applications


Returns a list of Okta applications data.

Base Command

okta-iam-list-applications

Input

Argument Name Description Required
query Search for applications by their names. Optional
page Page number (0-based). Default is 0. Optional
limit Maximum number of apps to retrieve (maximal value is 200). Default is 50. Optional

Context Output

Path Type Description
Okta.Application.ID string ID of the application.
Okta.Application.Name string Name of the application.
Okta.Application.Label string Label of the application.
Okta.Application.Logo string Logo of the application.

Command Example

!okta-iam-list-applications limit=5 query="Workday"

Human Readable Output

Okta Applications (1 - 3)
ID Name Label Logo
0ob8zlypk6GVPRr2T0h7 workday Workday - Preview
0oabz0ozy5dDpEKyA0h7 workday Workday - Prod - DryRun
0oae3ioe51sQ64Aui2h7 workday Workday - Impl1

okta-list-user-applications


Returns a list of Okta applications data.

Base Command

okta-iam-list-user-applications

Input

Argument Name Description Required
user_id ID of the user for which to get the information. Required

Context Output

Path Type Description
Okta.Application.ID string ID of the application.
Okta.Application.Name string Name of the application.
Okta.Application.Label string Label of the application.
Okta.Application.Status string Status of the application.

Command Example

!okta-iam-list-user-applications user_id=00ux9v19bvTfQIjur0h7"

Human Readable Output

Okta User Applications
ID Name Label Status
0ob8zlypk6GVPRr2T0h7 active_directory pantest.local ACTIVE
0oabz0ozy5dDpEKyA0h7 test_app martsheet Test App ACTIVE

okta-iam-get-configuration


Gets the IAM configuration data from the integration context.

Base Command

okta-iam-get-configuration

Input

There are no input arguments for this command.

Context Output

Path Type Description
Okta.IAMConfiguration.ApplicationID String An Okta application ID.
Okta.IAMConfiguration.Name String Name of the Okta application.
Okta.IAMConfiguration.Label String Label of the Okta application.
Okta.IAMConfiguration.Logo String Logo of the application.
Okta.IAMConfiguration.Instance String An XSOAR IAM integration instance name.

Command Example

!okta-iam-get-configuration using="Okta IAM_instance_1_copy"

Human Readable Output

Okta IAM Configuration
ApplicationID Instance Label Logo Name
0oc8zlypk6GVPRr2G0h7 ServiceNow IAM_instance_1 ServiceNow servicenow

okta-iam-set-configuration


Updates IAM configuration data in the integration context.

Base Command

okta-iam-set-configuration

Input

Argument Name Description Required
configuration IAM configuration data. Required

Context Output

There is no context output for this command.

iam-get-group


Retrieves the group information, including its members.

Base Command

iam-get-group

Input

Argument Name Description Required
scim Group SCIM Data. Required
includeMembers Field to indicate if members need to be included in the response. . Possible values are: true, false. Default is true. Required

Context Output

Path Type Description
GetGroup.id String ID of the group.
GetGroup.displayName String The display name of the group.
GetGroup.members.display String The display name of the group member.
GetGroup.members.value String ID of the group member.
GetGroup.success Boolean Indicates whether the command succeeded.
GetGroup.errorCode Number HTTP error response code.
GetGroup.errorMessage String Reason why the API failed.

okta-get-logs


Gets logs by providing optional filters.

Base Command

okta-get-logs

Input

Argument Name Description Required
filter Useful for performing structured queries where constraints on LogEvent attribute values can be explicitly targeted. The following expressions are supported for events with the filter query parameter: eventType eq “ :eventType” -Events that have a specific action; eventType target.id eq “:id” - Events published with a specific target id; actor.id eq “:id”- Events published with a specific actor ID. For more information about filtering, visit https://developer.okta.com/docs/api/getting_started/design_principles#filtering. Optional
since Filters the lower time bound of the log events in the Internet Date/Time Format profile of ISO 8601. For example: 2017-05-03T16:22:18Z. Optional
until Filters the upper time bound of the log events in the Internet Date/Time Format profile of ISO 8601. For example: 2017-05-03T16:22:18Z. Optional
sortOrder The order of the returned events. Can be “ASCENDING” or “DESCENDING”. The default is “ASCENDING”. Possible values are: ASCENDING, DESCENDING. Default is ASCENDING. Optional
limit The maximum number of results to return. The default and maximum is 100. Optional

Context Output

Path Type Description
Okta.Logs.Events.actor.alternateId String Alternative ID of the actor.
Okta.Logs.Events.actor.displayName String Display name of the actor.
Okta.Logs.Events.actor.id String ID of the actor.
Okta.Logs.Events.client.userAgent.rawUserAgent String A raw string representation of user agent, formatted according to section 5.5.3 of HTTP/1.1 Semantics and Content. Both the browser and the OS fields can be derived from this field.
Okta.Logs.Events.client.userAgent.os String The operating system on which the client runs. For example, Microsoft Windows 10.
Okta.Logs.Events.client.userAgent.browser String Identifies the type of web browser, if relevant. For example, Chrome.
Okta.Logs.Events.client.device String Type of device from which the client operated. For example, Computer.
Okta.Logs.Events.client.id String For OAuth requests, the ID of the OAuth client making the request. For SSWS token requests, the ID of the agent making the request.
Okta.Logs.Events.client.ipAddress String IP address from which the client made its request.
Okta.Logs.Events.client.geographicalContext.city String The city encompassing the area containing the geo-location coordinates, if available. For example, Seattle, San Francisco.
Okta.Logs.Events.client.geographicalContext.state String Full name of the state or province encompassing the area containing the geo-location coordinates. For example, Montana, Incheon.
Okta.Logs.Events.client.geographicalContext.country String Full name of the country encompassing the area containing the geo-location coordinates. For example, France, Uganda.
Okta.Logs.Events.displayMessage String The display message for an event.
Okta.Logs.Events.eventType String Type of event that was published.
Okta.Logs.Events.outcome.result String Result of the action. Can be “SUCCESS”, “FAILURE”, “SKIPPED”, or “UNKNOWN”.
Okta.Logs.Events.outcome.reason String Reason for the result. For example, INVALID_CREDENTIALS.
Okta.Logs.Events.published String Timestamp when the event was published.
Okta.Logs.Events.severity String The event severity. Can be “DEBUG”, “INFO”, “WARN”, or “ERROR”.
Okta.Logs.Events.securityContext.asNumber Number Autonomous system number associated with the autonomous system that the event request was sourced to.
Okta.Logs.Events.securityContext.asOrg String Organization associated with the autonomous system that the event request was sourced to.
Okta.Logs.Events.securityContext.isp String Internet service provider used to send the event’s request.
Okta.Logs.Events.securityContext.domain String Specifies whether an event’s request is from a known proxy.
Okta.Logs.Events.request.ipChain.IP String IP address.
Okta.Logs.Events.request.ipChain.geographicalContext.city String The city encompassing the area containing the geo-location coordinates, if available. For example, Seattle, San Francisco.
Okta.Logs.Events.request.ipChain.geographicalContext.state String Full name of the state or province encompassing the area containing the geo-location coordinates. For example, Montana, Incheon.
Okta.Logs.Events.request.ipChain.geographicalContext.country String Full name of the country encompassing the area containing the geo-location coordinates. For example, France, Uganda.
Okta.Logs.Events.request.ipChain.source String Details regarding the source.
Okta.Logs.Events.target.id String ID of a target.
Okta.Logs.Events.target.type String Type of a target.
Okta.Logs.Events.target.alternateId String Alternative ID of a target.
Okta.Logs.Events.target.displayName String Display name of a target.

Configuration parameters

  • url — Okta URL (https://<domain>.okta.com) (required)
  • credentials
  • apitoken — API Token (See detailed instructions)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • create-user-enabled — Allow creating users
  • update-user-enabled — Allow updating users
  • enable-user-enabled — Allow enabling users
  • disable-user-enabled — Allow disabling users
  • create-if-not-exists — Automatically create user if not found in update command
  • mapper-in — Incoming Mapper (required)
  • mapper-out — Outgoing Mapper (required)
  • max_fetch — Fetch limit (recommended value is less than 200)
  • isFetch — Fetch incidents
  • incidentFetchInterval — Incidents Fetch Interval
  • incidentType — Incident type
  • auto_generate_query_filter — Query only application events configured in IAM Configuration
  • fetch_query_filter — Fetch Query Filter (Okta system log events)
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)

Commands (12)

  • get-mapping-fields

    Retrieves a User Profile schema which holds all of the user fields within the application. Used for outgoing-mapping through the Get Schema option.

  • iam-create-user

    Creates a user.

  • iam-disable-user

    Disable an active user.

  • iam-get-group

    Retrieves the group information, including its members.

  • iam-get-user

    Retrieves a single user resource.

  • iam-update-user

    Updates an existing user with the data passed in the user-profile argument.

  • okta-get-app-user-assignment

    Returns information about application's user assignment.

  • okta-get-logs

    Gets logs by providing optional filters.

  • okta-iam-get-configuration

    Gets the IAM configuration data from the integration context.

  • okta-iam-list-applications

    Returns a list of Okta applications data.

  • okta-iam-list-user-applications

    List all applications assigned to a user.

  • okta-iam-set-configuration

    Updates IAM configuration data in the integration context.

import pytest
from CommonServerPython import EntryType
from IAMApiModule import *
from Okta_IAM import (
    Client,
    create_user_command,
    disable_user_command,
    fetch_incidents,
    get_app_user_assignment_command,
    get_mapping_fields_command,
    get_user_command,
    update_user_command,
)
from requests import Response, Session

OKTA_USER_OUTPUT = {
    "id": "mock_id",
    "status": "PROVISIONED",
    "profile": {
        "firstName": "mock_first_name",
        "lastName": "mock_last_name",
        "login": "testdemisto2@paloaltonetworks.com",
        "email": "testdemisto2@paloaltonetworks.com",
    },
}

OKTA_DISABLED_USER_OUTPUT = {
    "id": "mock_id",
    "status": "DEPROVISIONED",
    "profile": {
        "firstName": "mock_first_name",
        "lastName": "mock_last_name",
        "login": "testdemisto2@paloaltonetworks.com",
        "email": "testdemisto2@paloaltonetworks.com",
    },
}

BASE_URL = "https://test.com"


def mock_client():
    client = Client(base_url=BASE_URL)
    return client


def get_outputs_from_user_profile(user_profile):
    entry_context = user_profile.to_entry()
    outputs = entry_context.get("Contents")
    return outputs


@pytest.mark.parametrize(
    "args, mock_url",
    [
        (
            {"user-profile": {"email": "testdemisto2@paloaltonetworks.com"}},
            f'{BASE_URL}/users?filter=profile.email eq "testdemisto2@paloaltonetworks.com"',
        ),
        (
            {"user-profile": {"email": "testdemisto2@paloaltonetworks.com", "login": "testdemisto2@paloaltonetworks.com"}},
            f'{BASE_URL}/users?filter=profile.login eq "testdemisto2@paloaltonetworks.com"',
        ),
        (
            {
                "user-profile": {
                    "email": "testdemisto2@paloaltonetworks.com",
                    "login": "testdemisto2@paloaltonetworks.com",
                    "id": "mock_id",
                }
            },
            f'{BASE_URL}/users?filter=id eq "mock_id"',
        ),
    ],
)
def test_get_user_command__existing_user(mocker, requests_mock, args, mock_url):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains an email of a user
    When:
        - The user exists in Okta
        - Calling function get_user_command
    Then:
        - Ensure the resulted User Profile object holds the correct user details
    """
    client = mock_client()

    requests_mock.get(mock_url, json=[OKTA_USER_OUTPUT])
    mocker.patch.object(IAMUserProfile, "update_with_app_data", return_value={})

    user_profile = get_user_command(client, args, "mapper_in", "mapper_out")
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.GET_USER
    assert outputs.get("success") is True
    assert outputs.get("active") is True
    assert outputs.get("id") == "mock_id"
    assert outputs.get("username") == "testdemisto2@paloaltonetworks.com"
    assert outputs.get("details", {}).get("profile", {}).get("firstName") == "mock_first_name"
    assert outputs.get("details", {}).get("profile", {}).get("lastName") == "mock_last_name"


def test_get_user_command__non_existing_user(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains an email a user
    When:
        - The user does not exist in Okta
        - Calling function get_user_command
    Then:
        - Ensure the resulted User Profile object holds information about an unsuccessful result.
    """
    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com"}}

    mocker.patch.object(client, "get_user", return_value=None)

    user_profile = get_user_command(client, args, "mapper_in", "mapper_out")
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.GET_USER
    assert outputs.get("success") is False
    assert outputs.get("errorCode") == IAMErrors.USER_DOES_NOT_EXIST[0]
    assert outputs.get("errorMessage") == IAMErrors.USER_DOES_NOT_EXIST[1]


def test_get_user_command__bad_response(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains an email of a non-existing user in Okta
    When:
        - Calling function get_user_command
        - A bad response (500) is returned from Okta's API
    Then:
        - Ensure the resulted User Profile object holds information about the bad response.
    """
    import demistomock as demisto

    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com"}}

    bad_response = Response()
    bad_response.status_code = 500
    bad_response._content = (
        b'{"errorCode": "mock_error_code", '
        b'"errorSummary": "mock_error_summary", '
        b'"errorCauses": [{"errorSummary": "reason_1"}, '
        b'{"errorSummary": "reason_2"}]}'
    )

    mocker.patch.object(demisto, "error")
    mocker.patch.object(Session, "request", return_value=bad_response)

    user_profile = get_user_command(client, args, "mapper_in", "mapper_out")
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.GET_USER
    assert outputs.get("success") is False
    assert outputs.get("errorCode") == "mock_error_code"
    assert outputs.get("errorMessage") == "mock_error_summary. Reason:\n1. reason_1\n2. reason_2\n"


def test_create_user_command__success(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains an email of a non-existing user in Okta
    When:
        - Calling function create_user_command
    Then:
        - Ensure a User Profile object with the user data is returned
    """
    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com"}}

    mocker.patch.object(client, "get_user", return_value=None)
    mocker.patch.object(client, "create_user", return_value=OKTA_USER_OUTPUT)
    mocker.patch.object(client, "activate_user", return_value=None)

    user_profile = create_user_command(
        client, args, "mapper_out", is_command_enabled=True, is_update_user_enabled=True, is_enable_enabled=True
    )
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.CREATE_USER
    assert outputs.get("success") is True
    assert outputs.get("active") is True
    assert outputs.get("id") == "mock_id"
    assert outputs.get("username") == "testdemisto2@paloaltonetworks.com"
    assert outputs.get("details", {}).get("profile", {}).get("firstName") == "mock_first_name"
    assert outputs.get("details", {}).get("profile", {}).get("lastName") == "mock_last_name"


def test_update_user_command__allow_enable(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains user data
    When:
        - The user is disabled in Okta
        - allow-enable argument is true
        - Calling function update_user_command
    Then:
        - Ensure the user is enabled at the end of the command execution.
    """
    client = mock_client()
    args = {
        "user-profile": {"email": "testdemisto2@paloaltonetworks.com", "givenname": "mock_first_name"},
        "allow-enable": "true",
    }

    mocker.patch.object(client, "get_user", return_value=OKTA_DISABLED_USER_OUTPUT)
    mocker.patch.object(client, "activate_user", return_value=None)

    user_profile = update_user_command(
        client,
        args,
        "mapper_out",
        is_command_enabled=True,
        is_enable_enabled=True,
        is_create_user_enabled=False,
        create_if_not_exists=False,
    )
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.ENABLE_USER
    assert outputs.get("success") is True
    assert outputs.get("active") is True
    assert outputs.get("id") == "mock_id"
    assert outputs.get("username") == "testdemisto2@paloaltonetworks.com"
    assert outputs.get("details", {}).get("profile", {}).get("firstName") == "mock_first_name"
    assert outputs.get("details", {}).get("profile", {}).get("lastName") == "mock_last_name"


def test_update_user_command__non_existing_user(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains user data
    When:
        - The user does not exist in Okta
        - create-if-not-exists parameter is checked
        - Create User command is enabled
        - Calling function update_user_command
    Then:
        - Ensure the create action is executed
        - Ensure a User Profile object with the user data is returned
    """
    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com", "givenname": "mock_first_name"}}

    mocker.patch.object(client, "get_user", return_value=None)
    mocker.patch.object(client, "create_user", return_value=OKTA_USER_OUTPUT)
    mocker.patch.object(client, "activate_user", return_value=None)

    user_profile = update_user_command(
        client,
        args,
        "mapper_out",
        is_command_enabled=True,
        is_enable_enabled=True,
        is_create_user_enabled=True,
        create_if_not_exists=True,
    )
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.CREATE_USER
    assert outputs.get("success") is True
    assert outputs.get("active") is True
    assert outputs.get("id") == "mock_id"
    assert outputs.get("username") == "testdemisto2@paloaltonetworks.com"
    assert outputs.get("details", {}).get("profile", {}).get("firstName") == "mock_first_name"
    assert outputs.get("details", {}).get("profile", {}).get("lastName") == "mock_last_name"


def test_update_user_command__command_is_disabled(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains user data
    When:
        - Update User command is disabled
        - Calling function update_user_command
    Then:
        - Ensure the command is considered successful and skipped
    """
    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com", "givenname": "mock_first_name"}}

    mocker.patch.object(client, "get_user", return_value=None)
    mocker.patch.object(IAMUserProfile, "map_object", return_value={})
    mocker.patch.object(client, "update_user", return_value=OKTA_USER_OUTPUT)

    user_profile = update_user_command(
        client,
        args,
        "mapper_out",
        is_command_enabled=False,
        is_enable_enabled=True,
        is_create_user_enabled=False,
        create_if_not_exists=False,
    )
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.UPDATE_USER
    assert outputs.get("success") is True
    assert outputs.get("skipped") is True
    assert outputs.get("reason") == "Command is disabled."


def test_update_user_command__rate_limit_error(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument
    When:
        - Calling function update_user_command
        - API call exceeded rate limit
    Then:
        - Ensure an error entry is returned, as rate limit error code is in ERROR_CODES_TO_RETURN_ERROR list.
    """
    import demistomock as demisto

    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com", "givenname": "mock_first_name"}}

    bad_response = Response()
    bad_response.status_code = 429
    bad_response._content = b'{"errorCode": "E0000047", "errorSummary": "API call exceeded rate limit due to too many requests."}'

    mocker.patch.object(demisto, "error")
    mocker.patch.object(Session, "request", return_value=bad_response)

    user_profile = update_user_command(
        client,
        args,
        "mapper_out",
        is_command_enabled=True,
        is_enable_enabled=True,
        is_create_user_enabled=False,
        create_if_not_exists=False,
    )

    entry_context = user_profile.to_entry()
    outputs = entry_context.get("Contents")

    assert entry_context.get("Type") == EntryType.ERROR
    assert outputs.get("action") == IAMActions.UPDATE_USER
    assert outputs.get("success") is False
    assert outputs.get("errorCode") == "E0000047"


def test_disable_user_command__user_is_already_disabled(mocker):
    """
    Given:
        - An Okta IAM client object
        - A user-profile argument that contains an email of a user
    When:
        - The user is already disabled in Okta
        - Calling function disable_user_command
    Then:
        - Ensure the command is considered successful and skipped
    """
    import demistomock as demisto

    client = mock_client()
    args = {"user-profile": {"email": "testdemisto2@paloaltonetworks.com"}}

    bad_response = Response()
    bad_response.status_code = 400
    bad_response._content = (
        b'{"errorCode": "E0000007", '
        b'"errorSummary": "mock_error_summary", '
        b'"errorCauses": [{"errorSummary": "reason_1"}, '
        b'{"errorSummary": "reason_2"}]}'
    )

    mocker.patch.object(demisto, "error")
    mocker.patch.object(Session, "request", return_value=bad_response)

    user_profile = disable_user_command(client, args, is_command_enabled=True, mapper_out="mapper_out")
    outputs = get_outputs_from_user_profile(user_profile)

    assert outputs.get("action") == IAMActions.DISABLE_USER
    assert outputs.get("success") is True
    assert outputs.get("skipped") is True


def test_get_mapping_fields_command(mocker):
    """
    Given:
        - An Okta IAM client object
    When:
        - Okta user schema contains the fields 'field1' and 'field2'
        - Calling function get_mapping_fields_command
    Then:
        - Ensure a GetMappingFieldsResponse object that contains the Okta fields is returned
    """
    client = mock_client()

    mocker.patch.object(client, "get_okta_fields", return_value={"field1": "description1", "field2": "description2"})

    mapping_response = get_mapping_fields_command(client)
    mapping = mapping_response.extract_mapping()

    assert mapping.get(IAMUserProfile.DEFAULT_INCIDENT_TYPE, {}).get("field1") == "description1"
    assert mapping.get(IAMUserProfile.DEFAULT_INCIDENT_TYPE, {}).get("field2") == "description2"


def test_get_app_user_assignment_command(mocker):
    """
    Given:
        - An Okta IAM client object
        - Okta User ID
        - Okta Application ID
    When:
        - Calling function get_assigned_user_for_app_command
    Then:
        - Ensure a User Assignment object to the application is retrieved in the correct format.
    """
    client = mock_client()

    args = {"user_id": "mock_user_id", "application_id": "mock_app_id"}

    get_assignment_response = Response()
    get_assignment_response.status_code = 200
    get_assignment_response._content = (
        b'{"id": "mock_user_id", '
        b'"profile": {}, '
        b'"created": "2020-11-03T09:59:30.000Z", '
        b'"credentials": {"userName": "mock_username"}, '
        b'"externalId": null, '
        b'"status": "ACTIVE"}'
    )

    mocker.patch.object(Session, "request", return_value=get_assignment_response)

    command_result = get_app_user_assignment_command(client, args)

    assert command_result.outputs.get("UserID") == "mock_user_id"
    assert command_result.outputs.get("AppID") == "mock_app_id"
    assert command_result.outputs.get("IsAssigned") is True


def test_fetch_incidents__two_logs_batches(mocker):
    """
    Given:
        - An Okta IAM client object and fetch-relevant instance parameters
    When:
        - Calling function fetch_incidents
        - Events should come in two batches of two events in the first batch, and one event in the second batch.
    Then:
        - Ensure three events are returned in incident the correct format.
    """
    import json

    mocker.patch.object(Client, "get_logs_batch", side_effect=mock_get_logs_batch)
    mocker.patch("Okta_IAM.get_all_user_profiles", return_value={})
    events, _ = fetch_incidents(
        client=mock_client(), last_run={}, query_filter="mock_query_filter", first_fetch_str="7 days", fetch_limit=5
    )

    assert len(events) == 3
    assert json.loads(events[0]["rawJSON"]).get("mock_log1") == "mock_value1"
    assert json.loads(events[1]["rawJSON"]).get("mock_log2") == "mock_value2"
    assert json.loads(events[2]["rawJSON"]).get("mock_log3") == "mock_value3"


def test_fetch_incidents__fetch_limit(mocker):
    """
    Given:
        - An Okta IAM client object and fetch-relevant instance parameters
    When:
        - Calling function fetch_incidents
        - Three events exist Okta logs.
        - Fetch limit is 2.
    Then:
        - Ensure only two events are returned in incident the correct format.
    """
    mocker.patch.object(Client, "get_logs_batch", side_effect=mock_get_logs_batch)
    mocker.patch("Okta_IAM.get_all_user_profiles", return_value={})
    events, _ = fetch_incidents(
        client=mock_client(), last_run={}, query_filter="mock_query_filter", first_fetch_str="7 days", fetch_limit=2
    )

    assert len(events) == 2


def test_fetch_incidents__last_run(mocker):
    """
    Given:
        - An Okta IAM client object and fetch-relevant instance parameters
        - Last run object contains three incidents.
    When:
        - Calling function fetch_incidents
        - Fetch Limit is 2.
    Then:
        - Ensure only the first two incidents from the last run are retrieved.
        - Ensure that the next_run object returned contains the third incident.
        - Ensure 'last_run_time' key exists and holds a datetime string in the correct format.
    """
    from datetime import datetime

    last_run = {"incidents": [{"mock_log1": "mock_value1"}, {"mock_log2": "mock_value2"}, {"mock_log3": "mock_value3"}]}
    mocker.patch("Okta_IAM.get_all_user_profiles", return_value={})

    events, next_run = fetch_incidents(
        client=mock_client(), last_run=last_run, query_filter="mock_query_filter", first_fetch_str="7 days", fetch_limit=2
    )

    last_run_time = datetime.strptime(next_run.get("last_run_time"), "%Y-%m-%dT%H:%M:%SZ")

    assert len(events) == 2
    assert len(next_run.get("incidents")) == 1
    assert next_run["incidents"][0].get("mock_log3") == "mock_value3"
    assert isinstance(last_run_time, datetime)


LOGS = [{"mock_log1": "mock_value1"}, {"mock_log2": "mock_value2"}, {"mock_log3": "mock_value3"}]


def mock_get_logs_batch(url_suffix="", params=None, full_url=""):
    if url_suffix:
        # first iteration
        return LOGS[:2], "mock_next_page"

    elif full_url:
        # second iteration
        return LOGS[2:], None

    # third iteration - nothing is returned
    return None, None


SHOULD_DROP_EVENT_ARGS = [
    # no user email in log entry - do not drop event
    ({"target": [{"type": "Group", "alternateId": "testGroupId"}]}, {}, False),
    # no user email in log entry - do not drop event
    ({"target": [{"type": "Group", "alternateId": "testGroupId"}]}, {"test@example.com": {"username": "test"}}, False),
    # user email in both log entry and in xsoar - do not drop event
    ({"target": [{"type": "User", "alternateId": "test@example.com"}]}, {"test@example.com": {"username": "test"}}, False),
    # user email in log entry but not in xsoar - drop event
    ({"target": [{"type": "User", "alternateId": "test@example.com"}]}, {}, True),
]


@pytest.mark.parametrize("log_entry, email_to_user_profile, expected", SHOULD_DROP_EVENT_ARGS)
def test_should_drop_event(log_entry, email_to_user_profile, expected):
    from Okta_IAM import should_drop_event

    assert should_drop_event(log_entry, email_to_user_profile) == expected


LOGS_WITH_LIMIT = [(None, 3), (1, 1), (3, 3), (1001, 3)]


@pytest.mark.parametrize("limit, logs_amount", LOGS_WITH_LIMIT)
def test_get_logs_command(mocker, requests_mock, limit, logs_amount):
    """
    Given:
        - An Okta IAM client object.
    When:
        - Calling function okta-get-logs
        - Events should come in two batches of two events in the first batch, and one event in the second batch.
    Then:
        - Ensure three events are returned in incident the correct format.
    """
    from Okta_IAM import get_logs_command

    mocker.patch.object(Client, "get_logs_batch", side_effect=mock_get_logs_batch)
    requests_mock.get(f"{BASE_URL}/logs?limit={limit}", json=LOGS[:limit])
    args = {"limit": limit}
    results = get_logs_command(client=mock_client(), args=args)
    assert len(results.outputs) == logs_amount