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