Microsoft Graph Groups
Entra ID Groups integration (formely Azure Active Directory Groups) enables you to create and manage different types of groups and group functionality according to your requirements.
Authentication & Identity Management · Microsoft Graph Groups
Details
| ID | Microsoft Graph Groups |
|---|---|
| Provider | Microsoft |
| Category | Authentication & Identity Management |
| From Version | 5.0.0 |
| Docker Image | demisto/crypto:1.0.0.10120494 |
| Supported Modules | Agentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud |
README
Entra ID Groups integration (formely Azure Active Directory Groups) enables you to create and manage different types of groups and group functionality according to your requirements. This integration was integrated and tested with version 1.0 of Microsoft Graph Groups API
Use Cases
- Manage the organization groups.
Authentication
For more details about the authentication used in this integration, see Microsoft Integrations - Authentication.
Required Permissions
Note
Using “GroupMember.ReadWrite.All” permission instead of Group.ReadWrite.All:
This permission allows the app to list groups, read basic properties, and read and update the membership of the groups the signed-in user has access to.
Group properties and owners cannot be updated and groups cannot be deleted.
Using this permission will raise errors on the following commands:
Configure Entra ID Groups on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Entra ID Groups.
-
Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Server URL
- ID (received from the admin consent - see Detailed Instructions (?)
- Token (received from the admin consent - see Detailed Instructions (?) section)
- Key (received from the admin consent - see Detailed Instructions (?)
- Certificate Thumbprint
- Private Key
- Trust any certificate (not secure)
- Use system proxy settings
- Click Test to validate the new instance.
Commands
You can execute these commands from the Cortex XSOAR 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.
- Provides a list of groups: msgraph-groups-list-groups
- Returns details of a group: msgraph-groups-get-group
- Create a group: msgraph-groups-create-group
- Deletes a group: msgraph-groups-delete-group
- Lists group members: msgraph-groups-list-members
- Add a member to a group: msgraph-groups-add-member
- Removes a member from a group: msgraph-groups-remove-member
- Generates the login url used for Authorization code flow.: msgraph-groups-generate-login-url
1. msgraph-groups-list-groups
Provides a list of groups.
Base Command
msgraph-groups-list-groups
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| order_by | Sorts groups in an organization by the field values. For example, displayName. | Optional |
| next_link | The URL to the next results page. | Optional |
| top | Sets the page size of the results. | Optional |
| filter | Filters group results. For example, startswith(displayName,'J'), groupTypes/any(c:c+eq+'Unified'). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphGroups.Classification | String | A classification for the group (such as low, medium or high business impact). |
| MSGraphGroups.CreatedDateTime | String | The timestamp when the group was created. |
| MSGraphGroups.DeletedDateTime | String | The timestamp when the group was deleted. |
| MSGraphGroups.Description | String | An optional description for the group. |
| MSGraphGroups.GroupTypes | String | Specifies the group type and its membership. If the group collection contains a Unified value, the group is an Office 365 group; otherwise it's a security group. If the collection includes DynamicMembership, the group has dynamic membership; otherwise, membership is static. |
| MSGraphGroups.ID | String | The unique identifier for the group. |
| MSGraphGroup.IsAssignableToRole | String | Whether the group assigned to a specific role. |
| MSGraphGroup.Mail | String | The SMTP address for the group. For example, "serviceadmins@contoso.onmicrosoft.com". |
| MSGraphGroup.MailEnabled | Boolean | Specifies whether the group is mail-enabled. |
| MSGraphGroup.MailNickname | String | The mail alias for the group, which is unique in the organization. |
| MSGraphGroup.OnPremisesDomainName | String | Contains the on-premises domain FQDN. Also called dnsDomainName, which is synchronized from the on-premises directory. |
| MSGraphGroup.OnPremisesLastSyncDateTime | String | Indicates the last time at which the group was synced with the on-premises directory. The Timestamp type represents date and time information using ISO 8601 format in UTC time. For example, midnight UTC on Jan 1, 2019 is '2019-01-01T00:00:00Z'. |
| MSGraphGroup.OnPremisesSyncEnabled | String | Whether this group is synced from an on-premises directory (true). This group was originally synced from an on-premises directory but is no longer synced (false). Null if this object has never been synced from an on-premises directory (default). |
| MSGraphGroup.ProxyAddresses | String | Email addresses for the group that directs to the same group mailbox. For example: ["SMTP: example@demisto.com", "smtp: example@demisto.com"]. |
| MSGraphGroup.RenewedDateTime | String | Timestamp of when the group was last renewed, which represents the time and date information using ISO 8601 format. Always in UTC time. For example, midnight UTC on Jan 1, 2019 is '2019-01-01T00:00:00Z'. |
| MSGraphGroup.SecurityEnabled | Boolean | Specifies whether the group is a security group. |
| MSGraphGroup.Visibility | String | Specifies the visibility of an Office 365 group. Can be: "Private", "Public", or "Hiddenmembership". Blank values are treated as public. |
| MSGraphGroup.NextLink | String | The URL of the next results page. |
Command Example
!msgraph-groups-list-groups top=4
Context Example
{
"MSGraphGroups": "https://graph.microsoft.com/v1.0/groups?$top=4&$skiptoken={skip_token}"
}
Human Readable Output
Groups (Note that there are more results. Please use the next_link argument to see them.):
| ID | Display Name | Description | Created Date Time | |
|---|---|---|---|---|
| id | DemistoTeam | DemistoTeam | 2019-08-24T09:39:03Z | DemistoTeam@demistodev.onmicrosoft.com |
| id | Graph Groups Test - TEMP | 2019-12-04T11:57:29Z | ||
| id | TestPublic | TestPublic | 2018-12-26T09:44:16Z | testpublic@demistodev.onmicrosoft.com |
| id | Graph Groups Test - DELETE | 2019-11-17T11:50:59Z |
2. msgraph-groups-get-group
Returns details of a group.
Base Command
msgraph-groups-get-group
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The ID of the group. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphGroups.Classification | String | A classification for the group (such as low, medium or high business impact). |
| MSGraphGroups.CreatedDateTime | String | The timestamp when the group was created. |
| MSGraphGroups.DeletedDateTime | String | The timestamp when the group was deleted. |
| MSGraphGroups.Description | String | An optional description for the group. |
| MSGraphGroups.GroupTypes | String | Specifies the group type and its membership. If the group collection contains a Unified value, the group is an Office 365 group; otherwise it's a security group. If the collection includes DynamicMembership, the group has dynamic membership; otherwise, membership is static. |
| MSGraphGroups.ID | String | The unique identifier for the group. |
| MSGraphGroup.IsAssignableToRole | String | Whether the group assigned to a specific role. |
| MSGraphGroup.Mail | String | The SMTP address for the group. For example, "serviceadmins@contoso.onmicrosoft.com". |
| MSGraphGroup.MailEnabled | Boolean | Specifies whether the group is mail-enabled. |
| MSGraphGroup.MailNickname | String | The mail alias for the group, unique in the organization. |
| MSGraphGroup.OnPremisesDomainName | String | Contains the on-premises domain FQDN. Also called dnsDomainName, which is synchronized from the on-premises directory. |
| MSGraphGroup.OnPremisesLastSyncDateTime | String | Indicates the last time at which the group was synced with the on-premises directory.The Timestamp type represents date and time information using ISO 8601 format in UTC time. For example, midnight UTC on Jan 1, 2019 is '2019-01-01T00:00:00Z'. |
| MSGraphGroup.OnPremisesSyncEnabled | String | Whether the group is synced from an on-premises directory (true). This group was originally synced from an on-premises directory but is no longer synced (false). Null if this object has never been synced from an on-premises directory (default). |
| MSGraphGroup.ProxyAddresses | String | Email addresses for the group that directs to the same group mailbox. For example: ["SMTP: example@demisto.com", "smtp: example@demisto.com"]. |
| MSGraphGroup.RenewedDateTime | String | The timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action. The Timestamp type represents date and time information using ISO 8601 format in UTC time. For example, midnight UTC on Jan 1, 2019 is '2019-01-01T00:00:00Z'. |
| MSGraphGroup.SecurityEnabled | Boolean | Specifies whether the group is a security group. |
| MSGraphGroup.Visibility | String | Specifies the visibility of an Office 365 group. Possible values are: Private, Public, or Hiddenmembership. Blank values are treated as public. |
Command Example
!msgraph-groups-get-group group_id={group_id}
Context Example
{
"MSGraphGroups": {
"Classification": null,
"CreatedDateTime": "2019-08-24T09:39:03Z",
"DeletedDateTime": null,
"Description": "DemistoTeam",
"DisplayName": "DemistoTeam",
"GroupTypes": [
"Unified"
],
"ID": "id",
"IsAssignableToRole": null,
"Mail": "DemistoTeam@demistodev.onmicrosoft.com",
"MailEnabled": true,
"MailNickname": "DemistoTeam",
"OnPremisesDomainName": null,
"OnPremisesLastSyncDateTime": null,
"OnPremisesSyncEnabled": null,
"ProxyAddresses": [
"SPO:spo",
"SMTP:DemistoTeam@demistodev.onmicrosoft.com"
],
"RenewedDateTime": "2019-11-07T11:40:09Z",
"SecurityEnabled": false,
"Visibility": "Public"
}
}
Human Readable Output
Groups:
| ID | Display Name | Description | Created Date Time | Security Enabled | Visibility | |
|---|---|---|---|---|---|---|
| id | DemistoTeam | DemistoTeam | 2019-08-24T09:39:03Z | DemistoTeam@demistodev.onmicrosoft.com | false | Public |
3. msgraph-groups-create-group
Create a group.
Base Command
msgraph-groups-create-group
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| display_name | The display name of the group. | Required |
| mail_enabled | Set to true for mail-enabled groups. False for groups without an email. | Optional |
| mail_nickname | The mail alias for the group. | Required |
| security_enabled | Set to true for security groups. False for non security groups (regular groups). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphGroups.Classification | String | A classification for the group (such as low, medium or high business impact). |
| MSGraphGroups.CreatedDateTime | String | The timestamp when the group was created. |
| MSGraphGroups.DeletedDateTime | String | The timestamp when the group was deleted. |
| MSGraphGroups.Description | String | An optional description for the group. |
| MSGraphGroups.GroupTypes | String | Specifies the group type and its membership. If the group collection contains a Unified value, the group is an Office 365 group; otherwise it's a security group. If the group collection includes DynamicMembership, the group has dynamic membership; otherwise, membership is static. |
| MSGraphGroups.ID | String | The unique identifier for the group. |
| MSGraphGroup.IsAssignableToRole | String | Whether the group is assigned to a specific role. |
| MSGraphGroup.Mail | String | The SMTP address for the group. For example, "serviceadmins@contoso.onmicrosoft.com". |
| MSGraphGroup.MailEnabled | Boolean | Specifies whether the group is mail-enabled. |
| MSGraphGroup.MailNickname | String | The mail alias for the group, unique in the organization. |
| MSGraphGroup.OnPremisesDomainName | String | Contains the on-premises domain FQDN. Also called dnsDomainName, which is synchronized from the on-premises directory. |
| MSGraphGroup.OnPremisesLastSyncDateTime | String | Indicates the last time at which the group was synced with the on-premises directory.The Timestamp type represents date and time information using ISO 8601 format in UTC time. For example, midnight UTC on Jan 1, 2019 is '2019-01-01T00:00:00Z'. |
| MSGraphGroup.OnPremisesSyncEnabled | String | Whether this group is synced from an on-premises directory (true). This group was originally synced from an on-premises directory but is no longer synced (false). Null if this object has never been synced from an on-premises directory (default). |
| MSGraphGroup.ProxyAddresses | String | Email addresses for the group that directs to the same group mailbox. For example, ["SMTP: example@demisto.com", "smtp: example@demisto.com"]. |
| MSGraphGroup.RenewedDateTime | String | Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: '2014-01-01T00:00:00Z'. |
| MSGraphGroup.SecurityEnabled | Boolean | Specifies whether the group is a security group. |
| MSGraphGroup.Visibility | String | Specifies the visibility of an Office 365 group. Possible values are: Private, Public, or Hiddenmembership; blank values are treated as public. |
Command Example
!msgraph-groups-create-group display_name="Graph Groups Test - TEMP" mail_nickname="Test_Group_101" security_enabled="true"
Context Example
{
"MSGraphGroups": {
"Classification": null,
"CreatedDateTime": "2019-12-04T11:59:44Z",
"DeletedDateTime": null,
"Description": null,
"DisplayName": "Graph Groups Test - TEMP",
"GroupTypes": [],
"ID": "id",
"IsAssignableToRole": null,
"Mail": null,
"MailEnabled": false,
"MailNickname": "Test_Group_101",
"OnPremisesDomainName": null,
"OnPremisesLastSyncDateTime": null,
"OnPremisesSyncEnabled": null,
"ProxyAddresses": [],
"RenewedDateTime": "2019-12-04T11:59:44Z",
"SecurityEnabled": true,
"Visibility": null
}
}
Human Readable Output
Graph Groups Test - TEMP was created successfully:
| ID | Display Name | Created Date Time | Security Enabled | Mail Enabled |
|---|---|---|---|---|
| id | Graph Groups Test - TEMP | 2019-12-04T11:59:44Z | true | false |
4. msgraph-groups-delete-group
Deletes a group.
Base Command
msgraph-groups-delete-group
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The group ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphGroups.ID | String | The unique identifier for the group. |
| MSGraphGroup.Deleted | Boolean | Specifies whether the group was deleted. |
Command Example
!msgraph-groups-delete-group group_id="id"
Human Readable Output
5. msgraph-groups-list-members
Lists group members.
Base Command
msgraph-groups-list-members
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The group ID. | Required |
| next_link | The URL for the next results page. | Optional |
| top | Sets the page size of results. | Optional |
| filter | Filters members results. For example, startswith(displayName,'user'). | Optional |
| count | Retrieves the total count of matching resources. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| MSGraphGroups.Members.BussinessPhones | String | The telephone numbers for the user. |
| MSGraphGroups.Members.GivenName | String | The given name (first name) of the user. |
| MSGraphGroups.Members.MobilePhone | String | The primary mobile telephone number for the user. |
| MSGraphGroups.Members.DisplayName | String | The name displayed in the address book for the user. Usually the combination of the user's first name, middle initial and last name. |
| MSGraphGroups.Members.UserPrincipalName | Unknown | The user principal name (UPN) of the user. The UPN is an Internet-style login name for the user based on the Internet standard RFC 822. By convention, this should map to the user's email name. The general format is alias@domain, where the domain must be present in the tenant’s collection of verified domains. |
| MSGraphGroups.Members.OfficeLocation | String | The office location in the user's place of business. |
| MSGraphGroups.Members.Mail | String | The SMTP address for the user. For example, "jeff@contoso.onmicrosoft.com". |
| MSGraphGroups.Members.PreferredLanguage | String | The preferred language for the user. Should follow ISO 639-1 Code. For example, "en-US". |
| MSGraphGroups.Members.Surname | String | The user's surname (family name or last name). |
| MSGraphGroups.Members.JobTitle | String | The user’s job title. |
| MSGraphGroups.Members.ID | String | The unique identifier for the user. |
Command Example
!msgraph-groups-list-members group_id=id
Context Example
{
"MSGraphGroups": {
"Classification": null,
"CreatedDateTime": "2019-08-24T09:39:03Z",
"DeletedDateTime": null,
"Description": "DemistoTeam",
"DisplayName": "DemistoTeam",
"GroupTypes": [
"Unified"
],
"ID": "id",
"IsAssignableToRole": null,
"Mail": "DemistoTeam@demistodev.onmicrosoft.com",
"MailEnabled": true,
"MailNickname": "DemistoTeam",
"Members": [
{
"BusinessPhones": [],
"DisplayName": "name",
"GivenName": "name",
"ID": "id",
"JobTitle": "test",
"Mail": "name@demistodev.onmicrosoft.com",
"MobilePhone": null,
"OfficeLocation": null,
"PreferredLanguage": "en-US",
"Surname": "name",
"UserPrincipalName": "name@demistodev.onmicrosoft.com"
},
],
"OnPremisesDomainName": null,
"OnPremisesLastSyncDateTime": null,
"OnPremisesSyncEnabled": null,
"ProxyAddresses": [
"SPO:spo",
"SMTP:DemistoTeam@demistodev.onmicrosoft.com"
],
"RenewedDateTime": "2019-11-07T11:40:09Z",
"SecurityEnabled": false,
"Visibility": "Public"
}
}
Human Readable Output
Group {group_id} members:
| ID | Display Name | Job Title | |
|---|---|---|---|
| id | name | test | name@demistodev.onmicrosoft.com |
6. msgraph-groups-add-member
Add a member to a group.
Base Command
msgraph-groups-add-member
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The group ID. | Required |
| user_id | The user ID. | Required |
Context Output
There are no context output for this command.
Command Example
!msgraph-groups-add-member group_id="id" user_id="id"
Context Example
{}
Human Readable Output
User {user_id} was added to the Group {group_id} successfully.
7. msgraph-groups-remove-member
Removes a member from a group.
Base Command
msgraph-groups-remove-member
Required Permissions
The following permissions are required for this command.
- Directory.ReadWrite.All - Delegated
- Group.ReadWrite.All - Application
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The group ID. | Required |
| user_id | The user ID. | Required |
Context Output
There are no context output for this command.
Command Example
!msgraph-groups-remove-member group_id="id" user_id="id"
Context Example
{}
Human Readable Output
User {user_id} was removed from the Group {group_id} successfully.
8. msgraph-groups-auth-reset
Run this command if for some reason you need to rerun the authentication process.
Base Command
msgraph-groups-auth-reset
Input
There are no input arguments for this command.
Context Output
There are no context output for this command.
Additional Information
Troubleshooting
Known Limitations
As per, Microsoft also supports dynamic distribution groups which cannot be managed or retrieved through Microsoft Graph.
Configuration parameters
url— Server URL (required)creds_auth_id—creds_tenant_id—_auth_id— ID_tenant_id— Tokencredentials—certificate_thumbprint— Certificate Thumbprintcredentials_certificate_thumbprint—private_key— Private Keyuse_managed_identities— Use Azure Managed Identitiesmanaged_identities_client_id—insecure— Trust any certificate (not secure)proxy— Use system proxy settingsself_deployed— Use a self-deployed Azure Applicationenc_key— Key (received from the admin consent - see Detailed Instructions (?) (Deprecated)auth_id— ID (received from the admin consent - see Detailed Instructions (?) (Deprecated)tenant_id— Token (received from the admin consent - see Detailed Instructions (?) (Deprecated) section)handle_error— Suppress Errors for Non Found Groupsredirect_uri— Application redirect URI (for Authorization Code flow mode)creds_auth_code—auth_code— Authorization code (for Authorization Code flow mode - received from the authorization step. see Detailed Instructions (?) section)
Commands (9)
-
msgraph-groups-add-memberAdd a member to a group.
-
msgraph-groups-auth-resetRun this command if for some reason you need to rerun the authentication process.
-
msgraph-groups-create-groupCreate a group.
-
msgraph-groups-delete-groupDeletes a group.
-
msgraph-groups-generate-login-urlGenerate the login url used for Authorization code flow.
-
msgraph-groups-get-groupReturns details of a group.
-
msgraph-groups-list-groupsProvides a list of groups.
-
msgraph-groups-list-membersLists group members.
-
msgraph-groups-remove-memberRemoves a member from a group.
import demistomock as demisto import urllib3 from CommonServerPython import * from MicrosoftApiModule import * # noqa: E402 # Disable insecure warnings urllib3.disable_warnings() INTEGRATION_CONTEXT_NAME = "MSGraphGroups" NO_OUTPUTS: dict = {} APP_NAME = "ms-graph-groups" def camel_case_to_readable(text: str) -> str: """'camelCase' -> 'Camel Case' Args: text: the text to transform Returns: A Camel Cased string. """ if text == "id": return "ID" return "".join(" " + char if char.isupper() else char.strip() for char in text).strip().title() def parse_outputs(groups_data: dict[str, str]) -> tuple[dict, dict]: """Parse group data as received from Microsoft Graph API into Demisto's conventions Args: groups_data: a dictionary containing the group data Returns: A Camel Cased dictionary with the relevant fields. groups_readable: for the human readable groups_outputs: for the entry context """ # Unnecessary fields, dropping as to not load the incident context. fields_to_drop = [ "@odata.context", "@odata.nextLink", "@odata.deltaLink", "@odata.type", "@removed", "resourceProvisioningOptions", "securityIdentifier", "onPremisesSecurityIdentifier", "onPremisesNetBiosName", "onPremisesProvisioningErrors", "onPremisesSamAccountName", "resourceBehaviorOptions", "creationOptions", "preferredDataLocation", ] if isinstance(groups_data, list): groups_readable, groups_outputs = [], [] for group_data in groups_data: group_readable = {camel_case_to_readable(i): j for i, j in group_data.items() if i not in fields_to_drop} if "@removed" in group_data: group_readable["Status"] = "deleted" groups_readable.append(group_readable) groups_outputs.append({k.replace(" ", ""): v for k, v in group_readable.copy().items()}) return groups_readable, groups_outputs group_readable = {camel_case_to_readable(i): j for i, j in groups_data.items() if i not in fields_to_drop} if "@removed" in groups_data: group_readable["Status"] = "deleted" group_outputs = {k.replace(" ", ""): v for k, v in group_readable.copy().items()} return group_readable, group_outputs class MsGraphClient: """ Microsoft Graph Mail Client enables authorized access to a user's Office 365 mail data in a personal account. """ def __init__( self, tenant_id, auth_id, enc_key, app_name, base_url, verify, proxy, self_deployed, handle_error, redirect_uri=None, auth_code=None, certificate_thumbprint: str | None = None, private_key: str | None = None, managed_identities_client_id: str | None = None, ): grant_type = AUTHORIZATION_CODE if auth_code and redirect_uri else CLIENT_CREDENTIALS resource = None if self_deployed else "" self.ms_client = MicrosoftClient( tenant_id=tenant_id, auth_id=auth_id, enc_key=enc_key, app_name=app_name, base_url=base_url, verify=verify, proxy=proxy, self_deployed=self_deployed, redirect_uri=redirect_uri, auth_code=auth_code, grant_type=grant_type, resource=resource, certificate_thumbprint=certificate_thumbprint, private_key=private_key, managed_identities_client_id=managed_identities_client_id, managed_identities_resource_uri=Resources.graph, command_prefix="msgraph-groups", ) self.handle_error = handle_error def test_function(self): """Performs basic GET request to check if the API is reachable and authentication is successful. Returns: ok if successful. """ self.ms_client.http_request(method="GET", url_suffix="groups", params={"$orderby": "displayName"}) demisto.results("ok") def list_groups(self, order_by: str = None, next_link: str = None, top: int = None, filter_: str = None): """Returns all groups by sending a GET request. Args: order_by: the group fields to order by the response. next_link: the link for the next page of results, if exists. see Microsoft documentation for more details. docs.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0 top: sets the page size of results. filter_: filters results. Returns: Response from API. """ if next_link: # pagination return self.ms_client.http_request(method="GET", full_url=next_link) # default value = 100 params = {"$top": top} if order_by: params["$orderby"] = order_by # type: ignore if filter_: params["$filter"] = filter_ # type: ignore return self.ms_client.http_request(method="GET", url_suffix="groups", params=params) def get_group(self, group_id: str) -> dict: """Returns a single group by sending a GET request. Args: group_id: the group id. Returns: Response from API. """ group = self.ms_client.http_request(method="GET", url_suffix=f"groups/{group_id}") return group def create_group(self, properties: dict[str, Any | None]) -> dict: """Create a single group by sending a POST request. Args: properties: the group properties. Returns: Response from API. """ group = self.ms_client.http_request(method="POST", url_suffix="groups", json_data=properties) return group def delete_group(self, group_id: str): """Delete a single group by sending a DELETE request. Args: group_id: the group id to delete. """ # If successful, this method returns 204 No Content response code. # It does not return anything in the response body. # Using resp_type="text" to avoid parsing error in the calling method. self.ms_client.http_request(method="DELETE", url_suffix=f"groups/{group_id}", resp_type="text") def list_members(self, group_id: str, next_link: str = None, top: int = None, filter_: str = None): """List all group members by sending a GET request. Args: group_id: the group id to list its members. next_link: the link for the next page of results, if exists. see Microsoft documentation for more details. docs.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0 top: sets the page size of results. filter_: filters results. Returns: Response from API. """ headers = {} if next_link: # pagination return self.ms_client.http_request(method="GET", full_url=next_link) params = {"$top": top} if filter_: params["$filter"] = filter_ # type: ignore if count := demisto.args().get("count"): params["$count"] = count headers["ConsistencyLevel"] = "eventual" return self.ms_client.http_request(method="GET", url_suffix=f"groups/{group_id}/members", params=params, headers=headers) def add_member(self, group_id: str, properties: dict[str, str]): """Add a single member to a group by sending a POST request. Args: group_id: the group id to add the member to. properties: the member properties. """ # If successful, this method returns 204 No Content response code. # It does not return anything in the response body. # Using resp_type="text" to avoid parsing error in the calling method. self.ms_client.http_request( method="POST", url_suffix=f"groups/{group_id}/members/$ref", json_data=properties, resp_type="text" ) def remove_member(self, group_id: str, user_id: str): """Remove a single member to a group by sending a DELETE request. Args: group_id: the group id to add the member to. user_id: the user id to remove. """ # If successful, this method returns 204 No Content response code. # It does not return anything in the response body. # Using resp_type="text" to avoid parsing error in the calling method. self.ms_client.http_request(method="DELETE", url_suffix=f"groups/{group_id}/members/{user_id}/$ref", resp_type="text") def suppress_errors_with_404_code(func): def wrapper(client: MsGraphClient, args: dict): try: return func(client, args) except NotFoundError: if client.handle_error: human_readable = f'#### Group id -> {args.get("group_id")} does not exist' return human_readable, None, None raise return wrapper def test_function_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Performs a basic GET request to check if the API is reachable and authentication is successful. Args: client: Client object with request args: Usually demisto.args() Returns: Tuple. """ client.test_function() return "ok", {}, {} def list_groups_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Lists all groups and return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ order_by = args.get("order_by") next_link = args.get("next_link") top = args.get("top") filter_ = args.get("filter") groups = client.list_groups(order_by, next_link, top, filter_) groups_readable, groups_outputs = parse_outputs(groups["value"]) next_link_response = "" if "@odata.nextLink" in groups: next_link_response = groups["@odata.nextLink"] if next_link_response: entry_context = { f"{INTEGRATION_CONTEXT_NAME}NextLink": {"GroupsNextLink": next_link_response}, f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": groups_outputs, } title = ( "Groups (Note that there are more results. Please use the next_link argument to see them. The value " "can be found in the context under MSGraphGroupsNextLink.GroupsNextLink): " ) else: entry_context = {f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": groups_outputs} title = "Groups:" human_readable = tableToMarkdown( name=title, t=groups_readable, headers=["ID", "Display Name", "Description", "Created Date Time", "Mail"], removeNull=True ) return human_readable, entry_context, groups @suppress_errors_with_404_code def get_group_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Get a group by group id and return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ group_id = str(args.get("group_id")) group = client.get_group(group_id) group_readable, group_outputs = parse_outputs(group) human_readable = tableToMarkdown( name="Groups:", t=group_readable, headers=["ID", "Display Name", "Description", "Created Date Time", "Mail", "Security Enabled", "Visibility"], removeNull=True, ) entry_context = {f"{INTEGRATION_CONTEXT_NAME}(obj.ID === {group_id})": group_outputs} return human_readable, entry_context, group def create_group_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Create a group and return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ required_properties = { "displayName": str(args.get("display_name")), "mailNickname": str(args.get("mail_nickname")), "mailEnabled": args.get("mail_enabled") == "true", "securityEnabled": args.get("security_enabled"), } # create the group group = client.create_group(required_properties) # display the new group and it's properties group_readable, group_outputs = parse_outputs(group) human_readable = tableToMarkdown( name=f"{required_properties['displayName']} was created successfully:", t=group_readable, headers=["ID", "Display Name", "Description", "Created Date Time", "Mail", "Security Enabled", "Mail Enabled"], removeNull=True, ) entry_context = {f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": group_outputs} return human_readable, entry_context, group @suppress_errors_with_404_code def delete_group_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Delete a group by group id and return outputs in Demisto's format Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ group_id = str(args.get("group_id")) client.delete_group(group_id) # get the group data from the context group_data = demisto.dt(demisto.context(), f'{INTEGRATION_CONTEXT_NAME}(val.ID === "{group_id}")') if isinstance(group_data, list): group_data = group_data[0] # add a field that indicates that the group was deleted group_data["Deleted"] = True # add a field with the members to the group entry_context = {f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": group_data} human_readable = f'Group: "{group_id}" was deleted successfully.' return human_readable, entry_context, NO_OUTPUTS @suppress_errors_with_404_code def list_members_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """List a group members by group id. return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ group_id = str(args.get("group_id")) next_link = args.get("next_link") top = args.get("top") filter_ = args.get("filter") members = client.list_members(group_id, next_link, top, filter_) if not members["value"]: human_readable = f"The group {group_id} has no members." return human_readable, NO_OUTPUTS, NO_OUTPUTS members_readable, members_outputs = parse_outputs(members["value"]) # get the group data from the context group_data = demisto.dt(demisto.context(), f'{INTEGRATION_CONTEXT_NAME}(val.ID === "{group_id}")') if not group_data: return_error('Could not find group data in the context, please run "!msgraph-groups-get-group" to retrieve group data.') if isinstance(group_data, list): group_data = group_data[0] if "@odata.nextLink" in members: next_link_response = members["@odata.nextLink"] group_data["Members"] = members_outputs # add a field with the members to the group group_data["MembersNextLink"] = next_link_response entry_context = {f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": group_data} title = ( f"Group {group_id} members " f"(Note that there are more results. Please use the next_link argument to see them. The value can be " f"found in the context under {INTEGRATION_CONTEXT_NAME}.MembersNextLink): " ) else: group_data["Members"] = members_outputs # add a field with the members to the group entry_context = {f"{INTEGRATION_CONTEXT_NAME}(val.ID === obj.ID)": group_data} title = f"Group {group_id} members:" human_readable = tableToMarkdown( name=title, t=members_readable, headers=["ID", "Display Name", "Job Title", "Mail"], removeNull=True ) return human_readable, entry_context, members @suppress_errors_with_404_code def add_member_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Add a member to a group by group id and user id. return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ group_id = str(args.get("group_id")) user_id = str(args.get("user_id")) required_properties = {"@odata.id": f"https://graph.microsoft.com/v1.0/users/{user_id}"} client.add_member(group_id, required_properties) human_readable = f"User {user_id} was added to the Group {group_id} successfully." return human_readable, NO_OUTPUTS, NO_OUTPUTS @suppress_errors_with_404_code def remove_member_command(client: MsGraphClient, args: dict) -> tuple[str, dict, dict]: """Remove a member from a group by group id and user id. return outputs in Demisto's format. Args: client: Client object with request args: Usually demisto.args() Returns: Outputs. """ group_id = str(args.get("group_id")) user_id = str(args.get("user_id")) client.remove_member(group_id, user_id) human_readable = f'User {user_id} was removed from the Group "{group_id}" successfully.' return human_readable, NO_OUTPUTS, NO_OUTPUTS def main(): """ PARSE AND VALIDATE INTEGRATION PARAMS """ params: dict = demisto.params() base_url = params.get("url", "").rstrip("/") + "/v1.0/" tenant = params.get("creds_tenant_id", {}).get("password", "") or params.get("tenant_id") or params.get("_tenant_id") auth_and_token_url = params.get("creds_auth_id", {}).get("password", "") or params.get("auth_id") or params.get("_auth_id") enc_key = params.get("credentials", {}).get("password") or params.get("enc_key") verify = not params.get("insecure", False) redirect_uri = params.get("redirect_uri", "") auth_code = params.get("creds_auth_code", {}).get("password", "") or params.get("auth_code", "") proxy = params.get("proxy") handle_error: bool = argToBoolean(params.get("handle_error", "true")) certificate_thumbprint = params.get("credentials_certificate_thumbprint", {}).get("password", "") or params.get( "certificate_thumbprint" ) private_key = params.get("private_key") managed_identities_client_id = get_azure_managed_identities_client_id(params) self_deployed: bool = params.get("self_deployed", False) or managed_identities_client_id is not None if not managed_identities_client_id: if not self_deployed and not enc_key: raise DemistoException( "Key must be provided. For further information see " "https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication" ) elif self_deployed and auth_code and not redirect_uri: raise DemistoException( "Please provide both Application redirect URI and Authorization code " "for Authorization Code flow, or None for the Client Credentials flow" ) elif not enc_key and not (certificate_thumbprint and private_key): raise DemistoException("Key or Certificate Thumbprint and Private Key must be provided.") if not auth_and_token_url: raise Exception("Authentication ID must be provided.") if not tenant: raise Exception("Token must be provided.") commands = { "test-module": test_function_command, "msgraph-groups-list-groups": list_groups_command, "msgraph-groups-get-group": get_group_command, "msgraph-groups-create-group": create_group_command, "msgraph-groups-delete-group": delete_group_command, "msgraph-groups-list-members": list_members_command, "msgraph-groups-add-member": add_member_command, "msgraph-groups-remove-member": remove_member_command, } command = demisto.command() LOG(f"Command being called is {command}") try: client: MsGraphClient = MsGraphClient( tenant_id=tenant, auth_id=auth_and_token_url, enc_key=enc_key, app_name=APP_NAME, base_url=base_url, verify=verify, proxy=proxy, self_deployed=self_deployed, redirect_uri=redirect_uri, auth_code=auth_code, handle_error=handle_error, certificate_thumbprint=certificate_thumbprint, private_key=private_key, managed_identities_client_id=managed_identities_client_id, ) if command == "msgraph-groups-generate-login-url": return_results(generate_login_url(client.ms_client)) elif command == "msgraph-groups-auth-reset": return_results(reset_auth()) else: human_readable, entry_context, raw_response = commands[command](client, demisto.args()) # type: ignore return_outputs(readable_output=human_readable, outputs=entry_context, raw_response=raw_response) except Exception as err: return_error(str(err)) if __name__ in ["__main__", "builtin", "builtins"]: main()