Skip to content

Commit

Permalink
Groups namespace updates
Browse files Browse the repository at this point in the history
  • Loading branch information
vgrem committed Sep 23, 2023
1 parent 221c5e4 commit 11ac01e
Show file tree
Hide file tree
Showing 19 changed files with 348 additions and 54 deletions.
18 changes: 18 additions & 0 deletions examples/directory/groups/create_m365.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Create a Microsoft 365 group
The following example creates a Microsoft 365 group. Because the owners have not been specified,
the calling user is automatically added as the owner of the group.
https://learn.microsoft.com/en-us/graph/api/group-post-groups?view=graph-rest-1.0
"""
from office365.graph_client import GraphClient
from tests import create_unique_name
from tests.graph_case import acquire_token_by_username_password

grp_name = create_unique_name("Group")
client = GraphClient(acquire_token_by_username_password)
group = client.groups.create_m365_group(grp_name).execute_query()

# clean up resources
group.delete_object(True).execute_query()
25 changes: 25 additions & 0 deletions examples/directory/groups/create_with_team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Create group and team.
https://learn.microsoft.com/en-us/graph/teams-create-group-and-team
"""

from office365.graph_client import GraphClient
from tests import create_unique_name
from tests.graph_case import acquire_token_by_username_password


def print_failure(retry_number, ex):
"""
Print progress status
"""
print(f"{retry_number}: Team creation still in progress, waiting...")


client = GraphClient(acquire_token_by_username_password)
group_name = create_unique_name("Flight")
group = client.groups.create_with_team(group_name).execute_query_retry(max_retry=10, failure_callback=print_failure)
print("Team has been created: {0}".format(group.team.web_url))

# clean up resources
group.delete_object(True).execute_query()
14 changes: 14 additions & 0 deletions examples/directory/groups/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
List groups
https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0
"""
import json

from office365.graph_client import GraphClient
from tests.graph_case import acquire_token_by_username_password

client = GraphClient(acquire_token_by_username_password)
groups = client.groups.get().top(10).execute_query()
display_names = [g.display_name for g in groups]
print(display_names)
13 changes: 7 additions & 6 deletions examples/teams/create_from_group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Create a new team under a group.
Create team from group
https://learn.microsoft.com/en-us/graph/api/team-put-teams?view=graph-rest-1.0&tabs=http
https://learn.microsoft.com/en-us/graph/api/team-put-teams?view=graph-rest-1.0
"""

from office365.graph_client import GraphClient
Expand All @@ -14,10 +14,11 @@ def print_failure(retry_number, ex):


client = GraphClient(acquire_token_by_username_password)
group_name = create_unique_name("Team")
group = client.groups.create_with_team(group_name).execute_query_retry(max_retry=10, failure_callback=print_failure)
print("Team has been created: {0}".format(group.team.web_url))
group_name = create_unique_name("Flight")
group = client.groups.create_m365_group(group_name)
team = group.add_team().execute_query_retry(max_retry=10, failure_callback=print_failure)
print("Team has been created: {0}".format(team.web_url))

