From abf4aa604beaa7d6ed32531b5c9ae459e1912aed Mon Sep 17 00:00:00 2001 From: baechir Date: Thu, 28 Mar 2024 13:11:14 +0100 Subject: [PATCH 1/7] modified: plugins/modules/azure_rm_aduser.py Add option: on_premises_extension_attributes modified: plugins/modules/azure_rm_aduser_info.py Add option: on_premises_extension_attributes modified: tests/integration/targets/azure_rm_aduser/tasks/main.yml Add option: on_premises_extension_attributes Fix: Domain name, password and username --- plugins/modules/azure_rm_aduser.py | 73 +++++++++++++--- plugins/modules/azure_rm_aduser_info.py | 30 +++++-- .../targets/azure_rm_aduser/tasks/main.yml | 84 ++++++++++++------- 3 files changed, 139 insertions(+), 48 deletions(-) diff --git a/plugins/modules/azure_rm_aduser.py b/plugins/modules/azure_rm_aduser.py index 1e0a238c0..1704d2dad 100644 --- a/plugins/modules/azure_rm_aduser.py +++ b/plugins/modules/azure_rm_aduser.py @@ -119,6 +119,17 @@ - The maximum length is 64 characters.Returned only on $select. - Supports $filter (eq, ne, not, ge, le, in, startsWith, and eq on null values). type: str + on_premises_extension_attributes: + description: + - Contains extensionAttributes1-15 for the user. + - These extension attributes are also known as Exchange custom attributes 1-15. + - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. + - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + type: dict + aliases: + - extension_attributes + extends_documentation_fragment: - azure.azcollection.azure @@ -143,6 +154,10 @@ usage_location: "US" mail: "{{ user_principal_name }}@contoso.com" company_name: 'Test Company' + on_premises_extension_attributes: + extension_attribute1: "test_extension_attribute1" + extension_attribute2: "test_extension_attribute2" + extension_attribute11: "test_extension_attribute11" - name: Update user with new value for account_enabled azure_rm_aduser: @@ -205,6 +220,16 @@ type: str returned: always sample: 'Test Company' +on_premises_extension_attributes: + description: + - Contains extensionAttributes1-15 for the user. + - These extension attributes are also known as Exchange custom attributes 1-15. + - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. + - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + type: dict + returned: always + sample: {} ''' from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBase @@ -212,6 +237,7 @@ try: import asyncio from msgraph.generated.models.password_profile import PasswordProfile + from msgraph.generated.models.on_premises_extension_attributes import OnPremisesExtensionAttributes from msgraph.generated.models.user import User from msgraph.generated.users.users_request_builder import UsersRequestBuilder except ImportError: @@ -239,7 +265,8 @@ def __init__(self): surname=dict(type='str'), user_type=dict(type='str'), mail=dict(type='str'), - company_name=dict(type='str') + company_name=dict(type='str'), + on_premises_extension_attributes=dict(type='dict', aliases=['extension_attributes']) ) self.user_principal_name = None @@ -259,6 +286,7 @@ def __init__(self): self.user_type = None self.mail = None self.company_name = None + self.on_premises_extension_attributes = None self.log_path = None self.log_mode = None @@ -288,6 +316,13 @@ def exec_module(self, **kwargs): if self.state == 'present': + extension_attributes = None + + if self.on_premises_extension_attributes: + extension_attributes=OnPremisesExtensionAttributes( + **self.on_premises_extension_attributes + ) + if ad_user: # Update, changed password = None @@ -295,10 +330,9 @@ def exec_module(self, **kwargs): if self.password_profile: password = PasswordProfile( password=self.password_profile, - ) + ) should_update = False - if self.on_premises_immutable_id and ad_user.on_premises_immutable_id != self.on_premises_immutable_id: should_update = True if should_update or self.usage_location and ad_user.usage_location != self.usage_location: @@ -321,9 +355,11 @@ def exec_module(self, **kwargs): should_update = True if should_update or self.company_name and ad_user.company_name != self.company_name: should_update = True + if should_update or self.on_premises_extension_attributes and self.on_premises_extension_attributes_to_dict(ad_user.on_premises_extension_attributes) != self.on_premises_extension_attributes: + should_update = True if should_update: - asyncio.get_event_loop().run_until_complete(self.update_user(ad_user, password)) + asyncio.get_event_loop().run_until_complete(self.update_user(ad_user, password, extension_attributes)) self.results['changed'] = True @@ -335,7 +371,7 @@ def exec_module(self, **kwargs): self.results['changed'] = False else: # Create, changed - asyncio.get_event_loop().run_until_complete(self.create_user()) + asyncio.get_event_loop().run_until_complete(self.create_user(extension_attributes)) self.results['changed'] = True ad_user = self.get_exisiting_user() @@ -391,6 +427,16 @@ def get_exisiting_user(self): raise return ad_user + def on_premises_extension_attributes_to_dict(self, on_premises_extension_attributes): + extension_attributes = {} + for index in range(1, 16 + 1): + attribute_name = f'extension_attribute{index}' + if hasattr(on_premises_extension_attributes, attribute_name): + attr_value = getattr(on_premises_extension_attributes, attribute_name) + if attr_value is not None: + extension_attributes[attribute_name] = attr_value + return extension_attributes + def to_dict(self, object): return dict( object_id=object.id, @@ -400,10 +446,11 @@ def to_dict(self, object): mail=object.mail, account_enabled=object.account_enabled, user_type=object.user_type, - company_name=object.company_name + company_name=object.company_name, + on_premises_extension_attributes=self.on_premises_extension_attributes_to_dict(object.on_premises_extension_attributes) ) - async def update_user(self, ad_user, password): + async def update_user(self, ad_user, password, extension_attributes): request_body = User( on_premises_immutable_id=self.on_premises_immutable_id, usage_location=self.usage_location, @@ -415,11 +462,12 @@ async def update_user(self, ad_user, password): password_profile=password, user_principal_name=self.user_principal_name, mail_nickname=self.mail_nickname, - company_name=self.company_name + company_name=self.company_name, + on_premises_extension_attributes=extension_attributes ) return await self._client.users.by_user_id(ad_user.id).patch(body=request_body) - async def create_user(self): + async def create_user(self, extension_attributes): password = PasswordProfile( password=self.password_profile ) @@ -435,7 +483,8 @@ async def create_user(self): surname=self.surname, user_type=self.user_type, mail=self.mail, - company_name=self.company_name + company_name=self.company_name, + on_premises_extension_attributes=extension_attributes ) return await self._client.users.post(body=request_body) @@ -446,7 +495,7 @@ async def get_user(self, object): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", - "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName"] + "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", "OnPremisesExtensionAttributes"] ), ) return await self._client.users.by_user_id(object).get(request_configuration=request_configuration) @@ -457,7 +506,7 @@ async def get_users_by_filter(self, filter): query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( filter=filter, select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", - "userType", "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName"], + "userType", "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", "OnPremisesExtensionAttributes"], count=True ), headers={'ConsistencyLevel': "eventual", } diff --git a/plugins/modules/azure_rm_aduser_info.py b/plugins/modules/azure_rm_aduser_info.py index 98c30be57..8ca4fde0d 100644 --- a/plugins/modules/azure_rm_aduser_info.py +++ b/plugins/modules/azure_rm_aduser_info.py @@ -143,6 +143,16 @@ type: str returned: always sample: "Test Company" +on_premises_extension_attributes: + description: + - Contains extensionAttributes1-15 for the user. + - These extension attributes are also known as Exchange custom attributes 1-15. + - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. + - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + type: dict + returned: always + sample: {} ''' from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBase @@ -224,7 +234,6 @@ def exec_module(self, **kwargs): elif self.all: # this returns as a list, since we parse multiple pages ad_users = asyncio.get_event_loop().run_until_complete(self.get_users()) - self.results['ad_users'] = [self.to_dict(user) for user in ad_users] except Exception as e: @@ -232,6 +241,16 @@ def exec_module(self, **kwargs): return self.results + def on_premises_extension_attributes_to_dict(self, on_premises_extension_attributes): + extension_attributes = {} + for index in range(1, 16 + 1): + attribute_name = f'extension_attribute{index}' + if hasattr(on_premises_extension_attributes, attribute_name): + attr_value = getattr(on_premises_extension_attributes, attribute_name) + if attr_value is not None: + extension_attributes[attribute_name] = attr_value + return extension_attributes + def to_dict(self, object): return dict( object_id=object.id, @@ -241,13 +260,14 @@ def to_dict(self, object): mail=object.mail, account_enabled=object.account_enabled, user_type=object.user_type, - company_name=object.company_name + company_name=object.company_name, + on_premises_extension_attributes=self.on_premises_extension_attributes_to_dict(object.on_premises_extension_attributes) ) async def get_user(self, object): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( - select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName"] + select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName", "onPremisesExtensionAttributes"] ), ) return await self._client.users.by_user_id(object).get(request_configuration=request_configuration) @@ -255,7 +275,7 @@ async def get_user(self, object): async def get_users(self): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( - select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName"] + select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName", "onPremisesExtensionAttributes"] ), ) users = [] @@ -276,7 +296,7 @@ async def get_users_by_filter(self, filter): query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( filter=filter, select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", - "userType", "companyName"], + "userType", "companyName", "onPremisesExtensionAttributes"], count=True ), )) diff --git a/tests/integration/targets/azure_rm_aduser/tasks/main.yml b/tests/integration/targets/azure_rm_aduser/tasks/main.yml index c02a263bb..24e686e52 100644 --- a/tests/integration/targets/azure_rm_aduser/tasks/main.yml +++ b/tests/integration/targets/azure_rm_aduser/tasks/main.yml @@ -1,35 +1,50 @@ - name: Prepare facts ansible.builtin.set_fact: - user_id: "user{{ 999999999999999999994 | random | to_uuid }}@contoso.com" - object_id: "{{ 999999999999999999994 | random | to_uuid }}" - user_principal_name: "{{ 999999999999999999994 | random | to_uuid }}" + user_name: "test_user_{{ 999999999999999999994 | random | to_uuid }}" + on_premises_immutable_id: "{{ 999999999999999999994 | random | to_uuid }}" + password_profile: "{{ lookup('community.general.random_string', length=12, min_lower=1, min_upper=1, min_special=1, min_numeric=1) }}" + domain: change_me.com run_once: true - name: Create test user azure_rm_aduser: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" state: "present" account_enabled: true - display_name: "Test_{{ user_principal_name }}_Display_Name" - password_profile: "password" - mail_nickname: "Test_{{ user_principal_name }}_mail_nickname" - immutable_id: "{{ object_id }}" + display_name: "{{ user_name }}_display_name" + password_profile: "{{ password_profile }}" + mail_nickname: "{{ user_name }}_mail_nickname" + on_premises_immutable_id: "{{ on_premises_immutable_id }}" given_name: "First" surname: "Last" user_type: "Member" usage_location: "US" - mail: "{{ user_principal_name }}@contoso.com" + mail: "{{ user_name }}@{{ domain }}" + company_name: "Test Company" + on_premises_extension_attributes: + extension_attribute1: "test_extension_attribute1" + extension_attribute2: "test_extension_attribute2" + extension_attribute11: "test_extension_attribute11" register: create_user_should_pass - name: Try to update existing user - idempotent check azure_rm_aduser: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" state: "present" - display_name: "Test_{{ user_principal_name }}_Display_Name" - mail_nickname: "Test_{{ user_principal_name }}_mail_nickname" + account_enabled: true + display_name: "{{ user_name }}_display_name" + mail_nickname: "{{ user_name }}_mail_nickname" + on_premises_immutable_id: "{{ on_premises_immutable_id }}" given_name: "First" surname: "Last" - mail: "{{ user_principal_name }}@contoso.com" + user_type: "Member" + usage_location: "US" + mail: "{{ user_name }}@{{ domain }}" + company_name: "Test Company" + on_premises_extension_attributes: + extension_attribute1: "test_extension_attribute1" + extension_attribute2: "test_extension_attribute2" + extension_attribute11: "test_extension_attribute11" register: attempted_update_with_no_changes_should_pass - name: Assert Nothing Changed @@ -39,42 +54,49 @@ - name: User_principal_name Should Pass azure_rm_aduser_info: - user_principal_name: "{{ user_id }}" - register: get_user_should_pass + user_principal_name: "{{ user_name }}@{{ domain }}" + register: get_user_by_upn_should_pass + +- name: Attribute_name mail Should Pass + azure_rm_aduser_info: + attribute_name: "mail" + attribute_value: "{{ user_name }}@{{ domain }}" + register: get_user_by_mail_should_pass - name: Assert user was created and account is enabled ansible.builtin.assert: that: - - "create_user_should_pass['ad_users'][0]['account_enabled'] == True" - - "get_user_should_pass['ad_users'][0]['account_enabled'] == True" + - "create_user_should_pass['ad_user']['account_enabled'] == True" + - "get_user_by_upn_should_pass['ad_users'][0]['account_enabled'] == True" + - "get_user_by_mail_should_pass['ad_users'][0]['account_enabled'] == True" - name: Update test user azure_rm_aduser: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" state: "present" account_enabled: false register: update_user_should_pass - name: User_principal_name on updated user Should Pass azure_rm_aduser_info: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" register: get_updated_user_should_pass - name: Assert user was updated and account is disabled ansible.builtin.assert: that: - - "update_user_should_pass['ad_users'][0]['account_enabled'] == False" + - "update_user_should_pass['ad_user']['account_enabled'] == False" - "get_updated_user_should_pass['ad_users'][0]['account_enabled'] == False" - name: Delete test user azure_rm_aduser: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" state: "absent" register: delete_user_should_pass - name: User_principal_name Should Fail azure_rm_aduser_info: - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" register: get_user_should_fail ignore_errors: true @@ -91,19 +113,19 @@ - name: Assert task failed ansible.builtin.assert: that: - - "missing_any_identifiers is undefined" + - "missing_any_identifiers is defined" - name: Too many identifiers Should Fail azure_rm_aduser_info: - user_principal_name: "{{ user_id }}" - object_id: "{{ object_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" + object_id: "{{ on_premises_immutable_id }}" register: too_many_identifiers ignore_errors: true - name: Assert task failed ansible.builtin.assert: that: - - "too_many_identifiers is undefined" + - "too_many_identifiers is defined" - name: Missing attribute_value Should Fail azure_rm_aduser_info: @@ -114,27 +136,27 @@ - name: Assert task failed ansible.builtin.assert: that: - - "missing_attribute_value is undefined" + - "missing_attribute_value is defined" - name: Missing attribute_name Should Fail azure_rm_aduser_info: - attribute_value: SMTP:user@contoso.com + attribute_value: SMTP:user@stadtluzern.ch register: missing_attribute_name ignore_errors: true - name: Assert task failed ansible.builtin.assert: that: - - "missing_attribute_name is undefined" + - "missing_attribute_name is defined" - name: Using all with principal name should fail azure_rm_aduser_info: all: true - user_principal_name: "{{ user_id }}" + user_principal_name: "{{ user_name }}@{{ domain }}" register: using_all_with_principal_name ignore_errors: true - name: Assert task failed ansible.builtin.assert: that: - - "using_all_with_principal_name is undefined" + - "using_all_with_principal_name is defined" From 034abe8bab688d4400d0cb2d07deb18ff032ec78 Mon Sep 17 00:00:00 2001 From: TheRapac <55585899+therapac@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:58:03 +0200 Subject: [PATCH 2/7] Update plugins/modules/azure_rm_aduser.py Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_aduser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/azure_rm_aduser.py b/plugins/modules/azure_rm_aduser.py index 1704d2dad..ad2a23c58 100644 --- a/plugins/modules/azure_rm_aduser.py +++ b/plugins/modules/azure_rm_aduser.py @@ -125,7 +125,8 @@ - These extension attributes are also known as Exchange custom attributes 1-15. - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. - - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph\ + but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. type: dict aliases: - extension_attributes From 6b0336a02cddadd0aca1f3c1e20f5290e772c7f1 Mon Sep 17 00:00:00 2001 From: TheRapac <55585899+therapac@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:59:23 +0200 Subject: [PATCH 3/7] Update plugins/modules/azure_rm_aduser.py Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_aduser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/azure_rm_aduser.py b/plugins/modules/azure_rm_aduser.py index ad2a23c58..fcd4a765b 100644 --- a/plugins/modules/azure_rm_aduser.py +++ b/plugins/modules/azure_rm_aduser.py @@ -331,7 +331,7 @@ def exec_module(self, **kwargs): if self.password_profile: password = PasswordProfile( password=self.password_profile, - ) + ) should_update = False if self.on_premises_immutable_id and ad_user.on_premises_immutable_id != self.on_premises_immutable_id: From 5c18ae446f88172ceb9014af3b7f37499b591795 Mon Sep 17 00:00:00 2001 From: TheRapac <55585899+therapac@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:59:34 +0200 Subject: [PATCH 4/7] Update plugins/modules/azure_rm_aduser.py Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_aduser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/azure_rm_aduser.py b/plugins/modules/azure_rm_aduser.py index fcd4a765b..ca5330cfc 100644 --- a/plugins/modules/azure_rm_aduser.py +++ b/plugins/modules/azure_rm_aduser.py @@ -437,7 +437,6 @@ def on_premises_extension_attributes_to_dict(self, on_premises_extension_attribu if attr_value is not None: extension_attributes[attribute_name] = attr_value return extension_attributes - def to_dict(self, object): return dict( object_id=object.id, From 7918916cac489ae7e262e80b7f84f0ee22021083 Mon Sep 17 00:00:00 2001 From: TheRapac <55585899+therapac@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:59:42 +0200 Subject: [PATCH 5/7] Update plugins/modules/azure_rm_aduser_info.py Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_aduser_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/azure_rm_aduser_info.py b/plugins/modules/azure_rm_aduser_info.py index 8ca4fde0d..e54a612d3 100644 --- a/plugins/modules/azure_rm_aduser_info.py +++ b/plugins/modules/azure_rm_aduser_info.py @@ -250,7 +250,6 @@ def on_premises_extension_attributes_to_dict(self, on_premises_extension_attribu if attr_value is not None: extension_attributes[attribute_name] = attr_value return extension_attributes - def to_dict(self, object): return dict( object_id=object.id, From 43b9132c2705c31be24af34f509e854c0bbe0620 Mon Sep 17 00:00:00 2001 From: baechir Date: Thu, 4 Apr 2024 16:02:29 +0200 Subject: [PATCH 6/7] modified: plugins/modules/azure_rm_aduser.py modified: plugins/modules/azure_rm_aduser_info.py change formatting errors --- plugins/modules/azure_rm_aduser.py | 19 ++++++++++++------- plugins/modules/azure_rm_aduser_info.py | 9 ++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/plugins/modules/azure_rm_aduser.py b/plugins/modules/azure_rm_aduser.py index ca5330cfc..e1c792649 100644 --- a/plugins/modules/azure_rm_aduser.py +++ b/plugins/modules/azure_rm_aduser.py @@ -227,7 +227,8 @@ - These extension attributes are also known as Exchange custom attributes 1-15. - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. - - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph\ + but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. type: dict returned: always sample: {} @@ -320,9 +321,9 @@ def exec_module(self, **kwargs): extension_attributes = None if self.on_premises_extension_attributes: - extension_attributes=OnPremisesExtensionAttributes( + extension_attributes = OnPremisesExtensionAttributes( **self.on_premises_extension_attributes - ) + ) if ad_user: # Update, changed @@ -356,9 +357,10 @@ def exec_module(self, **kwargs): should_update = True if should_update or self.company_name and ad_user.company_name != self.company_name: should_update = True - if should_update or self.on_premises_extension_attributes and self.on_premises_extension_attributes_to_dict(ad_user.on_premises_extension_attributes) != self.on_premises_extension_attributes: + if should_update or ( + self.on_premises_extension_attributes and + self.on_premises_extension_attributes_to_dict(ad_user.on_premises_extension_attributes) != self.on_premises_extension_attributes): should_update = True - if should_update: asyncio.get_event_loop().run_until_complete(self.update_user(ad_user, password, extension_attributes)) @@ -437,6 +439,7 @@ def on_premises_extension_attributes_to_dict(self, on_premises_extension_attribu if attr_value is not None: extension_attributes[attribute_name] = attr_value return extension_attributes + def to_dict(self, object): return dict( object_id=object.id, @@ -495,7 +498,8 @@ async def get_user(self, object): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", - "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", "OnPremisesExtensionAttributes"] + "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", + "OnPremisesExtensionAttributes"] ), ) return await self._client.users.by_user_id(object).get(request_configuration=request_configuration) @@ -506,7 +510,8 @@ async def get_users_by_filter(self, filter): query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( filter=filter, select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", - "userType", "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", "OnPremisesExtensionAttributes"], + "userType", "onPremisesImmutableId", "usageLocation", "givenName", "surname", "companyName", + "OnPremisesExtensionAttributes"], count=True ), headers={'ConsistencyLevel': "eventual", } diff --git a/plugins/modules/azure_rm_aduser_info.py b/plugins/modules/azure_rm_aduser_info.py index e54a612d3..19582b2e1 100644 --- a/plugins/modules/azure_rm_aduser_info.py +++ b/plugins/modules/azure_rm_aduser_info.py @@ -149,7 +149,8 @@ - These extension attributes are also known as Exchange custom attributes 1-15. - For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises and is read-only. - For a cloud-only user (where onPremisesSyncEnabled is false), these properties can be set during the creation or update of a user object. - - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. + - For a cloud-only user previously synced from on-premises Active Directory, these properties are read-only in Microsoft Graph/ + but can be fully managed through the Exchange Admin Center or the Exchange Online V2 module in PowerShell. type: dict returned: always sample: {} @@ -266,7 +267,8 @@ def to_dict(self, object): async def get_user(self, object): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( - select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName", "onPremisesExtensionAttributes"] + select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", + "userType", "companyName", "onPremisesExtensionAttributes"] ), ) return await self._client.users.by_user_id(object).get(request_configuration=request_configuration) @@ -274,7 +276,8 @@ async def get_user(self, object): async def get_users(self): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( - select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", "userType", "companyName", "onPremisesExtensionAttributes"] + select=["accountEnabled", "displayName", "mail", "mailNickname", "id", "userPrincipalName", + "userType", "companyName", "onPremisesExtensionAttributes"] ), ) users = [] From 9d4dc032a6a598ff41274c7ed24e210c34311535 Mon Sep 17 00:00:00 2001 From: TheRapac <55585899+therapac@users.noreply.github.com> Date: Mon, 8 Apr 2024 07:56:48 +0200 Subject: [PATCH 7/7] Update plugins/modules/azure_rm_aduser_info.py Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- plugins/modules/azure_rm_aduser_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/azure_rm_aduser_info.py b/plugins/modules/azure_rm_aduser_info.py index 19582b2e1..e71066a89 100644 --- a/plugins/modules/azure_rm_aduser_info.py +++ b/plugins/modules/azure_rm_aduser_info.py @@ -251,6 +251,7 @@ def on_premises_extension_attributes_to_dict(self, on_premises_extension_attribu if attr_value is not None: extension_attributes[attribute_name] = attr_value return extension_attributes + def to_dict(self, object): return dict( object_id=object.id,