diff --git a/playbooks/modules/info.yml b/playbooks/modules/info.yml index 7a7dbbdb..ac93a657 100644 --- a/playbooks/modules/info.yml +++ b/playbooks/modules/info.yml @@ -248,3 +248,12 @@ api_password: "{{ api_password }}" gather_subset: - support_assist_settings + + - name: Get auth roles from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - roles diff --git a/playbooks/modules/role.yml b/playbooks/modules/role.yml new file mode 100644 index 00000000..326f5c8f --- /dev/null +++ b/playbooks/modules/role.yml @@ -0,0 +1,95 @@ +--- +- name: Role Module Operations on PowerScale Storage + hosts: localhost + connection: local + vars: + onefs_host: "10.**.**.**" + port_no: "8080" + api_user: "******" + api_password: "p******" + verify_ssl: false + access_zone: "System" + + tasks: + - name: Create Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + description: "Description" + access_zone: "System" + privileges: + - name: "Audit" + permission: "w" + state: "present" + - name: "Backup" + permission: "r" + state: "present" + members: + - name: "Everyone" + type: "wellknown" + state: "present" + state: "present" + + - name: Copy Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + description: "Copy Role via Ansible" + copy_role: true + new_role_name: "" + access_zone: "{{ access_zone }}" + privileges: + - name: "Cluster" + permission: "r" + state: "present" + state: "present" + + - name: Get role details + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + access_zone: "{{ access_zone }}" + + - name: Modify Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + new_role_name: "Test_Copy_Modify" + description: "Test_Description_Modify12" + access_zone: "System" + privileges: + - name: "Audit" + permission: "w" + state: "present" + members: + - name: "esa" + provider_type: "local" + type: "user" + state: "absent" + state: "present" + + - name: Delete Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + state: "absent" + access_zone: "{{ access_zone }}" diff --git a/plugins/module_utils/storage/dell/shared_library/auth.py b/plugins/module_utils/storage/dell/shared_library/auth.py index b8b42e19..1e8f46df 100644 --- a/plugins/module_utils/storage/dell/shared_library/auth.py +++ b/plugins/module_utils/storage/dell/shared_library/auth.py @@ -109,3 +109,18 @@ def get_group_user_id(self, persona, persona_type, zone): zone=zone, provider=persona['provider_type'])['groups'][0]['gid'] return details + + def get_auth_roles(self, zone): + """ + Get details of the auth role + """ + LOG.info("Getting auth role details.") + try: + resp = self.auth_api.list_auth_roles(zone=zone).to_dict() + return resp + except Exception as e: + error_msg = utils.determine_error(error_obj=e) + error_message = f'Failed to get the auth role list ' \ + f'due to error {error_msg}.' + LOG.error(error_message) + self.module.fail_json(msg=error_message) diff --git a/plugins/module_utils/storage/dell/utils.py b/plugins/module_utils/storage/dell/utils.py index ac904ecc..93329f2c 100644 --- a/plugins/module_utils/storage/dell/utils.py +++ b/plugins/module_utils/storage/dell/utils.py @@ -549,3 +549,7 @@ def get_nfs_map_object(): def is_email_address_valid(address): if address is not None and re.search(r'^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$', address) is None: return True + + +def is_param_length_valid(item): + return len(item) <= 225 diff --git a/plugins/modules/info.py b/plugins/modules/info.py index c08d8289..af7642bc 100644 --- a/plugins/modules/info.py +++ b/plugins/modules/info.py @@ -131,6 +131,7 @@ - Cluster owner C(cluster_owner) - SNMP settings - C(snmp_settings). - Server certificate - C(server_certificate). + - roles - C(roles). - Support assist settings- C(support_assist_settings). required: true choices: [attributes, access_zones, nodes, providers, users, groups, @@ -140,7 +141,7 @@ node_pools, storagepool_tiers, smb_files, user_mapping_rules, ldap, nfs_zone_settings, nfs_default_settings, nfs_global_settings, synciq_global_settings, s3_buckets, smb_global_settings, ntp_servers, email_settings, cluster_identity, cluster_owner, snmp_settings, - server_certificate, support_assist_settings] + server_certificate, roles, support_assist_settings] type: list elements: str notes: @@ -2164,6 +2165,65 @@ "status": "valid", "subject": "C=IN, ST=Karnataka, L=Bangalore, O=Dell, OU=ISG, CN=powerscale, emailAddress=contact@dell.com" }] +roles: + description: List of auth roles. + type: dict + returned: Always + contains: + description: + description: Description of the auth role. + type: str + id: + description: id of the auth role. + type: str + name: + description: Name of the auth role. + type: str + members: + description: Specifies the members of auth role. + type: list + contains: + id: + description: ID of the member. + type: str + name: + description: Name of the member. + type: str + type: + description: Specifies the type of the member. + type: str + privileges: + description: Specifies the privileges of auth role. + type: list + contains: + id: + description: ID of the privilege. + type: str + name: + description: Name of the privilege. + type: str + permission: + description: Specifies the permission of the privilege. + type: str + sample: + { + "roles": + [{ + "description" : "Test_Description", + "id" : "Test_Role", + "members" : [{ + "id" : "UID:2008", + "name" : "esa", + "type" : "user" + }], + "name" : "Test_Role", + "privileges" : [{ + "id" : "ISI_PRIV_LOGIN_PAPI", + "name" : "Platform API", + "permission" : "r" + }] + }] + } support_assist_settings: description: The support assist settings details. type: dict @@ -2350,6 +2410,8 @@ import Cluster from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.certificate \ import Certificate +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.auth \ + import Auth from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.support_assist \ import SupportAssist from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ @@ -2964,6 +3026,7 @@ def perform_module_operation(self): cluster_owner = {} snmp_settings = {} server_certificate = [] + roles = {} support_assist_settings = {} if 'attributes' in str(subset): @@ -3042,6 +3105,8 @@ def perform_module_operation(self): self.protocol_api, self.module).get_snmp_settings() if 'server_certificate' in str(subset): server_certificate = Certificate(self.certificate_api, self.module).get_server_certificate_with_default() + if 'roles' in str(subset): + roles = Auth(self.auth_api, self.module).get_auth_roles(access_zone) if 'support_assist_settings' in str(subset): support_assist_settings = SupportAssist( self.support_assist_api, self.module).get_support_assist_settings() @@ -3083,6 +3148,7 @@ def perform_module_operation(self): ClusterOwner=cluster_owner, SnmpSettings=snmp_settings, ServerCertificate=server_certificate, + roles=roles, support_assist_settings=support_assist_settings ) @@ -3153,6 +3219,7 @@ def get_info_parameters(): 'cluster_owner', 'snmp_settings', 'server_certificate', + 'roles', 'support_assist_settings' ]), ) diff --git a/plugins/modules/role.py b/plugins/modules/role.py new file mode 100644 index 00000000..41c24362 --- /dev/null +++ b/plugins/modules/role.py @@ -0,0 +1,751 @@ +#!/usr/bin/python +# Copyright: (c) 2024, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Ansible module for managing auth roles on PowerScale""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: role +version_added: '3.1.0' +short_description: Manage Auth Roles on a PowerScale Storage System +description: +- Managing Auth Roles on an PowerScale system includes retrieving details of + auth role, creating auth role, modifying and deleting auth role. + +extends_documentation_fragment: + - dellemc.powerscale.powerscale + +author: +- Meenakshi Dembi (@dembim) + +options: + access_zone: + description: + - Specifies the access zone in which the auth role exists. + - Access zone once set cannot be changed. + type: str + default: System + role_name: + description: + - Name of the Auth Role. + type: str + required: true + new_role_name: + description: + - Name of the Auth Role to be used for modify or copy the role. + type: str + description: + description: + - Specifies the description of the auth role. + - Pass empty string to remove the I(description). + type: str + copy_role: + description: + - Copy the role + - C(true) will copy the role from the I(role_name). + type: bool + privileges: + description: + - Specifies the privileges granted for this role. + type: list + elements: dict + suboptions: + permission: + description: + - Specifies the permission being allowed for auth role. + - C(r) indicates read permission. + - C(w) indicates writepermission. + - C(x) indicates execute permission. + - C(-) indicates none permission. + type: str + choices: ['r', 'x', 'w', '-'] + name: + description: + - Specifies the name of the permission. + type: str + state: + description: + - Specifies if the permission is to be added or removed. + type: str + choices: ['absent', 'present'] + default: present + members: + description: + - Specifies the members of the auth role. + type: list + elements: dict + suboptions: + name: + description: + - Specifies the name of the member. + type: str + state: + description: + - Specifies if the member is to be added or removed. + type: str + choices: ['absent', 'present'] + default: present + provider_type: + description: + - Specifies the provider type of the member. + type: str + choices: ['local', 'file', 'ldap', 'ads','nis'] + default: local + type: + description: + - Specifies the type of the member. + type: str + choices: ['user', 'group', 'wellknown'] + state: + description: + - Defines whether the auth role should exist or not. + - Value C(present) indicates that the auth role should exist in system. + - Value C(absent) indicates that the auth role should not exist in system. + type: str + choices: ['absent', 'present'] + default: present +notes: +- The I(check_mode) is supported. +''' + +EXAMPLES = r''' +- name: Create Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role123sdfsdfsdf" + description: "Test_Description" + access_zone: "System" + privileges: + - name: "Antivirus" + permission: "w" + state: "present" + members: + - name: "esa" + provider_type: "local" + type: "user" + state: "present" + - name: "admin" + provider_type: "local" + type: "user" + state: "present" + state: "present" + +- name: Get Role + dellemc.powerscale.role: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + role_name: "Test_Role" + access_zone: "{{access_zone}}" + +- name: Modify Role + dellemc.powerscale.role: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + role_name: "Test_Role" + new_role_name: "Test_Role2" + description: "Test_Description_Modify" + access_zone: "System" + privileges: + - name: "Antivirus" + permission: "w" + state: "absent" + members: + - name: "User11_Ansible_Test_SMB" + type: "user" + state: "absent" + state: "present" + +- name: Delete Role + dellemc.powerscale.role: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + role_name: "Test_Role" + access_zone: "{{access_zone}}" + state: "absent" +''' + +RETURN = r''' +changed: + description: A boolean indicating if the task had to make changes. + returned: always + type: bool + sample: "false" +role_details: + description: The updated auth role details. + type: complex + returned: always + contains: + description: + description: Specifies the description of the auth role. + type: str + id: + description: Auth Role ID. + type: str + name: + description: Auth Role name. + type: str + members: + description: Specifies the members of auth role. + type: list + contains: + id: + description: ID of the member. + type: str + name: + description: Name of the member. + type: str + type: + description: Specifies the type of the member. + type: str + privileges: + description: Specifies the privileges of auth role. + type: list + contains: + id: + description: ID of the privilege. + type: str + name: + description: Name of the privilege. + type: str + permission: + description: Specifies the permission of the privilege. + type: str + sample: { + "description": "Test_Description", + "id": "Test_Role2", + "members": [ + { + "id": "UID:1XXX", + "name": "admin", + "type": "user" + }, + { + "id": "UID:2XXX", + "name": "esa", + "type": "user" + } + ], + "name": "Test_Role2", + "privileges": [ + { + "id": "ISI_PRIV_ANTIVIRUS", + "name": "Antivirus", + "permission": "w" + } + ] + } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.powerscale_base \ + import PowerScaleBase +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ + import utils +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.auth \ + import Auth +import copy + +LOG = utils.get_logger('role') + + +class Role(PowerScaleBase): + """Class with auth role operations""" + + def __init__(self): + """ Define all parameters required by this module""" + ansible_module_params = { + 'argument_spec': self.get_role_parameters(), + 'supports_check_mode': True + } + + super().__init__(AnsibleModule, ansible_module_params) + + self.result = { + "changed": False, + "role_details": {} + } + + def get_role_details(self, role_name, access_zone): + """ + Get details of a Role + """ + msg = f"Getting Role details {role_name}" + LOG.info(msg) + try: + role_obj = self.auth_api.get_auth_role( + auth_role_id=role_name, zone=access_zone) + if role_obj: + role_details = role_obj.roles[0] + role_details = role_details.to_dict() + msg = f"Role details are: {role_details}" + LOG.info(msg) + return role_details + + except utils.ApiException as e: + if str(e.status) == "404": + log_msg = f"Role {role_name} status is {e.status}" + LOG.info(log_msg) + return None + else: + error_msg = utils.determine_error(error_obj=e) + error_message = f"Failed to get details of Role " \ + f"{role_name} with error {str(error_msg)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + except Exception as e: + error_msg = f"Got error {utils.determine_error(e)} while getting" \ + f" Role details: {role_name}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def remove_duplicate_entries(self, entry): + """Remove duplicate members""" + new_entry = [] + for item in entry: + if item not in new_entry: + new_entry.append(item) + return new_entry + + def update_member(self, member_dict): + """ + :param grantee_dict: dict contains grantee + """ + new_member_dict = {} + user_details = {} + if 'provider_type' in member_dict.keys(): + if member_dict['type'] == "user": + user_details = Auth.get_user_details( + self, name=member_dict['name'], + zone=self.module.params["access_zone"], + provider=member_dict['provider_type'])['users'][0]['uid'] + elif member_dict['type'] == "group": + user_details = Auth.get_group_details( + self, name=member_dict['name'], + zone=self.module.params["access_zone"], + provider=member_dict['provider_type'])['groups'][0]['gid'] + else: + user_details = Auth.get_wellknown_details( + self, name=member_dict['name']) + else: + user_details['id'] = member_dict['id'] + user_details['name'] = member_dict['name'] + user_details['type'] = member_dict['type'] + keys = ['id', 'name', 'type'] + for key in keys: + new_member_dict[key] = user_details[key] + return new_member_dict + + def update_privileges(self, privilege_dict): + """ + :param grantee_dict: dict contains grantee + """ + new_privilege_dict = {} + new_privilege_dict_new = {} + new_privilege_dict = self.auth_api.get_auth_privileges(zone=self.module.params["access_zone"],).to_dict() + is_name_found = False + for item in new_privilege_dict['privileges']: + if item['name'] == privilege_dict['name']: + is_name_found = True + id = item['id'] + + if is_name_found: + new_privilege_dict_new['id'] = id + new_privilege_dict_new['name'] = privilege_dict['name'] + new_privilege_dict_new['permission'] = privilege_dict['permission'] + else: + err_msg = "Privilege " + privilege_dict['name'] + " is either invalid or cannot be added to role in non-System access zone." + LOG.error(err_msg) + self.module.fail_json(msg=err_msg) + + return new_privilege_dict_new + + def _create_role_params_object(self, role_params_1): + """Create params for role""" + role_params = copy.deepcopy(role_params_1) + if role_params['description'] is None: + role_params['description'] = '' + role_input_params = { + 'name': role_params['role_name'], + 'description': role_params['description'], + 'members': [], + 'privileges': [] + } + + if 'members' in role_params and role_params['members'] is not None: + new_members = self.remove_duplicate_entries(role_params['members']) + role_input_params['members'] = [ + self.update_member(member) + for member in new_members if member['state'] == 'present' + ] + if 'privileges' in role_params and role_params['privileges'] is not None: + new_privileges = self.remove_duplicate_entries(role_params['privileges']) + role_input_params['privileges'] = [ + self.update_privileges(privilege) + for privilege in new_privileges if privilege['state'] == 'present' + ] + + return role_input_params + + def create_role(self, role_params): + """Create Role""" + role = self._create_role_params_object(role_params) + try: + msg = f'Creating Role with parameters: {role})' + LOG.info(msg) + role_details = {} + if not self.module.check_mode: + response = self.auth_api.create_auth_role(role, zone=role_params['access_zone']) + msg = f'reponse from array: {response})' + LOG.info(msg) + if response: + role_details = self.get_role_details(role_params['role_name'], role_params['access_zone']) + msg = f"Successfully created auth role with " \ + f"details {role_details}." + LOG.info(msg) + return role_details + + except Exception as e: + error_msg = f"Create role with failed with error" \ + f": {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def delete_role(self, role_name, access_zone): + """ + Delete the role + :param role_name: Name of the role + :param zone: Access zone name + """ + try: + msg = f"Deleting Role with identifier {role_name}." + LOG.info(msg) + if not self.module.check_mode: + role_obj = self.auth_api.delete_auth_role( + auth_role_id=role_name, zone=access_zone) + if role_obj: + role_details = role_obj.to_dict() + msg = f"Role details are: {role_details}" + LOG.info(msg) + return role_details + + LOG.info("Successfully Deleted the role.") + return self.get_role_details(role_name, access_zone) + + except Exception as e: + error_msg = f"Delete role {role_name}failed with " \ + f"error: {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def modify_role(self, modify_params, name_existing, role_params): + """ + Modify the role based on modify dict + :param modify_params: contains parameter which needs to be modified + :param role_params: contains params passed through playbook + """ + try: + msg = f'Modify role with parameters: {modify_params})' + LOG.info(msg) + if role_params['copy_role']: + name = name_existing + else: + name = role_params['role_name'] + + if not self.module.check_mode: + self.auth_api.update_auth_role(modify_params, name, zone=role_params['access_zone']) + LOG.info("Successfully modified role.") + return True + + except Exception as e: + error_msg = f"Modify role with {modify_params} " \ + f"failed with " \ + f"error: {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def modify_member_list(self, role_params, role_details, role_details_draft, modify_role_dict): + existing_member_names = [member['name'] for member in role_details['members']] + new_members_to_add = [m for m in role_params['members'] if (m['state'] == 'present' and m['name'] not in existing_member_names)] + existing_members_to_remove = [m for m in role_params['members'] if m['state'] == 'absent' and m['name'] in existing_member_names] + existing_members_to_remove_names = [m['name'] for m in existing_members_to_remove] + members_to_remove = [m for m in role_details['members'] if m['name'] in existing_members_to_remove_names] + + for item in members_to_remove: + role_details_draft['members'].remove(item) + + for item in new_members_to_add: + role_details_draft['members'].append(item) + + if len(new_members_to_add) > 0 or len(existing_members_to_remove) > 0: + temp = self.remove_duplicate_entries(role_details_draft['members']) + target_member_list = [] + for item in temp: + temp1 = self.update_member(item) + target_member_list.append(temp1) + modify_role_dict['members'] = target_member_list + return modify_role_dict + + def get_new_privileges_to_add(self, role_params, existing_privileges_names): + return [p for p in role_params['privileges'] if (p['state'] == 'present' and p['name'] not in existing_privileges_names)] + + def get_existing_privileges_to_remove(self, role_params, existing_privileges_names): + return [p for p in role_params['privileges'] if p['state'] == 'absent' and p['name'] in existing_privileges_names] + + def get_new_privileges_to_remove(self, role_details, existing_privileges_to_remove_names): + return [p for p in role_details['privileges'] if p['name'] in existing_privileges_to_remove_names] + + def modify_privileges_list(self, role_params, role_details, role_details_draft, modify_role_dict): + existing_privileges_names = [privileges['name'] for privileges in role_details['privileges']] + new_privileges_to_add = self.get_new_privileges_to_add(role_params, existing_privileges_names) + existing_privileges_to_remove = self.get_existing_privileges_to_remove(role_params, existing_privileges_names) + existing_privileges_to_remove_names = [p['name'] for p in existing_privileges_to_remove] + privileges_to_remove = self.get_new_privileges_to_remove(role_details, existing_privileges_to_remove_names) + existing_privileges = role_details['privileges'] + + existing_privileges_to_update = [] + for item in role_params['privileges']: + privilege = [p for p in existing_privileges if p['name'] == item['name'] and p['permission'] != item['permission']] + if len(privilege) > 0: + item['id'] = privilege[0]['id'] + existing_privileges_to_update.append(item) + + for item in privileges_to_remove: + role_details_draft['privileges'].remove(item) + + for item in new_privileges_to_add: + item.pop('state') + role_details_draft['privileges'].append(item) + + for item in existing_privileges_to_update: + item.pop('state') + permission = [p for p in role_details_draft['privileges'] if p['name'] == item['name']] + if len(permission) > 0: + role_details_draft['privileges'].remove(permission[0]) + role_details_draft['privileges'].append(item) + + if len(new_privileges_to_add) > 0 or len(existing_privileges_to_remove) > 0 or len(existing_privileges_to_update) > 0: + temp = self.remove_duplicate_entries(role_details_draft['privileges']) + target_privileges_list = [] + for item in temp: + temp1 = self.update_privileges(item) + target_privileges_list.append(temp1) + modify_role_dict['privileges'] = target_privileges_list + return modify_role_dict + + def is_role_modify_required(self, role_params, role_details): + """ + Check whether modification is required in role + """ + modify_role_dict = self.check_modify_for_common_params(role_params, role_details) + role_details_draft = copy.deepcopy(role_details) + if role_params.get('members'): + if len(role_details['members']) != 0: + modify_role_dict = self.modify_member_list(role_params, role_details, role_details_draft, modify_role_dict) + else: + new_members = self.remove_duplicate_entries(role_params['members']) + modify_role_dict['members'] = [self.update_member(members) for members in new_members if members['state'] == 'present'] + + if role_params.get('privileges'): + if len(role_details['privileges']) != 0: + modify_role_dict = self.modify_privileges_list(role_params, role_details, role_details_draft, modify_role_dict) + else: + new_privileges = self.remove_duplicate_entries(role_params['privileges']) + modify_role_dict['privileges'] = [self.update_privileges(privilege) for privilege in new_privileges if privilege['state'] == 'present'] + return modify_role_dict + + def check_modify_for_common_params(self, role_params, role_details): + """ + Check whether modification is required in common parameters + """ + modify_role_dict = {} + + role_keys = { + 'new_role_name': (role_details['name'], 'name'), + 'description': (role_details.get('description'), 'description') + } + + for key, (role_details_value, role_details_key) in role_keys.items(): + if role_params.get(key) and role_params[key] != role_details_value: + modify_role_dict[role_details_key] = role_params[key] + + return modify_role_dict + + def do_update(self, source, target): + return source != target + + def validate_create_role_params(self, role_params): + """validate the input parameter for creating the Role""" + if utils.is_input_empty(role_params["role_name"]): + err_msg = "Role name cannot be empty" + LOG.error(err_msg) + self.module.fail_json(msg=err_msg) + + params = ['role_name', 'description', 'new_role_name'] + + for item in params: + if role_params[item]: + if not utils.is_param_length_valid(role_params.get(item)): + err_msg = item + " must be less than or equal to 255 characters." + LOG.error(err_msg) + self.module.fail_json(msg=err_msg) + + def create_copy_params(self, new_name, role_params, role_details): + """Create payload for copying the role""" + copy_role = {} + if role_details is not None: + copy_role = role_details + copy_role['name'] = new_name + + if role_params['description'] is not None and role_params['description'] != "": + copy_role['description'] = role_params['description'] + elif role_details['description'] is not None and role_details['description'] != "": + copy_role['description'] = role_details['description'] + else: + copy_role['description'] = '' + + if copy_role['id'] is not None: + copy_role.pop("id") + return copy_role + + def copy_role(self, new_name, role_params, role_details): + """Copy the role""" + copy_role = self.create_copy_params(new_name, role_params, role_details) + try: + msg = f'Creating Role with parameters in copy: {copy_role})' + LOG.info(msg) + role_details = {} + if not self.module.check_mode: + response = self.auth_api.create_auth_role(copy_role, zone=role_params['access_zone']) + msg = f'reponse from array: {response})' + LOG.info(msg) + if response: + role_details = self.get_role_details(new_name, role_params['access_zone']) + msg = f"Successfully created auth role with " \ + f"details {role_details}." + LOG.info(msg) + return role_details + except Exception as e: + error_msg = f"Create role with failed with error" \ + f": {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def get_role_parameters(self): + return dict( + role_name=dict(type='str', required=True), + new_role_name=dict(type='str'), + access_zone=dict(type='str', default='System'), + copy_role=dict(type='bool'), + description=dict(type='str'), + privileges=dict(type='list', elements='dict', options=dict( + name=dict(type='str'), + permission=dict(type='str', choices=['r', 'w', 'x', '-']), + state=dict(type='str', choices=['present', 'absent'], default='present'))), + members=dict(type='list', elements='dict', options=dict( + name=dict(type='str'), + type=dict(type='str', choices=['user', 'group', 'wellknown']), + provider_type=dict(type='str', choices=['local', 'file', 'ldap', 'ads', 'nis'], default='local'), + state=dict(type='str', choices=['present', 'absent'], default='present'))), + state=dict(type='str', choices=['present', 'absent'], default='present') + ) + + +class RoleExitHandler(): + def handle(self, role_obj, role_details): + role_obj.result["role_details"] = role_details + role_obj.module.exit_json(**role_obj.result) + + +class RoleDeleteHandler(): + def handle(self, role_obj, role_params, role_details): + if role_params["state"] == "absent" and role_details: + role_details = role_obj.delete_role( + role_params['role_name'], role_params['access_zone']) + role_obj.result["changed"] = True + RoleExitHandler().handle(role_obj, role_details) + + +class RoleModifyHandler(): + def handle(self, role_obj, name, role_params, role_details): + if role_params["state"] == "present" and role_details: + modify_params = role_obj.is_role_modify_required(role_params, + role_details) + if modify_params: + role_obj.modify_role(modify_params, name, role_params) + if role_params['copy_role']: + role_details = role_obj.get_role_details(name, role_params['access_zone']) + elif role_params['new_role_name']: + role_details = role_obj.get_role_details(role_params['new_role_name'], role_params['access_zone']) + else: + role_details = role_obj.get_role_details(role_params['role_name'], role_params['access_zone']) + role_obj.result["changed"] = True + role_obj.result["role_details"] = role_details + + RoleDeleteHandler().handle(role_obj=role_obj, role_params=role_params, role_details=role_details) + + +class RoleCopyHandler(): + def handle(self, role_obj, role_params, role_details): + details = role_details + new_name = role_params['role_name'] + if role_params["state"] == "present" and role_params['copy_role']: + if role_params['new_role_name'] is None or role_params['new_role_name'] == "": + new_name = "Copy of " + role_params['role_name'] + else: + new_name = role_params['new_role_name'] + copyied_role_details = role_obj.get_role_details(new_name, role_params['access_zone']) + if copyied_role_details is None: + role_details = role_obj.copy_role(new_name, role_params, role_details) + role_obj.result["changed"] = True + role_obj.result["role_details"] = role_details + details = role_details + else: + role_obj.result["role_details"] = copyied_role_details + details = copyied_role_details + + RoleModifyHandler().handle(role_obj=role_obj, name=new_name, role_params=role_params, role_details=details) + + +class RoleCreateHandler(): + def handle(self, role_obj, role_params, role_details): + if role_params["state"] == "present" and role_details is None: + role_details = role_obj.create_role(role_params) + role_obj.result["changed"] = True + + RoleCopyHandler().handle(role_obj=role_obj, role_params=role_params, role_details=role_details) + + +class RoleHandler(): + def handle(self, role_obj, role_params): + role_obj.validate_create_role_params(role_params) + role_details = role_obj.get_role_details(role_params['role_name'], role_params['access_zone']) + RoleCreateHandler().handle(role_obj=role_obj, role_params=role_params, role_details=role_details) + + +def main(): + """ Create PowerScale Role object and perform action on it + based on user input from playbook.""" + obj = Role() + RoleHandler().handle(obj, obj.module.params) + + +if __name__ == '__main__': + main() diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index cfc09c2a..e320a18f 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -137,11 +137,16 @@ plugins/modules/server_certificate.py import-2.7 plugins/modules/server_certificate.py import-3.5 plugins/modules/server_certificate.py compile-2.7 plugins/modules/server_certificate.py compile-3.5 +plugins/modules/server_certificate.py validate-modules:missing-gplv3-license plugins/module_utils/storage/dell/shared_library/certificate.py compile-2.7 plugins/module_utils/storage/dell/shared_library/certificate.py import-2.7 plugins/module_utils/storage/dell/shared_library/certificate.py compile-3.5 plugins/module_utils/storage/dell/shared_library/certificate.py import-3.5 -plugins/modules/server_certificate.py validate-modules:missing-gplv3-license +plugins/modules/role.py validate-modules:missing-gplv3-license +plugins/modules/role.py import-2.7 +plugins/modules/role.py import-3.5 +plugins/modules/role.py compile-2.7 +plugins/modules/role.py compile-3.5 plugins/modules/support_assist.py import-2.7 plugins/modules/support_assist.py import-3.5 plugins/modules/support_assist.py compile-2.7 diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 2595c0ae..7d975b7d 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -92,6 +92,9 @@ plugins/modules/server_certificate.py compile-2.7 plugins/module_utils/storage/dell/shared_library/certificate.py compile-2.7 plugins/module_utils/storage/dell/shared_library/certificate.py import-2.7 plugins/modules/server_certificate.py validate-modules:missing-gplv3-license +plugins/modules/role.py validate-modules:missing-gplv3-license +plugins/modules/role.py import-2.7 +plugins/modules/role.py compile-2.7 plugins/modules/support_assist.py import-2.7 plugins/modules/support_assist.py compile-2.7 plugins/module_utils/storage/dell/shared_library/support_assist.py compile-2.7 diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index 1939db6d..ee6f2942 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -37,4 +37,5 @@ plugins/modules/synciqcertificate.py validate-modules:missing-gplv3-license plugins/modules/smb_global_settings.py validate-modules:missing-gplv3-license plugins/modules/snmp_settings.py validate-modules:missing-gplv3-license plugins/modules/server_certificate.py validate-modules:missing-gplv3-license +plugins/modules/role.py validate-modules:missing-gplv3-license plugins/modules/support_assist.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index 1939db6d..d24bc2ff 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -38,3 +38,4 @@ plugins/modules/smb_global_settings.py validate-modules:missing-gplv3-license plugins/modules/snmp_settings.py validate-modules:missing-gplv3-license plugins/modules/server_certificate.py validate-modules:missing-gplv3-license plugins/modules/support_assist.py validate-modules:missing-gplv3-license +plugins/modules/role.py validate-modules:missing-gplv3-license diff --git a/tests/unit/plugins/module_utils/mock_info_api.py b/tests/unit/plugins/module_utils/mock_info_api.py index 9ffd0fd8..10f3b14f 100644 --- a/tests/unit/plugins/module_utils/mock_info_api.py +++ b/tests/unit/plugins/module_utils/mock_info_api.py @@ -82,6 +82,7 @@ class MockGatherfactsApi: 'ClusterIdentity': {}, 'ClusterOwner': {}, 'ServerCertificate': [], + 'roles': {}, 'support_assist_settings': {} } API = "api" @@ -1294,6 +1295,54 @@ def get_clients_response(response_type): } ] + @staticmethod + def get_auth_roles(response_type): + if response_type == "api": + return [{ + "description" : "Test_Description", + "id" : "Test_Role", + "members" : + [ + { + "id" : "UID:2008", + "name" : "esa", + "type" : "user" + } + ], + "name" : "Test_Role", + "privileges" : + [ + { + "id" : "ISI_PRIV_LOGIN_PAPI", + "name" : "Platform API", + "permission" : "r" + } + ] + }] + elif response_type == "module": + return [ + { + "description" : "Test_Description", + "id" : "Test_Role", + "members" : [ + { + "id" : "UID:2008", + "name" : "esa", + "type" : "user" + } + ], + "name" : "Test_Role", + "privileges" : [ + { + "id" : "ISI_PRIV_LOGIN_PAPI", + "name" : "Platform API", + "permission" : "r" + } + ] + }] + else: + return "Failed to get the auth role list due to error SDK Error message." + @staticmethod def get_support_assist_settings(response_type): if response_type == "api" or response_type == "module": @@ -1381,6 +1430,7 @@ def get_gather_facts_module_response(gather_subset): "access_zones": MockGatherfactsApi.get_access_zones_response(param), "clients": MockGatherfactsApi.get_clients_response(param), "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + "roles": MockGatherfactsApi.get_auth_roles(param), "support_assist_settings": MockGatherfactsApi.get_support_assist_settings(param) } return subset_error_dict.get(gather_subset) @@ -1419,6 +1469,7 @@ def get_gather_facts_api_response(gather_subset): "access_zones": MockGatherfactsApi.get_access_zones_response(param), "clients": MockGatherfactsApi.get_clients_response(param), "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + "roles": MockGatherfactsApi.get_auth_roles(param), "support_assist_settings": MockGatherfactsApi.get_support_assist_settings(param) } return subset_error_dict.get(gather_subset) @@ -1458,6 +1509,7 @@ def get_gather_facts_error_response(gather_subset): "access_zones": MockGatherfactsApi.get_access_zones_response(param), "clients": MockGatherfactsApi.get_clients_response(param), "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + "roles": MockGatherfactsApi.get_auth_roles(param), "support_assist_settings": MockGatherfactsApi.get_support_assist_settings(param) } return subset_error_dict.get(gather_subset) @@ -1502,6 +1554,7 @@ def get_gather_facts_error_method(gather_subset): "access_zones": "list_zones", "clients": "get_summary_client", + "roles": "list_auth_roles", "support_assist_settings": "get_supportassist_settings" } return subset_method_dict.get(gather_subset) diff --git a/tests/unit/plugins/module_utils/mock_role_api.py b/tests/unit/plugins/module_utils/mock_role_api.py new file mode 100644 index 00000000..544242c3 --- /dev/null +++ b/tests/unit/plugins/module_utils/mock_role_api.py @@ -0,0 +1,296 @@ +# Copyright: (c) 2024, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock API responses for PowerScale Role module""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockRoleApi: + + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.modules.role.utils' + + IP_ADDRESS = '1.1.1.1' + + ROLE_COMMON_ARGS = { + "onefs_host": IP_ADDRESS, + "role_name": "Test_Role", + "new_role_name": "", + "access_zone": "System", + "copy_role": False, + "description": "This is role description", + "privileges": None, + "members": None, + "state": "present"} + + PRIVILEGES = {"privileges": [{'id': 'ISI_PRIV_AUDIT', 'name': 'Audit', 'permission': 'w'}]} + MEMBERS = {'id': 'UID:2140', 'name': 'esa', 'type': 'user'} + MEMBERS_GROUP = {'id': 'UID:2146', 'name': 'Guest', 'type': 'group'} + MEMBERS_WELLKNOW = {"wellknowns": [{'id': 'SID:S-1-1-0', 'name': 'user', 'type': 'wellknown'}]} + + GET_ROLE = { + "role": + { + "description": + "Test_Description", + "id": "Test_Role", + "members": [ + { + "id": "UID:2140", + "name": "esa", + "type": "user" + } + ], + "name": "Test_Role", + "privileges": [ + { + "id": "ISI_PRIV_AUDIT", + "name": "Audit", + "permission": "w" + } + ] + } + } + + GET_ROLE_GROUP = { + "role": + { + "description": "Test_Description", + "id": "Test_Role", + "members": [ + { + "id": "UID:2146", + "name": "esa", + "type": "group" + } + ], + "name": "Test_Role", + "privileges": [ + { + "id": "ISI_PRIV_AUDIT", + "name": "Audit", + "permission": "w" + } + ] + } + } + + GET_ROLE_WELLKNOWN = { + "role": + { + "description": "Test_Description", + "id": "Test_Role", + "members": + [ + { + "id": "SID:S-1-1-0", + "name": "user", + "type": "wellknown" + } + ], + "name": "Test_Role", + "privileges": + [ + { + "id": "ISI_PRIV_AUDIT", + "name": "Audit", + "permission": "w" + } + ] + } + } + + GET_COPY_ROLE = { + "role": + { + "description": "Test_Description", + "id": "Test_Role_Copy", + "name": "Test_Role_Copy" + } + } + + GET_COPY_ROLE_WITHOUT_NEW_ROLE_NAME = { + "role": + { + "description": "Test_Description", + "id": "Test_Role_Copy", + "members": + [ + { + "id": "UID:2140", + "name": "esa", + "type": "user" + } + ], + "name": "Test_Role", + "privileges": + [ + { + "id": "ISI_PRIV_AUDIT", + "name": "Audit", + "permission": "w" + } + ] + } + } + + GET_ROLE_RESPONSE = { + "description": "Test_Description", + "id": "Test_Role2", + "members": [ + { + "id": "UID:1XXX", + "name": "admin", + "type": "user" + }, + { + "id": "UID:2XXX", + "name": "esa", + "type": "user" + } + ], + "name": "Test_Role2", + "privileges": [ + { + "id": "ISI_PRIV_ANTIVIRUS", + "name": "Antivirus", + "permission": "w" + }, + { + "id": "ISI_PRIV_RECOVERY_SHELL", + "name": "Recovery Shell", + "permission": "r" + } + ] + } + + GET_EMPTY_ROLE_RESPONSE = { + "description": "Test_Description", + "id": "Test_Role1", + "name": "Test_Role1", + "members": [], + "privileges": [] + } + + USER_DETAILS = { + "users": [ + { + "uid": { + "id": "User12_Ansible_Test_SMB", + "name": "User12_Ansible_Test_SMB", + "type": "user"} + } + ] + } + + GROUP_DETAILS = { + "groups": [ + { + "gid": { + "id": "Group_Ansible_Test_SMB", + "name": "Group_Ansible_Test_SMB", + "type": "group"} + } + ] + } + + WELLKNOWN_DETAILS = { + "id": "Everyone", + "name": "Everyone", + "type": "wellknown" + } + NEW_MEMBER_LIST = [ + { + "id": "User12_Ansible_Test_SMB", + "name": "User12_Ansible_Test_SMB", + "provider_type": "local", + "type": "user" + }, + { + "id": "Group_Ansible_Test_SMB", + "name": "Group_Ansible_Test_SMB", + "provider_type": "local", + "type": "group" + }, + { + "id": "Everyone", + "name": "Everyone", + "provider_type": "local", + "type": "wellknown" + } + ] + + NEW_MEMBER_LIST_1 = [ + { + "id": "User12_Ansible_Test_SMB", + "name": "User12_Ansible_Test_SMB", + "provider_type": "local", + "type": "user", + "state": "present" + } + ] + PRIVILEGE_LIST = { + "privileges": [ + { + "category": "Login", + "description": "Log in from the console", + "id": "ISI_PRIV_LOGIN_CONSOLE", + "name": "Console", + "parent_id": "ISI_PRIV_ZERO", + "permission": "r", + "privilegelevel": "flag", + "uri": "" + }, + { + "category": "Configuration", + "description": "Configure antivirus scanning", + "id": "ISI_PRIV_ANTIVIRUS", + "name": "Antivirus", + "parent_id": "ISI_PRIV_ZERO", + "permission": "w", + "privilegelevel": "flag", + "uri": "" + }, + { + "category": "Security", + "description": "Privilege to enter Recovery Shell from Restricted Shell", + "id": "ISI_PRIV_RECOVERY_SHELL", + "name": "Recovery Shell", + "parent_id": "ISI_PRIV_ZERO", + "permission": "r", + "privilegelevel": "flag", + "uri": "" + } + ] + } + NEW_PRIVILEGE_LIST = [ + { + "id": "ISI_PRIV_LOGIN_CONSOLE", + "name": "Console", + "permission": "r" + } + ] + + @staticmethod + def get_role_exception_response(response_type): + if response_type == 'delete_role_exception': + return "Delete role Test_Role123failed with error: SDK Error message" + elif response_type == 'create_role_exception': + return "Create role with failed with error: SDK Error message" + elif response_type == 'role_name_empty': + return "Role name cannot be empty" + elif response_type == 'description_invalid_length': + return "description must be less than or equal to 255 characters." + elif response_type == 'create_role_with_invalid_priilage': + return "Privilege test is either invalid or cannot be added to role in non-System access zone." + if response_type == 'get_details_exception': + return "Failed to get details of Role" + elif response_type == 'modify_exception': + return "failed with error: SDK Error message" + elif response_type == 'empty_name_exception': + return "Role name cannot be empty" + elif response_type == 'invalid_privilege_exception': + return "Privilage invalid_privilege is either invalid or cannot be added to role in non-System access zone." diff --git a/tests/unit/plugins/modules/test_info.py b/tests/unit/plugins/modules/test_info.py index b0114863..52339ddb 100644 --- a/tests/unit/plugins/modules/test_info.py +++ b/tests/unit/plugins/modules/test_info.py @@ -326,6 +326,7 @@ def test_get_facts_storagepool_api_exception(self, gatherfacts_module_mock, gath {"gather_subset": "providers", "return_key": "Providers"}, {"gather_subset": "users", "return_key": "Users"}, {"gather_subset": "groups", "return_key": "Groups"}, + {"gather_subset": "roles", "return_key": "roles"}, ] ) def test_get_facts_auth_api_module(self, gatherfacts_module_mock, input_params): @@ -345,7 +346,7 @@ def test_get_facts_auth_api_module(self, gatherfacts_module_mock, input_params): assert MockGatherfactsApi.get_gather_facts_module_response( gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] - @pytest.mark.parametrize("gather_subset", ["ldap", "user_mapping_rules", "providers", "users", "groups"]) + @pytest.mark.parametrize("gather_subset", ["ldap", "user_mapping_rules", "providers", "users", "groups", "roles"]) def test_get_facts_auth_api_exception(self, gatherfacts_module_mock, gather_subset): """Test the get_facts that uses the auth api endpoint to get the exception""" diff --git a/tests/unit/plugins/modules/test_role.py b/tests/unit/plugins/modules/test_role.py new file mode 100644 index 00000000..ad23a502 --- /dev/null +++ b/tests/unit/plugins/modules/test_role.py @@ -0,0 +1,490 @@ +# Copyright: (c) 2024, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for Role module on PowerScale""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +# pylint: disable=unused-import +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ + import utils + +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.powerscale_unit_base \ + import PowerScaleUnitBase +from ansible_collections.dellemc.powerscale.plugins.modules.role import Role +from ansible_collections.dellemc.powerscale.plugins.modules.role import RoleHandler +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_role_api \ + import MockRoleApi +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.auth \ + import Auth + + +class TestRole(PowerScaleUnitBase): + role_args = MockRoleApi.ROLE_COMMON_ARGS + + @pytest.fixture + def module_object(self): + return Role + + def test_create_role(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "privileges": [ + { + "name": "Audit", + "permission": "w", + "state": "present" + } + ], + "members": [ + { + "name": "esa", + "type": "user", + 'provider_type': "local", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_ROLE) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_role_without_description(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.role_args, {"description": ""}) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_ROLE) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_role_with_type_group(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "members": [ + { + "name": "Guest", + "type": "group", + 'provider_type': "local", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS_GROUP) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_ROLE_GROUP) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_role_with_type_wellknown(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "members": [ + { + "name": "user", + "type": "wellknown", + 'provider_type': "local", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.auth_api.get_auth_wellknowns().to_dict = MagicMock(return_value=MockRoleApi.MEMBERS_WELLKNOW) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_ROLE_WELLKNOWN) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_role_with_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.role_args, {}) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('create_role_exception'), + powerscale_module_mock, RoleHandler) + + def test_create_role_with_invalid_privilege(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "privileges": [ + { + "name": "test", + "permission": "w", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('create_role_with_invalid_priilage'), + powerscale_module_mock, RoleHandler) + + def test_delete_role(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role", + "state": "absent" + }) + powerscale_module_mock.auth_api.get_role.return_value = MagicMock(return_value=MockRoleApi.GET_ROLE) + powerscale_module_mock.auth_api.delete_auth_role.return_value = MagicMock(return_value=True) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_role_exception(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "state": "absent", + "role_name": "Test_Role123" + }) + powerscale_module_mock.get_role = MagicMock(return_value=None) + powerscale_module_mock.auth_api.delete_auth_role = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('delete_role_exception'), + powerscale_module_mock, RoleHandler) + + def test_copy_role_with_new_role_name(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "copy_role": True, + "new_role_name": "Test_Role_Copy", + "privileges": [ + { + "name": "Audit", + "permission": "w", + "state": "present" + } + ], + "members": [ + { + "name": "esa", + "type": "user", + 'provider_type': "local", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_COPY_ROLE) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_copy_role_without_new_role_name(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "copy_role": True, + "privileges": + [ + { + "name": "Audit", + "permission": "w", + "state": "present" + } + ], + "members": + [ + { + "name": "esa", + "type": "user", + 'provider_type': "local", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock(return_value=None) + powerscale_module_mock.Auth = MagicMock() + powerscale_module_mock.Auth.get_user_details(zone="System").to_dict = MagicMock(return_value=MockRoleApi.MEMBERS) + powerscale_module_mock.auth_api.get_auth_privileges(zone="System").to_dict = MagicMock(return_value=MockRoleApi.PRIVILEGES) + powerscale_module_mock.auth_api.create_auth_role = MagicMock(return_value=MockRoleApi.GET_COPY_ROLE_WITHOUT_NEW_ROLE_NAME) + RoleHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_role_without_role_name(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "", + "privileges": [ + { + "name": "Audit", + "permission": "w", + "state": "present" + } + ], + "members": [ + { + "name": "esa", + "type": "user", + 'provider_type': "local", + "state": "present" + } + ] + }) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('role_name_empty'), + powerscale_module_mock, RoleHandler) + + def test_create_role_without_invalid_length_description(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "description": 'PIE-IsilonS-24241-ClusterPIE-IsilonS-242PIE-IsilonS-24241-ClusterPIE-' + 'IsilonS-242PIE-IsilonS-24241-ClusterPIE-IsilonS-242PIE-IsilonS-24241P' + 'IE-IsilonS-24241-ClusterPIE-IsilonS-242PIE-IsilonS-24241-ClusterPIE-' + 'IsilonS-242PIEPIE-IsilonS-24241PIE-IsilonS-2424123', + "privileges": [ + { + "name": "Audit", + "permission": "w", + "state": "present" + } + ], + "members": [ + { + "name": "esa", + "type": "user", + 'provider_type': "local", + "state": "present" + } + ] + }) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('description_invalid_length'), + powerscale_module_mock, RoleHandler) + + def test_get_role_details(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.role_args, {"role_name": "Test_Role2"}) + powerscale_module_mock.auth_api.get_auth_role.roles[0] = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.get_auth_role.assert_called() + + def test_get_role_details_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.role_args, {"role_name": "Test_Role2"}) + powerscale_module_mock.auth_api.get_auth_role = MagicMock( + side_effect=MockApiException) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('get_details_exception'), + powerscale_module_mock, RoleHandler) + + def test_modify_role_priveleges_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role2", + "new_role_name": "Test_Role_new", + "description": "Test_Description_Modify", + "privileges": [ + { + "name": "Antivirus", + "permission": "w", + "state": "absent" + }, + { + "name": "Console", + "permission": "r", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.auth_api.get_auth_privileges().to_dict = MagicMock( + return_value=MockRoleApi.PRIVILEGE_LIST) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called() + + def test_modify_role_priveleges_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role2", + "new_role_name": "Test_Role_new", + "description": "Test_Description_Modify", + "privileges": [ + { + "name": "Antivirus", + "permission": "w", + "state": "absent" + }, + { + "name": "Recovery Shell", + "permission": "w", + "state": "present" + }, + { + "name": "Console", + "permission": "r", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.auth_api.get_auth_privileges().to_dict = MagicMock( + return_value=MockRoleApi.PRIVILEGE_LIST) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called() + + def test_modify_role_add_members_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role2", + "members": [ + { + "name": "User12_Ansible_Test_SMB", + "type": "user", + "state": "present" + }, + { + "name": "Group_Ansible_Test_SMB", + "type": "group", + "state": "present" + }, + { + "name": "Everyone", + "type": "wellknown", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.remove_duplicate_entries = MagicMock( + return_value=MockRoleApi.NEW_MEMBER_LIST + ) + Auth.get_user_details = MagicMock(return_value=MockRoleApi.USER_DETAILS) + Auth.get_group_details = MagicMock(return_value=MockRoleApi.GROUP_DETAILS) + Auth.get_wellknown_details = MagicMock(return_value=MockRoleApi.WELLKNOWN_DETAILS) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called() + + def test_modify_role_remove_members_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role2", + "members": [ + { + "name": "admin", + "type": "user", + "state": "absent" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called() + + def test_modify_role_remove_members_exception(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role2", + "members": [ + { + "name": "admin", + "type": "user", + "state": "absent" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.auth_api.update_auth_role = MagicMock( + side_effect=MockApiException) + self.capture_fail_json_call( + MockRoleApi.get_role_exception_response('modify_exception'), + powerscale_module_mock, RoleHandler) + + def test_add_role_priveleges_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role1", + "privileges": [ + { + "name": "Recovery Shell", + "permission": "w", + "state": "present" + } + ] + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_EMPTY_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.auth_api.get_auth_privileges().to_dict = MagicMock( + return_value=MockRoleApi.PRIVILEGE_LIST) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called() + + def test_add_role_members_response(self, powerscale_module_mock): + self.set_module_params( + powerscale_module_mock, self.role_args, + { + "role_name": "Test_Role1", + "members": [ + { + "name": "User12_Ansible_Test_SMB", + "type": "user", + "state": "present" + } + ] + + }) + powerscale_module_mock.get_role_details = MagicMock( + return_value=MockRoleApi.GET_EMPTY_ROLE_RESPONSE) + powerscale_module_mock.module.check_mode = False + powerscale_module_mock.remove_duplicate_entries = MagicMock( + return_value=MockRoleApi.NEW_MEMBER_LIST_1) + Auth.get_user_details = MagicMock(return_value=MockRoleApi.USER_DETAILS) + RoleHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.auth_api.update_auth_role.assert_called()