From 086d87177f5452ffba81e4c629cb548280ad7a3e Mon Sep 17 00:00:00 2001 From: TheNotoriousRMM <164023381+TheNotoriousRMM@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:00:34 +0200 Subject: [PATCH] Feature azure rm adapplication info diff (#1560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modified: plugins/modules/azure_rm_adapplication_info.py * modified: plugins/modules/azure_rm_adapplication_info.py app_diff hinzugefĆ¼gt * modified: plugins/modules/azure_rm_adapplication_info.py Anpassungen * Anpassungen * Anpassungen * modified: plugins/modules/azure_rm_adapplication_info.py Finalizing modified: tests/integration/targets/azure_rm_adapplication/tasks/main.yml Add tests for app_diff * modified: plugins/modules/azure_rm_adapplication_info.py modify doc for sentry test * modified: plugins/modules/azure_rm_adapplication_info.py documentation modify examples * modified: tests/integration/targets/azure_rm_adapplication/tasks/main.yml Fix: No new line character at the end of file * modified: plugins/modules/azure_rm_adapplication_info.py Add new documentation for the option app_diff * modified: plugins/modules/azure_rm_adapplication_info.py Fix wrong indentation * Update tests/integration/targets/azure_rm_adapplication/tasks/main.yml I hope this will resolve the issue. Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> * modified: tests/integration/targets/azure_rm_adapplication/tasks/main.yml Change No new line character at the end of file * modified: tests/integration/targets/azure_rm_adapplication/tasks/main.yml Undo changes at the last line. Sanity test was OK. * modified: tests/integration/targets/azure_rm_adapplication/tasks/main.yml Add new line at the end of the file. Opened in vim and save, the new line automaticly added. --------- Co-authored-by: MehrR Co-authored-by: baechir Co-authored-by: TheRapac <55585899+therapac@users.noreply.github.com> Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- .../modules/azure_rm_adapplication_info.py | 138 +++++++++++++++++- .../azure_rm_adapplication/tasks/main.yml | 63 +++++--- 2 files changed, 172 insertions(+), 29 deletions(-) diff --git a/plugins/modules/azure_rm_adapplication_info.py b/plugins/modules/azure_rm_adapplication_info.py index e3eb53aaca..d1d2d17888 100644 --- a/plugins/modules/azure_rm_adapplication_info.py +++ b/plugins/modules/azure_rm_adapplication_info.py @@ -34,8 +34,20 @@ type: str app_display_name: description: - - The applications' Name. + - The application Name. type: str + app_diff: + description: + - A list of applications + - The application name or application ID is mandatory + - All fields of the applications can also be provided (parsing from a JSON or YAML). + - With this option, you can compare your self-defined applications with the current state using an application list.\ + The applications that are present only in current state but not in your list will be returned as a list with all fields\ + and will receive an additional status of ABSENT.\ + This allows you to first add applications with azure_rm_adapplication and then use azure_rm_adapplication_info (option diff)\ + to identify the applications that should not be in the current state and subsequently delete them with azure_rm_adapplication + type: list + elements: dict extends_documentation_fragment: - azure.azcollection.azure @@ -63,6 +75,12 @@ - name: get ad app info ---- by display name azure_rm_adapplication_info: app_display_name: "{{ display_name }}" + +- name: get ad app diff ---- by display name + azure_rm_adapplication_info: + app_diff: + - app_display_name: "{{ display_name }}" + - app_id: "{{ app_id }}" ''' RETURN = ''' @@ -153,6 +171,99 @@ type: list returned: always sample: ['name': 'acct', 'source': null, 'essential': false, 'additional_properties': []] +app_diff: + description: + - The info of the ad application. + type: complex + returned: aways + contains: + app_display_name: + description: + - Object's display name or its prefix. + type: str + returned: always + sample: app + app_id: + description: + - The application ID. + returned: always + type: str + sample: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + identifier_uris: + description: + - The identifiers_uri list of app. + type: list + returned: always + sample: ["http://ansible-atodorov"] + object_id: + description: + - It's application's object ID. + returned: always + type: str + sample: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + sign_in_audience: + description: + - The application can be used from any Azure AD tenants + type: str + returned: always + sample: AzureADandPersonalMicrosoftAccount + available_to_other_tenants: + description: + - The application can be used from any Azure AD tenants + type: str + returned: always + sample: AzureADandPersonalMicrosoftAccount + public_client_reply_urls: + description: + - The public client redirect urls. + - Space-separated URIs to which Azure AD will redirect in response to an OAuth 2.0 request. + returned: always + type: list + sample: [] + web_reply_urls: + description: + - The web redirect urls. + - Space-separated URIs to which Azure AD will redirect in response to an OAuth 2.0 request. + returned: always + type: list + sample: [] + spa_reply_urls: + description: + - The spa redirect urls. + - Space-separated URIs to which Azure AD will redirect in response to an OAuth 2.0 request. + returned: always + type: list + sample: [] + state: + description: + - absent --> The app isn't in the app_diff. + returned: always + type: str + sample: absent + optional_claims: + description: + - Declare the optional claims for the application. + type: complex + returned: always + contains: + access_token_claims : + description: + - The optional claims returned in the JWT access token + type: list + returned: always + sample: ['name': 'aud', 'source': null, 'essential': false, 'additional_properties': []] + id_token_claims: + description: + - The optional claims returned in the JWT ID token + type: list + returned: always + sample: ['name': 'acct', 'source': null, 'essential': false, 'additional_properties': []] + saml2_token_claims: + description: + - The optional claims returned in the SAML token + type: list + returned: always + sample: ['name': 'acct', 'source': null, 'essential': false, 'additional_properties': []] ''' from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBase @@ -173,7 +284,8 @@ def __init__(self): app_id=dict(type='str'), object_id=dict(type='str'), identifier_uri=dict(type='str'), - app_display_name=dict(type='str') + app_display_name=dict(type='str'), + app_diff=dict(type='list', elements='dict') ) self.app_id = None self.app_display_name = None @@ -191,7 +303,6 @@ def exec_module(self, **kwargs): setattr(self, key, kwargs[key]) applications = [] - try: self._client = self.get_msgraph_client() if self.object_id: @@ -207,12 +318,27 @@ def exec_module(self, **kwargs): apps = asyncio.get_event_loop().run_until_complete(self.get_applications(sub_filters)) applications = list(apps) self.results['applications'] = [self.to_dict(app) for app in applications] + current_app = [self.to_dict(app) for app in applications] except APIError as e: if e.response_status_code != 404: self.fail("failed to get application info {0}".format(str(e))) - except Exception as ge: - self.fail("failed to get application info {0}".format(str(ge))) - + except Exception as e: + if "Name or service not known" in str(e): + self.fail("DNS resolution error occurred.") + else: + self.fail("An unexpected error occurred: {0}".format(str(e))) + if self.app_diff: + temp_app = [] + for app in current_app: + found = False + for diff in self.app_diff: + if app.get("app_id") == diff.get("app_id") or app.get("app_display_name") == diff.get("app_display_name"): + found = True + break + if not found: + app['state'] = 'absent' + temp_app.append(app) + self.results['app_diff'] = temp_app return self.results def serialize_claims(self, claims): diff --git a/tests/integration/targets/azure_rm_adapplication/tasks/main.yml b/tests/integration/targets/azure_rm_adapplication/tasks/main.yml index 86a06c6750..43a2d390c0 100644 --- a/tests/integration/targets/azure_rm_adapplication/tasks/main.yml +++ b/tests/integration/targets/azure_rm_adapplication/tasks/main.yml @@ -1,8 +1,12 @@ -- name: Set variables +- name: Set display name variable ansible.builtin.set_fact: - display_name: "app{{ resource_group | hash('sha1') | truncate(20, True, '') }}" + display_name: "test_app_{{ 999999999999999999994 | random | to_uuid }}" run_once: true +- name: Get full list from ad app info + azure_rm_adapplication_info: + register: app_info_output + - name: Create application azure_rm_adapplication: display_name: "{{ display_name }}" @@ -12,22 +16,22 @@ ansible.builtin.assert: that: create_output.changed -- name: Create application again idempotent test +- name: Create application by app_id again idempotent test azure_rm_adapplication: app_id: "{{ create_output.app_id }}" - register: output + display_name: "{{ display_name }}" + register: output_app_id -- name: Assert the idempotent success +- name: Assert application by app_id the idempotent success ansible.builtin.assert: - that: not output.changed + that: not output_app_id.changed - name: Create application with more parameters azure_rm_adapplication: - display_name: "{{ display_name }}-01" - sign_in_audience: AzureADandPersonalMicrosoftAccount - credential_description: "for test" - end_date: 2021-10-01 - start_date: 2021-05-18 + display_name: "{{ display_name }}" + app_id: "{{ create_output.app_id }}" + homepage: "https://{{ display_name }}.test.com" + sign_in_audience: AzureADMultipleOrgs identifier_uris: - "{{ display_name }}.com" app_roles: @@ -56,29 +60,41 @@ - name: Get ad app info by object id azure_rm_adapplication_info: object_id: "{{ create_output.object_id }}" - register: output + register: object_id_output - name: Get ad app info by app id azure_rm_adapplication_info: app_id: "{{ create_output.app_id }}" - register: output + register: app_id_output - name: Get ad app info by display name azure_rm_adapplication_info: - app_display_name: "{{ create_output.app_display_name }}" - register: display_name_test_output + app_display_name: "{{ display_name }}" + register: display_name_output - name: Assert the application facts ansible.builtin.assert: that: - - output.applications[0].app_display_name == "{{ display_name }}" - - output.applications | length == 1 - - display_name_test_output.applications[0].app_display_name == "{{ display_name }}" - - display_name_test_output.applications | length == 1 + - object_id_output.applications[0].app_display_name == "{{ display_name }}" + - object_id_output.applications | length == 1 + - app_id_output.applications[0].app_display_name == "{{ display_name }}" + - app_id_output.applications | length == 1 + - display_name_output.applications[0].app_display_name == "{{ display_name }}" + - display_name_output.applications | length == 1 + +- name: Get difference from app_info_output and current + azure_rm_adapplication_info: + app_diff: "{{ app_info_output.applications }}" + register: diff_output + +- name: Assert the difference + ansible.builtin.assert: + that: diff_output.app_diff[0].app_display_name == "{{ display_name }}" -- name: Delete ad app by app id +- name: Delete ad app by app_id azure_rm_adapplication: app_id: "{{ create_output.app_id }}" + display_name: "{{ display_name }}" state: absent register: output @@ -86,15 +102,16 @@ ansible.builtin.assert: that: output.changed -- name: Delete ad app by app id +- name: Delete ad app by app id again azure_rm_adapplication: app_id: "{{ second_output.app_id }}" + display_name: "{{ display_name }}" state: absent register: output -- name: Assert the secondary application delete success +- name: Assert the secondary application delete no changed ansible.builtin.assert: - that: output.changed + that: not output.changed - name: Get ad app info by app id azure_rm_adapplication_info: