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

IDMicrosoftGraphIdentityandAccess
ProviderMicrosoft
CategoryAuthentication & Identity Management
From Version5.0.0
Docker Imagedemisto/crypto:1.0.0.10120494
Supported ModulesAgentix 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.All
  • IdentityRiskyUser.ReadWrite.All
  • RoleManagement.ReadWrite.Directory
  • Policy.ReadWrite.ConditionalAccess
  • Policy.Read.All
  • Application.Read.All
  • AuditLog.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:
Field state is 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 Thumbprint
  • use_managed_identities — Use Azure Managed Identities
  • managed_identities_client_id
  • azure_ad_endpoint — Azure AD endpoint
  • incidentType — Incident type
  • client_credentials — Use Client Credentials Authorization Flow
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • first_fetch — First Fetch Time Interval
  • max_fetch — Max Fetch Incidents Returned
  • incidentFetchInterval — Incidents Fetch Interval
  • alerts_to_fetch — Alerts to Fetch
  • fetch_filter_expression — Fetch query
  • override_issue_severity — Override Microsoft Entra ID Protection risk level
  • issue_severity — Issue severity

Commands (24)

  • msgraph-identity-audit-signin-event-get

    Retrieve Microsoft Entra ID sign-in event.

  • 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-start

    Run this command to start the authorization process and follow the instructions in the command results.

  • msgraph-identity-auth-test

    Tests connectivity to Microsoft.

  • msgraph-identity-ca-policies-list

    Retrieve one or all Conditional Access policies from Microsoft Graph API.

  • msgraph-identity-ca-policy-create

    Creates a Conditional Access policy.

  • msgraph-identity-ca-policy-delete

    Delete specific Conditional Access policy by ID.

  • 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.

  • msgraph-identity-directory-role-activate

    Activates a role by its template ID.

  • msgraph-identity-directory-role-member-add

    Adds a user to a role.

  • msgraph-identity-directory-role-member-remove

    Removes a user from a role.

  • msgraph-identity-directory-role-members-list

    Gets all members in a role ID.

  • msgraph-identity-directory-roles-list

    Lists the roles in the directory.

  • msgraph-identity-ip-named-locations-create

    Create an ip named location.

  • msgraph-identity-ip-named-locations-delete

    Delete an ip named location by id.

  • msgraph-identity-ip-named-locations-get

    Retrieve an ip named location by id.

  • msgraph-identity-ip-named-locations-list

    Retrieve all ip named locations.

  • msgraph-identity-ip-named-locations-update

    update an ip named location by id.

  • msgraph-identity-protection-risks-list

    Retrieve all the detected risks.

  • msgraph-identity-protection-risky-user-confirm-compromised

    Delete an ip named location by id.

  • msgraph-identity-protection-risky-user-dismiss

    Delete an ip named location by id.

  • msgraph-identity-protection-risky-user-history-list

    Retrieve the risky users history in active directory.

  • msgraph-identity-protection-risky-user-list

    Retrieve 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")