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 MicrosoftGraphIdentityandAccess import pytest from CommonServerPython import DemistoException, CommandResults from MicrosoftApiModule import NotFoundError ipv4 = {"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "12.34.221.11/22"} # noqa ipv6 = {"@odata.type": "#microsoft.graph.iPv6CidrRange", "cidrAddress": "2001:0:9d38:90d6:0:0:0:0/63"} # noqa @pytest.mark.parametrize( "ips,expected", [ ("12.34.221.11/22,2001:0:9d38:90d6:0:0:0:0/63", [ipv4, ipv6]), ("12.34.221.11/22,12.34.221.11/22", [ipv4, ipv4]), ("2001:0:9d38:90d6:0:0:0:0/63,2001:0:9d38:90d6:0:0:0:0/63", [ipv6, ipv6]), ], ) def test_ms_ip_string_to_list(ips, expected): """ Given: - Ips in a string When: - Convetting them to an ip list. Then: - Ensure that the list we get is what we expected. """ assert MicrosoftGraphIdentityandAccess.ms_ip_string_to_list(ips) == expected @pytest.mark.parametrize( "ips", [ "0.0.0.0", # bare IPv4 without CIDR suffix (XSUP-71053) "192.168.0.1", # bare IPv4 without CIDR suffix "2001:0:9d38:90d6:0:0:0:0", # bare IPv6 without CIDR suffix "12.34.221.11/22,0.0.0.0", # one valid, one invalid "not-an-ip", # not an IP / not CIDR at all "12.34.221.11/40", # invalid prefix length for IPv4 ], ) def test_ms_ip_string_to_list_invalid_cidr_raises(ips): """ Given: - An ips string that contains a value which is not valid CIDR notation. The Microsoft Graph ipNamedLocation API requires every cidrAddress to be an IPv4 or IPv6 address range in CIDR notation (https://learn.microsoft.com/en-us/graph/api/conditionalaccessroot-post-namedlocations). When: - Converting the string to an ip list. Then: - Ensure a DemistoException is raised mentioning CIDR notation, instead of silently forwarding the invalid value to the Graph API (XSUP-71053). """ with pytest.raises(DemistoException, match="CIDR"): MicrosoftGraphIdentityandAccess.ms_ip_string_to_list(ips) def test_ms_ip_string_to_list_empty_raises(): """ Given: - An ips string that produces no valid ranges (empty / whitespace only). When: - Converting the string to an ip list. Then: - Ensure a DemistoException is raised, since the ipNamedLocation API requires the ipRanges collection to contain at least one range. """ with pytest.raises(DemistoException, match="CIDR"): MicrosoftGraphIdentityandAccess.ms_ip_string_to_list(" ") def test_ms_ip_string_to_list_host_bits_allowed(): """ Given: - CIDR values where host bits are set (e.g. 0.0.0.0/0 or 12.34.221.11/24). When: - Converting the string to an ip list. Then: - Ensure the values are accepted and trimmed, since Graph accepts CIDR ranges with host bits set. """ result = MicrosoftGraphIdentityandAccess.ms_ip_string_to_list("0.0.0.0/0, 12.34.221.11/24") assert result == [ {"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "0.0.0.0/0"}, {"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "12.34.221.11/24"}, ] @pytest.mark.parametrize("last,expected", [({"latest_detection_found": "2022-06-06"}, "2022-06-06")]) def test_get_last_fetch_time(last, expected): """ Given: - A dict with the last run details. When: - Getting the last run time value. Then: - Ensure that the time is what we expected. """ assert MicrosoftGraphIdentityandAccess.get_last_fetch_time(last, {}) == expected @pytest.mark.parametrize("date,expected", [("2022-06-06", "2022-06-06.000")]) def test_date_str_to_azure_format(date, expected): """ Given: - A date to convert to Azure format. When: - Converting the date value. Then: - Ensure that the date is what we expected. """ assert MicrosoftGraphIdentityandAccess.date_str_to_azure_format(date) == expected @pytest.mark.parametrize( "incident,expected", [ # Test empty riskDetection object returned by Microsoft. # Is it relevant to trigger an incident in such a scenario ? ( {}, { "name": "Azure AD: ", "severity": 2, "occurred": "2022-06-06Z", "rawJSON": "{}", "details": "", }, ), # Test if riskLevel is not defined ( {"riskEventType": "3", "riskDetail": "2", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "id": "1", "userPrincipalName": "test@domain.com"}', "details": "", }, ), # Test the 6 riskLevel values according to https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "low", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 1, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "low", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "medium", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "medium", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "high", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 3, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "high", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "hidden", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "hidden", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "none", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "none", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ( { "riskEventType": "3", "riskDetail": "2", "riskLevel": "unknownFutureValue", "id": "1", "userPrincipalName": "test@domain.com", }, # noqa: E501 { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "unknownFutureValue", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), # Test anomalousToken incident ( { "riskEventType": "anomalousToken", "riskDetail": "2", "riskLevel": "high", "id": "1", "userPrincipalName": "test@domain.com", }, { "name": "Azure AD: 1 anomalousToken 2", "details": ( "Sign-in detected with abnormal characteristics in the token, such as an unusual lifetime " "or a token played from an unfamiliar location, for user test@domain.com. " "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." ), "severity": 3, "occurred": "2022-06-06Z", "rawJSON": '{"riskEventType": "anomalousToken", "riskDetail": "2", "riskLevel": "high", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 }, ), ], ) def test_detection_to_incident_with_original_alert_severity(incident, expected): """ Given: - A dict with the incident details. When: - Getting the incident. Then: - Ensure that the dict is what we expected. - Ensure that the severity of the created incident equals to the severity of the original Microsoft Entra ID protection alert. """ assert MicrosoftGraphIdentityandAccess.detection_to_incident(incident, "2022-06-06", False, "") == expected @pytest.mark.parametrize( "incident,expected", [ # Test if riskLevel is not defined. Issue severity should be equal to medium. ( {"riskEventType": "3", "riskDetail": "2", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "id": "1", "userPrincipalName": "test@domain.com"}', "details": "", }, ), # Test the if riskLevel is different from "medium". Issue severity should be equal to medium. ( {"riskEventType": "3", "riskDetail": "2", "riskLevel": "low", "id": "1", "userPrincipalName": "test@domain.com"}, { "name": "Azure AD: 1 3 2", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "3", "riskDetail": "2", "riskLevel": "low", "id": "1", "userPrincipalName": "test@domain.com"}', # noqa: E501 "details": "", }, ), ], ) def test_detection_to_incident_with_severity_override(incident, expected): """ Given: - A dict with the incident details. When: - Getting the incident. Then: - Ensure that the dict is what we expected and that the severity is correctly overridden. """ assert MicrosoftGraphIdentityandAccess.detection_to_incident(incident, "2022-06-06", True, "medium") == expected @pytest.mark.parametrize( "incident,expected", [ # Test with None userPrincipalName and unknown risk type ( { "riskEventType": "unknownRiskType", "riskDetail": "someDetail", "riskLevel": "medium", "id": "test-id-123", "userPrincipalName": None, }, { "name": "Azure AD: test-id-123 unknownRiskType someDetail", "occurred": "2022-06-06Z", "severity": 2, "rawJSON": '{"riskEventType": "unknownRiskType", "riskDetail": "someDetail", "riskLevel": "medium", ' '"id": "test-id-123", "userPrincipalName": null}', "details": "", }, ), # Test with missing userPrincipalName field and known risk type ( { "riskEventType": "anomalousToken", "riskDetail": "someDetail", "riskLevel": "high", "id": "test-id-456", }, { "name": "Azure AD: test-id-456 anomalousToken someDetail", "occurred": "2022-06-06Z", "severity": 3, "rawJSON": '{"riskEventType": "anomalousToken", "riskDetail": "someDetail", ' '"riskLevel": "high", "id": "test-id-456"}', "details": ( "Sign-in detected with abnormal characteristics in the token, such as an unusual lifetime " "or a token played from an unfamiliar location, for user . " "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." ), }, ), # Test with None userPrincipalName and known risk type ( { "riskEventType": "leakedCredentials", "riskDetail": "userPerformedSecuredPasswordChange", "riskLevel": "high", "id": "test-id-789", "userPrincipalName": None, }, { "name": "Azure AD: test-id-789 leakedCredentials userPerformedSecuredPasswordChange", "occurred": "2022-06-06Z", "severity": 3, "rawJSON": '{"riskEventType": "leakedCredentials", "riskDetail": "userPerformedSecuredPasswordChange", ' '"riskLevel": "high", "id": "test-id-789", "userPrincipalName": null}', "details": "Credentials for user found in known data breaches.", }, ), ], ) def test_detection_to_incident_with_none_or_missing_upn(incident, expected): """ Given: - A detection dict with None or missing userPrincipalName. When: - Converting detection to incident. Then: - Ensure no error is raised and empty string is used for missing user. - Verify the incident is created successfully with empty user in details. """ assert MicrosoftGraphIdentityandAccess.detection_to_incident(incident, "2022-06-06", False, "") == expected @pytest.mark.parametrize( "incident,expected", [ ( {}, { "name": "Azure User at Risk: - - ", "severity": 2, "details": "Risk detected by Microsoft for Entra ID account. Risk level is .", "occurred": "2025-05-06Z", "rawJSON": "{}", }, ), ( {"userPrincipalName": "test", "riskLevel": "high", "riskState": "atRisk"}, { "name": "Azure User at Risk: test - atRisk - high", "severity": 3, "details": ( "High-risk of test 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" ), "occurred": "2025-05-06Z", "rawJSON": '{"userPrincipalName": "test", "riskLevel": "high", "riskState": "atRisk"}', }, ), ], ) def test_risky_user_to_incident(incident, expected): """ Given: - A dict with the incident details. When: - Getting the incident. Then: - Ensure that the dict is what we expected. """ assert MicrosoftGraphIdentityandAccess.risky_user_to_incident(incident, "2025-05-06", False, "") == expected @pytest.mark.parametrize( "incident,expected", [ ( {}, { "name": "Azure User at Risk: - - ", "severity": 2, "details": "Risk detected by Microsoft for Entra ID account. Risk level is .", "occurred": "2025-05-06Z", "rawJSON": "{}", }, ), ( {"userPrincipalName": "test", "riskLevel": "high", "riskState": "atRisk"}, { "name": "Azure User at Risk: test - atRisk - high", "severity": 2, "details": ( "High-risk of test 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" ), "occurred": "2025-05-06Z", "rawJSON": '{"userPrincipalName": "test", "riskLevel": "high", "riskState": "atRisk"}', }, ), ], ) def test_risky_user_to_incident_with_severity_override(incident, expected): """ Given: - A dict with the incident details. When: - Getting the incident. Then: - Ensure that the dict is what we expected. """ assert MicrosoftGraphIdentityandAccess.risky_user_to_incident(incident, "2025-05-06", True, "medium") == expected @pytest.mark.parametrize( "incident,expected", [ # Test with None userPrincipalName ( {"userPrincipalName": None, "riskLevel": "high", "riskState": "atRisk"}, { "name": "Azure User at Risk: - atRisk - high", "severity": 3, "details": ( "High-risk of 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" ), "occurred": "2025-05-06Z", "rawJSON": '{"userPrincipalName": null, "riskLevel": "high", "riskState": "atRisk"}', }, ), # Test with missing userPrincipalName field ( {"riskLevel": "medium", "riskState": "atRisk"}, { "name": "Azure User at Risk: - atRisk - medium", "severity": 2, "details": ( "One or more medium-severity anomalies were detected " "by Microsoft on Entra ID account. " "Sign-in patterns, behaviors, and other signals factor into the confidence level of the risk detection." ), "occurred": "2025-05-06Z", "rawJSON": '{"riskLevel": "medium", "riskState": "atRisk"}', }, ), ], ) def test_risky_user_to_incident_with_none_or_missing_upn(incident, expected): """ Given: - A risky user dict with None or missing userPrincipalName. When: - Converting risky user to incident. Then: - Ensure no error is raised and empty string is used for missing user. - Verify the incident is created successfully with empty user in details. """ assert MicrosoftGraphIdentityandAccess.risky_user_to_incident(incident, "2025-05-06", False, "") == expected @pytest.mark.parametrize( "incidents,expected", [ ([], ([], "2025-05-14T01:00:00.0000000Z")), ( [ # incidents input { "userPrincipalName": "test", "riskLevel": "medium", "riskState": "atRisk", "riskLastUpdatedDateTime": "2025-05-14T02:00:00.0000000Z", } ], # expected output ( [ { "name": "Azure User at Risk: test - atRisk - medium", "severity": 2, "details": ( "One or more medium-severity anomalies were detected " "by Microsoft on test Entra ID account. Sign-in patterns, behaviors, " "and other signals factor into the confidence level of the risk detection." ), "occurred": "2025-05-14T02:00:00.000000Z", "rawJSON": '{"userPrincipalName": "test", "riskLevel": "medium", "riskState": "atRisk", "riskLastUpdatedDateTime": "2025-05-14T02:00:00.0000000Z"}', # noqa: E501 } ], "2025-05-14T02:00:00.0000000Z", ), ), ], ) def test_risky_users_to_incidents(incidents, expected): """ Given: - A dict with the incident details. When: - Getting the incident. Then: - Ensure that the dict is what we expected. """ assert ( MicrosoftGraphIdentityandAccess.risky_users_to_incidents(incidents, "2025-05-14T01:00:00.0000000Z", False, "") == expected ) @pytest.mark.parametrize( "last_fetch,parameters,expected", [ ("2025-05-06", {"alerts_to_fetch": "Risk Detections"}, "detectedDateTime gt 2025-05-06"), ("2025-05-06", {"alerts_to_fetch": "Risky Users"}, "riskLastUpdatedDateTime gt 2025-05-06"), ], ) def test_build_filter(last_fetch, parameters, expected): """ Given: - A date to set a filter by. When: - Doing an odata query. Then: - Ensure that the filter is what we expected. """ assert MicrosoftGraphIdentityandAccess.build_filter(last_fetch, parameters) == expected @pytest.mark.parametrize(argnames="client_id", argvalues=["test_client_id", None]) def test_test_module_command_with_managed_identities(mocker, requests_mock, client_id): """ Given: - Managed Identities client id for authentication. When: - Calling test_module. Then: - Ensure the output are as expected. """ import demistomock as demisto import MicrosoftGraphIdentityandAccess from MicrosoftGraphIdentityandAccess import MANAGED_IDENTITIES_TOKEN_URL, Resources, main mock_token = {"access_token": "test_token", "expires_in": "86400"} get_mock = requests_mock.get(MANAGED_IDENTITIES_TOKEN_URL, json=mock_token) params = { "managed_identities_client_id": {"password": client_id}, "use_managed_identities": "True", "credentials": {"password": "pass"}, } mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(MicrosoftGraphIdentityandAccess, "return_results", return_value=params) mocker.patch("MicrosoftApiModule.get_integration_context", return_value={}) main() assert "ok" in MicrosoftGraphIdentityandAccess.return_results.call_args[0][0] qs = get_mock.last_request.qs assert qs["resource"] == [Resources.graph] assert (client_id and qs["client_id"] == [client_id]) or "client_id" not in qs @pytest.mark.parametrize( "expected_error", [ ( "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" ) ], ) def test_missing_creds_error_thrown(expected_error): """ Given: - expected_error When: - Attempting to create a client without key or Certificate Thumbprint and Private Key Then: - Ensure that the right option was returned. - Case 1: Should return param. """ from MicrosoftGraphIdentityandAccess import Client with pytest.raises(DemistoException) as e: Client("", False, False, client_credentials=True) assert str(e.value.message) == expected_error def test_list_role_members_command(mocker): """ Given: - A client - A role ID which does not exist or invalid When: - Executing the command 'msgraph-identity-directory-role-members-list' Then: - Ensure the Exception is caught and a CommandResults with an informative readable_output is returned """ from MicrosoftGraphIdentityandAccess import Client, list_role_members_command client = Client("", False, False) message = "Resource '0000c00f' does not exist or one of its queried reference-property objects are not present." mocker.patch.object(Client, "get_role_members", side_effect=NotFoundError(message=message)) result = list_role_members_command(ms_client=client, args={"role_id": "0000c00f", "limit": 1}) assert result.readable_output == "Role ID: 0000c00f, was not found or invalid" @pytest.mark.parametrize( "args, policies_response, expected_outputs, expected_readable_output, expected_prefix, expected_key_field", [ # Case 1: Multiple policies with limit ( {"limit": "2"}, [ { "id": "policy1", "displayName": "Policy One", "state": "enabled", "conditions": {"users": {"includeUsers": ["user1"], "excludeUsers": ["user2"]}}, }, { "id": "policy2", "displayName": "Policy Two", "state": "disabled", "conditions": {"users": {"includeUsers": ["user3"], "excludeUsers": []}}, }, {"id": "policy3", "displayName": "Policy Three", "state": "enabled"}, ], [ { "id": "policy1", "displayName": "Policy One", "state": "enabled", "conditions": {"users": {"includeUsers": ["user1"], "excludeUsers": ["user2"]}}, }, { "id": "policy2", "displayName": "Policy Two", "state": "disabled", "conditions": {"users": {"includeUsers": ["user3"], "excludeUsers": []}}, }, ], "Policy One", # Just checking a substring from one of the expected policies "MSGraphIdentity.ConditionalAccessPolicy", "ID", ), # Case 2: Filter query ( {"filter": "state eq 'enabled'"}, [{"id": "policy1", "displayName": "Policy One", "state": "enabled"}], [{"id": "policy1", "displayName": "Policy One", "state": "enabled"}], "Policy One", "MSGraphIdentity.ConditionalAccessPolicy", "ID", ), ], ) def test_list_conditional_access_policies_command_scenarios( mocker, args, policies_response, expected_outputs, expected_readable_output, expected_prefix, expected_key_field ): """ Given: - Different cases for listing conditional access policies: - Multiple policies with limit - Filter query When: - Calling list_conditional_access_policies_command Then: - Verify correct outputs and readable output are generated - Verify the correct number of policies are returned based on limits """ from MicrosoftGraphIdentityandAccess import Client, list_conditional_access_policies_command mock_client = mocker.Mock(spec=Client) mock_client.list_conditional_access_policies.return_value = policies_response result = list_conditional_access_policies_command(mock_client, args) # Check outputs match expected assert result.outputs == expected_outputs # Check prefix and key field assert result.outputs_prefix == expected_prefix assert result.outputs_key_field == expected_key_field # Check readable output contains expected policy names assert expected_readable_output in result.readable_output def test_list_conditional_access_policies_command_default_limit(mocker): """ Given: - Case for listing conditional access policies with default limit (50) - all_results=False (default limit = 50) When: - Calling list_conditional_access_policies_command Then: - Verify correct outputs and readable output are generated - Verify the correct number of policies are returned based on default limit """ from MicrosoftGraphIdentityandAccess import Client, list_conditional_access_policies_command args = {"all_results": "false"} policies_response = [{"id": f"policy{i}", "displayName": f"Policy {i}", "state": "enabled"} for i in range(1, 55)] expected_outputs = [{"id": f"policy{i}", "displayName": f"Policy {i}", "state": "enabled"} for i in range(1, 51)] expected_readable_output = "Policy 1" expected_prefix = "MSGraphIdentity.ConditionalAccessPolicy" expected_key_field = "ID" mock_client = mocker.Mock(spec=Client) mock_client.list_conditional_access_policies.return_value = policies_response result = list_conditional_access_policies_command(mock_client, args) # Check outputs match expected assert result.outputs == expected_outputs # Verify result contains exactly 50 results assert isinstance(result.outputs, list) assert len(result.outputs) == 50 # Check prefix and key field assert result.outputs_prefix == expected_prefix assert result.outputs_key_field == expected_key_field # Check readable output contains expected policy names assert expected_readable_output in result.readable_output def test_list_conditional_access_policies_command_empty_policies(mocker): """ Given: - Empty policies list returned from API When: - Calling list_conditional_access_policies_command Then: - Verify empty response is handled correctly """ from MicrosoftGraphIdentityandAccess import Client, list_conditional_access_policies_command mock_client = mocker.Mock(spec=Client) mock_client.list_conditional_access_policies.return_value = [] result = list_conditional_access_policies_command(mock_client, {}) expected_readable_output = "No Conditional Access policies were found" assert expected_readable_output in result.readable_output assert result.outputs is None @pytest.mark.parametrize( "args, expected_exception_message", [ # Case: both policy_id and filter provided ( {"policy_id": "abc123", "filter": "state eq 'enabled'"}, "Cannot provide both policy_id and filter_query at the same time", ), ], ) def test_list_conditional_access_policies_command_invalid_args(mocker, args, expected_exception_message): """ Given: - Invalid combinations of arguments (both policy_id and filter) When: - Calling list_conditional_access_policies_command Then: - Verify appropriate exceptions are raised """ from MicrosoftGraphIdentityandAccess import Client, list_conditional_access_policies_command, DemistoException mock_client = mocker.Mock(spec=Client) with pytest.raises(DemistoException) as e: list_conditional_access_policies_command(mock_client, args) assert expected_exception_message in str(e.value) def test_create_conditional_access_policy_command_json_policy(mocker): """ Given: - JSON policy string containing a valid conditional access policy When: - Calling create_conditional_access_policy_command Then: - Verify the policy is created successfully """ from MicrosoftGraphIdentityandAccess import create_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) args = {"policy": '{"displayName": "Test Policy", "state": "enabled"}'} expected_policy = {"displayName": "Test Policy", "state": "enabled"} mock_response = CommandResults( readable_output="Conditional Access policy policy123 was successfully created.", outputs={"id": "policy123"} ) expected_output = "Conditional Access policy policy123 was successfully created." mock_client.create_conditional_access_policy.return_value = mock_response mocker.patch("MicrosoftGraphIdentityandAccess.remove_empty_elements", return_value=expected_policy) result = create_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) assert expected_output in result.readable_output mock_client.create_conditional_access_policy.assert_called_once() def test_create_conditional_access_policy_command_clean_json_policy(mocker): """ Given: - JSON policy string containing a valid conditional access policy with empty elements When: - Calling create_conditional_access_policy_command Then: - Verify the policy is created successfully after empty elements are removed """ from MicrosoftGraphIdentityandAccess import create_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) args = { "policy": ( "{" '"displayName": "Clean Policy", ' '"state": "enabled", ' '"conditions": {' '"users": {' '"includeUsers": [], ' '"excludeUsers": null' "}" "}" "}" ) } expected_policy = {"displayName": "Clean Policy", "state": "enabled", "conditions": {"users": {"includeUsers": []}}} mock_response = CommandResults( readable_output="Conditional Access policy policy123 was successfully created.", outputs={"id": "policy123"} ) expected_output = "Conditional Access policy policy123 was successfully created." mock_client.create_conditional_access_policy.return_value = mock_response mocker.patch("MicrosoftGraphIdentityandAccess.remove_empty_elements", return_value=expected_policy) result = create_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) assert expected_output in result.readable_output mock_client.create_conditional_access_policy.assert_called_once() def test_create_conditional_access_policy_command_from_structured_args(mocker): """ Given: - Structured arguments for creating a conditional access policy When: - Calling create_conditional_access_policy_command Then: - Verify the policy is created successfully with the correct parameters """ from MicrosoftGraphIdentityandAccess import create_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) args = { "policy_name": "Structured Policy", "state": "enabled", "client_app_types": "browser,mobileAppsAndDesktopClients", "include_users": "user1,user2", "include_groups": "group1", "exclude_users": "admin1", "sign_in_risk_levels": "high", "user_risk_levels": "medium", "platform_include": "android,iOS", "grant_controls_operator": "AND", "grant_controls": "block", "session_controls": "cloudAppSecurity", } expected_policy = { "displayName": "Structured Policy", "state": "enabled", "conditions": { "clientAppTypes": ["browser", "mobileAppsAndDesktopClients"], "users": {"includeUsers": ["user1", "user2"], "includeGroups": ["group1"], "excludeUsers": ["admin1"]}, "signInRiskLevels": ["high"], "userRiskLevels": ["medium"], "platforms": {"includePlatforms": ["android", "iOS"]}, }, "grantControls": {"operator": "AND", "builtInControls": ["block"]}, "sessionControls": {"cloudAppSecurity": {}}, } mock_response = CommandResults( readable_output="Conditional Access policy policy123 was successfully created.", outputs={"id": "policy123"} ) expected_output = "Conditional Access policy policy123 was successfully created." mock_client.create_conditional_access_policy.return_value = mock_response mocker.patch("MicrosoftGraphIdentityandAccess.build_policy", return_value=expected_policy) mocker.patch("MicrosoftGraphIdentityandAccess.remove_empty_elements", return_value=expected_policy) result = create_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) assert expected_output in result.readable_output mock_client.create_conditional_access_policy.assert_called_once_with(expected_policy) def test_create_conditional_access_policy_command_invalid_json(mocker): """ Given: - Invalid JSON string in policy argument When: - Calling create_conditional_access_policy_command Then: - Verify a DemistoException is raised with the expected error message """ from MicrosoftGraphIdentityandAccess import create_conditional_access_policy_command, Client, DemistoException mock_client = mocker.Mock(spec=Client) args = {"policy": "{displayName: Test Policy, state: enabled}"} # Missing quotes expected_output = "The provided policy string is not a valid JSON" with pytest.raises(DemistoException) as e: create_conditional_access_policy_command(mock_client, args) assert expected_output in str(e.value) def test_create_conditional_access_policy_command_missing_required_fields(mocker): """ Tests error handling when missing required fields for building a policy. """ from MicrosoftGraphIdentityandAccess import create_conditional_access_policy_command, Client, DemistoException mock_client = mocker.Mock(spec=Client) args = {"policy_name": "Missing Fields Policy", "state": "enabled", "sign_in_risk_levels": "low", "user_risk_levels": "low"} expected_output = "Missing required field(s): client_app_types" mocker.patch("MicrosoftGraphIdentityandAccess.build_policy", side_effect=DemistoException(expected_output)) with pytest.raises(DemistoException) as e: create_conditional_access_policy_command(mock_client, args) assert expected_output in str(e.value) @pytest.mark.parametrize( "field, existing_list, new_list, expected, expected_messages", [ # Test for signInRiskLevels specific handling ("signInRiskLevels", ["low"], ["medium", "high"], sorted(["low", "medium", "high"]), []), # Test for signInRiskLevels with 'none' value ("signInRiskLevels", ["none"], ["low"], sorted(["none", "low"]), []), # Test with None value (lowercase) ("includeUsers", ["none"], ["user1"], ["user1"], []), # Test with multiple None values ("includeGroups", ["None"], ["group1"], ["group1"], []), # Test with special value 'all' (lowercase) ( "includeUsers", ["all"], ["user2"], ["all"], [ "Field 'includeUsers' kept as 'all' (special value cannot be merged).\n" "To update this field, use update_action='override'." ], ), # Test with mixed case in existing list (normal values) ("includeUsers", ["User1", "USER2"], ["user3"], sorted(["User1", "USER2", "user3"]), []), # Test when new list contains multiple special values ("includeLocations", ["loc1"], ["All"], ["All"], []), # Test with duplicated values between existing and new lists ("includeUsers", ["user1", "user2"], ["user2", "user3"], sorted(["user1", "user2", "user3"]), []), # Test with both lists containing the same values ("includeGroups", ["group1", "group2"], ["group1", "group2"], sorted(["group1", "group2"]), []), ], ) def test_resolve_merge_value_advanced_cases(field, existing_list, new_list, expected, expected_messages): """ Given: - Different field types (signInRiskLevels, includeUsers, etc.) - Various combinations of existing and new lists - Special values, case variations, and duplicates When: - Calling resolve_merge_value to merge these lists Then: - Verify the correct merging logic is applied based on field type and list content - Verify appropriate messages are generated for special cases """ from MicrosoftGraphIdentityandAccess import resolve_merge_value messages = [] result = resolve_merge_value(field, existing_list, new_list, messages) assert sorted(result) == sorted(expected) assert messages == expected_messages @pytest.mark.parametrize( "base_existing, new_dict, expected_messages, expected_new", [ # Test Case 1: Merging nested list fields ( {"state": "disabled"}, {"state": "enabled"}, ["Field `state` is not a list - overriding the value."], {"state": "enabled"}, ), # Test Case 2: Field doesn't exist in base ( {"conditions": {"locations": None}}, {"conditions": {"locations": ["AllTrusted"]}}, ["Field `conditions/locations` was empty - new list left untouched."], {"conditions": {"locations": ["AllTrusted"]}}, ), # Test Case 3: Empty dictionaries in path ( {"conditions": {}}, {"conditions": {"users": {"includeUsers": ["user1"]}}}, ["Field `conditions/users/includeUsers` was empty - new list left untouched."], {"conditions": {"users": {"includeUsers": ["user1"]}}}, ), ], ) def test_merge_policy_section(mocker, base_existing, new_dict, expected_messages, expected_new): """ Tests the merge_policy_section function with various test cases. Given: - Different policy structures with varying levels of nesting - Policies with non-matching fields or empty dictionaries - Fields of different types (lists vs scalar values) When: - The merge_policy_section function is called to merge these policies Then: - List fields are properly merged at different nesting levels - Fields that don't exist in the base are handled correctly - Empty dictionaries in the path are properly processed - Appropriate messages are generated for each merge scenario """ from MicrosoftGraphIdentityandAccess import merge_policy_section # Mock the resolve_merge_value function to return the new value # This isolates the test to focus on merge_policy_section's behavior mocker.patch("MicrosoftGraphIdentityandAccess.resolve_merge_value", side_effect=lambda field, existing, new, msgs: new) # Copy the dictionaries to avoid modifying the test parameters import copy base_copy = copy.deepcopy(base_existing) new_copy = copy.deepcopy(new_dict) # Run the function messages = [] merge_policy_section(base_copy, new_copy, messages) # Verify the messages match expected assert sorted(messages) == sorted(expected_messages) # Verify the new dictionary was modified as expected assert new_copy == expected_new def test_merge_policy_section_with_actual_resolve_logic(): """ Tests the merge_policy_section function with the actual resolve_merge_value logic. Given: - A base policy with user inclusions and exclusions - A new policy with additional user inclusions and exclusions When: - The merge_policy_section function is called to merge these policies Then: - The lists in nested structures are properly merged (includeUsers, excludeUsers) - The merge operation correctly combines values from both dictionaries - No error messages are generated during a standard merge operation - The integrated behavior of merge_policy_section and resolve_merge_value functions works as expected """ from MicrosoftGraphIdentityandAccess import merge_policy_section # Define test data with list fields that should be merged base_existing = {"conditions": {"users": {"includeUsers": ["user1", "user2"], "excludeUsers": ["admin1"]}}} new_dict = {"conditions": {"users": {"includeUsers": ["user3"], "excludeUsers": ["admin2"]}}} # Expected result after merging expected_new = {"conditions": {"users": {"includeUsers": ["user1", "user2", "user3"], "excludeUsers": ["admin1", "admin2"]}}} # Run the merge messages = [] merge_policy_section(base_existing, new_dict, messages) # Sort the lists to ensure consistent comparison new_dict["conditions"]["users"]["includeUsers"].sort() new_dict["conditions"]["users"]["excludeUsers"].sort() expected_new["conditions"]["users"]["includeUsers"].sort() expected_new["conditions"]["users"]["excludeUsers"].sort() # Verify the result matches expected assert new_dict == expected_new # Verify no error messages were generated assert len(messages) == 0 def test_merge_policy_section_with_special_values(): """ Tests that merge_policy_section correctly handles special values like 'All' in lists. Given: - A base policy with 'All' in includeUsers list and a regular value in excludeUsers - A new policy with a regular value in includeUsers and another value in excludeUsers When: - The merge_policy_section function is called to merge these policies Then: - The special value 'All' is preserved in the includeUsers list and not merged with other values - Regular lists like excludeUsers are properly merged - A warning message is generated about the special value """ from MicrosoftGraphIdentityandAccess import merge_policy_section # Define test data with special values base_existing = {"conditions": {"users": {"includeUsers": ["All"], "excludeUsers": ["admin1"]}}} new_dict = {"conditions": {"users": {"includeUsers": ["user1"], "excludeUsers": ["admin2"]}}} # Expected result should keep 'All' value expected_new = {"conditions": {"users": {"includeUsers": ["All"], "excludeUsers": ["admin1", "admin2"]}}} # Run the merge messages = [] merge_policy_section(base_existing, new_dict, messages) # Sort the excludeUsers list for consistent comparison new_dict["conditions"]["users"]["excludeUsers"].sort() expected_new["conditions"]["users"]["excludeUsers"].sort() # Verify the result matches expected assert new_dict["conditions"]["users"]["includeUsers"] == ["All"] assert sorted(new_dict["conditions"]["users"]["excludeUsers"]) == sorted(expected_new["conditions"]["users"]["excludeUsers"]) # Verify the message about special value was generated assert any("special value" in msg for msg in messages) @pytest.mark.parametrize( "args, expected_policy", [ ( { "policy_name": "Test Policy", "state": "enabled", "sign_in_risk_levels": "high", "user_risk_levels": "medium", "client_app_types": "browser,mobileAppsAndDesktopClients", "include_users": "user1,user2", "exclude_users": "admin1", "grant_control_enforcement": "mfa", "grant_control_operator": "AND", }, { "displayName": "Test Policy", "state": "enabled", "conditions": { "clientAppTypes": ["browser", "mobileAppsAndDesktopClients"], "applications": { "includeApplications": [], "excludeApplications": [], "includeUserActions": [], }, "users": { "includeUsers": ["user1", "user2"], "excludeUsers": ["admin1"], "includeRoles": [], "excludeRoles": [], "includeGroups": [], "excludeGroups": [], }, "platforms": { "includePlatforms": [], "excludePlatforms": [], }, "locations": { "includeLocations": [], "excludeLocations": [], }, "signInRiskLevels": ["high"], "userRiskLevels": ["medium"], }, "grantControls": {"operator": "AND", "builtInControls": ["mfa"]}, }, ), ( { "policy_name": "Complete Policy", "state": "disabled", "sign_in_risk_levels": "high,medium", "user_risk_levels": "low", "client_app_types": "browser", "include_applications": "Office365", "exclude_applications": "Salesforce", "include_user_actions": "urn:user:registerSecurityInfo", "include_users": "All", "exclude_users": "admin1,admin2", "include_roles": "GlobalAdmin", "exclude_roles": "Reader", "include_groups": "group1,group2", "exclude_groups": "group3", "include_platforms": "android,iOS", "exclude_platforms": "windows", "include_locations": "AllTrusted", "exclude_locations": "loc1", "grant_control_operator": "OR", "grant_control_enforcement": "block,mfa", }, { "displayName": "Complete Policy", "state": "disabled", "conditions": { "clientAppTypes": ["browser"], "applications": { "includeApplications": ["Office365"], "excludeApplications": ["Salesforce"], "includeUserActions": ["urn:user:registerSecurityInfo"], }, "users": { "includeUsers": ["All"], "excludeUsers": ["admin1", "admin2"], "includeRoles": ["GlobalAdmin"], "excludeRoles": ["Reader"], "includeGroups": ["group1", "group2"], "excludeGroups": ["group3"], }, "platforms": { "includePlatforms": ["android", "iOS"], "excludePlatforms": ["windows"], }, "locations": { "includeLocations": ["AllTrusted"], "excludeLocations": ["loc1"], }, "signInRiskLevels": ["high", "medium"], "userRiskLevels": ["low"], }, "grantControls": {"operator": "OR", "builtInControls": ["block", "mfa"]}, }, ), ], ) def test_build_policy(args, expected_policy): """ Given: - A set of arguments for policy creation When: - The build_policy function is called with these arguments Then: - The function should return a properly formatted policy object - The returned policy should match the expected policy structure """ from MicrosoftGraphIdentityandAccess import build_policy policy = build_policy(args) assert policy == expected_policy @pytest.mark.parametrize( "policy_id, response_mock, expected_output", [ # Case 1: Successful deletion ("policy123", {"status_code": 204, "text": ""}, "Conditional Access policy policy123 was successfully deleted."), ], ) def test_delete_conditional_access_policy_command_success(mocker, policy_id, response_mock, expected_output): """ Given: - a valid policy_id When: - the delete_conditional_access_policy_command is called Then: - it should successfully delete the policy and return the expected output """ from MicrosoftGraphIdentityandAccess import delete_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) mock_response = mocker.Mock() if response_mock: mock_response.status_code = response_mock["status_code"] mock_response.text = response_mock["text"] mock_client.delete_conditional_access_policy.return_value = CommandResults(readable_output=expected_output) result = delete_conditional_access_policy_command(mock_client, {"policy_id": policy_id}) assert isinstance(result, CommandResults) assert result.readable_output == expected_output # Verify client was called correctly mock_client.delete_conditional_access_policy.assert_called_once_with(policy_id) @pytest.mark.parametrize( "policy_id, expected_exception", [ # Case 2: Policy not found ( "nonexistent", DemistoException("Error deleting Conditional Access policy nonexistent."), ), ], ) def test_delete_conditional_access_policy_command_failure(mocker, policy_id, expected_exception): """ Given: - a non-existent policy_id When: - the delete_conditional_access_policy_command is called Then: - it should raise an exception with appropriate error message """ from MicrosoftGraphIdentityandAccess import delete_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) mock_client.delete_conditional_access_policy.side_effect = expected_exception with pytest.raises(type(expected_exception)) as e: delete_conditional_access_policy_command(mock_client, {"policy_id": policy_id}) assert str(e.value) == str(expected_exception) @pytest.mark.parametrize( "args, existing_policy, new_policy_built, mock_result, expected_messages, expected_output", [ # Case 1: Basic append mode with no special values ( {"policy_id": "policy123", "update_action": "append", "include_users": "user3", "state": "enabled"}, [{"id": "policy123", "state": "disabled", "conditions": {"users": {"includeUsers": ["user1", "user2"]}}}], {"state": "enabled", "conditions": {"users": {"includeUsers": ["user3"]}}}, CommandResults(readable_output="Conditional Access policy policy123 was successfully updated."), [], "Conditional Access policy policy123 was successfully updated.", ), # Case 2: Append mode with special value in existing policy ( {"policy_id": "policy123", "update_action": "append", "include_users": "user3", "state": "enabled"}, [{"id": "policy123", "state": "disabled", "conditions": {"users": {"includeUsers": ["All"]}}}], {"state": "enabled", "conditions": {"users": {"includeUsers": ["user3"]}}}, CommandResults(readable_output="Conditional Access policy policy123 was successfully updated."), [ "Field 'includeUsers' kept as 'All' (special value cannot be merged).\n" "To update this field, use update_action='override'." ], "Conditional Access policy policy123 was successfully updated.\n\nNote:\n" "Field 'includeUsers' kept as 'All' (special value cannot be merged).\n" "To update this field, use update_action='override'.", ), ], ) def test_update_conditional_access_policy_command_append( mocker, args, existing_policy, new_policy_built, mock_result, expected_messages, expected_output ): """ Given: - Command arguments for updating a conditional access policy in append mode - Mock existing policy data - Mock new policy data to be built - Mock command result - Expected warning messages - Expected command output When: - The update_conditional_access_policy_command function is called with append mode Then: - Function correctly handles append mode scenarios - Proper warning messages are generated for special values - The expected output is returned in the command results - Client methods are called with correct parameters for append mode """ from MicrosoftGraphIdentityandAccess import update_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) mock_client.list_conditional_access_policies.return_value = existing_policy mock_client.update_conditional_access_policy.return_value = mock_result # Mock build_policy to return our predefined policy mocker.patch("MicrosoftGraphIdentityandAccess.build_policy", return_value=new_policy_built) # Mock remove_empty_elements to return the same policy (no empty elements) mocker.patch("MicrosoftGraphIdentityandAccess.remove_empty_elements", return_value=new_policy_built) # Mock merge_policy_section to add our expected messages def mock_merge(existing, new, messages): messages.extend(expected_messages) mocker.patch("MicrosoftGraphIdentityandAccess.merge_policy_section", side_effect=mock_merge) # Mock return_results to avoid affecting test output mocker.patch("MicrosoftGraphIdentityandAccess.return_results") result = update_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) assert result.readable_output == expected_output # For append mode mock_client.list_conditional_access_policies.assert_called_once_with(args["policy_id"]) mock_client.update_conditional_access_policy.assert_called_once_with(args["policy_id"], new_policy_built) @pytest.mark.parametrize( "args, new_policy_built, mock_result, expected_output", [ # Case 3: Override mode ( {"policy_id": "policy123", "update_action": "override", "include_users": "user3", "state": "enabled"}, {"state": "enabled", "conditions": {"users": {"includeUsers": ["user3"]}}}, CommandResults(readable_output="Conditional Access policy policy123 was successfully updated."), "Conditional Access policy policy123 was successfully updated.", ), ], ) def test_update_conditional_access_policy_command_override(mocker, args, new_policy_built, mock_result, expected_output): """ Given: - Command arguments for updating a conditional access policy in override mode - Mock new policy data to be built - Mock command result - Expected command output When: - The update_conditional_access_policy_command function is called with override mode Then: - Function correctly handles override mode scenario - The expected output is returned in the command results - Client methods are called with correct parameters for override mode """ from MicrosoftGraphIdentityandAccess import update_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) mock_client.update_conditional_access_policy.return_value = mock_result # Mock build_policy to return our predefined policy mocker.patch("MicrosoftGraphIdentityandAccess.build_policy", return_value=new_policy_built) # Mock remove_empty_elements to return the same policy (no empty elements) mocker.patch("MicrosoftGraphIdentityandAccess.remove_empty_elements", return_value=new_policy_built) # Mock return_results to avoid affecting test output mocker.patch("MicrosoftGraphIdentityandAccess.return_results") result = update_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) assert result.readable_output == expected_output # For override mode assert mock_client.list_conditional_access_policies.call_count == 0 mock_client.update_conditional_access_policy.assert_called_once_with(args["policy_id"], new_policy_built) @pytest.mark.parametrize( "args, mock_result, expected_output", [ # Case 4: Direct policy provided as JSON string ( {"policy_id": "policy123", "policy": '{"state": "enabled", "conditions": {"users": {"includeUsers": ["user3"]}}}'}, CommandResults(readable_output="Conditional Access policy policy123 was successfully updated."), "Conditional Access policy policy123 was successfully updated.", ), ], ) def test_update_conditional_access_policy_command_direct_json(mocker, args, mock_result, expected_output): """ Given: - Command arguments with a direct policy JSON string - Mock command result - Expected command output When: - The update_conditional_access_policy_command function is called with direct policy JSON Then: - Function correctly handles direct policy JSON scenario - The expected output is returned in the command results - Client methods are called with correct parameters for direct policy JSON """ from MicrosoftGraphIdentityandAccess import update_conditional_access_policy_command, Client mock_client = mocker.Mock(spec=Client) mock_client.update_conditional_access_policy.return_value = mock_result # Mock return_results to avoid affecting test output mocker.patch("MicrosoftGraphIdentityandAccess.return_results") result = update_conditional_access_policy_command(mock_client, args) assert isinstance(result, CommandResults) # assert result.readable_output == expected_output # For direct policy JSON case mock_client.update_conditional_access_policy.assert_called_once() assert mock_client.list_conditional_access_policies.call_count == 0 @pytest.mark.parametrize( "args, mock_result", [ # Case 4: Direct policy provided as JSON string ( {"id": "ed015f68-15ad-4375-9cad-16ec81880100"}, { "riskDetail": "none", "userDisplayName": "TestUser", "riskState": "none", "createdDateTime": "2025-11-13T11:52:24Z", "userId": "cfzt37e3-c2cd-4c99-ad40-cf9ac726283u", "deviceDetail": { "browser": "Firefox Mobile 144.0", "deviceId": "", "displayName": "", "isCompliant": False, "isManaged": False, "operatingSystem": "Android", "trustType": "null", }, "resourceId": "00000002-0000-0ff1-ce00-000000000000", "appDisplayName": "One Outlook Web", "ipAddress": "AAA.XXX.YYY.ZZZ", "riskEventTypes_v2": "null", "userPrincipalName": "testUser@testdomain.onmicrosoft.com", "riskEventTypes": "null", "status": {"additionalDetails": "null", "errorCode": 0, "failureReason": "Other."}, "clientAppUsed": "Browser", "location": { "city": "Cape Town", "countryOrRegion": "ZA", "geoCoordinates": {"altitude": "null", "latitude": -33.9249, "longitude": 18.4241}, "state": "Western Cape", }, "isInteractive": True, "riskLevelDuringSignIn": "low", "riskLevelAggregated": "none", "id": "26e93953-93c2-4922-b752-78cf3e180300", "conditionalAccessStatus": "success", "appId": "9199bf20-a13f-4107-85dc-02114787ef48", "appliedConditionalAccessPolicies": "null", "correlationId": "8799925d-08ac-cf4d-368f-8a24549aaf98", "resourceDisplayName": "Office 365 Exchange Online", }, ), ], ) def test_get_user_signin_event_command(mocker, args, mock_result): """ Given: - Command arguments sign-in id - Mock command result - Expected command output When: - Calling the get_user_signin_event_command function Then: - Verify the returned object """ from MicrosoftGraphIdentityandAccess import get_user_signin_event_command, Client mock_client = mocker.Mock(spec=Client) mock_client.get_user_signin_event.return_value = mock_result # Mock return_results to avoid affecting test output mocker.patch("MicrosoftGraphIdentityandAccess.return_results") result = get_user_signin_event_command(mock_client, args) assert isinstance(result, CommandResults) assert result.outputs == [mock_result] @pytest.mark.parametrize( "root, path, expected", [ # nested dictionary access ({"a": {"b": {"c": "value"}}}, ["a", "b", "c"], "value"), # Non-existent key in path ({"a": {"b": {"c": "value"}}}, ["a", "b", "d"], None), # Empty path ({"a": {"b": {"c": "value"}}}, [], {"a": {"b": {"c": "value"}}}), # Path with non-existent root key ({"a": {"b": {"c": "value"}}}, ["x", "y", "z"], None), # Path with mixed types ({"a": {"b": [1, 2, {"c": "value"}]}}, ["a", "b"], [1, 2, {"c": "value"}]), # Root is empty dictionary ({}, ["a", "b", "c"], None), # Path that's partially valid (exists until a point) ({"a": {"b": {"c": "value"}}}, ["a", "b", "c", "d"], None), # Dictionary with numerical keys ({1: {2: {3: "value"}}}, [1, 2, 3], "value"), # Access to nested None value ({"a": {"b": None}}, ["a", "b"], None), # Dictionary with special characters in keys ({"a": {"@special": {"$key": "value"}}}, ["a", "@special", "$key"], "value"), ], ) def test_deep_get(root, path, expected): """ Given: - A root dictionary to search in - A path represented as a list of keys to traverse - An expected result value When: - The deep_get function is called with the root and path Then: - It correctly retrieves values from nested dictionaries - It returns None for non-existent paths - It handles special cases like empty paths, numerical keys, and special characters """ from MicrosoftGraphIdentityandAccess import deep_get result = deep_get(root, path) assert result == expected @pytest.mark.parametrize( "root, path, value, expected", [ # Basic test - create a new nested structure ({}, ["a", "b", "c"], 42, {"a": {"b": {"c": 42}}}), # Test with existing root dictionary ({"x": 1}, ["a", "b", "c"], 42, {"x": 1, "a": {"b": {"c": 42}}}), # Test with partial existing path ({"a": {"b": {}}}, ["a", "b", "c"], 42, {"a": {"b": {"c": 42}}}), # Test with fully existing path (overwrite value) ({"a": {"b": {"c": 10}}}, ["a", "b", "c"], 42, {"a": {"b": {"c": 42}}}), # Test with single level path ({}, ["key"], "value", {"key": "value"}), # Test with different value types ({}, ["a", "b"], [1, 2, 3], {"a": {"b": [1, 2, 3]}}), ({}, ["a", "b"], {"nested": "dict"}, {"a": {"b": {"nested": "dict"}}}), ({}, ["a", "b"], None, {"a": {"b": None}}), # Test with mixed key types (though not recommended, it's technically possible) ({"a": {}}, ["a", 1], "value", {"a": {1: "value"}}), ], ) def test_deep_set(root, path, value, expected): """ Tests the deep_set function with various cases. Given: - A root dictionary - A path to set - A value to set at that path When: - deep_set is called with these parameters Then: - The dictionary is updated correctly for valid inputs """ # Make a copy of the root to avoid modifying the test data root_copy = root.copy() MicrosoftGraphIdentityandAccess.deep_set(root_copy, path, value) assert root_copy == expected def test_deep_set_empty_path(): """ Tests that deep_set raises an error with empty path. Given: - A root dictionary - An empty path When: - deep_set is called with these parameters Then: - An IndexError is raised """ root = {} path = [] with pytest.raises(IndexError): MicrosoftGraphIdentityandAccess.deep_set(root, path, "any_value")