# clean up
# clean up resources
print("Deleting a group...")
group.delete_object(True).execute_query()
8 changes: 5 additions & 3 deletions examples/teams/create_team.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
Create a new team.
`TeamCollection.create` is an async operation. To ensure teams gets created `Team.ensure_created` method is called
https://learn.microsoft.com/en-us/graph/api/team-post?view=graph-rest-1.0&tabs=http
Since `TeamCollection.create` is an async operation, execute_query_and_wait is called to ensure teams gets created
https://learn.microsoft.com/en-us/graph/api/team-post?view=graph-rest-1.0
"""

from office365.graph_client import GraphClient
Expand All @@ -12,7 +14,7 @@
client = GraphClient(acquire_token_by_username_password)
team_name = create_unique_name("Team")
print("Creating a team '{0}' ...".format(team_name))
new_team = client.teams.create(team_name).ensure_created().execute_query()
new_team = client.teams.create(team_name).execute_query_and_wait()
print("Team has been created")

print("Cleaning up temporary resources... ")
Expand Down
11 changes: 11 additions & 0 deletions office365/directory/groups/assigned_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from office365.runtime.client_value import ClientValue


class AssignedLabel(ClientValue):
"""
Represents a sensitivity label assigned to a Microsoft 365 group. Sensitivity labels allow administrators
to enforce specific group settings on a group by assigning a classification to the group (such as Confidential,
Highly Confidential or General). Sensitivity labels are published by administrators in Microsoft 365 Security and
Compliance Center as part of Microsoft Purview Information Protection capabilities. For more information about
sensitivity labels, see Sensitivity labels overview.
"""
46 changes: 34 additions & 12 deletions office365/directory/groups/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,49 @@ def __init__(self, context, resource_path=None):
super(GroupCollection, self).__init__(context, Group, resource_path)

def add(self, group_properties):
"""Create a Group resource. You can create the following types of groups:
Office 365 group (unified group)
Security group
"""
Create a Group resource.
You can create the following types of groups:
- Microsoft 365 group (unified group)
- Security group
:type group_properties: GroupProfile"""
:param GroupProfile group_properties: Group properties
"""
return_type = Group(self.context)
self.add_child(return_type)
qry = CreateEntityQuery(self, group_properties, return_type)
self.context.add_query(qry)
return return_type

def create_with_team(self, group_name):
"""Provision a new group along with a team.
def create_m365_group(self, name, description=None):
"""
Creates a Microsoft 365 group.
If the owners have not been specified, the calling user is automatically added as the owner of the group.
:param str name: The display name for the group
:param str description: An optional description for the group
"""
params = GroupProfile(name, description, True, False, ["Unified"])
return self.add(params)

:param str group_name:
def create_security_group(self, name, description=None):
"""
params = GroupProfile(group_name)
params.securityEnabled = False
params.mailEnabled = True
params.groupTypes = ["Unified"]
Creates a Security group
:param str name: The display name for the group
:param str description: An optional description for the group
"""
params = GroupProfile(name, description, False, True, [])
return self.add(params)

def create_with_team(self, group_name):
"""
Provision a new group along with a team.
Note: After the group is successfully created, which can take up to 15 minutes,
create a Microsoft Teams team using this method could throw an error since
the group creation process might not be completed. For that scenario prefer submit the request to server via
execute_query_retry instead of execute_query when using this method.
:param str group_name: The display name for the group
"""
def _after_group_created(return_type):
return_type.add_team()
return self.context.groups.add(params).after_execute(_after_group_created)
return self.create_m365_group(group_name).after_execute(_after_group_created)
136 changes: 135 additions & 1 deletion office365/directory/groups/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

from office365.directory.applications.roles.assignment_collection import AppRoleAssignmentCollection
from office365.directory.extensions.extension import Extension
from office365.directory.groups.assigned_label import AssignedLabel
from office365.directory.licenses.assigned_license import AssignedLicense
from office365.directory.licenses.processing_state import LicenseProcessingState
from office365.directory.object import DirectoryObject
from office365.directory.object_collection import DirectoryObjectCollection
from office365.directory.permissions.grants.resource_specific import ResourceSpecificPermissionGrant
from office365.directory.profile_photo import ProfilePhoto
from office365.entity_collection import EntityCollection
from office365.onedrive.drives.drive import Drive
from office365.onenote.onenote import Onenote
Expand All @@ -18,12 +21,16 @@
from office365.runtime.http.http_method import HttpMethod
from office365.runtime.paths.resource_path import ResourcePath
from office365.runtime.queries.service_operation import ServiceOperationQuery
from office365.runtime.types.collections import StringCollection
from office365.teams.team import Team


class Group(DirectoryObject):
"""Represents an Azure Active Directory (Azure AD) group, which can be an Office 365 group, or a security group."""

def __repr__(self):
return self.display_name

