Okta IAM
Integrate with Okta's Identity Access Management service to execute CRUD operations to employee lifecycle processes.
Authentication & Identity Management · Okta
Details
| ID | Okta IAM |
|---|---|
| Provider | Okta |
| Category | Authentication & Identity Management |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix 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 | 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 | 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 | 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 | 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 settingscreate-user-enabled— Allow creating usersupdate-user-enabled— Allow updating usersenable-user-enabled— Allow enabling usersdisable-user-enabled— Allow disabling userscreate-if-not-exists— Automatically create user if not found in update commandmapper-in— Incoming Mapper (required)mapper-out— Outgoing Mapper (required)max_fetch— Fetch limit (recommended value is less than 200)isFetch— Fetch incidentsincidentFetchInterval— Incidents Fetch IntervalincidentType— Incident typeauto_generate_query_filter— Query only application events configured in IAM Configurationfetch_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-fieldsRetrieves 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-userCreates a user.
-
iam-disable-userDisable an active user.
-
iam-get-groupRetrieves the group information, including its members.
-
iam-get-userRetrieves a single user resource.
-
iam-update-userUpdates an existing user with the data passed in the user-profile argument.
-
okta-get-app-user-assignmentReturns information about application's user assignment.
-
okta-get-logsGets logs by providing optional filters.
-
okta-iam-get-configurationGets the IAM configuration data from the integration context.
-
okta-iam-list-applicationsReturns a list of Okta applications data.
-
okta-iam-list-user-applicationsList all applications assigned to a user.
-
okta-iam-set-configurationUpdates IAM configuration data in the integration context.
import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 # Disable insecure warnings urllib3.disable_warnings() """CONSTANTS""" BATCH_SIZE = 2000 DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DEPROVISIONED_STATUS = "DEPROVISIONED" USER_IS_DISABLED_MSG = "User is already disabled." USER_IS_DISABLED_ERROR = "E0000007" ERROR_CODES_TO_SKIP = [ "E0000016", # user is already enabled USER_IS_DISABLED_ERROR, ] ERROR_CODES_TO_RETURN_ERROR = [ "E0000047", # rate limit - resets after 1 minute ] FETCH_QUERY_EXCEPTION_MSG = ( 'If you marked the "Query only application events configured in IAM Configuration" ' "checkbox in the instance configuration, you must add at least one application in " "the IAM Configuration incident before fetching logs from Okta. " "Alternatively, you can unmark this checkbox and provide a " '"Fetch Query Filter" parameter instead.' ) GET_USER_ATTRIBUTES = ["id", "login", "email"] MAX_LOGS_LIMIT = 1000 """CLIENT CLASS""" class Client(BaseClient): """ Okta IAM Client class that implements logic to authenticate with Okta. """ def test_connection(self): uri = "users/me" self._http_request(method="GET", url_suffix=uri) def get_user(self, filter_name: str, filter_value: str): filter_name = filter_name if filter_name == "id" else f"profile.{filter_name}" uri = "users" query_params = {"filter": f'{filter_name} eq "{filter_value}"'} res = self._http_request(method="GET", url_suffix=uri, params=query_params) if res and len(res) == 1: return res[0] return None def deactivate_user(self, user_id): uri = f"users/{user_id}/lifecycle/deactivate" self._http_request(method="POST", url_suffix=uri) def activate_user(self, user_id): query_params = {"sendEmail": "false"} uri = f"users/{user_id}/lifecycle/activate" self._http_request(method="POST", url_suffix=uri, params=query_params) def create_user(self, user_data): # create a user in staged mode (not active) body = {"profile": user_data} uri = "users" query_params = {"activate": "false", "provider": "true"} res = self._http_request(method="POST", url_suffix=uri, json_data=body, params=query_params) return res def update_user(self, user_id, user_data): body = {"profile": user_data} uri = f"users/{user_id}" res = self._http_request(method="POST", url_suffix=uri, json_data=body) return res def get_okta_fields(self): okta_fields = {} uri = "meta/schemas/user/default" res = self._http_request(method="GET", url_suffix=uri) base_properties = res.get("definitions", {}).get("base", {}).get("properties", {}) okta_fields.update({k: base_properties[k].get("title") for k in base_properties}) custom_properties = res.get("definitions", {}).get("custom", {}).get("properties", {}) okta_fields.update({k: custom_properties[k].get("title") for k in custom_properties}) return okta_fields def http_request(self, method, url_suffix, full_url=None, params=None, data=None, headers=None): if headers is None: headers = self._headers full_url = full_url if full_url else urljoin(self._base_url, url_suffix) res = requests.request(method, full_url, verify=self._verify, headers=headers, params=params, json=data) return res def search_group(self, group_name): uri = "groups" query_params = {"q": encode_string_results(group_name)} return self.http_request(method="GET", url_suffix=uri, params=query_params) def get_group_by_id(self, group_id): uri = f"groups/{group_id}" return self.http_request(method="GET", url_suffix=uri) def get_group_members(self, group_id): uri = f"groups/{group_id}/users" return self.get_paged_results(uri) def get_paged_results(self, uri, query_param=None): response = self.http_request(method="GET", url_suffix=uri, params=query_param) paged_results = response.json() if response.status_code != 200: raise Exception(f"Error occurred while calling Okta API: {response.request.url}. Response: {response.json()}") while "next" in response.links and len(response.json()) > 0: next_page = response.links.get("next").get("url") response = self._http_request(method="GET", full_url=next_page, url_suffix="") if response.status_code != 200: raise Exception(f"Error occurred while calling Okta API: {response.request.url}. Response: {response.json()}") paged_results += response.json() return paged_results def get_app_user_assignment(self, application_id, user_id): uri = f"/apps/{application_id}/users/{user_id}" res = self._http_request(method="GET", url_suffix=uri, resp_type="response", ok_codes=(200, 404)) return res def list_user_apps(self, user_id): uri = "apps" query_params = {"filter": f'user.id eq "{user_id}"'} res = self._http_request(method="GET", url_suffix=uri, params=query_params) return res def list_apps(self, query, page, limit): query_params = {"q": query, "limit": limit} curr_page = 0 apps_batch, next_page = self.list_apps_batch(url_suffix="/apps", params=query_params) while apps_batch and curr_page != page: curr_page += 1 apps_batch, next_page = self.list_apps_batch(full_url=next_page) if not apps_batch: apps_batch = [] return apps_batch def list_apps_batch(self, url_suffix="", params=None, full_url=""): """Gets a batch of apps from Okta. Args: url_suffix (str): The apps API endpoint. params (dict): The API query params. full_url (str): The full url retrieved from the last API call. Return: apps_batch (dict): The logs batch. next_page (str): URL for next API call (equals '' on last batch). """ if not url_suffix and not full_url: return None, None res = self._http_request(method="GET", url_suffix=url_suffix, params=params, full_url=full_url, resp_type="response") logs_batch = res.json() next_page = res.links.get("next", {}).get("url") return logs_batch, next_page def get_logs( self, next_page=None, last_run_time=None, time_now=None, query_filter=None, auto_generate_filter=False, context=None, limit=None, ): logs = [] uri = "logs" if auto_generate_filter: query_filter = get_query_filter(context) params = {"filter": query_filter, "since": last_run_time, "until": time_now, "limit": limit} limit = int(limit) if limit else None if limit and limit <= MAX_LOGS_LIMIT: return self._http_request( method="GET", url_suffix=uri, params=params, ), None if limit and limit > MAX_LOGS_LIMIT: params["limit"] = MAX_LOGS_LIMIT logs_batch, next_page = self.get_logs_batch(url_suffix=uri, params=params, full_url=next_page) try: while logs_batch: logs.extend(logs_batch) if limit and len(logs) > limit: return logs[:limit], next_page logs_batch, next_page = self.get_logs_batch(full_url=next_page) except DemistoException as e: # in case of too many API calls, we return what we got and save the next_page for next fetch if not is_rate_limit_error(e): raise e return logs, next_page def get_logs_batch(self, url_suffix="", params=None, full_url=""): """Gets a batch of logs from Okta. Args: url_suffix (str): The logs API endpoint. params (dict): The API query params. full_url (str): The full url retrieved from the last API call. Preferred over url_suffix if not empty. Return: logs_batch (dict): The logs batch. next_page (str): URL for next API call (equals '' on last batch). """ if not url_suffix and not full_url: return None, None res = self._http_request(method="GET", url_suffix=url_suffix, params=params, full_url=full_url, resp_type="response") logs_batch = res.json() next_page = res.links.get("next", {}).get("url") return logs_batch, next_page """HELPER FUNCTIONS""" def get_all_user_profiles(): query = 'type:"User Profile"' email_to_user_profile = {} user_profiles: List[dict] = [] search_indicators = IndicatorsSearcher(query=query, size=BATCH_SIZE) for user_profile_res in search_indicators: user_profiles.extend(user_profile_res.get("iocs") or []) for user_profile in user_profiles: user_profile = user_profile.get("CustomFields", {}) email_to_user_profile[user_profile.get("email")] = user_profile return email_to_user_profile def get_event_username(log_entry): for target in log_entry.get("target", []): if target.get("type") == "User": return target.get("alternateId") return None def should_drop_event(log_entry, email_to_user_profile): """Returns a boolean value indicates whether the incident should be dropped. Args: log_entry (dict): The log entry. Returns: (bool) True iff the event should be dropped. """ username = get_event_username(log_entry) if username is not None and email_to_user_profile.get(username) is None: demisto.info(f"Dropping incident for user with username {username} - User Profile does not exist in XSOAR.") return True return False def add_user_profile_data_to_entry(log_entry, email_to_user_profile): username = get_event_username(log_entry) user_profile = email_to_user_profile.get(username, {}) log_entry.update(user_profile) log_entry["UserProfile"] = user_profile def get_query_filter(context): iam_configuration = context.get("IAMConfiguration", []) if not iam_configuration: raise DemistoException(FETCH_QUERY_EXCEPTION_MSG) application_ids = [row["ApplicationID"] for row in iam_configuration] query_filter = '(eventType eq "application.user_membership.add" or eventType eq "application.user_membership.remove") and' query_filter += "(" + " or ".join([f'target.id co "{app_id}"' for app_id in application_ids]) + ")" return query_filter def is_rate_limit_error(e): if hasattr(e, "res") and e.res is not None: return e.res.status_code == 429 return False def handle_exception(user_profile, e, action, okta_user=None): """Handles failed responses from Okta API by setting the User Profile object with the results. Args: user_profile (IAMUserProfile): The User Profile object. e (Exception): The exception error. If DemistoException, holds the response json. action (IAMActions): An enum represents the current action (get, update, create, etc). """ if e.__class__ is DemistoException and hasattr(e, "res") and e.res is not None: try: resp = e.res.json() error_code = resp.get("errorCode") error_message = get_error_details(resp) except ValueError: error_code = e.res.status_code error_message = str(e) else: error_code = "" error_message = str(e) if error_code == USER_IS_DISABLED_ERROR: user_profile.set_user_is_already_disabled(okta_user) elif error_code in ERROR_CODES_TO_SKIP: user_profile.set_result(action=action, skip=True, skip_reason=error_message) else: should_return_error = error_code in ERROR_CODES_TO_RETURN_ERROR user_profile.set_result( action=action, success=False, return_error=should_return_error, error_code=error_code, error_message=error_message ) demisto.error(traceback.format_exc()) def get_error_details(res): """Parses the error details retrieved from Okta and outputs the resulted string. Args: res (dict): The data retrieved from Okta. Returns: (str) The parsed error details. """ error_msg = f'{res.get("errorSummary")}. ' causes = "" for idx, cause in enumerate(res.get("errorCauses", []), 1): causes += f'{idx}. {cause.get("errorSummary")}\n' if causes: error_msg += f"Reason:\n{causes}" return error_msg """COMMAND FUNCTIONS""" def test_module(client, is_fetch, fetch_query_filter, auto_generate_query_filter, context, first_fetch_str): if is_fetch: if auto_generate_query_filter: get_query_filter(context) # will raise an exception if configuration doesn't exist elif not fetch_query_filter: raise DemistoException(FETCH_QUERY_EXCEPTION_MSG) try: dateparser.parse(first_fetch_str).strftime(DATE_FORMAT) # type: ignore except AttributeError: raise DemistoException("First fetch timestamp parameter is not in the correct format.") client.test_connection() return_results("ok") def get_mapping_fields_command(client): okta_fields = client.get_okta_fields() incident_type_scheme = SchemeTypeMapping(type_name=IAMUserProfile.DEFAULT_INCIDENT_TYPE) for field, description in okta_fields.items(): incident_type_scheme.add_field(field, description) return GetMappingFieldsResponse([incident_type_scheme]) def get_user_command(client, args, mapper_in, mapper_out): user_profile = IAMUserProfile( user_profile=args.get("user-profile"), mapper=mapper_out, incident_type=IAMUserProfile.UPDATE_INCIDENT_TYPE ) try: iam_attr, iam_attr_value = user_profile.get_first_available_iam_user_attr(GET_USER_ATTRIBUTES) okta_user = client.get_user(iam_attr, iam_attr_value) if not okta_user: error_code, error_message = IAMErrors.USER_DOES_NOT_EXIST user_profile.set_result(action=IAMActions.GET_USER, success=False, error_code=error_code, error_message=error_message) else: user_profile.update_with_app_data(okta_user, mapper_in) user_profile.set_result( action=IAMActions.GET_USER, success=True, active=okta_user.get("status") != DEPROVISIONED_STATUS, iden=okta_user.get("id"), email=okta_user.get("profile", {}).get("email"), username=okta_user.get("profile", {}).get("login"), details=okta_user, ) except Exception as e: handle_exception(user_profile, e, IAMActions.GET_USER) return user_profile def disable_user_command(client, args, is_command_enabled, mapper_out): user_profile = IAMUserProfile( user_profile=args.get("user-profile"), mapper=mapper_out, incident_type=IAMUserProfile.UPDATE_INCIDENT_TYPE ) okta_user = None if not is_command_enabled: user_profile.set_result(action=IAMActions.DISABLE_USER, skip=True, skip_reason="Command is disabled.") else: try: iam_attr, iam_attr_value = user_profile.get_first_available_iam_user_attr(GET_USER_ATTRIBUTES) okta_user = client.get_user(iam_attr, iam_attr_value) if not okta_user: _, error_message = IAMErrors.USER_DOES_NOT_EXIST user_profile.set_result(action=IAMActions.DISABLE_USER, skip=True, skip_reason=error_message) else: client.deactivate_user(okta_user.get("id")) user_profile.set_result( action=IAMActions.DISABLE_USER, success=True, active=False, iden=okta_user.get("id"), email=okta_user.get("profile", {}).get("email"), username=okta_user.get("profile", {}).get("login"), details=okta_user, ) except Exception as e: handle_exception(user_profile, e, IAMActions.DISABLE_USER, okta_user) return user_profile def create_user_command(client, args, mapper_out, is_command_enabled, is_update_user_enabled, is_enable_enabled): user_profile = IAMUserProfile( user_profile=args.get("user-profile"), mapper=mapper_out, incident_type=IAMUserProfile.CREATE_INCIDENT_TYPE ) if not is_command_enabled: user_profile.set_result(action=IAMActions.CREATE_USER, skip=True, skip_reason="Command is disabled.") else: try: iam_attr, iam_attr_value = user_profile.get_first_available_iam_user_attr(GET_USER_ATTRIBUTES) okta_user = client.get_user(iam_attr, iam_attr_value) if okta_user: # if user exists, update its data return update_user_command( client, args, mapper_out, is_update_user_enabled, is_enable_enabled, is_create_user_enabled=False, create_if_not_exists=False, ) else: okta_profile = user_profile.map_object(mapper_out, incident_type=IAMUserProfile.CREATE_INCIDENT_TYPE) created_user = client.create_user(okta_profile) client.activate_user(created_user.get("id")) user_profile.set_result( action=IAMActions.CREATE_USER, success=True, active=created_user.get("status") != DEPROVISIONED_STATUS, iden=created_user.get("id"), email=created_user.get("profile", {}).get("email"), username=created_user.get("profile", {}).get("login"), details=created_user, ) except Exception as e: handle_exception(user_profile, e, IAMActions.CREATE_USER) return user_profile def update_user_command( client, args, mapper_out, is_command_enabled, is_enable_enabled, is_create_user_enabled, create_if_not_exists ): user_profile = IAMUserProfile( user_profile=args.get("user-profile"), mapper=mapper_out, incident_type=IAMUserProfile.UPDATE_INCIDENT_TYPE ) allow_enable = args.get("allow-enable") == "true" if not is_command_enabled: user_profile.set_result(action=IAMActions.UPDATE_USER, skip=True, skip_reason="Command is disabled.") else: try: iam_attr, iam_attr_value = user_profile.get_first_available_iam_user_attr(GET_USER_ATTRIBUTES, use_old_user_data=True) okta_user = client.get_user(iam_attr, iam_attr_value) if okta_user: user_id = okta_user.get("id") if allow_enable and is_enable_enabled and okta_user.get("status") == DEPROVISIONED_STATUS: client.activate_user(user_id) user_profile.set_result( action=IAMActions.ENABLE_USER, success=True, active=True, iden=okta_user.get("id"), email=okta_user.get("profile", {}).get("email"), username=okta_user.get("profile", {}).get("login"), details=okta_user, ) else: okta_profile = user_profile.map_object(mapper_out, incident_type=IAMUserProfile.UPDATE_INCIDENT_TYPE) updated_user = client.update_user(user_id, okta_profile) user_profile.set_result( action=IAMActions.UPDATE_USER, success=True, active=okta_user.get("status") != DEPROVISIONED_STATUS, iden=updated_user.get("id"), email=updated_user.get("profile", {}).get("email"), username=updated_user.get("profile", {}).get("login"), details=updated_user, ) else: if create_if_not_exists: return create_user_command(client, args, mapper_out, is_create_user_enabled, False, False) else: _, error_message = IAMErrors.USER_DOES_NOT_EXIST user_profile.set_result(action=IAMActions.UPDATE_USER, skip=True, skip_reason=error_message) except Exception as e: handle_exception(user_profile, e, IAMActions.UPDATE_USER) return user_profile def get_app_user_assignment_command(client, args): user_id = args.get("user_id") application_id = args.get("application_id") res = client.get_app_user_assignment(application_id, user_id) raw_response = res.json() is_user_assigned_to_app = res.status_code == 200 outputs = {"UserID": user_id, "AppID": application_id, "IsAssigned": is_user_assigned_to_app} readable_output = tableToMarkdown( "App User Assignment", outputs, headers=["UserID", "AppID", "IsAssigned"], headerTransform=pascalToSpace ) if is_user_assigned_to_app: outputs["ProfileInApp"] = raw_response.get("profile") profile_readable = tableToMarkdown("Profile in App", raw_response.get("profile"), removeNull=True) readable_output += f"\n{profile_readable}" return CommandResults( outputs=outputs, outputs_prefix="Okta.AppUserAssignment", outputs_key_field=["UserID", "AppID"], readable_output=readable_output, raw_response=raw_response, ) def list_apps_command(client, args): query = args.get("query") page = int(args.get("page")) limit = min(int(args.get("limit")), 200) applications = client.list_apps(query, page, limit) outputs = [] for app in applications: outputs.append( { "ID": app.get("id"), "Name": app.get("name"), "Label": app.get("label"), "Logo": f".get('logo', [{}])[0].get('href')})", } ) title = "Okta Applications" if applications: from_idx = page * limit + 1 to_idx = from_idx + len(applications) - 1 title += f" ({from_idx} - {to_idx})" return CommandResults( outputs=outputs, outputs_prefix="Okta.Application", outputs_key_field="ID", readable_output=tableToMarkdown(title, outputs, headers=["ID", "Name", "Label", "Logo"]), ) def list_user_apps_command(client, args): user_id = args.get("user_id") applications = client.list_user_apps(user_id) outputs = [] for app in applications: outputs.append({"ID": app.get("id"), "Name": app.get("name"), "Label": app.get("label"), "Status": app.get("status")}) title = "Okta User Applications" return CommandResults( outputs=outputs, outputs_prefix="Okta.Application", outputs_key_field="ID", readable_output=tableToMarkdown(title, outputs, headers=["ID", "Name", "Label", "Status"]), ) def get_configuration(context): iam_configuration = context.get("IAMConfiguration", []) return CommandResults( outputs=iam_configuration, outputs_prefix="Okta.IAMConfiguration", outputs_key_field="ApplicationID", readable_output=tableToMarkdown("Okta IAM Configuration", iam_configuration), ) def set_configuration(args): iam_configuration = json.loads(args.get("configuration")) context = {"IAMConfiguration": iam_configuration} return context def fetch_incidents(client, last_run, first_fetch_str, fetch_limit, query_filter=None, auto_generate_filter=False, context=None): """If no events were saved from last run, returns new events from Okta's /log API. Otherwise, returns the events from last run. In both cases, no more than `fetch_limit` incidents will be returned, and the rest of them will be saved for next run. Args: client: (BaseClient) Okta client. last_run: (dict) The "last run" object that was set on the previous run. first_fetch_str: (str) First fetch time parameter (e.g. "1 day", "2 months", etc). fetch_limit: (int) Maximum number of incidents to return. query_filter: (str) Logs API query filter. auto_generate_filter: (bool) Whether or not to automatically generate the query filter. context: (dict) Integration Context object. Returns: incidents: (dict) Incidents/events that will be created in Cortex XSOAR next_run: (dict) The "last run" object for the next run. """ incidents = last_run.get("incidents", []) last_run_full_url = last_run.get("last_run_full_url") first_fetch_date = dateparser.parse(first_fetch_str) assert first_fetch_date is not None, f"could not parse {first_fetch_str}" first_fetch = first_fetch_date.strftime(DATE_FORMAT) last_run_time = last_run.get("last_run_time", first_fetch) # if last_run_time is undefined, use first_fetch time_now = datetime.now().strftime(DATE_FORMAT) demisto.debug(f"Okta: Fetching logs from {last_run_time} to {time_now}.") if not incidents: email_to_user_profile = get_all_user_profiles() log_events, last_run_full_url = client.get_logs( last_run_full_url, last_run_time, time_now, query_filter, auto_generate_filter, context ) for entry in log_events: if not should_drop_event(entry, email_to_user_profile): add_user_profile_data_to_entry(entry, email_to_user_profile) incident = {"rawJSON": json.dumps(entry)} incidents.append(incident) next_run = {"incidents": incidents[fetch_limit:], "last_run_time": time_now, "last_run_full_url": last_run_full_url} return incidents[:fetch_limit], next_run class OutputContext: """ Class to build a generic output and context. """ def __init__( self, success=None, active=None, id=None, username=None, email=None, errorCode=None, errorMessage=None, details=None, displayName=None, members=None, ): self.instanceName = demisto.callingContext["context"]["IntegrationInstance"] self.brand = demisto.callingContext["context"]["IntegrationBrand"] self.command = demisto.command().replace("-", "_").title().replace("_", "") self.success = success self.active = active self.id = id self.username = username self.email = email self.errorCode = errorCode self.errorMessage = errorMessage self.details = details self.displayName = displayName # Used in group self.members = members # Used in group self.data = { "brand": self.brand, "instanceName": self.instanceName, "success": success, "active": active, "id": id, "username": username, "email": email, "errorCode": errorCode, "errorMessage": errorMessage, "details": details, "displayName": displayName, "members": members, } # Remoove empty values self.data = {k: v for k, v in self.data.items() if v is not None} def get_group_command(client, args): scim = safe_load_json(args.get("scim")) group_id = scim.get("id") group_name = scim.get("displayName") if not (group_id or group_name): return_error("You must supply either 'id' or 'displayName' in the scim data") group_search_result = None if not group_id: res = client.search_group(group_name) res_json = res.json() if res.status_code == 200: if len(res_json) < 1: generic_iam_context = OutputContext( success=False, displayName=group_name, errorCode=404, errorMessage="Group Not Found", details=res_json ) else: generic_iam_context = OutputContext() group_search_result = res_json else: generic_iam_context = OutputContext( success=False, displayName=group_name, id=group_id, errorCode=res_json.get("errorCode"), errorMessage=res_json.get("errorSummary"), details=res_json, ) if not group_search_result: return CommandResults( raw_response=generic_iam_context.data, outputs_prefix=generic_iam_context.command, outputs_key_field="id", outputs=generic_iam_context.data, readable_output=tableToMarkdown("Okta Get Group:", generic_iam_context.data, removeNull=True), ) if group_search_result and len(group_search_result) > 1: generic_iam_context_data_list = [] for group in group_search_result: group_name = group.get("profile", {}).get("name") generic_iam_context = OutputContext(success=True, id=group.get("id"), displayName=group_name) generic_iam_context_data_list.append(generic_iam_context.data) return CommandResults( raw_response=generic_iam_context_data_list, outputs_prefix=generic_iam_context.command, outputs_key_field="id", outputs=generic_iam_context_data_list, readable_output=tableToMarkdown("Okta Get Group:", generic_iam_context_data_list, removeNull=True), ) elif not group_id and isinstance(group_search_result, list): group_id = group_search_result[0].get("id") res = client.get_group_by_id(group_id) res_json = res.json() if res.status_code == 200: group_member_profiles = [] include_members = args.get("includeMembers") if include_members.lower() == "true": group_members = client.get_group_members(group_id) for member in group_members: if member.get("status") != DEPROVISIONED_STATUS: profile = member.get("profile", {}) group_member_profile = {"value": member.get("id"), "display": profile.get("login")} group_member_profiles.append(group_member_profile) generic_iam_context = OutputContext( success=True, id=res_json.get("id"), displayName=res_json.get("profile", {}).get("name"), members=group_member_profiles, ) elif res.status_code == 404: generic_iam_context = OutputContext( success=False, displayName=group_name, id=group_id, errorCode=404, errorMessage="Group Not Found", details=res_json ) else: generic_iam_context = OutputContext( success=False, displayName=group_name, id=group_id, errorCode=res_json.get("errorCode"), errorMessage=res_json.get("errorSummary"), details=res_json, ) return CommandResults( raw_response=generic_iam_context.data, outputs_prefix=generic_iam_context.command, outputs_key_field="id", outputs=generic_iam_context.data, readable_output=tableToMarkdown("Okta Get Group:", generic_iam_context.data, removeNull=True), ) def get_logs_command(client, args): filter = args.get("filter") since = args.get("since") until = args.get("until") limit = args.get("limit") log_events, _ = client.get_logs(query_filter=filter, last_run_time=since, time_now=until, limit=limit) if not log_events: return CommandResults(readable_output="No logs found.", outputs={}, raw_response=log_events) return CommandResults( raw_response=log_events, outputs_prefix="Okta.Logs.Events", outputs_key_field="uuid", outputs=log_events, readable_output=tableToMarkdown("Okta Log Events:", log_events), ) def main(): user_profile = None params = demisto.params() base_url = urljoin(params["url"].strip("/"), "/api/v1/") token = params.get("credentials", {}).get("password", "") or params.get("apitoken", "") if not token: raise ValueError("Missing API token.") mapper_in = params.get("mapper-in") mapper_out = params.get("mapper-out") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) command = demisto.command() args = demisto.args() is_create_enabled = params.get("create-user-enabled") is_enable_enabled = params.get("enable-user-enabled") is_disable_enabled = params.get("disable-user-enabled") is_update_enabled = demisto.params().get("update-user-enabled") create_if_not_exists = demisto.params().get("create-if-not-exists") is_fetch = params.get("isFetch") first_fetch_str = params.get("first_fetch") fetch_limit = int(params.get("max_fetch", 1)) auto_generate_query_filter = params.get("auto_generate_query_filter") fetch_query_filter = params.get("fetch_query_filter") context = demisto.getIntegrationContext() headers = {"Content-Type": "application/json", "Accept": "application/json", "Authorization": f"SSWS {token}"} client = Client(base_url=base_url, verify=verify_certificate, proxy=proxy, headers=headers, ok_codes=(200,)) demisto.debug(f"Command being called is {command}") if command == "iam-get-user": user_profile = get_user_command(client, args, mapper_in, mapper_out) elif command == "iam-create-user": user_profile = create_user_command(client, args, mapper_out, is_create_enabled, is_update_enabled, is_enable_enabled) elif command == "iam-update-user": user_profile = update_user_command( client, args, mapper_out, is_update_enabled, is_enable_enabled, is_create_enabled, create_if_not_exists ) elif command == "iam-disable-user": user_profile = disable_user_command(client, args, is_disable_enabled, mapper_out) if user_profile: return_results(user_profile) try: if command == "test-module": test_module(client, is_fetch, fetch_query_filter, auto_generate_query_filter, context, first_fetch_str) elif command == "get-mapping-fields": return_results(get_mapping_fields_command(client)) elif command == "okta-get-app-user-assignment": return_results(get_app_user_assignment_command(client, args)) elif command == "okta-iam-list-applications": return_results(list_apps_command(client, args)) elif command == "okta-iam-list-user-applications": return_results(list_user_apps_command(client, args)) elif command == "okta-iam-get-configuration": return_results(get_configuration(context)) elif command == "okta-iam-set-configuration": context = set_configuration(args) demisto.setIntegrationContext(context) elif command == "iam-get-group": return_results(get_group_command(client, args)) elif command == "okta-get-logs": return_results(get_logs_command(client, args)) elif command == "fetch-incidents": last_run = demisto.getLastRun() context = demisto.getIntegrationContext() incidents, next_run = fetch_incidents( client, last_run, first_fetch_str, fetch_limit, fetch_query_filter, auto_generate_query_filter, context ) demisto.incidents(incidents) demisto.setLastRun(next_run) except Exception as e: # For any other integration command exception, return an error return_error(f"Failed to execute {command} command. Error: {e!s}") from IAMApiModule import * # noqa: E402 if __name__ in ("__main__", "__builtin__", "builtins"): main()