AzureRiskyUsers
Azure Risky Users provides access to all at-risk users and risk detections in the Azure AD environment.
Vulnerability Management · Azure Risky Users
Details
| ID | AzureRiskyUsers |
|---|---|
| Provider | Microsoft |
| Category | Vulnerability Management |
| From Version | 6.0.0 |
| Docker Image | demisto/crypto:1.0.0.10120494 |
| Supported Modules | Agentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud |
README
Azure Risky Users provides access to all at-risk users and risk detections in Azure AD environment.
This integration was integrated and tested with version 1.0 of Microsoft Graph Azure Risky Users.
Self-Deployed Application
To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal.
The application must have the following permissions:
- IdentityRiskEvent.Read.All
- IdentityRiskEvent.ReadWrite.All
- IdentityRiskyUser.Read.All
- IdentityRiskyUser.ReadWrite.All
- User.Read.All
In case you want to use Device code flow, you must allow public client flows (can be found under the Authentication section of the app).
Authentication Using the Client Credentials Flow (recommended)
Follow these steps for a self-deployed configuration:
- To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal. To add the registration, refer to the following Microsoft article steps 1-8.
- Select the client-credentials Authentication Type.
- Enter your Client/Application ID in the Application ID parameter.
- Enter your Client Secret in the Client Secret parameter.
- Enter your Tenant ID in the Tenant ID parameter.
- Save the instance.
- Run the !azure-risky-users-auth-test command - a ‘Success’ message should be printed to the War Room.
Authentication Using the Device Code Flow
Follow these steps for a self-deployed configuration:
- Fill in the required parameters.
- Run the !azure-risky-users-auth-start command.
- Follow the instructions that appear.
- Run the !azure-risky-users-auth-complete command.
At end of the process you’ll see a message that you’ve logged in successfully.
Cortex XSOAR Application
In order to use the Cortex XSOAR Azure application,
use the Client ID - (application_id) (ec854987-95fa-4c8f-8056-768dd0f409ac).
Authentication Using the Device Code Flow -
In order to connect to the Azure Risky Users using the Cortex XSOAR Azure App with Device Code flow authentication. See device authorization grant flow.
- Fill in the required parameters - use the above mentioned Client ID - (application_id).
- Run the !azure-risky-users-auth-start command.
- Follow the instructions that appear.
- Run the !azure-risky-users-auth-complete command.
At end of the process you’ll see a message that you’ve logged in successfully.
Configure AzureRiskyUsers on Cortex
- Navigate to one of the following:
- Cortex XSOAR 8: Settings & Info > Settings > Integrations > Instances
- Cortex XSOAR 6: Settings > Integrations
- Cortex XSIAM: Settings > Configurations > Automation & Feed Integrations
- Search for AzureRiskyUsers.
-
Click Add instance to create and configure a new integration instance.
Parameter Required Client ID True Authentication Type True Tenant ID (for Client Credentials mode) False Client Secret (for Client Credentials mode) False Azure Managed Identities Client ID False Use system proxy False Trust any certificate False - Click Test to validate the URLs, token, and connection.
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.
azure-risky-users-auth-test
Tests the connectivity to Azure.
Base Command
azure-risky-users-auth-test
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Command Example
#### Human Readable Output
> Success!
### azure-risky-users-auth-start
***
Run this command to start the authorization process and follow the instructions in the command results.
#### Base Command
`azure-risky-users-auth-start`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!azure-risky-users-auth-start```
#### Human Readable Output
>### Authorization instructions
>
>1. To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXX to authenticate.
>2. Run the ***!azure-risky-users-auth-complete*** command in the War Room.
### azure-risky-users-auth-complete
***
Run this command to complete the authorization process. Should be used after running the azure-risky-users-auth-start command.
#### Base Command
`azure-risky-users-auth-complete`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!azure-risky-users-auth-complete```
#### Human Readable Output
> Authorization completed successfully.
### azure-risky-users-auth-reset
***
Run this command if for some reason you need to rerun the authentication process.
#### Base Command
`azure-risky-users-auth-reset`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!azure-risky-users-auth-reset```
#### Human Readable Output
>Authorization was reset successfully. Run **!azure-risky-users-auth-start** to start the authentication process.
### azure-risky-users-list
***
Returns a list of all risky users and their properties.
#### Base Command
`azure-risky-users-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| risk_state | Sets the Risk State to retrieve. Possible values are: atRisk, confirmedCompromised, remediated, dismissed. | Optional |
| limit | Limit of results to retrieve. Default is 50. | Optional |
| page | Page number. | Optional |
| page_size | Amount of results per request. Value can be between 1 and 500. When only page_size is given, the first page results will be fetched. | Optional |
| next_token | The URL for the next set of items to return during pagination. (This URL can be retrieved from a previous call). | Optional |
| risk_level | Sets the Risk Level to retrieve. Possible values are: low, medium, high. | Optional |
| order_by | The method used to order the retrieved results. Possible values are: riskLastUpdatedDateTime desc, riskLastUpdatedDateTime asc. Default is riskLastUpdatedDateTime desc. | Optional |
| updated_before | Displays all RiskyUsers before a specific datetime. For Example "2024-02-27T04:49:26.257525Z", "10 days", "5 months", "2 hours". | Optional |
| updated_after | Displays all RiskyUsers after a specific datetime. For Example "2024-02-27T04:49:26.257525Z", "10 days", "5 months", "2 hours". | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| AzureRiskyUsers.RiskyUser.id | String | Unique ID of the user at risk. |
| AzureRiskyUsers.RiskyUser.userDisplayName | String | Risky user display name. |
| AzureRiskyUsers.RiskyUser.userPrincipalName | String | Risky user principal name. |
| AzureRiskyUsers.RiskyUser.riskLevel | String | Level of the detected risky user. Possible values are: low, medium, high, hidden, none, unknownFutureValue. |
| AzureRiskyUsers.RiskyUser.riskState | String | State of the user's risk. Possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised. |
| AzureRiskyUsers.RiskyUser.riskLastUpdatedDateTime | Date | The date and time that the risky user was last updated. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. |
| AzureRiskyUsers.RiskyUser.isDeleted | Boolean | Indicates whether the user is deleted. |
| AzureRiskyUsers.RiskyUser.isProcessing | Boolean | Indicates whether a user's risky state is being processed by the backend. |
| AzureRiskyUsers.RiskyUser.riskDetail | String | Details of the detected risk. Possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, unknownFutureValue. |
| AzureRiskyUsers.RiskyUserListNextToken | String | A property in the response that contains a URL to the next page of results. |
#### Command example
```!azure-risky-users-list page_size=2```
#### Context Example
```json
{
"AzureRiskyUsers": {
"RiskyUser": [
{
"id": "ID_1",
"isDeleted": false,
"isProcessing": false,
"riskDetail": "none",
"riskLastUpdatedDateTime": "2023-06-04T10:12:39.3625926Z",
"riskLevel": "medium",
"riskState": "atRisk",
"userDisplayName": "user Display Name",
"userPrincipalName": "User Principal Name"
},
{
"id": "ID_2",
"isDeleted": false,
"isProcessing": false,
"riskDetail": "none",
"riskLastUpdatedDateTime": "2022-02-23T17:50:40.3408199Z",
"riskLevel": "high",
"riskState": "atRisk",
"userDisplayName": "user Display Name",
"userPrincipalName": "User Principal Name"
},
],
"RiskyUserListNextToken": "token",
}
}
Human Readable Output
Risky Users List
Id User Display Name User Principal Name Risk Level Risk State Risk Detail Risk Last Updated Date Time ID_1 user Display Name User Principal Name medium atRisk none 2023-06-04T10:12:39.3625926Z ID_2 user Display Name User Principal Name high atRisk none 2022-02-23T17:50:40.3408199Z
Risky Users List Token
next_token token
azure-risky-user-get
Retrieve properties and relationships of a Risky User.
Base Command
azure-risky-user-get
Input
| Argument Name | Description | Required |
|---|---|---|
| id | Risky user ID to retrieve. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureRiskyUsers.RiskyUser.id | String | Unique ID of the user at risk. |
| AzureRiskyUsers.RiskyUser.userDisplayName | String | Risky user display name. |
| AzureRiskyUsers.RiskyUser.userPrincipalName | String | Risky user principal name. |
| AzureRiskyUsers.RiskyUser.riskLevel | String | Level of the detected risky user. Possible values are: low, medium, high, hidden, none, unknownFutureValue. |
| AzureRiskyUsers.RiskyUser.riskState | String | State of the user’s risk. Possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised. |
| AzureRiskyUsers.RiskyUser.riskLastUpdatedDateTime | Date | The date and time that the risky user was last updated. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskyUser.isDeleted | Boolean | Indicates whether the user is deleted. |
| AzureRiskyUsers.RiskyUser.isProcessing | Boolean | Indicates whether a user’s risky state is being processed by the backend. |
| AzureRiskyUsers.RiskyUser.riskDetail | String | Details of the detected risk. Possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, unknownFutureValue. |
Command Example
!azure-risky-user-get id=333
Context Example
{
"AzureRiskyUsers": {
"RiskyUser": {
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identityProtection/riskyUsers/$entity",
"id": "333",
"isDeleted": false,
"isProcessing": false,
"riskDetail": "userPerformedSecuredPasswordReset",
"riskLastUpdatedDateTime": "2020-10-05T12:12:17.2115592Z",
"riskLevel": "none",
"riskState": "remediated",
"userDisplayName": "Yossi Israeli",
"userPrincipalName": "yossi@test.com"
}
}
}
Human Readable Output
Found Risky User With ID: 333
Id User Display Name User Principal Name Risk Level Risk State Risk Detail Risk Last Updated Date Time 333 Yossi Israeli yossi@test.com none remediated userPerformedSecuredPasswordReset 2020-10-05T12:12:17.2115592Z
azure-risky-users-risk-detections-list
Get a list of the riskDetection objects and their properties.
Base Command
azure-risky-users-risk-detections-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | Limit of results to retrieve. Default is 50. | Optional |
| page | Page number. Default is 1. | Optional |
| risk_state | Risk State to retrieve. If not specified, all states will be retrieved. Possible values are: atRisk, confirmedCompromised, remediated, dismissed, confirmedSafe. | Optional |
| risk_level | Specify to get only results with the same Risk Level. Possible values are: low, medium, high. | Optional |
| detected_date_time_before | Filter events that created before specific time range starting, e.g. 2022-06-09T23:00:44.7420905Z. | Optional |
| detected_date_time_after | Filter events that created after specific time range starting, e.g. 2022-06-09T23:00:44.7420905Z. | Optional |
| order_by | The method used to order the retrieved results. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureRiskyUsers.RiskDetection.id | String | Unique ID of the risk detection. Inherited from entity. |
| AzureRiskyUsers.RiskDetection.userId | String | Unique ID of the user. |
| AzureRiskyUsers.RiskDetection.userDisplayName | String | The user display name of the user. |
| AzureRiskyUsers.RiskDetection.userPrincipalName | String | The user principal name (UPN) of the user. |
| AzureRiskyUsers.RiskDetection.riskDetail | String | Details of the detected risk. Possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.riskEventType | String | The type of risk event detected. The possible values are unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic,adminConfirmedUserCompromised, mcasImpossibleTravel, mcasSuspiciousInboxManipulationRules, investigationsThreatIntelligenceSigninLinked, maliciousIPAddressValidCredentialsBlockedIP, and unknownFutureValue. If the risk detection is a premium detection, will show generic |
| AzureRiskyUsers.RiskDetection.riskLevel | String | Level of the detected risk. Possible values are: low, medium, high, hidden, none, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.riskState | String | The state of a detected risky user or sign-in. Possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.ipAddress | String | Provides the IP address of the client from where the risk occurred. |
| AzureRiskyUsers.RiskDetection.source | String | Source of the risk detection. For example, activeDirectory. |
| AzureRiskyUsers.RiskDetection.detectionTimingType | String | Timing of the detected risk (real-time/offline). Possible values are: notDefined, realtime, nearRealtime, offline, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.lastUpdatedDateTime | Date | Date and time that the risk detection was last updated. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.location | String | Location of the sign-in. |
| AzureRiskyUsers.RiskDetection.activity | String | Indicates the activity type the detected risk is linked to. . Possible values are: signin, user, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.activityDateTime | Date | Date and time that the risky activity occurred. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.additionalInfo | String | Additional information associated with the risk detection in JSON format. |
| AzureRiskyUsers.RiskDetection.correlationId | String | Correlation ID of the sign-in associated with the risk detection. This property is null if the risk detection is not associated with a sign-in. |
| AzureRiskyUsers.RiskDetection.detectedDateTime | Date | Date and time that the risk was detected. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.requestId | String | Request ID of the sign-in associated with the risk detection. This property is null if the risk detection is not associated with a sign-in. |
| AzureRiskyUsers.RiskDetection.tokenIssuerType | String | Indicates the type of token issuer for the detected sign-in risk. Possible values are: AzureAD, ADFederationServices, UnknownFutureValue. |
Command Example
!azure-risky-users-risk-detections-list limit=2
Context Example
{
"AzureRiskyUsers": {
"RiskDetection": [
{
"activity": "signin",
"activityDateTime": "2021-06-20T03:51:32.9572792Z",
"additionalInfo": "[{\"Key\":\"userAgent\",\"Value\":\"Dalvik/2.1.0 (Linux; U; Android 9; VKY-L29 Build/HUAWEIVKY-L29) ;VKY-L29\"}]",
"correlationId": "aaaa1111",
"detectedDateTime": "2021-06-20T03:51:32.9572792Z",
"detectionTimingType": "realtime",
"id": "555",
"ipAddress": "1.1.1.1",
"lastUpdatedDateTime": "2021-06-20T03:53:58.853418Z",
"location": {
"city": "Pisgat Ze'ev",
"countryOrRegion": "IL",
"geoCoordinates": {
"latitude": 31,
"longitude": 35
},
"state": "Yerushalayim"
},
"requestId": "bbbb1111",
"riskDetail": "userPassedMFADrivenByRiskBasedPolicy",
"riskEventType": "unfamiliarFeatures",
"riskLevel": "low",
"riskState": "remediated",
"source": "IdentityProtection",
"tokenIssuerType": "AzureAD",
"userDisplayName": "Shalev Israeli",
"userId": "777",
"userPrincipalName": "ShalevI@test.com"
},
{
"activity": "signin",
"activityDateTime": "2021-06-27T19:16:19.9976898Z",
"additionalInfo": "[{\"Key\":\"userAgent\",\"Value\":\"Dalvik/2.1.0 (Linux; U; Android 9; SM-G950F Build/PPR1.180610.011) ;SM-G950F\"}]",
"correlationId": "aaaa2222",
"detectedDateTime": "2021-06-27T19:16:19.9976898Z",
"detectionTimingType": "realtime",
"id": "888",
"ipAddress": "1.1.1.1",
"lastUpdatedDateTime": "2021-06-27T19:19:44.4975416Z",
"location": {
"city": "Dniprodzerzhyns'k",
"countryOrRegion": "UA",
"geoCoordinates": {
"latitude": 48,
"longitude": 34
},
"state": "Dnipropetrovs'ka Oblast'"
},
"requestId": "bbbb2222",
"riskDetail": "userPassedMFADrivenByRiskBasedPolicy",
"riskEventType": "unfamiliarFeatures",
"riskLevel": "low",
"riskState": "remediated",
"source": "IdentityProtection",
"tokenIssuerType": "AzureAD",
"userDisplayName": "Svetlana Israeli",
"userId": "999",
"userPrincipalName": "SvetlanaI@test.com"
}
]
}
}
Human Readable Output
Risk Detections List
Current page size: 2
Showing page 1 out others that may exist
Id User Id User Display Name User Principal Name Risk Detail Risk Event Type Risk Level Risk State Risk Detail Last Updated Date Time Ip Address 555 777 Shalev Israeli ShalevI@test.com userPassedMFADrivenByRiskBasedPolicy unfamiliarFeatures low remediated userPassedMFADrivenByRiskBasedPolicy 2021-06-20T03:53:58.853418Z 1.1.1.1 888 999 Svetlana Israeli SvetlanaI@test.com userPassedMFADrivenByRiskBasedPolicy unfamiliarFeatures low remediated userPassedMFADrivenByRiskBasedPolicy 2021-06-27T19:19:44.4975416Z 1.1.1.1
azure-risky-users-risk-detection-get
Read the properties and relationships of a riskDetection object.
Base Command
azure-risky-users-risk-detection-get
Input
| Argument Name | Description | Required |
|---|---|---|
| id | ID of risk detection to retrieve. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureRiskyUsers.RiskDetection.id | String | Unique ID of the risk detection. Inherited from entity. |
| AzureRiskyUsers.RiskDetection.userId | String | Unique ID of the user. |
| AzureRiskyUsers.RiskDetection.userDisplayName | String | The user display name of the user. |
| AzureRiskyUsers.RiskDetection.userPrincipalName | String | The user principal name (UPN) of the user. |
| AzureRiskyUsers.RiskDetection.riskDetail | String | Details of the detected risk. Possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.riskEventType | String | The type of risk event detected. The possible values are unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic,adminConfirmedUserCompromised, mcasImpossibleTravel, mcasSuspiciousInboxManipulationRules, investigationsThreatIntelligenceSigninLinked, maliciousIPAddressValidCredentialsBlockedIP, and unknownFutureValue. If the risk detection is a premium detection, will show generic |
| AzureRiskyUsers.RiskDetection.riskLevel | String | Level of the detected risk. Possible values are: low, medium, high, hidden, none, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.riskState | String | The state of a detected risky user or sign-in. Possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.ipAddress | String | Provides the IP address of the client from where the risk occurred. |
| AzureRiskyUsers.RiskDetection.source | String | Source of the risk detection. For example, activeDirectory. |
| AzureRiskyUsers.RiskDetection.detectionTimingType | String | Timing of the detected risk (real-time/offline). Possible values are: notDefined, realtime, nearRealtime, offline, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.lastUpdatedDateTime | Date | Date and time that the risk detection was last updated. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.location | String | Location of the sign-in. |
| AzureRiskyUsers.RiskDetection.activity | String | Indicates the activity type the detected risk is linked to. . Possible values are: signin, user, unknownFutureValue. |
| AzureRiskyUsers.RiskDetection.activityDateTime | Date | Date and time that the risky activity occurred. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.additionalInfo | String | Additional information associated with the risk detection in JSON format. |
| AzureRiskyUsers.RiskDetection.correlationId | String | Correlation ID of the sign-in associated with the risk detection. This property is null if the risk detection is not associated with a sign-in. |
| AzureRiskyUsers.RiskDetection.detectedDateTime | Date | Date and time that the risk was detected. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is look like this: 2014-01-01T00:00:00Z |
| AzureRiskyUsers.RiskDetection.requestId | String | Request ID of the sign-in associated with the risk detection. This property is null if the risk detection is not associated with a sign-in. |
| AzureRiskyUsers.RiskDetection.tokenIssuerType | String | Indicates the type of token issuer for the detected sign-in risk. Possible values are: AzureAD, ADFederationServices, UnknownFutureValue. |
Command Example
!azure-risky-users-risk-detection-get id=6565
Context Example
{
"AzureRiskyUsers": {
"RiskDetection": {
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identityProtection/riskDetections/$entity",
"activity": "signin",
"activityDateTime": "2021-07-03T13:35:38.8773806Z",
"additionalInfo": "[{\"Key\":\"userAgent\",\"Value\":\"Dalvik/2.1.0 (Linux; U; Android 9; SM-G950F Build/PPR1.180610.011) ;SM-G950F\"}]",
"correlationId": "aaaa3333",
"detectedDateTime": "2021-07-03T13:35:38.8773806Z",
"detectionTimingType": "realtime",
"id": "6565",
"ipAddress": "3.3.3.3",
"lastUpdatedDateTime": "2021-07-03T13:38:04.6531838Z",
"location": {
"city": "Lviv",
"countryOrRegion": "UA",
"geoCoordinates": {
"latitude": 49,
"longitude": 24
},
"state": "L'vivs'ka Oblast'"
},
"requestId": "bbbb33333",
"riskDetail": "userPassedMFADrivenByRiskBasedPolicy",
"riskEventType": "unfamiliarFeatures",
"riskLevel": "low",
"riskState": "remediated",
"source": "IdentityProtection",
"tokenIssuerType": "AzureAD",
"userDisplayName": "Svetlana Israeli",
"userId": "999",
"userPrincipalName": "SvetlanaI@test.com"
}
}
}
Human Readable Output
Found Risk Detection with ID: 6565
Id User Id User Display Name User Principal Name Risk Detail Risk Event Type Risk Level Risk State Ip Address Detection Timing Type Last Updated Date Time Location 6565 999 Svetlana Israeli SvetlanaI@test.com userPassedMFADrivenByRiskBasedPolicy unfamiliarFeatures low remediated 3.3.3.3 realtime 2021-07-03T13:38:04.6531838Z city: Lviv
state: L’vivs’ka Oblast’
countryOrRegion: UA
geoCoordinates: {“latitude”: 49, “longitude”: 24}
azure-risky-users-confirm-safe
Confirms user(s) as safe (post-investigation).
Base Command
azure-risky-users-confirm-safe
Input
| Argument Name | Description | Required |
|---|---|---|
| user | A comma-separated list of User IDs or UPNs of the user(s) to confirm as safe. Results may not be immediately visible due to processing delays in Azure. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureRiskyUsers.Remediation.UserID | String | The ID of the user. |
| AzureRiskyUsers.Remediation.UserPrincipalName | String | The UPN of the user. This key will be populated only if the user’s UPN is provided. |
| AzureRiskyUsers.Remediation.Success | Boolean | Whether the new state was applied successfully. |
| AzureRiskyUsers.Remediation.RiskState | String | The new risk state of the user. |
Command Example
!azure-risky-users-confirm-safe user=user_id_1,name2@test.com,name3@test.com
Context Example
{
"AzureRiskyUsers": {
"Remediation": [
{
"UserID": "user_id_1",
"UserPrincipalName": "",
"Success": true,
"RiskState": "confirmedSafe"
},
{
"UserID": "user_id_2",
"UserPrincipalName": "name2@test.com",
"Success": true,
"RiskState": "confirmedSafe"
},
{
"UserID": "user_id_3",
"UserPrincipalName": "name3@test.com",
"Success": false,
"RiskState": ""
}
]
}
}
Human Readable Output
Successfully confirmed users as safe
UserID UserPrincipalName Success RiskState user_id_1 true confirmedSafe user_id_2 name2@test.com true confirmedSafe
Unable to confirm users as safe
User Error name3@test.com Error: confirmation failed.
azure-risky-users-confirm-compromise
Confirms user(s) as compromised.
Base Command
azure-risky-users-confirm-compromise
Input
| Argument Name | Description | Required |
|---|---|---|
| user | A comma-separated list of User IDs or UPNs of the user(s) to confirm as compromised. Results may not be immediately visible due to processing delays in Azure. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureRiskyUsers.Remediation.UserID | String | The ID of the user. |
| AzureRiskyUsers.Remediation.UserPrincipalName | String | The UPN of the user. This key will be populated only if the user’s UPN is provided. |
| AzureRiskyUsers.Remediation.Success | Boolean | Whether the new state was applied successfully. |
| AzureRiskyUsers.Remediation.RiskState | String | The new risk state of the user. |
Command Example
!azure-risky-users-confirm-compromise user=user_id_1,name2@test.com,name3@test.com
Context Example
{
"AzureRiskyUsers": {
"Remediation": [
{
"UserID": "user_id_1",
"UserPrincipalName": "",
"Success": true,
"RiskState": "confirmedCompromised"
},
{
"UserID": "user_id_2",
"UserPrincipalName": "name2@test.com",
"Success": true,
"RiskState": "confirmedCompromised"
},
{
"UserID": "user_id_3",
"UserPrincipalName": "name3@test.com",
"Success": false,
"RiskState": ""
}
]
}
}
Human Readable Output
Successfully confirmed users as compromised
UserID UserPrincipalName Success RiskState user_id_1 true confirmedCompromised user_id_2 name2@test.com true confirmedCompromised
Unable to confirm users as compromised
User Error name3@test.com Error: confirmation failed.
Configuration parameters
client_id— Client IDauthentication_type— Authentication Type (required)tenant_id— Tenant ID (for Client Credentials mode)client_secret— Client Secret (for Client Credentials mode)managed_identities_client_id—proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (10)
-
azure-risky-user-getRetrieves properties and relationships of a Risky User.
-
azure-risky-users-auth-completeCompletes the authorization process. Run this command after executing the azure-risky-users-auth-start command.
-
azure-risky-users-auth-resetRun this command if for some reason you need to rerun the authentication process.
-
azure-risky-users-auth-startStarts the authorization process. Follow the instructions in the command results.
-
azure-risky-users-auth-testTests the connectivity to Azure.
-
azure-risky-users-confirm-compromiseConfirms user(s) as compromised.
-
azure-risky-users-confirm-safeConfirms user(s) as safe (post-investigation).
-
azure-risky-users-listReturns a list of all risky users and their properties.
-
azure-risky-users-risk-detection-getReads the properties and relationships of a riskDetection object.
-
azure-risky-users-risk-detections-listReturns a comma-separated list of the Risk Detection objects and their properties.
from collections.abc import Callable from urllib.parse import parse_qs, urlparse import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 from MicrosoftApiModule import * # noqa: E402 from CommonServerUserPython import * CLIENT_CREDENTIALS_FLOW = "Client Credentials" DEVICE_FLOW = "Device Code" MAX_ITEMS_PER_REQUEST = 500 class Client: """ API Client to communicate with AzureRiskyUsers. """ def __init__( self, client_id: str, verify: bool, proxy: bool, authentication_type: str, tenant_id: str = None, client_secret: str = None, managed_identities_client_id: str = None, ): if "@" in client_id: # for use in test-playbook client_id, refresh_token = client_id.split("@") integration_context = get_integration_context() integration_context.update(current_refresh_token=refresh_token) set_integration_context(integration_context) self.authentication_type = authentication_type client_args = assign_params( self_deployed=True, auth_id=client_id, grant_type=self.get_grant_by_auth_type(authentication_type), base_url="https://graph.microsoft.com/v1.0", verify=verify, proxy=proxy, scope=self.get_scope_by_auth_type(authentication_type), # used for device code flow token_retrieval_url=self.get_token_retrieval_url_by_auth_type(authentication_type), # used for client credentials flow tenant_id=tenant_id, enc_key=client_secret, managed_identities_client_id=managed_identities_client_id, managed_identities_resource_uri=Resources.graph, command_prefix="azure-risky-users", ) self.ms_client = MicrosoftClient(**client_args) @staticmethod def get_grant_by_auth_type(authentication_type: str) -> str: """ Gets the grant type by the given authentication type. Args: authentication_type: desirable authentication type, could be Client credentials or Device Code. Returns: the grant type. """ if authentication_type == CLIENT_CREDENTIALS_FLOW: # Client credentials flow return CLIENT_CREDENTIALS else: # Device Code Flow return DEVICE_CODE @staticmethod def get_scope_by_auth_type(authentication_type: str) -> str: """ Gets the scope by the given authentication type. Args: authentication_type: desirable authentication type, could be Client credentials or Device Code. Returns: the scope. """ if authentication_type == CLIENT_CREDENTIALS_FLOW: # Client credentials flow return Scopes.graph else: # Device Code Flow return ( "https://graph.microsoft.com/IdentityRiskyUser.Read.All" " IdentityRiskEvent.ReadWrite.All IdentityRiskyUser.Read.All" " IdentityRiskyUser.ReadWrite.All offline_access" ) @staticmethod def get_token_retrieval_url_by_auth_type(authentication_type: str) -> None | str: """ Gets the token retrieval url by the given authentication type. Args: authentication_type: desirable authentication type, could be Client credentials or Device Code. Returns: the token retrieval url. """ if authentication_type == CLIENT_CREDENTIALS_FLOW: # Client credentials flow return None else: # Device Code Flow return "https://login.microsoftonline.com/organizations/oauth2/v2.0/token" def upn_to_user_id(self, upn: str) -> str: """Retrieves the user ID of a user by their UPN. Args: upn (str): A UPN Returns: str: The user ID """ return self.ms_client.http_request(method="GET", url_suffix=f"users/{upn}")["id"] def risky_users_list_request( self, limit: int = None, risk_state: str | None = None, risk_level: str | None = None, skip_token: str | None = None, order_by: str | None = None, update_before: str | None = None, updated_after: str | None = None, ) -> dict: """ List risky users. Args: risk_state (str): Risk State to retrieve. risk_level (str): Specify to get only results with the same Risk Level. limit (int): Limit of results to retrieve. order_by (str): Order results by this attribute. update_before (str): Filter events by updated before. updated_after (str): Filter events by updated after. skip_token (str): Skip token. Returns: response (dict): API response from AzureRiskyUsers. """ if skip_token: return self.ms_client.http_request(method="GET", full_url=skip_token) params = remove_empty_elements( { "$top": limit, "$orderby": order_by, "$filter": build_query_filter( risk_state=risk_state, risk_level=risk_level, updated_date_time_after=updated_after, updated_date_time_before=update_before, ), } ) return self.ms_client.http_request(method="GET", url_suffix="identityProtection/riskyUsers", params=params) def risky_user_get_request(self, id: str) -> dict: """ Get risky user by ID. Args: id (str): Risky user ID to get. return: Response (dict): API response from AzureRiskyUsers. """ return self.ms_client.http_request(method="GET", url_suffix=f"identityProtection/riskyUsers/{id}") def confirm_compromised_request(self, user_ids: list) -> None: """ Confirms user(s) as compromised. Args: user_ids (list): List of user IDs to confirm as compromised. Returns: Response (dict): API response from AzureRiskyUsers. """ res: requests.Response = self.ms_client.http_request( method="POST", url_suffix="identityProtection/riskyUsers/confirmCompromised", json_data={"userIds": user_ids}, resp_type="response", ) # The status code must be 204: https://learn.microsoft.com/en-us/graph/api/riskyuser-confirmcompromised if res.status_code != 204: raise DemistoException(f"Unable to confirm risky user:\n{res.text}") def confirm_safe_request(self, user_ids: list) -> None: """ Confirms user(s) as safe. Args: user_ids (list): List of user IDs to confirm as safe. Returns: Response (dict): API response from AzureRiskyUsers. """ res: requests.Response = self.ms_client.http_request( method="POST", url_suffix="identityProtection/riskyUsers/confirmSafe", json_data={"userIds": user_ids}, resp_type="response", ) # The status code must be 204: https://learn.microsoft.com/en-us/graph/api/riskyuser-confirmsafe if res.status_code != 204: raise DemistoException(f"Unable to confirm risky user:\n{res.text}") def risk_detections_list_request( self, risk_state: str | None, risk_level: str | None, detected_date_time_before: str | None, detected_date_time_after: str | None, limit: int, order_by: str, skip_token: str | None = None, ) -> dict: """ Get a list of the Risk Detection objects and their properties. Args: risk_state (str): Risk State to retrieve. risk_level (str): Specify to get only results with the same Risk Level. detected_date_time_before (str): Filter events by created before. detected_date_time_after (str): Filter events by created after. limit (int): Limit of results to retrieve. order_by (str): Order results by this attribute. skip_token (int): Skip token. return: Response (dict): API response from AzureRiskyUsers. """ params = remove_empty_elements( { "$top": limit, "$skiptoken": skip_token, "$orderby": order_by, "$filter": build_query_filter( risk_state=risk_state, risk_level=risk_level, detected_date_time_before=detected_date_time_before, detected_date_time_after=detected_date_time_after, ), } ) return self.ms_client.http_request(method="GET", url_suffix="/identityProtection/riskDetections", params=params) def risk_detection_get_request(self, id: str) -> dict: """ Read the properties and relationships of a riskDetection object. Args: id (str): ID of risk detection to retrieve. Return: Response (dict): API response from AzureRiskyUsers. """ return self.ms_client.http_request(method="GET", url_suffix=f"/identityProtection/riskDetections/{id}") def is_upn(user_id_or_upn: str) -> bool: """Checks if the given string is likely a User Principal Name (UPN). Args: user_id_or_upn (str): The string to check, which could be a user ID (GUID) or a User Principal Name (UPN). Returns: bool: True if the string appears to be a UPN, False otherwise. """ return "@" in user_id_or_upn def update_query(query: str, filter_name: str, filter_value: str | None, filter_operator: str): if not filter_value: return query if filter_operator == "eq": filter_value = f"'{filter_value}'" filter_str = f"{filter_name} {filter_operator} {filter_value}" if query: return f"{query} and {filter_str}" else: return filter_str def build_query_filter( risk_state=None, risk_level=None, detected_date_time_before=None, detected_date_time_after=None, updated_date_time_before=None, updated_date_time_after=None, ) -> str | None: """ Build query filter for API call, in order to get filtered results. API query syntax reference: https://docs.microsoft.com/en-us/graph/query-parameters. Args: risk_state (str): Wanted risk state for filter. risk_level (str): Wanted risk level for filter. detected_date_time_before (str): Filter events by created before. detected_date_time_after (str): Filter events by created after. updated_date_time_before (str): Filter events by updated before. updated_date_time_after (str): Filter events by updated after. Returns: str: Query filter string for API call. """ query = "" query = update_query(query, "riskState", risk_state, "eq") query = update_query(query, "riskLevel", risk_level, "eq") query = update_query(query, "detectedDateTime", detected_date_time_before, "le") query = update_query(query, "detectedDateTime", detected_date_time_after, "ge") query = update_query(query, "riskLastUpdatedDateTime", updated_date_time_before, "le") query = update_query(query, "riskLastUpdatedDateTime", updated_date_time_after, "ge") return query def get_skip_token( next_link: str | None, outputs_prefix: str, outputs_key_field: str, readable_output: str ) -> CommandResults | str: if not next_link: return CommandResults( outputs_prefix=outputs_prefix, outputs_key_field=outputs_key_field, outputs=[], readable_output=readable_output, raw_response=[], ) else: parsed_url = urlparse(next_link) return parse_qs(parsed_url.query)["$skiptoken"][0] def do_pagination(client: Client, response: dict[str, Any], limit: int = 1) -> dict: """ Retrieves a limited number of pages by repeatedly making requests to the API using the nextLink URL until it has reached the specified limit or there are no more pages to retrieve, :param response: response body, contains collection of chat/message objects :param limit: the requested limit :return: dict of the limited response_data and the last nextLink URL. """ response_data = response.get("value") or [] next_link = response.get("@odata.nextLink") while (next_link := response.get("@odata.nextLink")) and len(response_data) < limit: response = client.risky_users_list_request(skip_token=next_link) response_data.extend(response.get("value") or []) demisto.debug(f"The limited response contains: {len(response_data[:limit])}") return {"value": response_data[:limit], "@odata.context": response.get("@odata.context"), "@odata.nextLink": next_link} def get_user_human_readable(users: list) -> Any: """Creates the human readable fo the command_results object. Args: users (list): A list of users from the response. Returns: Any: tableToMarkdown function output. """ table_headers = [ "id", "userDisplayName", "userPrincipalName", "riskLevel", "riskState", "riskDetail", "riskLastUpdatedDateTime", ] table_outputs = [{key: user.get(key) for key in user if key in table_headers} for user in users] return tableToMarkdown( name="Risky Users List:", t=table_outputs, headers=table_headers, removeNull=True, headerTransform=pascalToSpace ) def risky_users_list_command(client: Client, args: dict[str, str]) -> List[CommandResults]: """ List all risky users. Args: client (Client): Azure Risky Users API client. args (dict): Arguments for API call. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ page = args.get("page") if page: raise DemistoException("Page argument is deprecated, please use next_token and page_size instead.") next_token = args.get("next_token") limit = arg_to_number(args.get("limit")) or 50 risk_state = args.get("risk_state") risk_level = args.get("risk_level") order_by = args.get("order_by", "riskLastUpdatedDateTime desc") if args.get("updated_before"): fmt_updated_before = dateparser.parse(str(args.get("updated_before")), settings={"TIMEZONE": "UTC"}) if fmt_updated_before is not None: updated_before = datetime.strftime(fmt_updated_before, "%Y-%m-%dT%H:%M:%S.%f") + "0Z" else: updated_before = None demisto.debug(f"{fmt_updated_before=} -> {updated_before}") else: updated_before = None if args.get("updated_after"): fmt_updated_after = dateparser.parse(str(args.get("updated_after")), settings={"TIMEZONE": "UTC"}) if fmt_updated_after is not None: updated_after = datetime.strftime(fmt_updated_after, "%Y-%m-%dT%H:%M:%S.%f") + "0Z" else: updated_after = None demisto.debug(f"{fmt_updated_after=} -> {updated_after=}") else: updated_after = None page_size = arg_to_number(args.get("page_size")) if page_size and (page_size < 1 or page_size > 500): raise DemistoException("Page size must be between 1 and 500.") if next_token: # the page_size already defined the in the token. raw_response = client.risky_users_list_request( skip_token=next_token, order_by=order_by, update_before=updated_before, updated_after=updated_after ) elif page_size: raw_response = client.risky_users_list_request( risk_state=risk_state, risk_level=risk_level, limit=page_size, order_by=order_by, update_before=updated_before, updated_after=updated_after, ) else: # there is only a limit top = min(MAX_ITEMS_PER_REQUEST, limit) raw_response = client.risky_users_list_request( risk_state=risk_state, risk_level=risk_level, limit=top, order_by=order_by, update_before=updated_before, updated_after=updated_after, ) raw_response = do_pagination(client, raw_response, limit) list_users = raw_response.get("value") or [] next_token_from_request = raw_response.get("@odata.nextLink") or "" command_results = [] command_results.append( CommandResults( outputs_prefix="AzureRiskyUsers.RiskyUser", outputs_key_field="id", outputs=list_users, readable_output=get_user_human_readable(list_users), raw_response=raw_response, ) ) # We won't display the next_token if the user does not choose to use pagination. if next_token_from_request and (next_token or page_size): command_results.append( CommandResults( outputs={"AzureRiskyUsers(true)": {"RiskyUserListNextToken": next_token_from_request}}, readable_output=tableToMarkdown( "Risky Users List Token:", {"next_token": next_token_from_request}, headers=["next_token"], removeNull=False ), ) ) return command_results def risky_user_get_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Get a risky user by ID. Args: client (Client): Azure Risky Users API client. args (dict): Arguments for API call. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ raw_response = client.risky_user_get_request(args["id"]) table_headers = [ "id", "userDisplayName", "userPrincipalName", "riskLevel", "riskState", "riskDetail", "riskLastUpdatedDateTime", ] outputs = {key: raw_response.get(key) for key in raw_response if key in table_headers} readable_output = tableToMarkdown( name=f'Found Risky User With ID: {raw_response.get("id")}', t=outputs, headers=table_headers, removeNull=True, headerTransform=pascalToSpace, ) return CommandResults( outputs_prefix="AzureRiskyUsers.RiskyUser", outputs_key_field="id", outputs=raw_response, readable_output=readable_output, raw_response=raw_response, ) def risk_detections_list_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Retrieve a list of the Risk-Detection objects and their properties. Args: client (Client): Azure Risky Users API client. args (dict): Arguments for API call. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ page = arg_to_number(args.get("page")) or 1 limit = arg_to_number(args.get("limit")) or 50 risk_state = args.get("risk_state") risk_level = args.get("risk_level") detected_date_time_before = args.get("detected_date_time_before", "") detected_date_time_after = args.get("detected_date_time_after", "") order_by = args.get("order_by", "detectedDateTime desc") skip_token: CommandResults | str | None = None if page > 1: offset = limit * (page - 1) raw_response = client.risk_detections_list_request( risk_state, risk_level, detected_date_time_before, detected_date_time_after, offset, order_by ) next_link = raw_response.get("@odata.nextLink") skip_token = get_skip_token( next_link=next_link, outputs_prefix="AzureRiskyUsers.RiskDetection", outputs_key_field="id", readable_output=f"Risk Detections List\nCurrent page size: " f"{limit}\nShowing page {page} out others that may exist", ) if isinstance(skip_token, CommandResults): return skip_token raw_response = client.risk_detections_list_request( risk_state, risk_level, detected_date_time_before, detected_date_time_after, limit, order_by, skip_token, # type: ignore[arg-type] ) table_headers = [ "id", "userId", "userDisplayName", "userPrincipalName", "riskDetail", "riskEventType", "riskLevel", "riskState", "riskDetail", "lastUpdatedDateTime", "ipAddress", ] outputs = raw_response.get("value", {}) table_outputs = [{key: item.get(key) for key in item if key in table_headers} for item in outputs] readable_output = tableToMarkdown( name=f'Risk Detections List\n' f'Current page size: {args["limit"]}\n' f'Showing page {args["page"]} out others that may exist', t=table_outputs, headers=table_headers, removeNull=True, headerTransform=pascalToSpace, ) return CommandResults( outputs_prefix="AzureRiskyUsers.RiskDetection", outputs_key_field="id", outputs=outputs, readable_output=readable_output, raw_response=raw_response, ) def risk_detection_get_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Read the properties and relationships of a riskDetection object. Args: client (Client): Azure Risky Users API client. args (dict): Arguments for API call. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ raw_response = client.risk_detection_get_request(args["id"]) table_headers = [ "id", "userId", "userDisplayName", "userPrincipalName", "riskDetail", "riskEventType", "riskLevel", "riskState", "ipAddress", "detectionTimingType", "lastUpdatedDateTime", "location", ] outputs = {key: raw_response.get(key) for key in raw_response if key in table_headers} readable_output = tableToMarkdown( name=f'Found Risk Detection with ID: {raw_response.get("id")}', t=outputs, headers=table_headers, removeNull=True, headerTransform=pascalToSpace, ) return CommandResults( outputs_prefix="AzureRiskyUsers.RiskDetection", outputs_key_field="id", outputs=raw_response, readable_output=readable_output, raw_response=raw_response, ) def risky_users_confirm(client: Client, args: dict[str, Any], confirm_func: Callable, verdict: str) -> list[CommandResults]: error_outputs = [] success_outputs = [] context_ouputs = [] results: list[CommandResults] = [] for user in argToList(args["user"]): try: user_id = client.upn_to_user_id(user) if is_upn(user) else user confirm_func(user_ids=[user_id]) output = { "UserID": user_id, "UserPrincipalName": user if is_upn(user) else "", "Success": True, "RiskState": f"confirmed{verdict.capitalize()}", } success_outputs.append(output) context_ouputs.append(output) except DemistoException as e: error_outputs.append({"User": user, "Error": str(e)}) context_ouputs.append( { "UserID": user if not is_upn(user) else "", "UserPrincipalName": user if is_upn(user) else "", "Success": False, "RiskState": "", } ) if success_outputs: results.append( CommandResults(readable_output=tableToMarkdown(f"Successfully confirmed users as {verdict}.", success_outputs)) ) if error_outputs: results.append( CommandResults( readable_output=tableToMarkdown(f"Unable to confirm users as {verdict}.", error_outputs), entry_type=EntryType.ERROR, content_format=EntryFormat.MARKDOWN, ) ) results[0].outputs_prefix = "AzureRiskyUsers.Remediation" results[0].outputs_key_field = "UserID" results[0].outputs = context_ouputs return results def test_module(client: Client): """Tests API connectivity and authentication' The test module is not functional for Device Code flow authentication, it raises the suitable exception instead. Args: client (Client): Azure Risky Users API client. Returns: None """ if client.authentication_type == DEVICE_FLOW: # Device Code flow raise DemistoException( "When using device code flow configuration, please enable the integration and run " "the `azure-risky-users-auth-start` command. Follow the instructions that will be printed" " as the output of the command.\n " "You can validate the connection by running `!azure-risky-users-auth-test`.\n" ) test_connection(client) return "ok" # Authentication Functions def start_auth(client: Client) -> CommandResults: result = client.ms_client.start_auth("!azure-risky-users-auth-complete") return CommandResults(readable_output=result) def complete_auth(client: Client) -> str: client.ms_client.get_access_token() return "Authorization completed successfully." def test_connection(client: Client) -> str: client.ms_client.get_access_token() return "Success!" def main(): # pragma: no cover """ PARSE AND VALIDATE INTEGRATION PARAMS """ params = demisto.params() args = demisto.args() client_id = params.get("client_id", {}).get("password", "") auth_type = params.get("authentication_type", "Device Code") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) managed_identities_client_id = get_azure_managed_identities_client_id(params) # Params for Client Credentials flow only: tenant_id = params.get("tenant_id") client_secret = params.get("client_secret", {}).get("password", "") command = demisto.command() demisto.info(f"Command being called is {command}") try: urllib3.disable_warnings() client = Client( client_id=client_id, verify=verify_certificate, proxy=proxy, authentication_type=auth_type, tenant_id=tenant_id, client_secret=client_secret, managed_identities_client_id=managed_identities_client_id, ) if command == "test-module": return_results(test_module(client)) elif command == "azure-risky-users-auth-reset": return_results(reset_auth()) elif command == "azure-risky-users-auth-start": return_results(start_auth(client)) elif command == "azure-risky-users-auth-complete": return_results(complete_auth(client)) elif command == "azure-risky-users-auth-test": return_results(test_connection(client)) elif command == "azure-risky-users-list": return_results(risky_users_list_command(client, args)) elif command == "azure-risky-user-get": return_results(risky_user_get_command(client, args)) elif command == "azure-risky-users-risk-detections-list": return_results(risk_detections_list_command(client, args)) elif command == "azure-risky-users-risk-detection-get": return_results(risk_detection_get_command(client, args)) elif command == "azure-risky-users-confirm-compromise": return_results(risky_users_confirm(client, args, client.confirm_compromised_request, "compromised")) elif command == "azure-risky-users-confirm-safe": return_results(risky_users_confirm(client, args, client.confirm_safe_request, "safe")) else: raise NotImplementedError(f"{command} command is not implemented.") except Exception as e: return_error(str(e)) if __name__ in ("__main__", "__builtin__", "builtins"): main()