def renew(self):
"""
Renews a group's expiration. When a group is renewed, the group expiration is extended by the number
Expand Down Expand Up @@ -104,13 +111,114 @@ def delete_object(self, permanent_delete=False):
deleted_item.delete_object()
return self

@property
def assigned_labels(self):
"""
The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group.
"""
return self.properties.get("assignedLabels", ClientValueCollection(AssignedLabel))

@property
def classification(self):
"""Describes a classification for the group (such as low, medium or high business impact). Valid values for
this property are defined by creating a ClassificationList setting value, based on the template definition.
:rtype: str or None
"""
return self.properties.get('classification', None)

@property
def display_name(self):
"""
The display name for the group. This property is required when a group is created and cannot be cleared during
updates. Maximum length is 256 characters.
Returned by default. Supports $filter (eq, ne, not, ge, le, in, startsWith, and eq on null values), $search,
and $orderby.
:rtype: str
"""
return self.properties.get("displayName", None)

@property
def group_types(self):
"""
Specifies the group type and its membership.
If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group
or distribution group. For details, see groups overview.
If the collection includes DynamicMembership, the group has dynamic membership; otherwise, membership is static.
Returned by default. Supports $filter (eq, not).
"""
return self.properties.get("groupTypes", StringCollection())

@property
def has_members_with_license_errors(self):
"""
Indicates whether there are members in this group that have license errors from its group-based license
assignment.
This property is never returned on a GET operation. You can use it as a $filter argument to get groups that
have members with license errors (that is, filter for this property being true)
:rtype: bool or None
"""
return self.properties.get('hasMembersWithLicenseErrors', None)

@property
def is_assignable_to_role(self):
"""
Indicates whether this group can be assigned to an Azure Active Directory role or not. Optional.
This property can only be set while creating the group and is immutable. If set to true, the securityEnabled
property must also be set to true, visibility must be Hidden, and the group cannot be a dynamic group
(that is, groupTypes cannot contain DynamicMembership).
Only callers in Global Administrator and Privileged Role Administrator roles can set this property.
The caller must also be assigned the RoleManagement.ReadWrite.Directory permission to set this property or
update the membership of such groups. For more, see Using a group to manage Azure AD role assignments
:rtype: bool or None
"""
return self.properties.get('isAssignableToRole', None)

@property
def license_processing_state(self):
"""Indicates status of the group license assignment to all members of the group. Default value is false.
Read-only. Possible values: QueuedForProcessing, ProcessingInProgress, and ProcessingComplete.
"""
return self.properties.get('licenseProcessingState', LicenseProcessingState())

@property
def mail(self):
"""
The SMTP address for the group, for example, "[email protected]".
:rtype: str or None
"""
return self.properties.get('mail', None)

@property
def mail_enabled(self):
"""
Specifies whether the group is mail-enabled. Required.
:rtype: bool or None
"""
return self.properties.get('mailEnabled', None)

@property
def mail_nickname(self):
"""
The mail alias for the group, unique for Microsoft 365 groups in the organization. Maximum length is 64
characters.
:rtype: str or None
"""
return self.properties.get('mailNickname', None)

@property
def on_premises_domain_name(self):
"""
:rtype: str or None
"""
return self.properties.get('onPremisesDomainName', None)

@property
def conversations(self):
"""The group's conversations."""
Expand Down Expand Up @@ -177,7 +285,8 @@ def owners(self):

@property
def drives(self):
"""The group's drives. Read-only.
"""
The group's drives. Read-only.
"""
return self.properties.get('drives',
EntityCollection(self.context, Drive, ResourcePath("drives", self.resource_path)))
Expand Down Expand Up @@ -215,6 +324,23 @@ def planner(self):
return self.properties.get('planner',
PlannerGroup(self.context, ResourcePath("planner", self.resource_path)))

@property
def permission_grants(self):
"""
List permissions that have been granted to apps to access the group.
"""
return self.properties.setdefault('permissionGrants',
EntityCollection(self.context, ResourceSpecificPermissionGrant,
ResourcePath("permissionGrants")))

@property
def photo(self):
"""
The group's profile photo
"""
return self.properties.get('photo',
ProfilePhoto(self.context, ResourcePath("photo", self.resource_path)))

@property
def team(self):
"""The team associated with this group."""
Expand All @@ -223,14 +349,22 @@ def team(self):

@property
def assigned_licenses(self):
"""
The licenses that are assigned to the group.
Returned only on $select. Supports $filter (eq).Read-only.
"""
return self.properties.get('assignedLicenses', ClientValueCollection(AssignedLicense))

def get_property(self, name, default_value=None):
if default_value is None:
property_mapping = {
"assignedLabels": self.assigned_labels,
"appRoleAssignments": self.app_role_assignments,
"assignedLicenses": self.assigned_licenses,
"createdDateTime": self.created_datetime,
"groupTypes": self.group_types,
"licenseProcessingState": self.license_processing_state,
"permissionGrants": self.permission_grants,
"transitiveMembers": self.transitive_members,
"transitiveMemberOf": self.transitive_member_of,
}
Expand Down
Loading

0 comments on commit 11ac01e

Please sign in to comment.