1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# Copyright: (c) 2018, Derek Rushing <derek.rushing@geekops.com>
5# Copyright: (c) 2018, VMware, Inc.
6# SPDX-License-Identifier: GPL-3.0-or-later
7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8
9from __future__ import absolute_import, division, print_function
10
11__metaclass__ = type
12
13
14DOCUMENTATION = r'''
15---
16module: vmware_object_role_permission
17short_description: Manage local roles on an ESXi host
18description: This module can be used to manage object permissions on the given host.
19author:
20- Derek Rushing (@kryptsi)
21- Joseph Andreatta (@vmwjoseph)
22notes:
23    - Tested on ESXi 6.5, vSphere 6.7
24    - The ESXi login user must have the appropriate rights to administer permissions.
25    - Permissions for a distributed switch must be defined and managed on either the datacenter or a folder containing the switch.
26requirements:
27    - "python >= 2.7"
28    - PyVmomi
29options:
30  role:
31    description:
32    - The role to be assigned permission.
33    - User can also specify role name presented in Web UI. Supported added in 1.5.0.
34    required: True
35    type: str
36  principal:
37    description:
38    - The user to be assigned permission.
39    - Required if C(group) is not specified.
40    - If specifying domain user, required separator of domain uses backslash.
41    type: str
42  group:
43    description:
44    - The group to be assigned permission.
45    - Required if C(principal) is not specified.
46    type: str
47  object_name:
48    description:
49    - The object name to assigned permission.
50    type: str
51    required: True
52  object_type:
53    description:
54    - The object type being targeted.
55    default: 'Folder'
56    choices: ['Folder', 'VirtualMachine', 'Datacenter', 'ResourcePool',
57              'Datastore', 'Network', 'HostSystem', 'ComputeResource',
58              'ClusterComputeResource', 'DistributedVirtualSwitch']
59    type: str
60  recursive:
61    description:
62    - Should the permissions be recursively applied.
63    default: True
64    type: bool
65  state:
66    description:
67    - Indicate desired state of the object's permission.
68    - When C(state=present), the permission will be added if it doesn't already exist.
69    - When C(state=absent), the permission is removed if it exists.
70    choices: ['present', 'absent']
71    default: present
72    type: str
73extends_documentation_fragment:
74- community.vmware.vmware.documentation
75
76'''
77
78EXAMPLES = r'''
79- name: Assign user to VM folder
80  community.vmware.vmware_object_role_permission:
81    hostname: '{{ esxi_hostname }}'
82    username: '{{ esxi_username }}'
83    password: '{{ esxi_password }}'
84    role: Admin
85    principal: user_bob
86    object_name: services
87    state: present
88  delegate_to: localhost
89
90- name: Remove user from VM folder
91  community.vmware.vmware_object_role_permission:
92    hostname: '{{ esxi_hostname }}'
93    username: '{{ esxi_username }}'
94    password: '{{ esxi_password }}'
95    role: Admin
96    principal: user_bob
97    object_name: services
98    state: absent
99  delegate_to: localhost
100
101- name: Assign finance group to VM folder
102  community.vmware.vmware_object_role_permission:
103    hostname: '{{ esxi_hostname }}'
104    username: '{{ esxi_username }}'
105    password: '{{ esxi_password }}'
106    role: Limited Users
107    group: finance
108    object_name: Accounts
109    state: present
110  delegate_to: localhost
111
112- name: Assign view_user Read Only permission at root folder
113  community.vmware.vmware_object_role_permission:
114    hostname: '{{ esxi_hostname }}'
115    username: '{{ esxi_username }}'
116    password: '{{ esxi_password }}'
117    role: ReadOnly
118    principal: view_user
119    object_name: rootFolder
120    state: present
121  delegate_to: localhost
122
123- name: Assign domain user to VM folder
124  community.vmware.vmware_object_role_permission:
125    hostname: "{{ vcenter_hostname }}"
126    username: "{{ vcenter_username }}"
127    password: "{{ vcenter_password }}"
128    validate_certs: false
129    role: Admin
130    principal: "vsphere.local\\domainuser"
131    object_name: services
132    state: present
133  delegate_to: localhost
134'''
135
136RETURN = r'''
137changed:
138    description: whether or not a change was made to the object's role
139    returned: always
140    type: bool
141'''
142
143try:
144    from pyVmomi import vim, vmodl
145except ImportError:
146    pass
147
148from ansible.module_utils.basic import AnsibleModule
149from ansible.module_utils._text import to_native
150from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec, find_obj
151
152
153class VMwareObjectRolePermission(PyVmomi):
154    def __init__(self, module):
155        super(VMwareObjectRolePermission, self).__init__(module)
156        self.module = module
157        self.params = module.params
158        self.is_group = False
159        self.role_list = {}
160        self.role = None
161        self.auth_manager = self.content.authorizationManager
162        self.populate_role_list()
163
164        if self.params.get('principal', None) is not None:
165            self.applied_to = self.params['principal']
166        elif self.params.get('group', None) is not None:
167            self.applied_to = self.params['group']
168            self.is_group = True
169
170        self.get_role()
171        self.get_object()
172        self.get_perms()
173        self.perm = self.setup_permission()
174        self.state = self.params['state']
175
176    def populate_role_list(self):
177        user_friendly_role_names = {
178            'Admin': ['Administrator'],
179            'ReadOnly': ['Read-Only'],
180            'com.vmware.Content.Admin': [
181                'Content library administrator (sample)',
182                'Content library administrator'
183            ],
184            'NoCryptoAdmin': ['No cryptography administrator'],
185            'NoAccess': ['No access'],
186            'VirtualMachinePowerUser': [
187                'Virtual machine power user (sample)',
188                'Virtual machine power user'
189            ],
190            'VirtualMachineUser': [
191                'Virtual machine user (sample)',
192                'Virtual machine user'
193            ],
194            'ResourcePoolAdministrator': [
195                'Resource pool administrator (sample)',
196                'Resource pool administrator'
197            ],
198            'VMwareConsolidatedBackupUser': [
199                'VMware Consolidated Backup user (sample)',
200                'VMware Consolidated Backup user'
201            ],
202            'DatastoreConsumer': [
203                'Datastore consumer (sample)',
204                'Datastore consumer'
205            ],
206            'NetworkConsumer': [
207                'Network administrator (sample)',
208                'Network administrator'
209            ],
210            'VirtualMachineConsoleUser': ['Virtual Machine console user'],
211            'InventoryService.Tagging.TaggingAdmin': ['Tagging Admin'],
212        }
213        for role in self.auth_manager.roleList:
214            self.role_list[role.name] = role
215            if user_friendly_role_names.get(role.name):
216                for role_name in user_friendly_role_names[role.name]:
217                    self.role_list[role_name] = role
218
219    def get_perms(self):
220        self.current_perms = self.auth_manager.RetrieveEntityPermissions(self.current_obj, False)
221
222    def same_permission(self, perm_one, perm_two):
223        return perm_one.principal.lower() == perm_two.principal.lower() \
224            and perm_one.roleId == perm_two.roleId
225
226    def get_state(self):
227        for perm in self.current_perms:
228            if self.same_permission(self.perm, perm):
229                return 'present'
230        return 'absent'
231
232    def process_state(self):
233        local_permission_states = {
234            'absent': {
235                'present': self.remove_permission,
236                'absent': self.state_exit_unchanged,
237            },
238            'present': {
239                'present': self.state_exit_unchanged,
240                'absent': self.add_permission,
241            }
242        }
243        try:
244            local_permission_states[self.state][self.get_state()]()
245        except vmodl.RuntimeFault as runtime_fault:
246            self.module.fail_json(msg=to_native(runtime_fault.msg))
247        except vmodl.MethodFault as method_fault:
248            self.module.fail_json(msg=to_native(method_fault.msg))
249        except Exception as e:
250            self.module.fail_json(msg=to_native(e))
251
252    def state_exit_unchanged(self):
253        self.module.exit_json(changed=False)
254
255    def setup_permission(self):
256        perm = vim.AuthorizationManager.Permission()
257        perm.entity = self.current_obj
258        perm.group = self.is_group
259        perm.principal = self.applied_to
260        perm.roleId = self.role.roleId
261        perm.propagate = self.params['recursive']
262        return perm
263
264    def add_permission(self):
265        if not self.module.check_mode:
266            self.auth_manager.SetEntityPermissions(self.current_obj, [self.perm])
267        self.module.exit_json(changed=True)
268
269    def remove_permission(self):
270        if not self.module.check_mode:
271            self.auth_manager.RemoveEntityPermission(self.current_obj, self.applied_to, self.is_group)
272        self.module.exit_json(changed=True)
273
274    def get_role(self):
275        self.role = self.role_list.get(self.params['role'], None)
276        if not self.role:
277            self.module.fail_json(msg="Specified role (%s) was not found" % self.params['role'])
278
279    def get_object(self):
280        # find_obj doesn't include rootFolder
281        if self.params['object_type'] == 'Folder' and self.params['object_name'] == 'rootFolder':
282            self.current_obj = self.content.rootFolder
283            return
284        try:
285            getattr(vim, self.params['object_type'])
286        except AttributeError:
287            self.module.fail_json(msg="Object type %s is not valid." % self.params['object_type'])
288        self.current_obj = find_obj(content=self.content,
289                                    vimtype=[getattr(vim, self.params['object_type'])],
290                                    name=self.params['object_name'])
291
292        if self.current_obj is None:
293            self.module.fail_json(
294                msg="Specified object %s of type %s was not found."
295                % (self.params['object_name'], self.params['object_type'])
296            )
297        if self.params['object_type'] == 'DistributedVirtualSwitch':
298            msg = "You are applying permissions to a Distributed vSwitch. " \
299                  "This will probably fail, since Distributed vSwitches inherits permissions " \
300                  "from the datacenter or a folder level. " \
301                  "Define permissions on the datacenter or the folder containing the switch."
302            self.module.warn(msg)
303
304
305def main():
306    argument_spec = vmware_argument_spec()
307    argument_spec.update(
308        dict(
309            role=dict(required=True, type='str'),
310            object_name=dict(required=True, type='str'),
311            object_type=dict(
312                type='str',
313                default='Folder',
314                choices=[
315                    'Folder',
316                    'VirtualMachine',
317                    'Datacenter',
318                    'ResourcePool',
319                    'Datastore',
320                    'Network',
321                    'HostSystem',
322                    'ComputeResource',
323                    'ClusterComputeResource',
324                    'DistributedVirtualSwitch',
325                ],
326            ),
327            principal=dict(type='str'),
328            group=dict(type='str'),
329            recursive=dict(type='bool', default=True),
330            state=dict(default='present', choices=['present', 'absent'], type='str'),
331        )
332    )
333
334    module = AnsibleModule(
335        argument_spec=argument_spec,
336        supports_check_mode=True,
337        mutually_exclusive=[
338            ['principal', 'group']
339        ],
340        required_one_of=[
341            ['principal', 'group']
342        ],
343    )
344
345    vmware_object_permission = VMwareObjectRolePermission(module)
346    vmware_object_permission.process_state()
347
348
349if __name__ == '__main__':
350    main()
351