From f8231b468c2f83b76bf78ad7dee39bc68d3c7c66 Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 14 Mar 2024 15:02:35 -0400 Subject: [PATCH 1/7] commit WIP group stuff --- plugins/modules/azure_rm_adgroup.py | 48 ++++++++++++++++++--- plugins/modules/azure_rm_adgroup_info.py | 55 ++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/plugins/modules/azure_rm_adgroup.py b/plugins/modules/azure_rm_adgroup.py index 44bfc16de..49f906d99 100644 --- a/plugins/modules/azure_rm_adgroup.py +++ b/plugins/modules/azure_rm_adgroup.py @@ -63,6 +63,12 @@ - The azure ad objects asserted to not be owners of the group. type: list elements: str + raw_membership: + description: + - By default the group_members return property is flattened and partially filtered of non-User objects + before return. This argument disables those transformations. + default: false + type: bool extends_documentation_fragment: - azure.azcollection.azure author: @@ -104,6 +110,15 @@ - "{{ ad_object_1_object_id }}" - "{{ ad_object_2_object_id }}" +- name: Ensure Users are Members of a Group using object_id. Specify the group_membership return should be unfiltered + azure_rm_adgroup: + object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + state: 'present' + present_members: + - "{{ ad_object_1_object_id }}" + - "{{ ad_object_2_object_id }}" + raw_membership: true + - name: Ensure Users are not Members of a Group using display_name and mail_nickname azure_rm_adgroup: display_name: "Group-Name" @@ -112,7 +127,7 @@ absent_members: - "{{ ad_object_1_object_id }}" -- name: Ensure Users are Members of a Group using object_id +- name: Ensure Users are not Members of a Group using object_id azure_rm_adgroup: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx state: 'present' @@ -145,7 +160,7 @@ - "{{ ad_object_1_object_id }}" - "{{ ad_object_2_object_id }}" -- name: Ensure Users are Owners of a Group using object_id +- name: Ensure Users are not Owners of a Group using object_id azure_rm_adgroup: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx state: 'present' @@ -198,7 +213,8 @@ type: list group_members: description: - - The members of the group. + - The members of the group. If raw_membership is true, this contains the transient members return property. + Otherwise, it contains the members return property. returned: always type: list ''' @@ -211,6 +227,7 @@ from msgraph.generated.models.group import Group from msgraph.generated.groups.item.transitive_members.transitive_members_request_builder import \ TransitiveMembersRequestBuilder + from msgraph.generated.groups.item.group_item_request_builder import GroupItemRequestBuilder from msgraph.generated.models.reference_create import ReferenceCreate except ImportError: # This is handled in azure_rm_common @@ -228,6 +245,7 @@ def __init__(self): present_owners=dict(type='list', elements='str'), absent_members=dict(type='list', elements='str'), absent_owners=dict(type='list', elements='str'), + raw_membership=dict(type='bool', default=False), state=dict( type='str', default='present', @@ -245,6 +263,8 @@ def __init__(self): self.state = None self.results = dict(changed=False) self._client = None + self.include_service_principals = False + self.raw_membership = False super(AzureRMADGroup, self).__init__(derived_arg_spec=self.module_arg_spec, supports_check_mode=False, @@ -255,9 +275,6 @@ def exec_module(self, **kwargs): for key in list(self.module_arg_spec.keys()): setattr(self, key, kwargs[key]) - # TODO remove ad_groups return. Returns as one object always - ad_groups = [] - try: self._client = self.get_msgraph_client() ad_groups = [] @@ -455,6 +472,12 @@ async def get_group_list(self, filter=None): return [] async def get_group_members(self, group_id, filters=None): + if self.raw_membership: + return await self.get_raw_group_members(group_id, filters) + else: + return await self.get_transitive_group_members(group_id, filters) + + async def get_transitive_group_members(self, group_id, filters=None): request_configuration = TransitiveMembersRequestBuilder.TransitiveMembersRequestBuilderGetRequestConfiguration( query_parameters=TransitiveMembersRequestBuilder.TransitiveMembersRequestBuilderGetQueryParameters( count=True, @@ -465,6 +488,19 @@ async def get_group_members(self, group_id, filters=None): return await self._client.groups.by_group_id(group_id).transitive_members.get( request_configuration=request_configuration) + async def get_raw_group_members(self, group_id, filters=None): + request_configuration = GroupItemRequestBuilder.GroupItemRequestBuilderGetRequestConfiguration( + query_parameters=GroupItemRequestBuilder.GroupItemRequestBuilderGetQueryParameters( + # this ensures service principals are returned + # see https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http + expand=["members"] + ), + ) + # if filters: + # request_configuration.query_parameters.filter = filters + group = await self._client.groups.by_group_id(group_id).get(request_configuration=request_configuration) + return group.members + async def add_group_member(self, group_id, obj_id): request_body = ReferenceCreate( odata_id="https://graph.microsoft.com/v1.0/directoryObjects/{0}".format(obj_id), diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index 05ff8c1ca..42022a1f0 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -55,6 +55,19 @@ - Indicate whether the groups in which a groups is a member should be returned with the returned groups. default: False type: bool + raw_membership: + description: + - Indicate whether group membership should be returned from the transitive_members property, as opposed to + the members property. Due to API limitations this property and include_service_principals cannot both + be true, as the transitive_members property only includes "User" objects + default: True + type: bool + raw_membership: + description: + - By default the group_members return property is flattened and partially filtered of non-User objects + before return. This argument disables those transformations. + default: false + type: bool all: description: - If True, will return all groups in tenant. @@ -74,6 +87,11 @@ azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +- name: Return a specific group using object_id and include service principals + azure_rm_adgroup_info: + object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + include_service_principals: true + - name: Return a specific group using object_id and return the owners of the group azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx @@ -84,6 +102,14 @@ object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx return_owners: true return_group_members: true + +- name: Return a specific group using object_id and return the owners and members of the group. Specify the +return should be unfiltered + azure_rm_adgroup_info: + object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + return_owners: true + return_group_members: true + raw_membership: true - name: Return a specific group using object_id and return the groups the group is a member of azure_rm_adgroup_info: @@ -153,7 +179,8 @@ type: list group_members: description: - - The members of the group. + - The members of the group. If raw_membership is set, this field may contain non-user objects + (groups, service principals, etc) returned: always type: list ''' @@ -167,6 +194,7 @@ TransitiveMembersRequestBuilder from msgraph.generated.groups.item.get_member_groups.get_member_groups_post_request_body import \ GetMemberGroupsPostRequestBody + from msgraph.generated.groups.item.members.members_request_builder import MembersRequestBuilder except ImportError: # This is handled in azure_rm_common pass @@ -184,6 +212,7 @@ def __init__(self): return_owners=dict(type='bool', default=False), return_group_members=dict(type='bool', default=False), return_member_groups=dict(type='bool', default=False), + include_service_principals=dict(type='bool', default=False), all=dict(type='bool', default=False), ) @@ -195,6 +224,7 @@ def __init__(self): self.return_owners = False self.return_group_members = False self.return_member_groups = False + self.raw_membership = False self.all = False self.results = dict(changed=False) @@ -345,12 +375,15 @@ async def get_group_owners(self, group_id): return await self._client.groups.by_group_id(group_id).owners.get(request_configuration=request_configuration) async def get_group_members(self, group_id, filters=None): + if self.raw_membership: + return await self.get_raw_group_members(group_id, filters) + else: + return await self.get_transitive_group_members(group_id, filters) + + async def get_transitive_group_members(self, group_id, filters=None): request_configuration = TransitiveMembersRequestBuilder.TransitiveMembersRequestBuilderGetRequestConfiguration( query_parameters=TransitiveMembersRequestBuilder.TransitiveMembersRequestBuilderGetQueryParameters( count=True, - select=['id', 'displayName', 'userPrincipalName', 'mailNickname', 'mail', 'accountEnabled', 'userType', - 'appId', 'appRoleAssignmentRequired'] - ), ) if filters: @@ -358,6 +391,20 @@ async def get_group_members(self, group_id, filters=None): return await self._client.groups.by_group_id(group_id).transitive_members.get( request_configuration=request_configuration) + async def get_raw_group_members(self, group_id, filters=None): + request_configuration = MembersRequestBuilder.MembersRequestBuilderGetRequestConfiguration( + query_parameters=MembersRequestBuilder.MembersRequestBuilderGetQueryParameters( + count=True, + # this ensures service principals are returned + # see https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http + expand=["members"] + ), + ) + if filters: + request_configuration.query_parameters.filter = filters + return await self._client.groups.by_group_id(group_id).members.get( + request_configuration=request_configuration) + async def get_member_groups(self, obj_id): request_body = GetMemberGroupsPostRequestBody(security_enabled_only=False) return await self._client.groups.by_group_id(obj_id).get_member_groups.post(body=request_body) From bef183c73dba8955aa57d0b06210623ec0eec1bb Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 21 Mar 2024 11:45:03 -0400 Subject: [PATCH 2/7] Fix: group returns --- plugins/modules/azure_rm_adgroup.py | 19 ++++++++++--------- plugins/modules/azure_rm_adgroup_info.py | 23 ++++++++--------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/plugins/modules/azure_rm_adgroup.py b/plugins/modules/azure_rm_adgroup.py index 49f906d99..7a51fd6f4 100644 --- a/plugins/modules/azure_rm_adgroup.py +++ b/plugins/modules/azure_rm_adgroup.py @@ -329,7 +329,7 @@ def update_members(self, group_id): if self.present_members or self.absent_members: ret = asyncio.get_event_loop().run_until_complete(self.get_group_members(group_id)) - current_members = [object.id for object in ret.value] + current_members = [object.id for object in ret] if self.present_members: present_members_by_object_id = self.dictionary_from_object_urls(self.present_members) @@ -365,7 +365,7 @@ def update_owners(self, group_id): if owners_to_add: for owner_object_id in owners_to_add: asyncio.get_event_loop().run_until_complete( - self.add_gropup_owner(group_id, present_owners_by_object_id[owner_object_id])) + self.add_group_owner(group_id, present_owners_by_object_id[owner_object_id])) self.results["changed"] = True if self.absent_owners: @@ -373,7 +373,7 @@ def update_owners(self, group_id): if owners_to_remove: for owner in owners_to_remove: - asyncio.get_event_loop().run_until_complete(self.remove_gropup_owner(group_id, owner)) + asyncio.get_event_loop().run_until_complete(self.remove_group_owner(group_id, owner)) self.results["changed"] = True def dictionary_from_object_urls(self, object_urls): @@ -442,7 +442,7 @@ def set_results(self, object): if results["object_id"] and (self.present_members or self.absent_members): ret = asyncio.get_event_loop().run_until_complete(self.get_group_members(results["object_id"])) - results["group_members"] = [self.result_to_dict(object) for object in ret.value] + results["group_members"] = [self.result_to_dict(object) for object in ret] return results @@ -485,8 +485,9 @@ async def get_transitive_group_members(self, group_id, filters=None): ) if filters: request_configuration.query_parameters.filter = filters - return await self._client.groups.by_group_id(group_id).transitive_members.get( + response = await self._client.groups.by_group_id(group_id).transitive_members.get( request_configuration=request_configuration) + return response.value async def get_raw_group_members(self, group_id, filters=None): request_configuration = GroupItemRequestBuilder.GroupItemRequestBuilderGetRequestConfiguration( @@ -496,8 +497,8 @@ async def get_raw_group_members(self, group_id, filters=None): expand=["members"] ), ) - # if filters: - # request_configuration.query_parameters.filter = filters + if filters: + request_configuration.query_parameters.filter = filters group = await self._client.groups.by_group_id(group_id).get(request_configuration=request_configuration) return group.members @@ -518,13 +519,13 @@ async def get_group_owners(self, group_id): ) return await self._client.groups.by_group_id(group_id).owners.get(request_configuration=request_configuration) - async def add_gropup_owner(self, group_id, obj_id): + async def add_group_owner(self, group_id, obj_id): request_body = ReferenceCreate( odata_id="https://graph.microsoft.com/v1.0/users/{0}".format(obj_id), ) await self._client.groups.by_group_id(group_id).owners.ref.post(body=request_body) - async def remove_gropup_owner(self, group_id, obj_id): + async def remove_group_owner(self, group_id, obj_id): await self._client.groups.by_group_id(group_id).owners.by_directory_object_id(obj_id).ref.delete() diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index 42022a1f0..b1de4a449 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -55,13 +55,6 @@ - Indicate whether the groups in which a groups is a member should be returned with the returned groups. default: False type: bool - raw_membership: - description: - - Indicate whether group membership should be returned from the transitive_members property, as opposed to - the members property. Due to API limitations this property and include_service_principals cannot both - be true, as the transitive_members property only includes "User" objects - default: True - type: bool raw_membership: description: - By default the group_members return property is flattened and partially filtered of non-User objects @@ -194,7 +187,7 @@ TransitiveMembersRequestBuilder from msgraph.generated.groups.item.get_member_groups.get_member_groups_post_request_body import \ GetMemberGroupsPostRequestBody - from msgraph.generated.groups.item.members.members_request_builder import MembersRequestBuilder + from msgraph.generated.groups.item.group_item_request_builder import GroupItemRequestBuilder except ImportError: # This is handled in azure_rm_common pass @@ -212,7 +205,7 @@ def __init__(self): return_owners=dict(type='bool', default=False), return_group_members=dict(type='bool', default=False), return_member_groups=dict(type='bool', default=False), - include_service_principals=dict(type='bool', default=False), + raw_membership=dict(type='bool', default=False), all=dict(type='bool', default=False), ) @@ -324,7 +317,7 @@ def set_results(self, object): if results["object_id"] and self.return_group_members: ret = asyncio.get_event_loop().run_until_complete(self.get_group_members(results["object_id"])) - results["group_members"] = [self.result_to_dict(object) for object in ret.value] + results["group_members"] = [self.result_to_dict(object) for object in ret] if results["object_id"] and self.return_member_groups: ret = asyncio.get_event_loop().run_until_complete(self.get_member_groups(results["object_id"])) @@ -333,7 +326,7 @@ def set_results(self, object): if results["object_id"] and self.check_membership: filter = "id eq '{0}' ".format(self.check_membership) ret = asyncio.get_event_loop().run_until_complete(self.get_group_members(results["object_id"], filter)) - results["is_member_of"] = True if ret.value and len(ret.value) != 0 else False + results["is_member_of"] = True if ret and len(ret) != 0 else False return results @@ -388,13 +381,13 @@ async def get_transitive_group_members(self, group_id, filters=None): ) if filters: request_configuration.query_parameters.filter = filters - return await self._client.groups.by_group_id(group_id).transitive_members.get( + response = await self._client.groups.by_group_id(group_id).transitive_members.get( request_configuration=request_configuration) + return response.value async def get_raw_group_members(self, group_id, filters=None): - request_configuration = MembersRequestBuilder.MembersRequestBuilderGetRequestConfiguration( - query_parameters=MembersRequestBuilder.MembersRequestBuilderGetQueryParameters( - count=True, + request_configuration = GroupItemRequestBuilder.GroupItemRequestBuilderGetRequestConfiguration( + query_parameters=GroupItemRequestBuilder.GroupItemRequestBuilderGetQueryParameters( # this ensures service principals are returned # see https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http expand=["members"] From 8b53d0233ddb7aabd60616177ef2a833ddd80c10 Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 21 Mar 2024 11:52:26 -0400 Subject: [PATCH 3/7] remove errant parameter --- plugins/modules/azure_rm_adgroup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/azure_rm_adgroup.py b/plugins/modules/azure_rm_adgroup.py index 7a51fd6f4..08d9b8e89 100644 --- a/plugins/modules/azure_rm_adgroup.py +++ b/plugins/modules/azure_rm_adgroup.py @@ -263,7 +263,6 @@ def __init__(self): self.state = None self.results = dict(changed=False) self._client = None - self.include_service_principals = False self.raw_membership = False super(AzureRMADGroup, self).__init__(derived_arg_spec=self.module_arg_spec, From 55275ddacf5d132ac9d93ed974038b219a43d66f Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 21 Mar 2024 13:00:42 -0400 Subject: [PATCH 4/7] clean up docs --- plugins/modules/azure_rm_adgroup.py | 6 ++---- plugins/modules/azure_rm_adgroup_info.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/modules/azure_rm_adgroup.py b/plugins/modules/azure_rm_adgroup.py index 08d9b8e89..affc7e39c 100644 --- a/plugins/modules/azure_rm_adgroup.py +++ b/plugins/modules/azure_rm_adgroup.py @@ -65,8 +65,7 @@ elements: str raw_membership: description: - - By default the group_members return property is flattened and partially filtered of non-User objects - before return. This argument disables those transformations. + - By default the group_members return property is flattened and partially filtered of non-User objects before return. This argument disables those transformations. default: false type: bool extends_documentation_fragment: @@ -213,8 +212,7 @@ type: list group_members: description: - - The members of the group. If raw_membership is true, this contains the transient members return property. - Otherwise, it contains the members return property. + - The members of the group. If raw_membership is false, this contains the transitive members property. Otherwise, it contains the members property. returned: always type: list ''' diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index b1de4a449..f81bfaffd 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -57,8 +57,7 @@ type: bool raw_membership: description: - - By default the group_members return property is flattened and partially filtered of non-User objects - before return. This argument disables those transformations. + - By default the group_members return property is flattened and partially filtered of non-User objects before return. This argument disables those transformations. default: false type: bool all: @@ -172,8 +171,7 @@ type: list group_members: description: - - The members of the group. If raw_membership is set, this field may contain non-user objects - (groups, service principals, etc) + - The members of the group. If raw_membership is set, this field may contain non-user objects (groups, service principals, etc) returned: always type: list ''' From d5ce7d293c756f0851afa2ccdfc96edea15a6486 Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 21 Mar 2024 13:19:35 -0400 Subject: [PATCH 5/7] fix doc --- plugins/modules/azure_rm_adgroup_info.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index f81bfaffd..72fc84ef5 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -79,11 +79,6 @@ azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -- name: Return a specific group using object_id and include service principals - azure_rm_adgroup_info: - object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - include_service_principals: true - - name: Return a specific group using object_id and return the owners of the group azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx @@ -95,8 +90,7 @@ return_owners: true return_group_members: true -- name: Return a specific group using object_id and return the owners and members of the group. Specify the -return should be unfiltered +- name: Return a specific group using object_id and return the owners and members of the group. Return service_principals and nested groups. azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx return_owners: true From e935dcb9a17269ce38c030ec69349b0947a6077c Mon Sep 17 00:00:00 2001 From: kmcdonough Date: Thu, 21 Mar 2024 13:24:50 -0400 Subject: [PATCH 6/7] remove underscore --- plugins/modules/azure_rm_adgroup_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index 0c9af48ab..7420a9178 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -90,7 +90,7 @@ return_owners: true return_group_members: true -- name: Return a specific group using object_id and return the owners and members of the group. Return service_principals and nested groups. +- name: Return a specific group using object_id and return the owners and members of the group. Return service principals and nested groups. azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx return_owners: true From 1d21742aa73cd3a8b9eabd2cf4681bbd7f713937 Mon Sep 17 00:00:00 2001 From: Kent McDonough Date: Fri, 22 Mar 2024 09:09:39 -0400 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_adgroup.py | 3 ++- plugins/modules/azure_rm_adgroup_info.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/modules/azure_rm_adgroup.py b/plugins/modules/azure_rm_adgroup.py index db8cafd4e..9aea09bf5 100644 --- a/plugins/modules/azure_rm_adgroup.py +++ b/plugins/modules/azure_rm_adgroup.py @@ -65,7 +65,8 @@ elements: str raw_membership: description: - - By default the group_members return property is flattened and partially filtered of non-User objects before return. This argument disables those transformations. + - By default the group_members return property is flattened and partially filtered of non-User objects before return. \ + This argument disables those transformations. default: false type: bool description: diff --git a/plugins/modules/azure_rm_adgroup_info.py b/plugins/modules/azure_rm_adgroup_info.py index 7420a9178..04393c02e 100644 --- a/plugins/modules/azure_rm_adgroup_info.py +++ b/plugins/modules/azure_rm_adgroup_info.py @@ -57,7 +57,8 @@ type: bool raw_membership: description: - - By default the group_members return property is flattened and partially filtered of non-User objects before return. This argument disables those transformations. + - By default the group_members return property is flattened and partially filtered of non-User objects before return.\ + This argument disables those transformations. default: false type: bool all: @@ -89,7 +90,6 @@ object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx return_owners: true return_group_members: true - - name: Return a specific group using object_id and return the owners and members of the group. Return service principals and nested groups. azure_rm_adgroup_info: object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx