MicrosoftGraphIdentityandAccess
Use the Entra ID Identity And Access integration to manage roles and members (formerly Azure Active Directory Identity And Access).
Authentication & Identity Management · Microsoft Graph Identity and Access
Details
| ID | MicrosoftGraphIdentityandAccess |
|---|---|
| Provider | Microsoft |
| Category | Authentication & Identity Management |
| From Version | 5.0.0 |
| Docker Image | demisto/crypto:1.0.0.10120494 |
| Supported Modules | Agentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud |
README
Use the Entra ID Identity And Access (formerly Azure Active Directory Identity And Access) integration to manage roles and members.
Configure Entra ID Identity and Access in Cortex
| Parameter | Required | Default value |
|---|---|---|
| Application ID | False | |
| Private Key | False | |
| Certificate Thumbprint | False | |
| Use Azure Managed Identities | False | |
| Azure Managed Identities Client ID | False | |
| Azure AD endpoint | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Override Microsoft Entra ID Protection risk level | True | True |
| Issue severity | False | medium |
Required Permissions
To use this integration, the following permissions are required on the Azure app.
IdentityRiskEvent.Read.AllIdentityRiskyUser.ReadWrite.AllRoleManagement.ReadWrite.DirectoryPolicy.ReadWrite.ConditionalAccessPolicy.Read.AllApplication.Read.AllAuditLog.Read.All
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.
msgraph-identity-auth-start
Run this command to start the authorization process and follow the instructions in the command results.
msgraph-identity-auth-complete
Run this command to complete the authorization process.
Should be used after running the msgraph-identity-auth-start command.
msgraph-identity-auth-reset
Run this command if for some reason you need to rerun the authentication process.
msgraph-identity-auth-test
Tests connectivity to Microsoft.
msgraph-identity-directory-roles-list
Lists the roles in the directory.
Base Command
msgraph-identity-directory-roles-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | Maximum number of results to fetch. Default is 10. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphIdentity.Role.deletedDateTime | Date | The time when a role was deleted. Displays only if a role was deleted. |
| MSGraphIdentity.Role.description | String | The description of the directory role. |
| MSGraphIdentity.Role.displayName | String | The display name of the directory role. |
| MSGraphIdentity.Role.id | String | The unique identifier of the directory role. |
| MSGraphIdentity.Role.roleTemplateId | String | The ID of the directory role template on which the role is based. |
Command Example
!msgraph-identity-directory-roles-list limit=1
Context Example
{
"MSGraphIdentity": {
"Role": {
"deletedDateTime": null,
"description": "Can create and manage all aspects of app registrations and enterprise apps.",
"displayName": "Application Administrator",
"id": ":id:",
"roleTemplateId": "role-template-id"
}
}
}
Human Readable Output
Directory roles
id displayName description roleTemplateId id Application Administrator Can create and manage all aspects of app registrations and enterprise apps. role-template-id
msgraph-identity-directory-role-activate
Activates a role by its template ID.
Base Command
msgraph-identity-directory-role-activate
Input
| Argument Name | Description | Required |
|---|---|---|
| role_template_id | ID of the role template to activate. Can be retrieved using the msgraph-identity-directory-roles-list command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphIdentity.Role.deletedDateTime | Date | The time when the role was deleted. Displays only if the role was deleted. |
| MSGraphIdentity.Role.description | String | The description of the directory role. |
| MSGraphIdentity.Role.displayName | String | The display name of the directory role. |
| MSGraphIdentity.Role.id | String | The unique identifier of the directory role. |
| MSGraphIdentity.Role.roleTemplateId | String | The ID of the directory role template on which this role is based. |
Command Example
!msgraph-identity-directory-role-activate role_template_id=role-template-id
Context Example
{
"MSGraphIdentity": {
"Role": {
"deletedDateTime": null,
"description": "Can create and manage all aspects of app registrations and enterprise apps.",
"displayName": "Application Administrator",
"id": ":id:",
"roleTemplateId": "role-template-id"
}
}
}
Human Readable Output
Role has been activated
id roleTemplateId displayName description deletedDateTime id role-template-id Application Administrator Can create and manage all aspects of app registrations and enterprise apps.
msgraph-identity-directory-role-members-list
Gets all members in a role ID.
Base Command
msgraph-identity-directory-role-members-list
Input
| Argument Name | Description | Required |
|---|---|---|
| role_id | The ID of the application for which to get its role members list. Can be retrieved using the msgraph-identity-directory-roles-list command. | Required |
| limit | The maximum number of members to fetch. Default is 10. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphIdentity.RoleMember.user_id | String | The unique identifier of the user in the role. |
| MSGraphIdentity.RoleMember.role_id | String | The unique identifier of the role specified in the input. |
Command Example
!msgraph-identity-directory-role-members-list role_id=:role:
Context Example
{
"MSGraphIdentity": {
"RoleMember": {
"role_id": ":role:",
"user_id": [
"70585180-517a-43ea-9403-2d80b97ab19d",
"5d9ed8e5-be5c-4aaf-86f8-c133c5cd19de"
]
}
}
}
Human Readable Output
Role ‘:role:’ members
role_id user_id :role: 70585180-517a-43ea-9403-2d80b97ab19d,
5d9ed8e5-be5c-4aaf-86f8-c133c5cd19de,
“id”,
a7cedb37-c4e5-4cfb-a327-7bafb34a1f49
msgraph-identity-directory-role-member-add
Adds a user to a role.
Base Command
msgraph-identity-directory-role-member-add
Input
| Argument Name | Description | Required |
|---|---|---|
| role_id | The ID of the role to add the user to. Can be retrieved using the msgraph-identity-directory-roles-list command. | Required |
| user_id | The ID of the user to add to the role. Can be retrieved using the msgraph-identity-directory-role-members-list command. | Required |
Context Output
There is no context output for this command.
Command Example
!msgraph-identity-directory-role-member-add role_id=:role: user_id=:id:
Human Readable Output
User ID :id: has been added to role :role:
msgraph-identity-directory-role-member-remove
Removes a user from a role.
Base Command
msgraph-identity-directory-role-member-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| role_id | ID of the role from which to remove the user. Can be retrieved using the msgraph-identity-directory-roles-list command. | Required |
| user_id | ID of the user to remove from the role. Can be retrieved using the msgraph-identity-directory-role-members-list command. | Required |
Context Output
There is no context output for this command.
Command Example
!msgraph-identity-directory-role-member-remove role_id=:role: user_id=:id:
Human Readable Output
User ID :id: has been removed from role :role:
msgraph-identity-ip-named-locations-create
Create an ip named location.
Base Command
msgraph-identity-ip-named-locations-create
Input
| Argument Name | Description | Required |
|---|---|---|
| ips | A comma-separated list of IP ranges to add to the named location. Each range must be in CIDR notation (IPv4 or IPv6), for example: “12.34.221.11/22,2001:0:9d38:90d6:0:0:0:0/63”. Bare IP addresses without a CIDR suffix (for example “0.0.0.0”) are rejected by Microsoft Graph. See the Microsoft Graph documentation. | Optional |
| is_trusted | Whether the IPs can be trusted. Default is false. | Optional |
| display_name | The display name for the IP location. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraph.conditionalAccess.namedIpLocations.time_created | Date | The time when an ip named location was created. |
| MSGraph.conditionalAccess.namedIpLocations.time_modified | Date | The time when an ip named location was updated. |
| MSGraph.conditionalAccess.namedIpLocations.display_name | String | The display name of the ip named location. |
| MSGraph.conditionalAccess.namedIpLocations.id | String | The unique identifier of the ip named location. |
| MSGraph.conditionalAccess.namedIpLocations.is_trusted | String | Abollean that taled if the ip named location is a trusted source. |
| MSGraph.conditionalAccess.namedIpLocations.ip_ranges | Array | List of ip ranges for the ip named location. |
msgraph-identity-ip-named-locations-get
Gets an ip named location.
Base Command
msgraph-identity-ip-named-locations-get
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_id | The id of the ip named location to get. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraph.conditionalAccess.namedIpLocations.time_created | Date | The time of the ip named location creation. |
| MSGraph.conditionalAccess.namedIpLocations.time_modified | Date | The time the ip named location was last modified. |
| MSGraph.conditionalAccess.namedIpLocations.display_name | String | The ip named location display name. |
| MSGraph.conditionalAccess.namedIpLocations.id | String | The unique identifier of the ip named location. |
| MSGraph.conditionalAccess.namedIpLocations.is_trusted | String | The ip named location trust status. |
| MSGraph.conditionalAccess.namedIpLocations.ip_ranges | Array | The ip named location ip ranges. |
Command Example
!msgraph-identity-ip-named-locations-get ip_id=03f8c56f-2ffd-4699-84af-XXXXXXXCX
Human Readable Output
Ip named location ‘ID’: :ipNamedLocation:
msgraph-identity-ip-named-locations-delete
Deletes an ip named location.
Base Command
msgraph-identity-ip-named-locations-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_id | The id of the ip named location to delete. | Required |
Context Output
No context output
Command Example
!msgraph-identity-ip-named-locations-delete ip_id=03f8c56f-2ffd-4699-84af-XXXXXXXCX
Human Readable Output
Successfully deleted IP named location ‘X-X-X-X’
msgraph-identity-ip-named-locations-update
Updates an ip named location.
Base Command
msgraph-identity-ip-named-locations-update
msgraph-identity-ip-named-locations-update
update an ip named location by id.
Base Command
msgraph-identity-ip-named-locations-update
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_id | The id of the named ip location to get from the api. | Required |
| ips | A comma-separated list of IP ranges to set on the named location. Each range must be in CIDR notation (IPv4 or IPv6), for example: “12.34.221.11/22,2001:0:9d38:90d6:0:0:0:0/63”. Bare IP addresses without a CIDR suffix (for example “0.0.0.0”) are rejected by Microsoft Graph. See the Microsoft Graph documentation. | Optional |
| is_trusted | Whether the IPs can be trusted. | Optional |
| display_name | The display name for the IP location. | Optional |
Context Output
There is no context output for this command.
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The get request results limit. | Optional |
| page | The page to get the data from. | Optional |
| odata_query | An odata query to send to the api. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraph.conditionalAccess.namedIpLocations.ip_named_locations | Array | List of ip named locations. |
Command Example
### msgraph-identity-protection-risks-list
***
Retrieve the properties of a collection of riskDetection objects.
#### Required Permissions
`IdentityRiskEvent.Read.All`
#### Base Command
`msgraph-identity-protection-risks-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| odata_query | An odata query to send to the api. | Optional |
| limit | Number of results to provide. Default is 50. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| MSGraph.identityProtection.risks.id | string | Unique ID of the risk detection. |
| MSGraph.identityProtection.risks.requestId | string | The 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. |
| MSGraph.identityProtection.risks.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. |
| MSGraph.identityProtection.risks.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. |
| MSGraph.identityProtection.risks.riskType | string | Deprecated. Use riskEventType instead. List of risk event types. |
| MSGraph.identityProtection.risks.riskLevel | string | Risk level of the detected risky user. The possible values are low, medium, high, hidden, none, and unknownFutureValue. |
| MSGraph.identityProtection.risks.riskstate | string | State of the user's risk. The possible values are none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.risks.riskDetail | string | Reason why the user is considered a risky user. The possible values are limited to none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.risks.source | string | Source of the risk detection. For example, `activeDirectory`. |
| MSGraph.identityProtection.risks.detectionTimingType | string | Timing of the detected risk \(real-time/offline\). The possible values are notDefined, realtime, nearRealtime, offline, and unknownFutureValue. |
| MSGraph.identityProtection.risks.activity | string | Indicates the activity type the detected risk is linked to. The possible values are signin, user, and unknownFutureValue. |
| MSGraph.identityProtection.risks.tokenIssuerType | string | Indicates the type of token issuer for the detected sign-in risk. The possible values are AzureAD, ADFederationServices, and unknownFutureValue. |
| MSGraph.identityProtection.risks.ipAddress | string | Provides the IP address of the client from where the risk occurred. |
| MSGraph.identityProtection.risks.location.city | string | City of the sign-in. |
| MSGraph.identityProtection.risks.location.countryOrRegion | string | Country or region of the sign-in. |
| MSGraph.identityProtection.risks.location.geoCoordinates.latitude | string | Latitude of the sign-in. |
| MSGraph.identityProtection.risks.location.geoCoordinates.longitude | string | Longitude of the sign-in. |
| MSGraph.identityProtection.risks.location.state | string | State of the sign-in. |
| MSGraph.identityProtection.risks.activityDateTime | string | Date and time that the risky activity occurred. The DateTimeOffset type represents date and time information using the ISO 8601 format and is always in UTC time. |
| MSGraph.identityProtection.risks.detectedDateTime | string | Date and time that the risk was detected. The DateTimeOffset type represents date and time information using the ISO 8601 format and is always in UTC time. |
| MSGraph.identityProtection.risks.lastUpdatedDateTime | string | Date and time that the risk detection was last updated. The DateTimeOffset type represents date and time information using the ISO 8601 format and is always in UTC time. |
| MSGraph.identityProtection.risks.userId | string | Unique ID of the user. |
| MSGraph.identityProtection.risks.userDisplayName | string | Risky user display name. |
| MSGraph.identityProtection.risks.userPrincipalName | string | Risky user principal name. |
| MSGraph.identityProtection.risks.additionalInfo | string | Additional information associated with the risk detection in JSON format. |
#### Command Example
```!msgraph-identity-protection-risks-list```
#### Human Readable Output
>### risks (6 results)
>
>|User ID|User Principal Name|User Display Name|IP Address|Detected Date Time|Activity|Activity Date Time|Additional Info|Correlation ID|Detection Timing Type|ID|Last Updated Date Time|Location|Request ID|Risk Detail|Risk Event Type|Risk Level|Risk State|Risk Type|Source|Token Issuer Type|
>|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 1.1.1.1 | 2021-04-25T09:00:40.7780969Z | signin | 2021-04-25T09:00:40.7780969Z | [{"Key":"userAgent","Value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36"}] | 271ac223-695b-418e-85b3-7809070ee33e | realtime | 86a45315157fb75c3a6e0936ef854c139df99bdfbde4bd7e7f1bc685c3638908 | 2021-05-23T08:20:41.9161522Z | city: San Jose<br/>state: California<br/>countryOrRegion: US<br/>geoCoordinates: {"latitude": 37.33053, "longitude": -121.8382} | 86b6e4a1-25cb-40c7-af2b-9e79c6106000 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 2.2.2.2 | 2021-04-28T11:40:11.333738Z | signin | 2021-04-28T11:40:11.333738Z | [{"Key":"userAgent","Value":"python-requests/2.18.4"}] | 6f74b0f4-dabc-49af-aa87-3aaba042baba | realtime | c0e94938cddbb849ef64dbb6a98189ab3d93cdec4c4f95923ac935a91486def2 | 2021-05-23T08:20:29.027631Z | city: Frankfurt Am Main<br/>state: Hessen<br/>countryOrRegion: DE<br/>geoCoordinates: {"latitude": 50.1109, "longitude": 8.6821} | 64b01b65-25fa-4811-b4cd-411c9accc000 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 3.3.3.3 | 2021-04-29T08:03:23.5302796Z | signin | 2021-04-29T08:03:23.5302796Z | [{"Key":"userAgent","Value":"python-requests/2.18.4"}] | 069f7e67-3692-4191-a84d-14ab0aa1baba | realtime | c197aea67197503695f6dbddd9af2b3adcd1e8571f8381e96707ac71162d1cdf | 2021-05-23T08:20:42.1561664Z | city: Paris<br/>state: Paris<br/>countryOrRegion: FR<br/>geoCoordinates: {"latitude": 48.86023, "longitude": 2.34107} | 22e0bc21-61f2-4661-aa0b-afe40985e100 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 5.5.5.5 | 2021-05-07T06:00:45.0034244Z | signin | 2021-05-07T06:00:45.0034244Z | [{"Key":"userAgent","Value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"}] | dec7bb21-5a9b-45ff-84d6-b1538da801bc | realtime | 8b29fae724e168a32412e2bdc630540588df7558ac647772c36d957656b6e156 | 2021-05-23T08:20:42.2461705Z | city: Tanglin<br/>state: South West<br/>countryOrRegion: SG<br/>geoCoordinates: {"latitude": 1.32, "longitude": 103.8198} | 5fd28f4a-b172-4aa6-92b2-883832460400 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 4.4.4.4 | 2021-05-09T09:41:24.9769131Z | signin | 2021-05-09T09:41:24.9769131Z | [{"Key":"userAgent","Value":"BAV2ROPC"}] | f9dbd73b-8e7f-4bcd-93a7-2a7c1d4cbaba | realtime | dbc1272033adf3a2e960ce438a671de91b4b1b917e250ec575492156eb64f6eb | 2021-05-23T08:20:29.0726385Z | city: Stockholm<br/>state: Stockholms Lan<br/>countryOrRegion: SE<br/>geoCoordinates: {"latitude": 59.31512, "longitude": 18.05132} | d6e81927-c8e1-40f4-ad38-aa4d5408aa00 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | 1.2.3.4 | 2021-05-11T07:15:33.6885155Z | signin | 2021-05-11T07:15:33.6885155Z | [{"Key":"userAgent","Value":"python-requests/2.25.1"}] | 5bb85e1f-1933-4698-831d-fbeb40aebaba | realtime | 969476f4d6d20717dfaea9f2df92945f9d736240d53b4187b50579003bf2d011 | 2021-05-23T08:20:42.2911741Z | city: Dublin<br/>state: Dublin<br/>countryOrRegion: IE<br/>geoCoordinates: {"latitude": 53.35389, "longitude": -6.24333} | 7b7d098c-edcc-4139-b171-fc64c38d0d00 | userPerformedSecuredPasswordChange | unfamiliarFeatures | low | remediated | unfamiliarFeatures | IdentityProtection | AzureAD |
### msgraph-identity-protection-risky-user-list
***
Retrieves the properties of a collection of riskDetection objects.
#### Required Permissions
`IdentityRiskyUser.Read.All`
#### Base Command
`msgraph-identity-protection-risky-user-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| updated_time | The time elapsed since the risky user was last updated, formatted as `<number> <time unit>`, e.g., `12 hours` or `7 days`. | Optional |
| risk_level | Risk level of the detected risky user. The possible values are low, medium, high, hidden, none, and unknownFeatureValue. | Optional |
| risk_state | State of the user's risk. The possible values are none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, and unknownFutureValue. | Optional |
| risk_detail | Details of the detected risk. The possible values are none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, and unknownFutureValue. | Optional |
| filter_expression | A custom query in OData syntax. Using this overrides all arguments, except for next_link. For more details, see https://docs.microsoft.com/en-us/graph/query-parameters. | Optional |
| limit | Number of results to provide. Default is 50. | Optional |
| next_link | A link that specifies a starting point for subsequent calls. Using this argument overrides all other arguments. | Optional |
| user_name | Risky user principal name. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| MSGraph.identityProtection.risky-users.id | string | Unique ID of the risky user. |
| MSGraph.identityProtection.risky-users.isDeleted | Boolean | Indicates whether a user is delted |
| MSGraph.identityProtection.risky-users.isProcessing | Boolean | Indicates whether a user's risky state is being processed by the backend. |
| MSGraph.identityProtection.risky-users.riskLastUpdatedDateTime | DateTime | The date and time that the risky user was last updated. The DateTimeOffset type represents date and time information using the ISO 8601 format and is always in UTC time. |
| MSGraph.identityProtection.risky-users.riskLevel | string | Risk level of the detected risky user. The possible values are low, medium, high, hidden, none, and unknownFutureValue. |
| MSGraph.identityProtection.risky-users.riskstate | string | State of the user's risk. The possible values are none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.risky-users.riskDetail | string | Reason why the user is considered a risky user. The possible values are limited to none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.risky-users.userDisplayName | string | Risky user display name. |
| MSGraph.identityProtection.risky-users.userPrincipalName | string | Risky user principal name. |
#### Command Example
```!msgraph-identity-protection-risky-user-list```
#### Human Readable Output
>### Risky Users (1 result)
>
>|User Principal Name|User Display Name|ID|Is Deleted|Is Processing|Risk Detail|Risk Last Updated Date Time|Risk Level|Risk State|
>|---|---|---|---|---|---|---|---|---|
>| jdoe@example.com | John Doe | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | false | false | none | 2021-07-21T17:56:28.958147Z | medium | atRisk |
### msgraph-identity-protection-risky-user-history-list
***
Gets the risk history of a riskyUser resource.
#### Required Permissions
`IdentityRiskyUser.Read.All`
`IdentityRiskyUser.ReadWrite.All`
#### Base Command
`msgraph-identity-protection-risky-user-history-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| user_id | Unique ID of the user. | Required |
| limit | Number of results to provide. Default is 50. | Optional |
| filter_expression | A custom query in OData syntax. Using this overrides all arguments, except for next_link. For more details, see https://docs.microsoft.com/en-us/graph/query-parameters. | Optional |
| next_link | A link that specifies a starting point for subsequent calls. Using this argument overrides all other arguments. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| MSGraph.identityProtection.RiskyUserHistory.id | string | Unique ID of the risky user. |
| MSGraph.identityProtection.RiskyUserHistory.isDeleted | Boolean | Indicates whether the user is deleted. |
| MSGraph.identityProtection.RiskyUserHistory.isProcessing | Boolean | Indicates whether a user's risky state is being processed by the backend. |
| MSGraph.identityProtection.RiskyUserHistory.riskLastUpdatedDateTime | DateTime | The date and time that the risky user was last updated. The DateTimeOffset type represents date and time information using the ISO 8601 format and is always in UTC time. |
| MSGraph.identityProtection.RiskyUserHistory.riskLevel | string | Risk level of the detected risky user. The possible values are low, medium, high, hidden, none, and unknownFutureValue. |
| MSGraph.identityProtection.RiskyUserHistory.riskstate | string | State of the user's risk. The possible values are none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.RiskyUserHistory.riskDetail | string | Reason why the user is considered a risky user. The possible values are limited to none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, hidden, adminConfirmedUserCompromised, and unknownFutureValue. |
| MSGraph.identityProtection.RiskyUserHistory.userDisplayName | string | Risky user display name. |
| MSGraph.identityProtection.RiskyUserHistory.userPrincipalName | string | Risky user principal name. |
#### Command Example
```!msgraph-identity-protection-risky-user-history-list user_id="3fa9f28b-eb0e-463a-ba7b-8089fe9991e2"```
#### Human Readable Output
>### Risky User History For 3Fa9F28B-Eb0E-463A-Ba7B-8089Fe9991E2 (12 results)
>
>|User ID|User Principal Name|User Display Name|Activity|ID|Initiated By|Is Deleted|Is Processing|Risk Detail|Risk Last Updated Date Time|Risk Level|Risk State|
>|---|---|---|---|---|---|---|---|---|---|---|---|
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637571860258849619 | | false | false | none | 2021-05-21T09:27:05.8849619Z | high | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637579558855706894 | | false | false | none | 2021-05-30T07:18:05.5706894Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637581817194185440 | | false | false | none | 2021-06-01T22:01:59.418544Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637617844902084332 | | false | false | none | 2021-07-13T14:48:10.2084332Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures,<br/>mcasImpossibleTravel<br/>riskEventTypes: unfamiliarFeatures,<br/>mcasImpossibleTravel<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637617905007494900 | | false | false | none | 2021-07-13T16:28:20.74949Z | medium | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | | false | false | none | 2021-07-21T17:56:28.958147Z | medium | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: <br/>riskEventTypes: <br/>detail: userPerformedSecuredPasswordChange | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637573546620000000 | | false | false | userPerformedSecuredPasswordChange | 2021-05-23T08:17:42Z | none | remediated |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: mcasImpossibleTravel<br/>riskEventTypes: mcasImpossibleTravel<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637580439207803793 | | false | false | none | 2021-05-31T07:45:20.7803793Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637588246283692301 | | false | false | none | 2021-06-09T08:37:08.3692301Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: mcasImpossibleTravel<br/>riskEventTypes: mcasImpossibleTravel<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637597636382264783 | | false | false | none | 2021-06-20T05:27:18.2264783Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: unfamiliarFeatures<br/>riskEventTypes: unfamiliarFeatures<br/>detail: null | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637600753627454017 | | false | false | none | 2021-06-23T20:02:42.7454017Z | low | atRisk |
>| 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | jdoe@example.com | John Doe | eventTypes: <br/>riskEventTypes: <br/>detail: adminDismissedAllRiskForUser | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2637623861161706539 | 3fa9f28b-eb0e-463a-ba7b-8089fe9991e2 | false | false | adminDismissedAllRiskForUser | 2021-07-20T13:55:16.1706539Z | none | dismissed |
### msgraph-identity-protection-risky-user-confirm-compromised
***
Confirms one or more riskyUser objects as compromised. This action sets the targeted user's risk level to high.
#### Required Permissions
`IdentityRiskyUser.ReadWrite.All`
#### Base Command
`msgraph-identity-protection-risky-user-confirm-compromised`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| user_ids | One or more user IDs, comma-separated. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!MSGraph.identityProtection-risky-user-confirm-compromised user_ids="3fa9f28b-eb0e-463a-ba7b-8089fe9991e3"```
#### Human Readable Output
>✅ Confirmed successfully.
### msgraph-identity-protection-risky-user-dismiss
***
Dismisses the risk of one or more riskyUser objects. This action sets the targeted user's risk level to none.
#### Required Permissions
`IdentityRiskyUser.ReadWrite.All`
#### Base Command
`msgraph-identity-protection-risky-user-dismiss`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| user_ids | One or more user IDs, comma-separated. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!c user_ids="3fa9f28b-eb0e-463a-ba7b-8089fe9991e2"```
#### Human Readable Output
>✅ Dismissed successfully.
>
### msgraph-identity-ca-policy-create
***
Creates a Conditional Access policy.
#### Base Command
`msgraph-identity-ca-policy-create`
#### Required Permissions
`Permission type : Application or Delegated`
`Permissions:Policy.Read.All`
`Policy.ReadWrite.ConditionalAccess`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| policy | JSON object containing the policy details. Ensure a valid policy object is passed. <br/>. | Optional |
| policy_name | The name of the Conditional Access policy to be created. Required if policy argument not present. | Optional |
| state | The state of the policy.<br/>Required if policy argument not present.<br/>. Possible values are: enabled, disabled. | Optional |
| sign_in_risk_levels | List of sign-in risk levels for the policy. Options: `low`, `medium`, `high`, `none`.<br/>Required if policy argument not present.<br/>. | Optional |
| user_risk_levels | List of user risk levels for the policy. Options: `low`, `medium`, `high`.<br/>Required if policy argument not present.<br/>. | Optional |
| client_app_types | List of types of client apps to be included in the policy. Options: `all`, `browser`, `mobileAppsAndDesktopClients`, `exchangeActiveSync`, `easSupported`, `other`.<br/>Required if policy argument not present.<br/>. | Optional |
| include_applications | List of application IDs to be included in the policy. If no applications are defined, `includeApplications: 'none'` will be used.<br/>. | Optional |
| exclude_applications | List of application IDs to be excluded from the policy.<br/>. | Optional |
| include_user_actions | List of user actions to be included in the policy.<br/>. Possible values are: vrn:user:registersecurityinfo, urn:user:registerdevice. | Optional |
| include_users | List of user IDs to be included in the policy. Options: None, All, GuestsOrExternalUsers.<br/>. | Optional |
| exclude_users | List of user IDs to be excluded from the policy.<br/>. | Optional |
| include_groups | List of group IDs to be included in the policy.<br/>. | Optional |
| exclude_groups | List of group IDs to be excluded from the policy.<br/>. | Optional |
| include_roles | List of role IDs to be included in the policy.<br/>. | Optional |
| exclude_roles | List of role IDs to be excluded from the policy.<br/>. | Optional |
| include_platforms | List of platforms to be included in the policy. Options: `Android`, `iOS`, `Windows`, `windowsPhone`, `macOS`, `Linux`, `all`.<br/>. | Optional |
| exclude_platforms | List of platforms to be excluded from the policy.<br/>. | Optional |
| include_locations | List of location IDs to be included in the policy. Options: `All`, `AllTrusted`, or a list of specific location IDs.<br/>. | Optional |
| exclude_locations | List of location IDs to be excluded from the policy.<br/>. | Optional |
| grant_control_operator | The operator for grant control.<br/>Default value is 'AND'. Possible values are: AND, OR. Default is AND. | Optional |
| grant_control_enforcement | List of built-in controls for the policy. Options: `block`, `mfa`, `compliantDevice`, `domainJoinedDevice`, `approvedApplication`, `compliantApplication`, `passwordChange`.<br/>Default value is 'mfa'<br/>Special considerations when using `passwordChange`: <br/>- `passwordChange` must be accompanied by `mfa` using an `AND` operator.<br/>- `passwordChange` must be used with `userRiskLevels`.<br/>- The policy should target all applications and not exclude any.<br/>. Default is mfa. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| MSGraphIdentity.ConditionalAccessPolicy.id | string | The unique ID of the Conditional Access policy. |
| MSGraphIdentity.ConditionalAccessPolicy.displayName | string | The display name of the Conditional Access policy. |
| MSGraphIdentity.ConditionalAccessPolicy.createdDateTime | date | The date and time the policy was created. |
| MSGraphIdentity.ConditionalAccessPolicy.modifiedDateTime | date | The date and time the policy was last modified. |
| MSGraphIdentity.ConditionalAccessPolicy.state | string | The state of the policy \(enabled/disabled\). |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.signInRiskLevels | list | List of sign-in risk levels this policy applies to. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.clientAppTypes | list | Client application types this policy targets. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.includeApplications | list | List of applications included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.excludeApplications | list | List of applications excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.includeUserActions | list | User actions included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeUsers | list | User IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeUsers | list | User IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeGroups | list | Group IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeGroups | list | Group IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeRoles | list | Role IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeRoles | list | Role IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.platforms.includePlatforms | list | Platforms included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.platforms.excludePlatforms | list | Platforms excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.locations.includeLocations | list | Location IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.locations.excludeLocations | list | Location IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.operator | string | Logical operator used in the grant controls \(e.g., AND, OR\). |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.builtInControls | list | List of built-in access controls \(e.g., MFA, compliant device\). |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.customAuthenticationFactors | list | Custom authentication factors used in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.termsOfUse | list | List of Terms of Use policy IDs applied. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.cloudAppSecurity.cloudAppSecurityType | string | The type of cloud app security control. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.cloudAppSecurity.isEnabled | boolean | Whether cloud app security is enabled. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.value | number | Sign-in frequency value. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.type | string | Unit of the sign-in frequency \(e.g., hours, days\). |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.isEnabled | boolean | Whether sign-in frequency control is enabled. |
#### Command example
```!msgraph-identity-ca-policy-create policy_name="test" state=disabled client_app_types="all" include_users="All" include_applications="none" include_platforms="iOS" grant_control_enforcement="block" grant_control_operator="AND" sign_in_risk_levels="low" user_risk_levels="low"```
#### Context Example
```json
{
"MSGraphIdentity": {
"ConditionalAccessPolicy": {
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identity/conditionalAccess/policies/$entity",
"conditions": {
"applications": {
"applicationFilter": null,
"excludeApplications": [],
"includeApplications": [
"None"
],
"includeAuthenticationContextClassReferences": [],
"includeUserActions": []
},
"authenticationFlows": null,
"clientAppTypes": [
"all"
],
"clientApplications": null,
"devices": null,
"insiderRiskLevels": null,
"locations": null,
"platforms": {
"excludePlatforms": [],
"includePlatforms": [
"iOS"
]
},
"servicePrincipalRiskLevels": [],
"signInRiskLevels": [
"low"
],
"userRiskLevels": [
"low"
],
"users": {
"excludeGroups": [],
"excludeGuestsOrExternalUsers": null,
"excludeRoles": [],
"excludeUsers": [],
"includeGroups": [],
"includeGuestsOrExternalUsers": null,
"includeRoles": [],
"includeUsers": [
"All"
]
}
},
"createdDateTime": "2025-05-15T14:11:02.0356168Z",
"displayName": "test",
"grantControls": {
"authenticationStrength": null,
"authenticationStrength@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identity/conditionalAccess/policies('0a31b9ab-be62-4f1b-b4a7-523940eb9f54')/grantControls/authenticationStrength/$entity",
"builtInControls": [
"block"
],
"customAuthenticationFactors": [],
"operator": "OR",
"termsOfUse": []
},
"id": "0a31b9ab-be62-4f1b-b4a7-523940eb9f54",
"modifiedDateTime": null,
"sessionControls": null,
"state": "disabled",
"templateId": null
}
}
}
Human Readable Output
Conditional Access policy 0a31b9ab-be62-4f1b-b4a7-523940eb9f54 was successfully created.
msgraph-identity-ca-policy-update
Updates a Conditional Access policy. By default, the command attempts to append values to existing list-based fields (e.g., include_users, include_groups, etc.). If a field does not support appending (i.e., it’s not a list), the command overrides the existing value with the new one.
Base Command
msgraph-identity-ca-policy-update
Required Permissions
Permission type : Application or Delegated
Permissions:Policy.Read.All
Policy.ReadWrite.ConditionalAccess
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_id | Required. The ID of the Conditional Access policy to update. | Required |
| policy | JSON object containing the policy details. Ensure a valid policy object is passed. If no policy argument is provided,other required arguments like policy_name, state, etc. must be included.. |
Optional |
| policy_name | The name of the Conditional Access policy to be created. | Optional |
| state | The state of the policy. . Possible values are: enabled, disabled. |
Optional |
| sign_in_risk_levels | List of sign-in risk levels for the policy. Options: low, medium, high, none.. |
Optional |
| user_risk_levels | List of user risk levels for the policy. Options: low, medium, high.. |
Optional |
| client_app_types | Types of client apps to be included in the policy. Options: all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other.. |
Optional |
| include_applications | List of application IDs to be included in the policy. If no applications are defined, includeApplications: 'none' will be used.. |
Optional |
| exclude_applications | List of application IDs to be excluded from the policy. . |
Optional |
| include_user_actions | List of user actions to be included in the policy. Options: vrn:user:registersecurityinfo, urn:user:registerdevice.. Possible values are: vrn:user:registersecurityinfo, urn:user:registerdevice. |
Optional |
| include_users | List of user IDs to be included in the policy. Options: None, All, GuestsOrExternalUsers. . |
Optional |
| exclude_users | List of user IDs to be excluded from the policy. . |
Optional |
| include_groups | List of group IDs to be included in the policy. . |
Optional |
| exclude_groups | List of group IDs to be excluded from the policy. . |
Optional |
| include_roles | List of role IDs to be included in the policy. . |
Optional |
| exclude_roles | List of role IDs to be excluded from the policy. . |
Optional |
| include_platforms | List of platforms to be included in the policy. Options: Android, iOS, Windows, windowsPhone, macOS, Linux, all.. |
Optional |
| exclude_platforms | List of platforms to be excluded from the policy. . |
Optional |
| include_locations | List of location IDs to be included in the policy. Options: All, AllTrusted, or a list of specific location IDs.. |
Optional |
| exclude_locations | List of location IDs to be excluded from the policy. . |
Optional |
| grant_control_operator | The operator for grant control. . Possible values are: AND, OR. |
Optional |
| grant_control_enforcement | List of built-in controls for the policy. Options: block, mfa, compliantDevice, domainJoinedDevice, approvedApplication, compliantApplication, passwordChange.Special considerations when using passwordChange: - passwordChange must be accompanied by mfa using an AND operator.- passwordChange must be used with userRiskLevels.- The policy should target all applications and not exclude any. . |
Optional |
| update_action | Defines how to update fields in the policy. - override (default): Replaces existing values.- append: Adds new values to existing ones without removing them.. Possible values are: override, append. |
Optional |
Context Output
There is no context output for this command.
Command example
!msgraph-identity-ca-policy-update policy_id="58c3a52b-190f-4be3-a19c-a4d03f8ce684" state="disabled"
Human Readable Output
Conditional Access policy 58c3a52b-190f-4be3-a19c-a4d03f8ce684 was successfully updated.
Note:
Fieldstateis not a list - overriding the value.
msgraph-identity-ca-policies-list
Retrieve one or all Conditional Access policies from Microsoft Graph API.
Base Command
msgraph-identity-ca-policies-list
Required Permissions
Permission type : Application or Delegated
Permissions:Policy.Read.All
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_id | Optional. The ID of the Conditional Access policy to retrieve. If not provided, all policies will be returned. | Optional |
| filter | Optional. The OData filter query to retrieve specific Conditional Access policies. | Optional |
| limit | Optional. The maximum number of Conditional Access policies to return. Applicable only when all_results is set to false. | Optional |
| all_results | Optional. Whether to retrieve all results without applying the ‘limit’. Default value is true. Possible values are: true, false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphIdentity.ConditionalAccessPolicy.id | string | The unique ID of the Conditional Access policy. |
| MSGraphIdentity.ConditionalAccessPolicy.displayName | string | The display name of the Conditional Access policy. |
| MSGraphIdentity.ConditionalAccessPolicy.createdDateTime | date | The date and time the policy was created. |
| MSGraphIdentity.ConditionalAccessPolicy.modifiedDateTime | date | The date and time the policy was last modified. |
| MSGraphIdentity.ConditionalAccessPolicy.state | string | The state of the policy (enabled/disabled). |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.signInRiskLevels | list | List of sign-in risk levels this policy applies to. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.clientAppTypes | list | Client application types this policy targets. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.includeApplications | list | List of applications included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.excludeApplications | list | List of applications excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.applications.includeUserActions | list | User actions included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeUsers | list | User IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeUsers | list | User IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeGroups | list | Group IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeGroups | list | Group IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.includeRoles | list | Role IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.users.excludeRoles | list | Role IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.platforms.includePlatforms | list | Platforms included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.platforms.excludePlatforms | list | Platforms excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.locations.includeLocations | list | Location IDs included in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.conditions.locations.excludeLocations | list | Location IDs excluded from the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.operator | string | Logical operator used in the grant controls (e.g., AND, OR). |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.builtInControls | list | List of built-in access controls (e.g., MFA, compliant device). |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.customAuthenticationFactors | list | Custom authentication factors used in the policy. |
| MSGraphIdentity.ConditionalAccessPolicy.grantControls.termsOfUse | list | List of Terms of Use policy IDs applied. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.cloudAppSecurity.cloudAppSecurityType | string | The type of cloud app security control. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.cloudAppSecurity.isEnabled | boolean | Whether cloud app security is enabled. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.value | number | Sign-in frequency value. |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.type | string | Unit of the sign-in frequency (e.g., hours, days). |
| MSGraphIdentity.ConditionalAccessPolicy.sessionControls.signInFrequency.isEnabled | boolean | Whether sign-in frequency control is enabled. |
Command example
!msgraph-identity-ca-policies-list limit=2 all_results=false
Context Example
{
"MSGraphIdentity": {
"ConditionalAccessPolicy": [
{
"conditions": {
"applications": {
"applicationFilter": null,
"excludeApplications": [],
"includeApplications": [
"Office365"
],
"includeAuthenticationContextClassReferences": [],
"includeUserActions": []
},
"authenticationFlows": null,
"clientAppTypes": [
"all"
],
"clientApplications": null,
"devices": null,
"insiderRiskLevels": null,
"locations": {
"excludeLocations": [],
"includeLocations": [
"All"
]
},
"platforms": null,
"servicePrincipalRiskLevels": [],
"signInRiskLevels": [],
"userRiskLevels": [],
"users": {
"excludeGroups": [],
"excludeGuestsOrExternalUsers": null,
"excludeRoles": [],
"excludeUsers": [],
"includeGroups": [],
"includeGuestsOrExternalUsers": null,
"includeRoles": [],
"includeUsers": [
"d629c370-61c9-4851-8f3d-80ea6c07655f"
]
}
},
"createdDateTime": "2025-02-12T11:58:25.6358652Z",
"displayName": "block office test policy",
"grantControls": {
"authenticationStrength": null,
"authenticationStrength@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identity/conditionalAccess/policies('24d40f2e-1998-4bf1-96ff-db841b6d51e6')/grantControls/authenticationStrength/$entity",
"builtInControls": [
"mfa"
],
"customAuthenticationFactors": [],
"operator": "OR",
"termsOfUse": []
},
"id": "24d40f2e-1998-4bf1-96ff-db841b6d51e6",
"modifiedDateTime": "2025-02-13T06:51:23.575868Z",
"sessionControls": null,
"state": "disabled",
"templateId": null
},
{
"conditions": {
"applications": {
"applicationFilter": null,
"excludeApplications": [],
"includeApplications": [
"All"
],
"includeAuthenticationContextClassReferences": [],
"includeUserActions": []
},
"authenticationFlows": null,
"clientAppTypes": [
"all"
],
"clientApplications": null,
"devices": null,
"insiderRiskLevels": null,
"locations": null,
"platforms": null,
"servicePrincipalRiskLevels": [],
"signInRiskLevels": [
"high"
],
"userRiskLevels": [],
"users": {
"excludeGroups": [],
"excludeGuestsOrExternalUsers": null,
"excludeRoles": [],
"excludeUsers": [],
"includeGroups": [
"66b0b57b-25c5-4ef3-9d4f-b7bdea0e8794"
],
"includeGuestsOrExternalUsers": null,
"includeRoles": [],
"includeUsers": []
}
},
"createdDateTime": "2025-02-12T22:01:32.2461656Z",
"displayName": "Microsoft-managed: Multifactor authentication and reauthentication for risky sign-ins",
"grantControls": {
"authenticationStrength": null,
"authenticationStrength@odata.context": "https://graph.microsoft.com/v1.0/$metadata#identity/conditionalAccess/policies('f1b54946-07ee-4c43-a78a-3f33ee248495')/grantControls/authenticationStrength/$entity",
"builtInControls": [
"mfa"
],
"customAuthenticationFactors": [],
"operator": "OR",
"termsOfUse": []
},
"id": "f1b54946-07ee-4c43-a78a-3f33ee248495",
"modifiedDateTime": "2025-05-14T20:18:55.0820676Z",
"sessionControls": {
"applicationEnforcedRestrictions": null,
"cloudAppSecurity": null,
"disableResilienceDefaults": null,
"persistentBrowser": null,
"signInFrequency": {
"authenticationType": "primaryAndSecondaryAuthentication",
"frequencyInterval": "everyTime",
"isEnabled": true,
"type": null,
"value": null
}
},
"state": "enabled",
"templateId": "4200930c-0da2-4e33-ca01-000000000007"
}
]
}
}
Human Readable Output
Conditional Access Policies
CreatedDateTime DisplayName ID IncludeUsers State 2025-02-12T11:58:25.6358652Z block office test policy 24d40f2e-1998-4bf1-96ff-db841b6d51e6 d629c370-61c9-4851-8f3d-80ea6c07655f disabled 2025-02-12T22:01:32.2461656Z Microsoft-managed: Multifactor authentication and reauthentication for risky sign-ins f1b54946-07ee-4c43-a78a-3f33ee248495 enabled
msgraph-identity-ca-policy-delete
Delete specific Conditional Access policy by ID.
Base Command
msgraph-identity-ca-policy-delete
Required Permissions
Permission type : Application or Delegated
Permissions:Policy.Read.All
Policy.ReadWrite.ConditionalAccess
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_id | Required. The ID of the Conditional Access policy to delete. | Required |
Context Output
There is no context output for this command.
Command example
!msgraph-identity-ca-policy-delete policy_id="fc271abb-e52c-4c40-aff9-5fd1e534a58d"
Human Readable Output
Conditional Access policy fc271abb-e52c-4c40-aff9-5fd1e534a58d was successfully deleted.
msgraph-identity-audit-signin-event-get
Retrieve Microsoft Entra ID sign-in event.
Base Command
msgraph-identity-audit-signin-event-get
Required Permissions
Permission type : Application or Delegated
Permissions:AuditLog.Read.All
Input
| Argument Name | Description | Required |
msgraph-identity-audit-signin-event-get
Retrieve Microsoft Entra ID sign-in event.
Base Command
msgraph-identity-audit-signin-event-get
Input
| Argument Name | Description | Required |
|---|---|---|
| id | Unique ID representing the sign-in event. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphIdentity.AuditLog.signIns.appDisplayName | string | App name displayed in the Microsoft Entra admin center. |
| MSGraphIdentity.AuditLog.signIns.appId | string | Unique GUID that represents the app ID in the Microsoft Entra ID. |
| MSGraphIdentity.AuditLog.signIns.appliedConditionalAccessPolicies | array | List of conditional access policies that the corresponding sign-in activity triggers. |
| MSGraphIdentity.AuditLog.signIns.clientAppUsed | string | Identifies the client used for the sign-in activity. Modern authentication clients include Browser, modern clients. Legacy authentication clients include Exchange ActiveSync, IMAP, MAPI, SMTP, POP, and other clients. |
| MSGraphIdentity.AuditLog.signIns.conditionalAccessStatus | string | Reports status of activated conditional access policy. Possible values are success, failure, notApplied, and unknownFutureValue. |
| MSGraphIdentity.AuditLog.signIns.correlationId | string | The request ID sent from the client when the sign-in is initiated. Used to troubleshoot sign-in activity. |
| MSGraphIdentity.AuditLog.signIns.createdDateTime | string | Date and time (UTC) the sign-in was initiated. |
| MSGraphIdentity.AuditLog.signIns.deviceDetail | object | Device information from where the sign-in occurred; includes device ID, operating system, and browser. |
| MSGraphIdentity.AuditLog.signIns.id | string | Unique ID representing the sign-in activity. |
| MSGraphIdentity.AuditLog.signIns.ipAddress | string | IP address of the client used to sign in. |
| MSGraphIdentity.AuditLog.signIns.isInteractive | boolean | Indicates whether a sign-in is interactive. |
| MSGraphIdentity.AuditLog.signIns.location | object | Provides the city, state, and country code where the sign-in originated. |
| MSGraphIdentity.AuditLog.signIns.resourceDisplayName | string | Name of the resource the user signed into. |
| MSGraphIdentity.AuditLog.signIns.riskDetail | array | The reason behind a specific state of a risky user, sign-in, or a risk event. The value none means that Microsoft Entra risk detection did not flag the user or the sign-in as a risky event so far. |
| MSGraphIdentity.AuditLog.signIns.riskEventTypes_v2 | array | The list of risk event types associated with the sign-in. |
| MSGraphIdentity.AuditLog.signIns.riskLevelAggregated | string | Aggregated risk level. The value hidden means the user or sign-in wasn’t enabled for Microsoft Entra ID Protection. |
| MSGraphIdentity.AuditLog.signIns.riskLevelDuringSignIn | string | Risk level during sign-in. The value hidden means the user or sign-in wasn’t enabled for Microsoft Entra ID Protection. |
| MSGraphIdentity.AuditLog.signIns.riskState | string | Reports status of the risky user, sign-in, or a risk event. |
| MSGraphIdentity.AuditLog.signIns.status | object | Sign-in status. Includes the error code and description of the error (if a sign-in failure occurs). |
| MSGraphIdentity.AuditLog.signIns.userDisplayName | string | Display name of the user that initiated the sign-in. |
| MSGraphIdentity.AuditLog.signIns.userId | string | ID of the user that initiated the sign-in. |
| MSGraphIdentity.AuditLog.signIns.userPrincipalName | string | User principal name of the user that initiated the sign-in. This value is always in lowercase. |
Configuration parameters
app_id— Application ID (Client ID for Client credentials mode)tenant_id— Tenant ID (required for Client Credentials mode)credentials—creds_certificate— Certificate Thumbprintuse_managed_identities— Use Azure Managed Identitiesmanaged_identities_client_id—azure_ad_endpoint— Azure AD endpointincidentType— Incident typeclient_credentials— Use Client Credentials Authorization Flowinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsfirst_fetch— First Fetch Time Intervalmax_fetch— Max Fetch Incidents ReturnedincidentFetchInterval— Incidents Fetch Intervalalerts_to_fetch— Alerts to Fetchfetch_filter_expression— Fetch queryoverride_issue_severity— Override Microsoft Entra ID Protection risk levelissue_severity— Issue severity
Commands (24)
-
msgraph-identity-audit-signin-event-getRetrieve Microsoft Entra ID sign-in event.
-
msgraph-identity-auth-completeRun this command to complete the authorization process. Should be used after running the msgraph-identity-auth-start command.
-
msgraph-identity-auth-resetRun this command if for some reason you need to rerun the authentication process.
-
msgraph-identity-auth-startRun this command to start the authorization process and follow the instructions in the command results.
-
msgraph-identity-auth-testTests connectivity to Microsoft.
-
msgraph-identity-ca-policies-listRetrieve one or all Conditional Access policies from Microsoft Graph API.
-
msgraph-identity-ca-policy-createCreates a Conditional Access policy.
-
msgraph-identity-ca-policy-deleteDelete specific Conditional Access policy by ID.
-
msgraph-identity-ca-policy-updateUpdates a Conditional Access policy. By default, the command attempts to **append** values to existing list-based fields (e.g., `include_users`, `include_groups`, etc.). If a field does not support appending (i.e., it's not a list), the command **overrides** the existing value with the new one.
-
msgraph-identity-directory-role-activateActivates a role by its template ID.
-
msgraph-identity-directory-role-member-addAdds a user to a role.
-
msgraph-identity-directory-role-member-removeRemoves a user from a role.
-
msgraph-identity-directory-role-members-listGets all members in a role ID.
-
msgraph-identity-directory-roles-listLists the roles in the directory.
-
msgraph-identity-ip-named-locations-createCreate an ip named location.
-
msgraph-identity-ip-named-locations-deleteDelete an ip named location by id.
-
msgraph-identity-ip-named-locations-getRetrieve an ip named location by id.
-
msgraph-identity-ip-named-locations-listRetrieve all ip named locations.
-
msgraph-identity-ip-named-locations-updateupdate an ip named location by id.
-
msgraph-identity-protection-risks-listRetrieve all the detected risks.
-
msgraph-identity-protection-risky-user-confirm-compromisedDelete an ip named location by id.
-
msgraph-identity-protection-risky-user-dismissDelete an ip named location by id.
-
msgraph-identity-protection-risky-user-history-listRetrieve the risky users history in active directory.
-
msgraph-identity-protection-risky-user-listRetrieve the risky users in active directory.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 demisto.debug("pack name = Microsoft Graph Identity and Access, pack version = 1.2.60") """ An integration to MS Graph Identity and Access endpoint. https://docs.microsoft.com/en-us/graph/api/resources/serviceprincipal?view=graph-rest-1.0 """ import urllib3 from MicrosoftApiModule import * # noqa: E402 # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" REQUIRED_PERMISSIONS = ( "offline_access", # allows device-flow login "IdentityRiskEvent.Read.All", "IdentityRiskyUser.ReadWrite.All", "RoleManagement.ReadWrite.Directory", "Policy.ReadWrite.ConditionalAccess", "Policy.Read.All", "Application.Read.All", ) SEVERITY_MAP = {"low": 1, "medium": 2, "high": 3, "critical": 4} class Client: # pragma: no cover def __init__( self, app_id: str, verify: bool, proxy: bool, azure_ad_endpoint: str = "https://login.microsoftonline.com", client_credentials: bool = False, tenant_id: str = None, enc_key: str = None, managed_identities_client_id: Optional[str] = None, private_key: Optional[str] = None, certificate_thumbprint: Optional[str] = None, ): if app_id and "@" in app_id: app_id, refresh_token = app_id.split("@") integration_context = get_integration_context() integration_context["current_refresh_token"] = refresh_token set_integration_context(integration_context) elif client_credentials and (not enc_key and not (certificate_thumbprint and private_key)): raise DemistoException( "Either enc_key or (Certificate Thumbprint and Private Key) must be provided. For further " "information see " "https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication" ) args = { "azure_ad_endpoint": azure_ad_endpoint, "self_deployed": True, "auth_id": app_id, "grant_type": CLIENT_CREDENTIALS if client_credentials else DEVICE_CODE, "base_url": "https://graph.microsoft.com", "verify": verify, "proxy": proxy, "tenant_id": tenant_id, "enc_key": enc_key, "managed_identities_client_id": managed_identities_client_id, "managed_identities_resource_uri": Resources.graph, "certificate_thumbprint": certificate_thumbprint, "private_key": private_key, "command_prefix": "msgraph-identity", } if not client_credentials: args["scope"] = " ".join(REQUIRED_PERMISSIONS) args["token_retrieval_url"] = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token" self.ms_client = MicrosoftClient(**args) # type: ignore def get_directory_roles(self, limit: int) -> list: """Get all service principals. Args: limit: Maximum of services to get. Returns: All given service principals Docs: https://docs.microsoft.com/en-us/graph/api/directoryrole-list?view=graph-rest-1.0&tabs=http """ results = [] res = self.ms_client.http_request("GET", "v1.0/directoryRoles") results.extend(res.get("value")) while (next_link := res.get("@odata.nextLink")) and len(results) < limit: res = self.ms_client.http_request("GET", "", next_link) results.extend(res.get("value")) return results[:limit] def get_role_members(self, role_id: str, limit: int) -> dict: """Get all members of a specific role Args: role_id: a role id to get its members. limit: Maximum roles to get. Returns: directoryObject Docs: https://docs.microsoft.com/en-us/graph/api/directoryrole-list-members?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request("GET", f"v1.0/directoryRoles/{role_id}/members")["value"][:limit] def get_ip_named_location(self, ip_id: str) -> dict: """Get an IP named location by id Args: ip_id: the id of the requested IP named location. Returns: a dictionary with the object from the api Docs: https://docs.microsoft.com/en-us/graph/api/ipnamedlocation-get?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request("GET", f"v1.0/identity/conditionalAccess/namedLocations/{ip_id}") def update_ip_named_location(self, ip_id: str, data: dict) -> dict: """Update an IP named location by id Args: data: the request necessary to create the IP named location, json body. ip_id: the id of the IP named location to update. Returns: None Docs: https://docs.microsoft.com/en-us/graph/api/ipnamedlocation-update?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request( "PUT", f"v1.0/identity/conditionalAccess/namedLocations/{ip_id}", return_empty_response=True, json_data=data ) def delete_ip_named_location(self, ip_id: str) -> dict: """Delete an IP named location by id Args: ip_id: the id of the requested IP named location. Returns: None Docs: https://docs.microsoft.com/en-us/graph/api/ipnamedlocation-delete?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request( "DELETE", f"v1.0/identity/conditionalAccess/namedLocations/{ip_id}", return_empty_response=True ) def create_ip_named_location(self, data: dict) -> dict: """Create an IP named location Args: data: the request necessary to create the IP named location, json body. Returns: THe created IP named location Docs: https://docs.microsoft.com/en-us/graph/api/conditionalaccessroot-post-namedlocations?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request("POST", "v1.0/identity/conditionalAccess/namedLocations", json_data=data) def compromise_users(self, data: dict) -> dict: """Compromise users in active directory Args: data: the request necessary to compromise the users, json body. Returns: Docs: https://docs.microsoft.com/en-us/graph/api/riskyuser-confirmcompromised?view=graph-rest-1.0&tabs=http # noqa """ return self.ms_client.http_request( "POST", "v1.0/identityProtection/riskyUsers/confirmCompromised", json_data=data, return_empty_response=True ) def dismiss_users(self, data: dict) -> dict: """dismiss users in active directory Args: data: the request necessary to dismiss the users, json body. Returns: Docs: https://docs.microsoft.com/en-us/graph/api/riskyuser-dismiss?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request( "POST", "v1.0/identityProtection/riskyUsers/dismiss", json_data=data, return_empty_response=True ) def list_ip_named_location(self, limit: str, page: str, odata: str) -> list: """Get a list of all IP named locations Args: limit: Maximum IP named locations to get. page: The page to take the data from. odata: An odata query to use in the api call. Returns: a list of dictionaries with the object from the api Docs: https://docs.microsoft.com/en-us/graph/api/conditionalaccessroot-list-namedlocations?view=graph-rest-1.0&tabs=http """ odata_query = "?" if limit: odata_query += f"$top={limit}&" if page: odata_query += f"$skip={page}&" if odata: odata_query += f"{odata}&" return self.ms_client.http_request("GET", f"v1.0/identity/conditionalAccess/namedLocations{odata_query}")["value"] def list_risk_detections(self, limit: str, odata: str, odata_filter: str) -> list: """Get a list of all risk detections Args: limit: Maximum IP named locations to get. page: The page to take the data from. odata: An odata query to use in the api call. odata_filter: An odata filter Returns: a list of dictionaries with the object from the api Docs: https://docs.microsoft.com/en-us/graph/api/riskdetection-list?view=graph-rest-1.0&tabs=http """ odata_query = "?" if limit: odata_query += f"$top={limit}&" if odata: odata_query += odata if odata_filter: odata_query += f"$filter={odata_filter}" return self.ms_client.http_request("GET", f"v1.0/identityProtection/riskDetections{odata_query}")["value"] def list_risky_users(self, limit: str, odata: str, odata_filter: str = None) -> list: """Get a list of all risky users Args: limit: Maximum IP named locations to get. page: The page to take the data from. odata: An odata query to use in the api call. odata_filter: An odata filter Returns: a list of dictionaries with the object from the api Docs: https://docs.microsoft.com/en-us/graph/api/riskyuser-list?view=graph-rest-1.0&tabs=http """ odata_query = "?" if limit: odata_query += f"$top={limit}&" if odata: odata_query += odata if odata_filter: odata_query += f"$filter={odata_filter}" return self.ms_client.http_request("GET", f"v1.0/identityProtection/riskyUsers{odata_query}")["value"] def list_risky_users_history(self, limit: str, odata: str, user_id: str) -> list: """Get a list of all risky user history Args: limit: Maximum IP named locations to get. page: The page to take the data from. odata: An odata query to use in the api call. user_id: The user id to get the history for. Returns: a list of dictionaries with the object from the api Docs: https://docs.microsoft.com/en-us/graph/api/riskyuser-list-history?view=graph-rest-1.0&tabs=http """ odata_query = "?" if limit: odata_query += f"$top={limit}&" if odata: odata_query += odata return self.ms_client.http_request("GET", f"v1.0/identityProtection/riskyUsers/{user_id}/history{odata_query}")["value"] def get_user_signin_event(self, signin_id: str) -> dict: """Retrieve a specific Microsoft Entra user sign-in event Args: signin_id: Unique ID representing the sign-in event. Returns: a list of dictionaries with the object from the api Docs: https://learn.microsoft.com/en-us/graph/api/signin-get?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request("GET", f"v1.0/auditLogs/signIns/{signin_id}") def activate_directory_role(self, template_id: str) -> dict: """Activating a role in the directory. Args: template_id: A template id to activate Returns: directoryRole object. Docs: https://docs.microsoft.com/en-us/graph/api/directoryrole-post-directoryroles?view=graph-rest-1.0&tabs=http """ return self.ms_client.http_request("POST", "v1.0/directoryRoles", json_data={"roleTemplateId": template_id}) def add_member_to_role(self, role_object_id: str, user_id: str): """Adds a member to a specific role. Args: role_object_id: A role to add the user to. user_id: The user to add to the role. Return: True if succeeded. Raises: Error on failed add (as long with requests errors). Docs: https://docs.microsoft.com/en-us/graph/api/directoryrole-post-members?view=graph-rest-1.0&tabs=http """ body = {"@odata.id": f"https://graph.microsoft.com/v1.0/directoryObjects/{user_id}"} self.ms_client.http_request( "POST", f"v1.0/directoryRoles/{role_object_id}/members/$ref", json_data=body, return_empty_response=True ) def remove_member_from_role(self, role_object_id: str, user_id: str): """Removing a member from a specific role. Args: role_object_id: A role to remove the user from. user_id: The user to remove from the role. Return: True if succeeded. Raises: Error on failed removal (as long with requests errors). Docs: https://docs.microsoft.com/en-us/graph/api/directoryrole-delete-member?view=graph-rest-1.0&tabs=http """ self.ms_client.http_request( "DELETE", f"v1.0/directoryRoles/{role_object_id}/members/{user_id}/$ref", return_empty_response=True ) def list_conditional_access_policies(self, policy_id: str, filter_query: Optional[str] = None) -> list: """ Retrieve Conditional Access policies, or a specific one by ID. Args: policy_id (str): The ID of the policy to retrieve. If not provided, lists all. filter_query (str, optional): An OData filter query to apply when listing policies (only relevant when policy_id is not provided). Returns: list: A list containing the retrieved policy or policies. - If policy_id is provided, returns a list with a single policy. - If policy_id is not provided, returns a list of all policies. - If no policies are found, returns an empty list. Raises: DemistoException: If an error occurs during the API request. """ if policy_id: url_suffix = f"v1.0/identity/conditionalAccess/policies/{policy_id}" else: url_suffix = "v1.0/identity/conditionalAccess/policies" if filter_query: url_suffix += f"?$filter={filter_query}" res = self.ms_client.http_request(method="GET", url_suffix=url_suffix, resp_type="json") # if a single policy is returned (dict), make it a list to standardize the structure if not res: return [] if not policy_id: return res.get("value") or [] else: return [res] def delete_conditional_access_policy(self, policy_id: str) -> CommandResults: """ Delete specific Conditional Access policy by ID. Args: policy_id (str, required): The ID of the policy to delete. Returns: CommandResults: Success message or error information. """ url_suffix = f"v1.0/identity/conditionalAccess/policies/{policy_id}" res = self.ms_client.http_request(method="DELETE", url_suffix=url_suffix, resp_type="response") if res.status_code != 204: demisto.error( f"Failed to delete Conditional Access policy {policy_id}. Status code: {res.status_code}," f" Response: {res.text}" ) raise DemistoException(f"Error deleting Conditional Access policy {policy_id}.:\n{str(res)}") demisto.info(f"Conditional Access policy {policy_id} was successfully deleted.") return CommandResults(readable_output=f"Conditional Access policy {policy_id} was successfully deleted.") def create_conditional_access_policy(self, policy: Dict[str, Any]) -> CommandResults: """ Sends a request to create a new Conditional Access policy in Microsoft Graph API. Args: policy (str | dict): The policy definition as a JSON string or dictionary. Returns: CommandResults: Results containing the created policy (context) and success message or error information. """ res = self.ms_client.http_request( method="POST", url_suffix="v1.0/identity/conditionalAccess/policies", json_data=policy, ) policy_id = res.get("id") if not policy_id: demisto.error(f"Error creating Conditional Access policy:\n{str(res)}") raise DemistoException(f"Error creating Conditional Access policy:\n{str(res)}") demisto.info(f"Conditional Access policy {policy_id} was successfully created:\n{str(res)}") return CommandResults( outputs_prefix="MSGraphIdentity.ConditionalAccessPolicy", outputs=res, readable_output=f"Conditional Access policy {policy_id} was successfully created.", raw_response=res, ) def update_conditional_access_policy(self, policy_id: str, policy: Union[dict, str]) -> CommandResults: """ Updates a Conditional Access policy using its ID and the provided policy data. Args: policy_id (str): The ID of the policy to update. policy (dict | str): The policy data to update with. Can be a dictionary or a JSON string. Returns: CommandResults: Object containing the operation outcome. """ res = self.ms_client.http_request( method="PATCH", url_suffix=f"v1.0/identity/conditionalAccess/policies/{policy_id}", json_data=policy, resp_type="response", ) if res.status_code != 204: demisto.error(f"Conditional Access policy {policy_id} could not be updated:\n{res}") raise DemistoException( f"Conditional Access policy {policy_id} could not be updated:\n{res}", ) demisto.info(f"Conditional Access policy {policy_id} was successfully updated:\n {res}") return CommandResults( readable_output=f"Conditional Access policy {policy_id} was successfully updated.", ) """ UTILITIES""" def build_policy(args: Dict[str, Any]) -> Dict[str, Any]: """ Builds a Conditional Access policy object from the provided arguments. Args: args (dict): Command arguments containing policy details. policy_name (str): Display name for the policy. state (str): State of the policy (enabled, disabled, etc.). sign_in_risk_levels (str): Risk levels for sign-in. user_risk_levels (str): Risk levels for users. client_app_types (str): List of client application types. include_applications (str, optional): Applications to include. exclude_applications (str, optional): Applications to exclude. include_user_actions (str, optional): User actions to include. include_users (str, optional): Users to include. exclude_users (str, optional): Users to exclude. include_roles (str, optional): Roles to include. exclude_roles (str, optional): Roles to exclude. include_groups (str, optional): Groups to include. exclude_groups (str, optional): Groups to exclude. include_platforms (str, optional): Platforms to include. exclude_platforms (str, optional): Platforms to exclude. include_locations (str, optional): Locations to include. exclude_locations (str, optional): Locations to exclude. grant_control_operator (str, optional): Operator for grant controls, default is AND. grant_control_enforcement (str, optional): Enforcement controls, default is mfa. Returns: dict: A structured policy object ready to be used in creating a Conditional Access policy. """ policy = { "displayName": args.get("policy_name"), "state": args.get("state"), "conditions": { "clientAppTypes": argToList(args.get("client_app_types")), "applications": { "includeApplications": argToList(args.get("include_applications")), "excludeApplications": argToList(args.get("exclude_applications")), "includeUserActions": argToList(args.get("include_user_actions")), }, "users": { "includeUsers": argToList(args.get("include_users")), "excludeUsers": argToList(args.get("exclude_users")), "includeRoles": argToList(args.get("include_roles")), "excludeRoles": argToList(args.get("exclude_roles")), "includeGroups": argToList(args.get("include_groups")), "excludeGroups": argToList(args.get("exclude_groups")), }, "platforms": { "includePlatforms": argToList(args.get("include_platforms")), "excludePlatforms": argToList(args.get("exclude_platforms")), }, "locations": { "includeLocations": argToList(args.get("include_locations")), "excludeLocations": argToList(args.get("exclude_locations")), }, "signInRiskLevels": argToList(args.get("sign_in_risk_levels")), "userRiskLevels": argToList(args.get("user_risk_levels")), }, "grantControls": { "operator": args.get("grant_control_operator"), "builtInControls": argToList(args.get("grant_control_enforcement")), }, } return policy def deep_get(root: dict[str, Any], path: list[str]) -> Any | None: """ Return the value at a nested path in a dictionary. Args: root (dict[str, Any]): The root dictionary to traverse. path (list[str]): The path to follow, with each element being a key in the dictionary. Returns: Any | None: The value at the specified path, or None if any key in the path doesn't exist. """ try: demisto.debug(f"Getting value from path {path} in dictionary") acc = root for i, key in enumerate(path): demisto.debug(f"Accessing key '{key}' ({i+1}/{len(path)})") acc = acc[key] demisto.debug(f"Successfully retrieved value from path {path}") return acc except (KeyError, TypeError) as e: demisto.info(f"Failed to get value from path {path}: {str(e)}") return None def deep_set(root: dict[str, Any], path: list[str], value: Any) -> None: """ Set a value at a nested path in a dictionary. Args: root (dict[str, Any]): The root dictionary to modify. path (list[str]): The path to follow, with each element being a key in the dictionary. value (Any): The value to set at the specified path. Note: This function creates any missing dictionaries along the path. Example: For a path like ['a', 'b', 'c'] and a value 42, if 'a' or 'b' don't exist, they will be created as empty dictionaries using setdefault: root = {} deep_set(root, ['a', 'b', 'c'], 42) # Results in: {'a': {'b': {'c': 42}}} The setdefault method returns the value for a key if it exists, otherwise it sets the key to a default value and returns that value. """ for key in path[:-1]: root = root.setdefault(key, {}) root[path[-1]] = value def resolve_merge_value( field: str, existing: List[str], new: List[str], messages: List[str], ) -> List[str]: """ Merge two lists of values based on field-specific rules. Args: field (str): The field name whose values are being merged. existing (List[str]): The existing list of values. new (List[str]): The new list of values to merge with existing. messages (List[str]): A list to collect messages about merge decisions. Returns: List[str]: The merged list of values based on specific rules: 1. For 'signInRiskLevels', performs a direct set union 2. If existing list only contains 'None' values, returns the new list 3. If existing list contains special values like 'All', keeps existing 4. If new list only contains special values, returns the new list 5. Otherwise, performs a set union of both lists """ specials = {"All", "all", "AllTrusted"} none_values = {"None", "none"} demisto.debug(f"resolve_merge_value: Merging '{field}' values. Existing: {existing}, New: {new}") # Sign-in risk levels are a straight set-union because this filed can contain combination of # "none" and other values like "low", "medium", "high" (i.e: signInRiskLevels = "none", "low") if field == "signInRiskLevels": result = list(set(existing) | set(new)) demisto.debug(f"resolve_merge_value: Field '{field}' is a sign-in risk level. Merged result: {result}") return result # Existing == ['None'] → take the new list if set(existing).issubset(none_values): demisto.debug(f"resolve_merge_value: Existing values for '{field}' are 'None'. Using new values: {new}") return new # Existing is a blocking special value → keep it, warn once if existing and existing[0] in specials: message = ( f"Field '{field}' kept as '{existing[0]}' (special value cannot be merged).\n" f"To update this field, use update_action='override'." ) messages.append(message) demisto.debug(f"resolve_merge_value: {message}") return existing # Incoming list is itself only a special value → take it if set(new).issubset(specials): demisto.debug(f"resolve_merge_value: New values for '{field}' contain only special values: {new}. Using new values.") return new # Otherwise, normal union result = list(set(existing) | set(new)) demisto.debug(f"resolve_merge_value: Performed normal union for '{field}'. Merged result: {result}") return result def merge_policy_section( base_existing: Dict[str, Any], new: Dict[str, Any], messages: List[str], ) -> None: """ Merges new policy section values into an existing policy structure. This function walks through the new policy dictionary and merges list values with existing ones according to specific rules defined in resolve_merge_value(). Args: base_existing (Dict[str, Any]): The existing policy configuration to merge into. new (Dict[str, Any]): The new policy configuration with values to merge. messages (List[str]): A list to collect messages about merge decisions. Returns: None: The function modifies the 'new' dictionary in place. """ def walk(node_new: Dict[str, Any], path: List[str]) -> None: for key, val in node_new.items(): current_path_to_key = path + [key] demisto.debug(f"Processing key {key} at path {'/'.join(current_path_to_key)}") # ── dive deeper if isinstance(val, dict): demisto.debug(f"Found dictionary at {'/'.join(current_path_to_key)}, diving deeper") walk(val, current_path_to_key) continue # ── override non-list leaves if not isinstance(val, list): demisto.debug("setting to the new policy value.") messages.append(f"Field `{'/'.join(current_path_to_key)}` is not a list - overriding the value.") continue existing_val = deep_get(base_existing, current_path_to_key) demisto.debug(f"Existing value at {'/'.join(current_path_to_key)}: {existing_val}") if isinstance(existing_val, list): merged = resolve_merge_value(key, existing_val, val, messages) demisto.debug(f"Merged result for {'/'.join(current_path_to_key)}: {merged}") deep_set(new, current_path_to_key, merged) else: # no existing list (or wrong type) → leave new value as-is if existing_val is None: messages.append(f"Field `{'/'.join(current_path_to_key)}` was empty - new list left untouched.") demisto.debug(f"No existing value at {'/'.join(current_path_to_key)}, new value is {val}") else: demisto.debug(f"Existing value at {'/'.join(current_path_to_key)} is not a list, new list left untouched.") messages.append( f"Field `{'/'.join(current_path_to_key)}` exists but is not a list - new list left untouched." ) demisto.debug("merge_policy_section: Starting policy merge process") walk(new, []) demisto.debug("merge_policy_section: Completed policy merge process") """ COMMAND FUNCTIONS """ def start_auth(client: Client) -> CommandResults: # pragma: no cover result = client.ms_client.start_auth("!msgraph-identity-auth-complete") return CommandResults(readable_output=result) def complete_auth(client: Client) -> str: # pragma: no cover client.ms_client.get_access_token() return "✅ Authorization completed successfully." def test_connection(client: Client) -> str: # pragma: no cover client.ms_client.get_access_token() return "✅ Success!" def list_directory_roles(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover """Lists all service principals Args: ms_client: The Client args: demisto.args() Returns: Results to post in demisto """ limit_str = args.get("limit", "") try: limit = int(limit_str) except ValueError: raise DemistoException(f'Limit must be an integer, not "{limit_str}"') results = ms_client.get_directory_roles(limit) return CommandResults( "MSGraphIdentity.Role", "id", outputs=results, readable_output=tableToMarkdown( "Directory roles:", results, ["id", "displayName", "description", "roleTemplateId", "deletedDateTime"], removeNull=True, ), ) def list_role_members_command(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover """Lists all service principals Args: ms_client: The Client args: demisto.args() Returns: Results to post in demisto """ limit_str = args.get("limit", "") try: limit = int(limit_str) except ValueError: raise DemistoException(f'Limit must be an integer, not "{limit_str}"') role_id = args.get("role_id") try: if results := ms_client.get_role_members(role_id, limit): # type: ignore ids = [member["id"] for member in results] context = {"role_id": role_id, "user_id": ids} return CommandResults( "MSGraphIdentity.RoleMember", "role_id", outputs=context, raw_response=results, readable_output=tableToMarkdown(f"Role '{role_id}' members:", context), ) else: return CommandResults(readable_output=f"No members found in {role_id}") except Exception as e: demisto.debug(f"Role ID: {role_id} was not found or invalid - {e}") return CommandResults(readable_output=f"Role ID: {role_id}, was not found or invalid") def activate_directory_role_command(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover template_id = args.get("role_template_id") results = ms_client.activate_directory_role(template_id) # type: ignore return CommandResults( "MSGraphIdentity.Role", "id", outputs=results, readable_output=tableToMarkdown( "Role has been activated", results, ["id", "roleTemplateId", "displayName", "description", "deletedDateTime"] ), ) def add_member_to_role_command(client: Client, args: dict) -> str: # pragma: no cover user_id = args.get("user_id") role_object_id = args.get("role_id") client.add_member_to_role(role_object_id, user_id) # type: ignore return f"User ID {user_id} has been added to role {role_object_id}" def remove_member_from_role(client: Client, args: dict) -> str: # pragma: no cover role_object_id = args.get("role_id") user_id = args.get("user_id") client.remove_member_from_role(role_object_id, user_id) # type: ignore return f"User ID {user_id} has been removed from role {role_object_id}" def ip_named_location_get(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover ip_id = args.get("ip_id") if results := ms_client.get_ip_named_location(ip_id): # type: ignore context = { "id": ip_id, "display_name": results.get("displayName"), "time_created": results.get("createdDateTime"), "time_modified": results.get("modifiedDateTime"), "is_trusted": results.get("isTrusted"), "ip_ranges": results.get("ipRanges"), } return CommandResults( "MSGraph.conditionalAccess.namedIpLocations", "namedIpLocations", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown(f"IP named location '{ip_id}':", context), ) return CommandResults(readable_output=f"No IP location found for {ip_id}") def ip_named_location_update(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover ip_id = args.get("ip_id") if results := ms_client.get_ip_named_location(ip_id): # type: ignore data = { "@odata.type": "#microsoft.graph.ipNamedLocation", "displayName": results.get("displayName"), "isTrusted": results.get("isTrusted"), "ipRanges": results.get("ipRanges"), } ips = args.get("ips") is_trusted = args.get("is_trusted") display_name = args.get("display_name") if ips: ips = ms_ip_string_to_list(ips) data["ipRanges"] = ips if is_trusted: data["isTrusted"] = is_trusted if display_name: data["displayName"] = display_name ms_client.update_ip_named_location(ip_id, data) # type: ignore return CommandResults( "MSGraph.conditionalAccess.namedIpLocations", "namedIpLocations", outputs={}, raw_response={}, ignore_auto_extract=True, readable_output=f"Successfully updated IP named location '{ip_id}'", ) return CommandResults(readable_output=f"Could not update IP named location '{ip_id}'") def ip_named_location_create(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover ips = args.get("ips") is_trusted = args.get("is_trusted") display_name = args.get("display_name") if ips and display_name and is_trusted: is_trusted = str(is_trusted).lower() == "true" ips_arr = ms_ip_string_to_list(ips) data = { "@odata.type": "#microsoft.graph.ipNamedLocation", "displayName": display_name, "isTrusted": is_trusted, "ipRanges": ips_arr, } if results := ms_client.create_ip_named_location(data): id = results.get("id") context = { "id": id, "display_name": results.get("displayName"), "time_created": results.get("createdDateTime"), "time_modified": results.get("modifiedDateTime"), "is_trusted": results.get("isTrusted"), "ip_ranges": results.get("ipRanges"), } return CommandResults( "MSGraph.conditionalAccess.namedIpLocations", "namedIpLocations", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown(f"created IP named location '{id}':", context), ) return CommandResults(readable_output="Could not create IP named location") def azure_ad_identity_protection_confirm_compromised_command( ms_client: Client, args: dict ) -> CommandResults: # pragma: no cover # noqa user_ids = str(args.get("user_ids")).split(",") data = {"userIds": user_ids} try: ms_client.compromise_users(data) return CommandResults(raw_response={}, ignore_auto_extract=True, readable_output=f"Successfully compromised {user_ids!s}") except Exception as e: return CommandResults(readable_output=f"Could not compromised {user_ids!s}:\n{e}") def azure_ad_identity_protection_risky_users_dismiss_command( ms_client: Client, args: dict ) -> CommandResults: # pragma: no cover # noqa user_ids = str(args.get("user_ids")).split(",") data = {"userIds": user_ids} try: ms_client.dismiss_users(data) return CommandResults(raw_response={}, ignore_auto_extract=True, readable_output=f"Successfully dismissed {user_ids!s}") except Exception as e: return CommandResults(readable_output=f"Could not dismiss {user_ids!s}:\n{e}") def ip_named_location_delete(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover ip_id = args.get("ip_id") if results := ms_client.delete_ip_named_location(ip_id): # type: ignore # noqa return CommandResults( "MSGraph.conditionalAccess.namedIpLocations", "namedIpLocations", outputs={}, raw_response={}, ignore_auto_extract=True, readable_output=f"Successfully deleted IP named location '{ip_id}'", ) return CommandResults(readable_output=f"Could not delete IP named location '{ip_id}'") def ip_named_location_list(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover limit = args.get("limit") page = args.get("page") odata = args.get("odata_query") if results := ms_client.list_ip_named_location(limit, page, odata): # type: ignore ip_named_locations = [] for result in results: ip_named_location = { "id": result.get("id"), "display_name": result.get("displayName"), "time_created": result.get("createdDateTime"), "time_modified": result.get("modifiedDateTime"), "is_trusted": result.get("isTrusted"), "ip_ranges": result.get("ipRanges"), } ip_named_locations.append(ip_named_location) context = {"ip_named_locations": ip_named_locations} return CommandResults( "MSGraph.conditionalAccess.namedIpLocations", "namedIpLocations", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown( "IP named locations:", ip_named_locations, ), ) else: return CommandResults(readable_output="could not list IP named locations") def azure_ad_identity_protection_risky_users_list(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover # noqa limit = args.get("limit") odata = args.get("odata_query") if results := ms_client.list_risky_users(limit, odata): # type: ignore risky_users = [] for result in results: risky_user = { "id": result.get("id"), "isDeleted": result.get("isDeleted"), "isProcessing": result.get("isProcessing"), "riskLevel": result.get("riskLevel"), "riskState": result.get("riskState"), "riskDetail": result.get("riskDetail"), "riskLastUpdatedDateTime": result.get("riskLastUpdatedDateTime"), "userDisplayName": result.get("userDisplayName"), "userPrincipalName": result.get("userPrincipalName"), } risky_users.append(risky_user) context = {"riskyUsers": risky_users} return CommandResults( "MSGraph.identityProtection.riskyUsers", "riskyUsers", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown( "Risky users:", risky_users, ), ) else: return CommandResults(readable_output="could not list IP named locations") def azure_ad_identity_protection_risky_users_history_list( ms_client: Client, args: dict ) -> CommandResults: # pragma: no cover # noqa limit = args.get("limit") odata = args.get("odata_query") user_id = args.get("user_id") if results := ms_client.list_risky_users_history(limit, odata, user_id): # type: ignore risky_users = [] for result in results: risky_user = { "id": result.get("id"), "isDeleted": result.get("isDeleted"), "isProcessing": result.get("isProcessing"), "riskLevel": result.get("riskLevel"), "riskState": result.get("riskState"), "riskDetail": result.get("riskDetail"), "riskLastUpdatedDateTime": result.get("riskLastUpdatedDateTime"), "userDisplayName": result.get("userDisplayName"), "userPrincipalName": result.get("userPrincipalName"), "userId": result.get("userId"), "initiatedBy": result.get("initiatedBy"), "activity": result.get("activity"), } risky_users.append(risky_user) context = {"riskyUsers": risky_users} return CommandResults( "MSGraph.identityProtection.riskyUsers", "riskyUsers", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown( "Risky users history:", risky_users, ), ) else: return CommandResults(readable_output="could not list IP named locations") def azure_ad_identity_protection_risk_detection_list(ms_client: Client, args: dict) -> CommandResults: # pragma: no cover # noqa limit = args.get("limit") odata = args.get("odata_query") odata_filter = args.get("filter") if results := ms_client.list_risk_detections(limit, odata, odata_filter): # type: ignore risks = [] for result in results: risk = { "id": result.get("id"), "requestId": result.get("requestId"), "correlationId": result.get("correlationId"), "riskEventType": result.get("riskEventType"), "riskState": result.get("riskState"), "riskLevel": result.get("riskLevel"), "riskDetail": result.get("riskDetail"), "source": result.get("source"), "detectionTimingType": result.get("detectionTimingType"), "activity": result.get("activity"), "ipAddress": result.get("ipAddress"), "activityDateTime": result.get("activityDateTime"), "detectedDateTime": result.get("detectedDateTime"), "lastUpdatedDateTime": result.get("lastUpdatedDateTime"), "userId": result.get("userId"), "userDisplayName": result.get("userDisplayName"), "userPrincipalName": result.get("userPrincipalName"), "additionalInfo": result.get("additionalInfo"), "location": result.get("location"), } risks.append(risk) context = {"riskDetections": risks} return CommandResults( "MSGraph.identityProtection.risks", "riskDetections", outputs=context, raw_response=results, ignore_auto_extract=True, readable_output=tableToMarkdown( "Risks detected:", risks, ), ) else: return CommandResults(readable_output="could not list IP named locations") def ms_ip_string_to_list(ips: str) -> list: """Convert a comma-separated string of IP ranges into the ipRanges payload Graph expects. The Microsoft Graph ipNamedLocation API requires every range to be in CIDR notation (e.g. "12.34.221.11/22" or "2001:0:9d38:90d6:0:0:0:0/63"); bare addresses such as "0.0.0.0" are rejected. See https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-post-namedlocations. Raises: DemistoException: If a value is not valid CIDR notation, or no valid range is provided. """ odata_type_by_indicator = { FeedIndicatorType.CIDR: "#microsoft.graph.iPv4CidrRange", FeedIndicatorType.IPv6CIDR: "#microsoft.graph.iPv6CidrRange", } ips_arr = [] for ip in argToList(ips): if not ip: continue indicator_type = FeedIndicatorType.ip_to_indicator_type(ip) or "" odata_type = odata_type_by_indicator.get(indicator_type) if not odata_type: raise DemistoException( f'Invalid IP range "{ip}". IP ranges must be in CIDR notation, for example ' f'"12.34.221.11/22" (IPv4) or "2001:0:9d38:90d6:0:0:0:0/63" (IPv6).' ) ips_arr.append({"@odata.type": odata_type, "cidrAddress": ip}) if not ips_arr: raise DemistoException( 'No valid IP ranges provided. Provide at least one IP range in CIDR notation, e.g. "12.34.221.11/22".' ) return ips_arr def get_last_fetch_time(last_run: dict, params: dict): last_fetch = last_run.get("latest_detection_found") if not last_fetch: demisto.debug("[AzureADIdentityProtection] First run") # handle first time fetch first_fetch = f"{params.get('first_fetch') or '1 days'} ago" default_fetch_datetime = dateparser.parse(date_string=first_fetch, date_formats=[DATE_FORMAT]) assert default_fetch_datetime is not None, f"failed parsing {first_fetch}" last_fetch = str(default_fetch_datetime.isoformat(timespec="milliseconds")) + "Z" demisto.debug(f"[AzureADIdentityProtection] last_fetch: {last_fetch}") return last_fetch def date_str_to_azure_format(date_str: str) -> str: """ Given a string representing a date in some general format, modifies the date to Azure format. That means removing the Z at the end and adding nanoseconds if they don't exist. Moreover, sometimes the date has too many digits for """ date_str = date_str[:-1] if date_str[-1].lower() == "z" else date_str if "." not in date_str: date_str = f"{date_str}.000" else: date_without_ns, ns = date_str.split(".") ns = ns[:6] date_str = f"{date_without_ns}.{ns}" return date_str def detection_to_incident(detection: dict, detection_date: str, severity_override: bool, overridden_issue_severity: str) -> dict: detection_id: str = detection.get("id", "") sign_in_risk_mapping = { "riskyIPAddress": { "alertDescription": ( "Sign-in by user {userId} from an IP address identified " "as an anonymous proxy IP address by Microsoft Defender for Cloud Apps." ), }, "generic": { "alertDescription": ( "One of the Microsoft Entra ID Protection premium detections was triggered for user {userId}. " "Since premium detections are only visible to Microsoft Entra ID P2 customers, " "they are labeled as Additional risk detected for users without Microsoft Entra ID P2 licenses." ), }, "adminConfirmedUserCompromised": { "alertDescription": ( "An admin selected Confirm user compromised " "in Microsoft Entra ID Protection UI or API for user {userId}. " ), }, "anomalousToken": { "alertDescription": ( "Sign-in detected with abnormal characteristics in the token, such as an unusual lifetime " "or a token played from an unfamiliar location, for user {userId}. This detection covers 'Session Tokens' " "and 'Refresh Tokens.' If the location, application, IP address, User Agent, or other characteristics " "are unexpected for the user, the administrator should consider " "this risk as an indicator of potential token replay." ), }, "anonymizedIPAddress": { "alertDescription": ( "Suspicious sign-in from an anonymous IP address " "(for example, Tor browser or anonymous VPN) detected for user {userId}." ), }, "unlikelyTravel": { "alertDescription": ( "Sign-ins originating from two geographically distant locations, where at least " "one of the locations might also be atypical for the user, given past behavior based on {userId} user history." ), }, "mcasImpossibleTravel": { "alertDescription": ( "Sign-in originating from geographically distant locations within a time period shorter " "than the time it takes to travel from the first location to the second detected for user {userId}." ), }, "maliciousIPAddress": { "alertDescription": ( "Sign-in from an IP address flagged as malicious based on high failure rates " "because of invalid credentials received from the IP address or other IP reputation sources for user {userId}." ), }, "mcasFinSuspiciousFileAccess": { "alertDescription": ( "User {userId} accessed an uncommon large number of files, and/or files containing " "sensitive information, from Microsoft SharePoint Online or Microsoft OneDrive." ), }, "investigationsThreatIntelligence": { "alertDescription": ( "User {userId} activity is unusual for the user or consistent with known attack patterns. " "This detection is based on Microsoft's internal and external threat intelligence sources." ), }, "newCountry": { "alertDescription": "Sign-in from an unusual country for user {userId} based on past activity locations.", }, "passwordSpray": { "alertDescription": ( "Multiple sign-in attempts detected across accounts, targeting user {userId}. " "The risk detection is triggered when an account's password is valid and has an attempted sign in." ), }, "suspiciousBrowser": { "alertDescription": ( "Sign-in detected from a browser associated with anomalous behavior based on " "suspicious sign-in activity across multiple tenants from different countries/regions " "in the same browser for user {userId}." ), }, "suspiciousInboxForwarding": { "alertDescription": ( "Suspicious rules that delete or move messages or folders are set on {userId} user's inbox. " "This detection might indicate: a user's account is compromised, messages are being intentionally hidden, " "and the mailbox is being used to distribute spam or malware in your organization." ), }, "mcasSuspiciousInboxManipulationRules": { "alertDescription": ( "Suspicious mailbox rule that delete or move messages or folders are set on user {userId} " "mailbox. This detection might indicate: a user's account is compromised, messages are being intentionally " "hidden, and the mailbox is being used to distribute spam or malware in your organization." ), }, "tokenIssuerAnomaly": { "alertDescription": ( "Sign-in detected for user {userId} with indications that the SAML token issuer " "for the associated SAML token is potentially compromised. " "The claims included in the token are unusual or match known attacker patterns." ), }, "unfamiliarFeatures": { "alertDescription": ( "Sign-in using features (IP, ASN, location, device, and/or browser) not previously seen " "for user {userId} based on past sign-in history. Unfamiliar sign-in properties can be detected on both " "interactive and non-interactive sign-ins. When this detection is detected on non-interactive sign-ins, " "it deserves increased scrutiny due to the risk of token replay attacks." ), }, "nationStateIP": { "alertDescription": ( "Sign-in detected from an IP address consistent with known IP addresses associated " "with nation state actors or cyber crime groups, " "based on data from the Microsoft Threat Intelligence Center (MSTIC), for user {userId}." ), }, "anomalousUserActivity": { "alertDescription": ( "Anomalous privileged user {userId} activity regarding user baseline. The detection " "is triggered against the privileged user account making the change or the object that was changed." ), }, "attackerinTheMiddle": { "alertDescription": ( "Sign-in from a malicious reverse proxy associated with known Adversary in the Middle activity " "detected for user {userId}. Thoughtful investigation is required when this detection is triggered " "to ensure the risk is cleared, which might require secure password reset and revocation of existing sessions." ), }, "leakedCredentials": { "alertDescription": "Credentials for user {userId} found in known data breaches.", }, "attemptedPrtAccess": { "alertDescription": ( "Possible attempt to access Primary Refresh Token (PRT) on {userId} device. A PRT is a " "JSON Web Token (JWT) issued to Microsoft first-party token brokers to enable single sign-on (SSO) " "across the applications used on those devices. This detection is high risk and prompt remediation " "of these users is recommended. It appears infrequently in most organizations due to its low volume." ), }, "suspiciousAPITraffic": { "alertDescription": ( "Abnormal GraphAPI traffic or directory enumeration detected for user {userId}. Suspicious " "API traffic might suggest that a user is compromised and conducting reconnaissance in the environment." ), }, "suspiciousSendingPatterns": { "alertDescription": ( "Suspicious email patterns detected by Microsoft Defender for Office 365 (MDO) " "in email sent by user {userId}." ), }, "userReportedSuspiciousActivity": { "alertDescription": ( "User {userId} denied a multi factor authentication (MFA) prompt " "and reported it as suspicious activity." ), }, } detection_type: str = detection.get("riskEventType", "") detection_detail: str = detection.get("riskDetail", "") detection_upn: str = detection.get("userPrincipalName", "") or "" risk = sign_in_risk_mapping.get(detection_type, {}) if severity_override: incident_severity = SEVERITY_MAP.get(overridden_issue_severity, 2) else: detection_severity: str = detection.get("riskLevel", "") incident_severity = SEVERITY_MAP.get(detection_severity, 2) incident = { "name": f"Azure AD: {detection_id} {detection_type} {detection_detail}", "severity": incident_severity, "occurred": f"{detection_date}Z", "rawJSON": json.dumps(detection), } incident["details"] = risk.get("alertDescription", "").replace("{userId}", detection_upn) return incident def detections_to_incidents( detections: List[Dict[str, str]], last_fetch_datetime: str, severity_override: bool, overridden_issue_severity: str ) -> tuple[List[Dict[str, str]], str]: # pragma: no cover # noqa """ Given the detections retrieved from Azure Identity Protection, transforms their data to incidents format. """ incidents: List[Dict[str, str]] = [] latest_incident_time = last_fetch_datetime for detection in detections: detection_datetime = detection.get("detectedDateTime", "") detection_datetime_in_azure_format = date_str_to_azure_format(detection_datetime) incident = detection_to_incident( detection, detection_datetime_in_azure_format, severity_override, overridden_issue_severity ) incidents.append(incident) if datetime.strptime(detection_datetime_in_azure_format, DATE_FORMAT) > datetime.strptime( date_str_to_azure_format(latest_incident_time), DATE_FORMAT ): latest_incident_time = detection_datetime return incidents, latest_incident_time def risky_user_to_incident(riskyuser: dict, riskyuser_date: str, severity_override: bool, overridden_issue_severity: str) -> dict: riskyuser_upn: str = riskyuser.get("userPrincipalName", "") or "" riskyuser_risk_level: str = riskyuser.get("riskLevel", "") riskyuser_risk_state: str = riskyuser.get("riskState", "") if severity_override: incident_severity = SEVERITY_MAP.get(overridden_issue_severity, 2) else: incident_severity = SEVERITY_MAP.get(riskyuser_risk_level, 2) if riskyuser_risk_level == "remediated": incident_details = ( f"User {riskyuser_upn} remediated. Risk might have been self-remediated by user through MFA " "or secure password change, or manually remediated by an administrator. Caution is required " "before dismissing this alert when risk detail is userPassedMFADrivenByRiskBasedPolicy " "as AiTM attacks on an MFA-enabled account trigger this alert." ) elif riskyuser_risk_level == "dismissed": incident_details = ( f"User {riskyuser_upn} risk has been dismissed because the user's account has been considered safe. " "In some cases, Microsoft Entra ID Protection can also automatically dismiss a user's risk state when " "both the risk detection and the corresponding risky sign-in are identified by ID Protection as no longer " "posing a security threat. This automatic intervention can happen if the user provides a second factor, " "such as multifactor authentication (MFA) or if the real-time and offline assessment determines that the sign-in " "is no longer risky" ) elif riskyuser_risk_state == "atRisk" and riskyuser_risk_level == "high": incident_details = ( f"High-risk of {riskyuser_upn} Entra ID account compromise. " "Microsoft is highly confident that the account is compromised. Signals such as threat intelligence " "and known attack patterns factor into the confidence level of the risk detection" ) elif riskyuser_risk_state == "atRisk" and (riskyuser_risk_level == "medium" or riskyuser_risk_level == "low"): incident_details = ( f"One or more {riskyuser_risk_level}-severity anomalies were detected " f"by Microsoft on {riskyuser_upn} Entra ID account. " "Sign-in patterns, behaviors, and other signals factor into the confidence level of the risk detection." ) else: incident_details = ( f"Risk detected by Microsoft for {riskyuser_upn} Entra ID account. Risk level is {riskyuser_risk_state}." ) incident = { "name": f"Azure User at Risk: {riskyuser_upn} - {riskyuser_risk_state} - {riskyuser_risk_level}", "details": incident_details, "severity": incident_severity, "occurred": f"{riskyuser_date}Z", "rawJSON": json.dumps(riskyuser), } return incident def risky_users_to_incidents( riskyusers: List[Dict[str, str]], last_fetch_datetime: str, severity_override: bool, overridden_issue_severity: str ) -> tuple[List[Dict[str, str]], str]: """ Given the risky users retrieved from Azure Identity Protection, transforms their data to incidents format. """ incidents: List[Dict[str, str]] = [] latest_incident_time = last_fetch_datetime for riskyuser in riskyusers: riskyuser_datetime = riskyuser.get("riskLastUpdatedDateTime", "") riskyuser_datetime_in_azure_format = date_str_to_azure_format(riskyuser_datetime) incident = risky_user_to_incident( riskyuser, riskyuser_datetime_in_azure_format, severity_override, overridden_issue_severity ) incidents.append(incident) if datetime.strptime(riskyuser_datetime_in_azure_format, DATE_FORMAT) > datetime.strptime( date_str_to_azure_format(latest_incident_time), DATE_FORMAT ): latest_incident_time = riskyuser_datetime return incidents, latest_incident_time def build_filter(last_fetch: datetime, params: dict) -> str: if params.get("alerts_to_fetch", "Risk Detections") == "Risky Users": start_time_enforcing_filter = f"riskLastUpdatedDateTime gt {last_fetch}" else: start_time_enforcing_filter = f"detectedDateTime gt {last_fetch}" user_supplied_filter = params.get("fetch_filter_expression", "") query_filter = ( f"({user_supplied_filter}) and {start_time_enforcing_filter}" if user_supplied_filter else start_time_enforcing_filter ) demisto.debug(f"[AzureADIdentityProtection] query_filter: {query_filter}") return query_filter def fetch_incidents(client: Client, params: Dict[str, str]): # pragma: no cover last_run: Dict[str, str] = demisto.getLastRun() demisto.debug(f"[AzureIdentityProtection] last run: {last_run}") last_fetch = get_last_fetch_time(last_run, params) query_filter = build_filter(last_fetch, params) demisto.debug(f"last fetch is: {last_fetch}, filter is: {query_filter}") limit = params.get("max_fetch", "50") filter_expression = query_filter severity_override = params.get("override_issue_severity", False) issue_severity = params.get("issue_severity", "medium") if params.get("alerts_to_fetch", "Risk Detections") == "Risky Users": riskyusers: list = client.list_risky_users(limit, None, filter_expression) # type: ignore incidents, latest_detection_time = risky_users_to_incidents( riskyusers, last_fetch_datetime=last_fetch, severity_override=severity_override, # type: ignore overridden_issue_severity=issue_severity, ) else: detections: list = client.list_risk_detections(limit, None, filter_expression) # type: ignore incidents, latest_detection_time = detections_to_incidents( detections, last_fetch_datetime=last_fetch, severity_override=severity_override, # type: ignore overridden_issue_severity=issue_severity, ) demisto.debug(f"Fetched {len(incidents)} incidents") demisto.debug(f"next run latest_detection_found: {latest_detection_time}") last_run = { "latest_detection_found": latest_detection_time, } return incidents, last_run def list_conditional_access_policies_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ Retrieves a list of Conditional Access policies or a specific one by ID. Args: client (Client): Microsoft Graph client. args (dict): Command arguments. Supports optional 'policy_id'. Returns: CommandResults: Results to return to Cortex XSOAR. """ policy_id = args.get("policy_id", "") filter_query = args.get("filter") limit = args.get("limit") all_results = argToBoolean(args.get("all_results", True)) if policy_id and filter_query: raise DemistoException("Cannot provide both policy_id and filter_query at the same time.\nPlease choose one.") policies: list[dict[str, Any]] = client.list_conditional_access_policies(policy_id, filter_query) if len(policies) == 0: demisto.info("no conditional policies were found") return CommandResults(readable_output="No Conditional Access policies were found.") elif limit: max_items = int(limit) elif all_results: max_items = len(policies) else: max_items = min(len(policies), 50) # Default limit policies_to_process = policies[:max_items] context = [] readable_policies = [] for policy in policies_to_process: context.append(policy) readable_policy = { "ID": policy.get("id"), "DisplayName": policy.get("displayName"), "CreatedDateTime": policy.get("createdDateTime"), "State": policy.get("state"), "GrantControls": policy.get("GrantControls"), "Platforms": policy.get("platforms"), "Locations": policy.get("locations"), "Devices": policy.get("devices"), "IncludeUsers": policy.get("conditions", {}).get("users", {}).get("includeUsers", []), "ExcludeUsers": policy.get("conditions", {}).get("users", {}).get("excludeUsers", []), } readable_policy = remove_empty_elements(readable_policy) readable_policies.append(readable_policy) return CommandResults( outputs_prefix="MSGraphIdentity.ConditionalAccessPolicy", outputs_key_field="ID", outputs=context, readable_output=tableToMarkdown("Conditional Access Policies", readable_policies), raw_response=policies, ) def delete_conditional_access_policy_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ Deletes a Conditional Access policy by its ID. Required Permissions: Policy.Read.All (Delegated or Application) Policy.ReadWrite.ConditionalAccess (Delegated or Application) Args: client (Client): Microsoft Graph client. args (dict): Command arguments. policy_id (str): The ID of the Conditional Access policy to delete. Returns: CommandResults: Results to return to Cortex XSOAR. """ policy_id = args.get("policy_id", "") return client.delete_conditional_access_policy(policy_id) def create_conditional_access_policy_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ Creates a Conditional Access policy. Args: client (Client): Microsoft Graph client. args (dict): Command arguments containing policy details. Returns: CommandResults: In case of success created policy data for Context and and success message for the user, otherwise error message """ policy = args.get("policy", {}) # in case user send json policy try: if isinstance(policy, str): policy = json.loads(policy) except json.JSONDecodeError as e: raise DemistoException(f"The provided policy string is not a valid JSON.\nError: {e}") if not policy: required_fields = ["policy_name", "state", "sign_in_risk_levels", "user_risk_levels", "client_app_types"] missing_fields = [field for field in required_fields if not args.get(field)] if missing_fields: missing_list = ", ".join(missing_fields) demisto.debug(f"Missing required field(s): {missing_list}.") raise DemistoException(f"Missing required field(s): {missing_list}.") policy = build_policy(args) policy = cast(Dict[str, Any], remove_empty_elements(policy)) return client.create_conditional_access_policy(policy) def update_conditional_access_policy_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ Update an existing Conditional Access policy using either full override or append logic. Args: client (Client): An authenticated Microsoft Graph API client. args (Dict[str, Any]): Command arguments including: - policy_id (str): ID of the policy to update. - policy (str or dict, optional): A complete policy in JSON format (used if provided). - update_action (str, optional): 'override' (default) or 'append'. - Other individual fields like include_users, state, etc., depending on mode. Returns: CommandResults: A result object indicating success or failure, along with messages. """ policy_id = args.get("policy_id", "") update_action = args.get("update_action", "append") messages: list[str] = [] policy = args.get("policy") if policy: try: policy = json.loads(policy) except json.JSONDecodeError as e: raise ValueError(f"The provided policy string is not a valid JSON. {str(e)}") return client.update_conditional_access_policy(policy_id, policy) new_policy = build_policy(args) new_policy = cast(Dict[str, Any], remove_empty_elements(new_policy)) if update_action == "append": existing_policy = client.list_conditional_access_policies(policy_id) if not existing_policy: demisto.info(f"No existing policy found with ID: {policy_id}") raise DemistoException(f"No existing policy found with ID: {policy_id}") merge_policy_section(existing_policy[0], new_policy, messages) new_policy = cast(Dict[str, Any], remove_empty_elements(new_policy)) result = client.update_conditional_access_policy(policy_id, new_policy) if messages and result.readable_output and not result.readable_output.startswith("Error"): result.readable_output += "\n\nNote:\n" + "\n".join(messages) return result def get_user_signin_event_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ Retrieve a specific Microsoft Entra ID user sign-in event Args: client (Client): An authenticated Microsoft Graph API client. args (Dict[str, Any]): Command arguments including: - id: Unique ID representing the sign-in event. Returns: CommandResults: A result object with the details of the requested user sign-in event. """ signin_id = args.get("id", "") context = [] readable_signin_events = [] if signin_event := client.get_user_signin_event(signin_id): context.append(signin_event) # Extract relevant items from the raw Graph API object to display them in war room readable_signin_event = { "id": signin_event.get("id"), "correlationId": signin_event.get("correlationId"), "appDisplayName": signin_event.get("appDisplayName"), "resourceDisplayName": signin_event.get("resourceDisplayName"), "status": signin_event.get("status"), "ipAddress": signin_event.get("ipAddress"), "conditionalAccessStatus": signin_event.get("conditionalAccessStatus"), "appliedConditionalAccessPolicies": signin_event.get("appliedConditionalAccessPolicies"), "deviceDetail": signin_event.get("deviceDetail"), "clientAppUsed": signin_event.get("clientAppUsed"), "userDisplayName": signin_event.get("userDisplayName"), "userPrincipalName": signin_event.get("userPrincipalName"), } readable_signin_events.append(readable_signin_event) return CommandResults( "MSGraphIdentity.AuditLog.signIns", "id", outputs=context, ignore_auto_extract=True, readable_output=tableToMarkdown( "Microsoft Entra ID user sign-in event :", readable_signin_events, ), ) else: return CommandResults(readable_output=f"Could not retrieve sign-in data for request_id {signin_id}") def main(): # pragma: no cover demisto.debug(f"Command being called is {demisto.command()}") try: command = demisto.command() params = demisto.params() args = demisto.args() handle_proxy() client = Client( app_id=params.get("app_id"), verify=not params.get("insecure", False), proxy=params.get("proxy", False), azure_ad_endpoint=params.get("azure_ad_endpoint", "https://login.microsoftonline.com") or "https://login.microsoftonline.com", tenant_id=params.get("tenant_id"), client_credentials=params.get("client_credentials", False), enc_key=(params.get("credentials") or {}).get("password"), managed_identities_client_id=get_azure_managed_identities_client_id(params), certificate_thumbprint=params.get("creds_certificate", {}).get("identifier"), private_key=(replace_spaces_in_credential(params.get("creds_certificate", {}).get("password"))), ) if command == "test-module": if client.ms_client.managed_identities_client_id or client.ms_client.grant_type == CLIENT_CREDENTIALS: test_connection(client=client) return_results("ok") else: return_results("The test module is not functional, run the msgraph-identity-auth-start command instead.") elif command == "msgraph-identity-auth-start": return_results(start_auth(client)) elif command == "msgraph-identity-auth-complete": return_results(complete_auth(client)) elif command == "msgraph-identity-auth-test": return_results(test_connection(client)) elif command == "msgraph-identity-auth-reset": return_results(reset_auth()) elif command == "msgraph-identity-directory-roles-list": return_results(list_directory_roles(client, args)) elif command == "msgraph-identity-directory-role-members-list": return_results(list_role_members_command(client, args)) elif command == "msgraph-identity-directory-role-activate": return_results(activate_directory_role_command(client, args)) elif command == "msgraph-identity-directory-role-member-add": return_results(add_member_to_role_command(client, args)) elif command == "msgraph-identity-directory-role-member-remove": return_results(remove_member_from_role(client, args)) elif command == "msgraph-identity-ip-named-locations-create": return_results(ip_named_location_create(client, args)) elif command == "msgraph-identity-ip-named-locations-get": return_results(ip_named_location_get(client, args)) elif command == "msgraph-identity-ip-named-locations-update": return_results(ip_named_location_update(client, args)) elif command == "msgraph-identity-ip-named-locations-delete": return_results(ip_named_location_delete(client, args)) elif command == "msgraph-identity-ip-named-locations-list": return_results(ip_named_location_list(client, args)) elif command == "msgraph-identity-protection-risks-list": return_results(azure_ad_identity_protection_risk_detection_list(client, args)) elif command == "msgraph-identity-protection-risky-user-list": return_results(azure_ad_identity_protection_risky_users_list(client, args)) elif command == "msgraph-identity-protection-risky-user-history-list": return_results(azure_ad_identity_protection_risky_users_history_list(client, args)) elif command == "msgraph-identity-protection-risky-user-confirm-compromised": return_results(azure_ad_identity_protection_confirm_compromised_command(client, args)) elif command == "msgraph-identity-protection-risky-user-dismiss": return_results(azure_ad_identity_protection_risky_users_dismiss_command(client, args)) elif command == "msgraph-identity-ca-policies-list": return_results(list_conditional_access_policies_command(client, args)) elif command == "msgraph-identity-ca-policy-delete": return_results(delete_conditional_access_policy_command(client, args)) elif command == "msgraph-identity-ca-policy-create": return_results(create_conditional_access_policy_command(client, args)) elif command == "msgraph-identity-ca-policy-update": return_results(update_conditional_access_policy_command(client, args)) elif command == "msgraph-identity-audit-signin-event-get": return_results(get_user_signin_event_command(client, args)) elif command == "fetch-incidents": incidents, last_run = fetch_incidents(client, params) demisto.incidents(incidents) demisto.setLastRun(last_run) else: raise NotImplementedError(f"Command '{command}' not found.") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): # pragma: no cover main()