1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# Copyright: (c) 2019, F5 Networks Inc.
5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10DOCUMENTATION = r'''
11---
12module: bigip_remote_user
13short_description: Manages default settings for remote user accounts on a BIG-IP
14description:
15  - Manages the default settings for remote user accounts on a BIG-IP system.
16version_added: "1.0.0"
17options:
18  default_role:
19    description:
20      - Specifies the default role for all remote user accounts.
21      - The default system value is C(no-access).
22    type: str
23    choices:
24      - acceleration-policy-editor
25      - admin
26      - application-editor
27      - auditor
28      - certificate-manager
29      - firewall-manager
30      - fraud-protection-manager
31      - guest
32      - irule-manager
33      - manager
34      - no-access
35      - operator
36      - resource-admin
37      - user-manager
38      - web-application-security-administrator
39      - web-application-security-editor
40  default_partition:
41    description:
42      - Specifies the default partition for all remote user accounts.
43      - The default system value is C(all) for all partitions.
44    type: str
45  console_access:
46    description:
47      - Enables or disables the default console access for all remote user accounts.
48      - The default system value is C(disabled).
49    type: bool
50  description:
51    description:
52      - User-defined description.
53    type: str
54extends_documentation_fragment: f5networks.f5_modules.f5
55author:
56  - Wojciech Wypior (@wojtek0806)
57'''
58
59EXAMPLES = r'''
60- name: Modify default partition and console access
61  bigip_remote_user:
62    default_partition: Common
63    console_access: yes
64    provider:
65      password: secret
66      server: lb.mydomain.com
67      user: admin
68  delegate_to: localhost
69
70- name: Modify default role, partition and console access
71  bigip_remote_user:
72    default_partition: Common
73    default_role: manager
74    console_access: yes
75    description: "Changed new settings"
76    provider:
77      password: secret
78      server: lb.mydomain.com
79      user: admin
80  delegate_to: localhost
81
82- name: Revert to default settings
83  bigip_remote_user:
84    default_partition: all
85    default_role: "no-access"
86    console_access: no
87    description: ""
88    provider:
89      password: secret
90      server: lb.mydomain.com
91      user: admin
92  delegate_to: localhost
93'''
94
95RETURN = r'''
96default_role:
97  description: The default role for all remote user accounts.
98  returned: changed
99  type: str
100  sample: auditor
101default_partition:
102  description: The default partition for all remote user accounts.
103  returned: changed
104  type: str
105  sample: Common
106console_access:
107  description: The default console access for all remote user accounts.
108  returned: changed
109  type: bool
110  sample: no
111description:
112  description: The user-defined description.
113  returned: changed
114  type: str
115  sample: Foo is bar
116'''
117from datetime import datetime
118
119from ansible.module_utils.basic import AnsibleModule
120
121from ..module_utils.bigip import F5RestClient
122from ..module_utils.common import (
123    F5ModuleError, AnsibleF5Parameters, f5_argument_spec, flatten_boolean
124)
125from ..module_utils.compare import cmp_str_with_none
126from ..module_utils.icontrol import tmos_version
127from ..module_utils.teem import send_teem
128
129
130class Parameters(AnsibleF5Parameters):
131    api_map = {
132        'defaultPartition': 'default_partition',
133        'defaultRole': 'default_role',
134        'remoteConsoleAccess': 'console_access',
135    }
136
137    api_attributes = [
138        'defaultPartition',
139        'defaultRole',
140        'description',
141        'remoteConsoleAccess',
142
143    ]
144
145    returnables = [
146        'default_partition',
147        'default_role',
148        'console_access',
149        'description',
150    ]
151
152    updatables = [
153        'default_partition',
154        'default_role',
155        'console_access',
156        'description',
157    ]
158
159
160class ApiParameters(Parameters):
161    pass
162
163
164class ModuleParameters(Parameters):
165    @property
166    def console_access(self):
167        result = flatten_boolean(self._values['console_access'])
168        if result == 'yes':
169            return 'tmsh'
170        if result == 'no':
171            return 'disabled'
172
173
174class Changes(Parameters):
175    def to_return(self):
176        result = {}
177        try:
178            for returnable in self.returnables:
179                result[returnable] = getattr(self, returnable)
180            result = self._filter_params(result)
181        except Exception:
182            pass
183        return result
184
185
186class UsableChanges(Changes):
187    pass
188
189
190class ReportableChanges(Changes):
191    @property
192    def console_access(self):
193        if self._values['console_access'] is None:
194            return None
195        if self._values['console_access'] == 'tmsh':
196            return 'yes'
197        if self._values['console_access'] == 'disabled':
198            return 'no'
199
200
201class Difference(object):
202    def __init__(self, want, have=None):
203        self.want = want
204        self.have = have
205
206    def compare(self, param):
207        try:
208            result = getattr(self, param)
209            return result
210        except AttributeError:
211            return self.__default(param)
212
213    def __default(self, param):
214        attr1 = getattr(self.want, param)
215        try:
216            attr2 = getattr(self.have, param)
217            if attr1 != attr2:
218                return attr1
219        except AttributeError:
220            return attr1
221
222    @property
223    def description(self):
224        result = cmp_str_with_none(self.want.description, self.have.description)
225        return result
226
227
228class ModuleManager(object):
229    def __init__(self, *args, **kwargs):
230        self.module = kwargs.get('module', None)
231        self.client = F5RestClient(**self.module.params)
232        self.want = ModuleParameters(params=self.module.params)
233        self.have = ApiParameters()
234        self.changes = UsableChanges()
235
236    def _update_changed_options(self):
237        diff = Difference(self.want, self.have)
238        updatables = Parameters.updatables
239        changed = dict()
240        for k in updatables:
241            change = diff.compare(k)
242            if change is None:
243                continue
244            else:
245                if isinstance(change, dict):
246                    changed.update(change)
247                else:
248                    changed[k] = change
249        if changed:
250            self.changes = UsableChanges(params=changed)
251            return True
252        return False
253
254    def _announce_deprecations(self, result):
255        warnings = result.pop('__warnings', [])
256        for warning in warnings:
257            self.client.module.deprecate(
258                msg=warning['msg'],
259                version=warning['version']
260            )
261
262    def exec_module(self):
263        start = datetime.now().isoformat()
264        version = tmos_version(self.client)
265        result = dict()
266
267        changed = self.update()
268
269        reportable = ReportableChanges(params=self.changes.to_return())
270        changes = reportable.to_return()
271        result.update(**changes)
272        result.update(dict(changed=changed))
273        self._announce_deprecations(result)
274        send_teem(start, self.client, self.module, version)
275        return result
276
277    def should_update(self):
278        result = self._update_changed_options()
279        if result:
280            return True
281        return False
282
283    def update(self):
284        self.have = self.read_current_from_device()
285        if not self.should_update():
286            return False
287        if self.module.check_mode:
288            return True
289        self.update_on_device()
290        return True
291
292    def update_on_device(self):
293        params = self.changes.api_params()
294        uri = "https://{0}:{1}/mgmt/tm/auth/remote-user/".format(
295            self.client.provider['server'],
296            self.client.provider['server_port'],
297        )
298        resp = self.client.api.patch(uri, json=params)
299        try:
300            response = resp.json()
301        except ValueError as ex:
302            raise F5ModuleError(str(ex))
303
304        if 'code' in response and response['code'] == 400:
305            if 'message' in response:
306                raise F5ModuleError(response['message'])
307            else:
308                raise F5ModuleError(resp.content)
309
310    def read_current_from_device(self):
311        uri = "https://{0}:{1}/mgmt/tm/auth/remote-user/".format(
312            self.client.provider['server'],
313            self.client.provider['server_port'],
314        )
315        resp = self.client.api.get(uri)
316        try:
317            response = resp.json()
318        except ValueError as ex:
319            raise F5ModuleError(str(ex))
320
321        if 'code' in response and response['code'] == 400:
322            if 'message' in response:
323                raise F5ModuleError(response['message'])
324            else:
325                raise F5ModuleError(resp.content)
326        return ApiParameters(params=response)
327
328
329class ArgumentSpec(object):
330    def __init__(self):
331        self.supports_check_mode = True
332        self.choices = [
333            'acceleration-policy-editor',
334            'admin',
335            'application-editor',
336            'auditor',
337            'certificate-manager',
338            'firewall-manager',
339            'fraud-protection-manager',
340            'guest',
341            'irule-manager',
342            'manager',
343            'no-access',
344            'operator',
345            'resource-admin',
346            'user-manager',
347            'web-application-security-administrator',
348            'web-application-security-editor'
349        ]
350        argument_spec = dict(
351            default_role=dict(
352                choices=self.choices
353            ),
354            default_partition=dict(),
355            console_access=dict(type='bool'),
356            description=dict()
357        )
358        self.argument_spec = {}
359        self.argument_spec.update(f5_argument_spec)
360        self.argument_spec.update(argument_spec)
361        self.required_one_of = [
362            ['default_role', 'default_partition']
363        ]
364
365
366def main():
367    spec = ArgumentSpec()
368
369    module = AnsibleModule(
370        argument_spec=spec.argument_spec,
371        supports_check_mode=spec.supports_check_mode,
372    )
373
374    try:
375        mm = ModuleManager(module=module)
376        results = mm.exec_module()
377        module.exit_json(**results)
378    except F5ModuleError as ex:
379        module.fail_json(msg=str(ex))
380
381
382if __name__ == '__main__':
383    main()
384