1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright: (c) 2018, 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_asm_policy_manage
18short_description: Manage BIG-IP ASM policies
19description:
20   - Manage BIG-IP ASM policies, create from templates and manage global policy settings.
21version_added: 2.8
22options:
23  active:
24    description:
25      - If C(yes) will apply and activate existing inactive policy. If C(no), it will
26        deactivate existing active policy. Generally should be C(yes) only in cases where
27        you want to activate new or existing policy.
28    default: no
29    type: bool
30  name:
31    description:
32      - The ASM policy to manage or create.
33    type: str
34    required: True
35  state:
36    description:
37      - When C(state) is C(present), and C(template) parameter is provided,
38        new ASM policy is created from template with the given policy C(name).
39      - When C(state) is present and no C(template) parameter is provided
40        new blank ASM policy is created with the given policy C(name).
41      - When C(state) is C(absent), ensures that the policy is removed, even if it is
42        currently active.
43    type: str
44    choices:
45      - present
46      - absent
47    default: present
48  template:
49    description:
50      - An ASM policy built-in template. If the template does not exist we will raise an error.
51      - Once the policy has been created, this value cannot change.
52      - The C(Comprehensive), C(Drupal), C(Fundamental), C(Joomla),
53        C(Vulnerability Assessment Baseline), and C(Wordpress) templates are only available
54        on BIG-IP versions >= 13.
55    type: str
56    choices:
57      - ActiveSync v1.0 v2.0 (http)
58      - ActiveSync v1.0 v2.0 (https)
59      - Comprehensive
60      - Drupal
61      - Fundamental
62      - Joomla
63      - LotusDomino 6.5 (http)
64      - LotusDomino 6.5 (https)
65      - OWA Exchange 2003 (http)
66      - OWA Exchange 2003 (https)
67      - OWA Exchange 2003 with ActiveSync (http)
68      - OWA Exchange 2003 with ActiveSync (https)
69      - OWA Exchange 2007 (http)
70      - OWA Exchange 2007 (https)
71      - OWA Exchange 2007 with ActiveSync (http)
72      - OWA Exchange 2007 with ActiveSync (https)
73      - OWA Exchange 2010 (http)
74      - OWA Exchange 2010 (https)
75      - Oracle 10g Portal (http)
76      - Oracle 10g Portal (https)
77      - Oracle Applications 11i (http)
78      - Oracle Applications 11i (https)
79      - PeopleSoft Portal 9 (http)
80      - PeopleSoft Portal 9 (https)
81      - Rapid Deployment Policy
82      - SAP NetWeaver 7 (http)
83      - SAP NetWeaver 7 (https)
84      - SharePoint 2003 (http)
85      - SharePoint 2003 (https)
86      - SharePoint 2007 (http)
87      - SharePoint 2007 (https)
88      - SharePoint 2010 (http)
89      - SharePoint 2010 (https)
90      - Vulnerability Assessment Baseline
91      - Wordpress
92  partition:
93    description:
94      - Device partition to manage resources on.
95    type: str
96    default: Common
97extends_documentation_fragment: f5
98author:
99  - Wojciech Wypior (@wojtek0806)
100'''
101
102EXAMPLES = r'''
103- name: Create ASM policy from template
104  bigip_asm_policy:
105    name: new_sharepoint_policy
106    template: SharePoint 2007 (http)
107    state: present
108    provider:
109      server: lb.mydomain.com
110      user: admin
111      password: secret
112  delegate_to: localhost
113
114- name: Create blank ASM policy
115  bigip_asm_policy:
116    name: new_blank_policy
117    state: present
118    provider:
119      server: lb.mydomain.com
120      user: admin
121      password: secret
122  delegate_to: localhost
123
124- name: Create blank ASM policy and activate
125  bigip_asm_policy:
126    name: new_blank_policy
127    active: yes
128    state: present
129    provider:
130      server: lb.mydomain.com
131      user: admin
132      password: secret
133  delegate_to: localhost
134
135- name: Activate ASM policy
136  bigip_asm_policy:
137    name: inactive_policy
138    active: yes
139    state: present
140    provider:
141      server: lb.mydomain.com
142      user: admin
143      password: secret
144  delegate_to: localhost
145
146- name: Deactivate ASM policy
147  bigip_asm_policy_manage:
148    name: active_policy
149    state: present
150    provider:
151      server: lb.mydomain.com
152      user: admin
153      password: secret
154  delegate_to: localhost
155'''
156
157RETURN = r'''
158active:
159  description: Set when activating/deactivating ASM policy
160  returned: changed
161  type: bool
162  sample: yes
163state:
164  description: Action performed on the target device.
165  returned: changed
166  type: str
167  sample: absent
168template:
169  description: Name of the built-in ASM policy template
170  returned: changed
171  type: str
172  sample: OWA Exchange 2007 (https)
173name:
174  description: Name of the ASM policy to be managed/created
175  returned: changed
176  type: str
177  sample: Asm_APP1_Transparent
178'''
179
180import time
181from ansible.module_utils.basic import AnsibleModule
182from ansible.module_utils.basic import env_fallback
183from distutils.version import LooseVersion
184
185try:
186    from library.module_utils.network.f5.bigip import F5RestClient
187    from library.module_utils.network.f5.common import F5ModuleError
188    from library.module_utils.network.f5.common import AnsibleF5Parameters
189    from library.module_utils.network.f5.common import fq_name
190    from library.module_utils.network.f5.common import transform_name
191    from library.module_utils.network.f5.common import f5_argument_spec
192    from library.module_utils.network.f5.icontrol import tmos_version
193    from library.module_utils.network.f5.icontrol import module_provisioned
194except ImportError:
195    from ansible.module_utils.network.f5.bigip import F5RestClient
196    from ansible.module_utils.network.f5.common import F5ModuleError
197    from ansible.module_utils.network.f5.common import AnsibleF5Parameters
198    from ansible.module_utils.network.f5.common import fq_name
199    from ansible.module_utils.network.f5.common import transform_name
200    from ansible.module_utils.network.f5.common import f5_argument_spec
201    from ansible.module_utils.network.f5.icontrol import tmos_version
202    from ansible.module_utils.network.f5.icontrol import module_provisioned
203
204
205class Parameters(AnsibleF5Parameters):
206    updatables = [
207        'active',
208    ]
209
210    returnables = [
211        'name',
212        'template',
213        'active',
214    ]
215
216    api_attributes = [
217        'name',
218        'active',
219    ]
220    api_map = {
221    }
222
223    @property
224    def template_link(self):
225        if self._values['template_link'] is not None:
226            return self._values['template_link']
227
228        result = None
229
230        uri = "https://{0}:{1}/mgmt/tm/asm/policy-templates/".format(
231            self.client.provider['server'],
232            self.client.provider['server_port'],
233        )
234
235        query = "?$filter=contains(name,'{0}')".format(self.template.upper())
236        resp = self.client.api.get(uri + query)
237
238        try:
239            response = resp.json()
240        except ValueError as ex:
241            raise F5ModuleError(str(ex))
242
243        if 'code' in response and response['code'] == 400:
244            if 'message' in response:
245                raise F5ModuleError(response['message'])
246            else:
247                raise F5ModuleError(resp.content)
248        if 'items' in response and response['items'] != []:
249            result = dict(link=response['items'][0]['selfLink'])
250
251        return result
252
253    def to_return(self):
254        result = {}
255        for returnable in self.returnables:
256            result[returnable] = getattr(self, returnable)
257        result = self._filter_params(result)
258        return result
259
260
261class V1Parameters(Parameters):
262    @property
263    def template(self):
264        if self._values['template'] is None:
265            return None
266        template_map = {
267            'ActiveSync v1.0 v2.0 (http)': 'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTP',
268            'ActiveSync v1.0 v2.0 (https)': 'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTPS',
269            'LotusDomino 6.5 (http)': 'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTP',
270            'LotusDomino 6.5 (https)': 'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTPS',
271            'OWA Exchange 2003 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTP',
272            'OWA Exchange 2003 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTPS',
273            'OWA Exchange 2003 with ActiveSync (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTP',
274            'OWA Exchange 2003 with ActiveSync (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTPS',
275            'OWA Exchange 2007 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTP',
276            'OWA Exchange 2007 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTPS',
277            'OWA Exchange 2007 with ActiveSync (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTP',
278            'OWA Exchange 2007 with ActiveSync (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTPS',
279            'OWA Exchange 2010 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTP',
280            'OWA Exchange 2010 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTPS',
281            'Oracle 10g Portal (http)': 'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTP',
282            'Oracle 10g Portal (https)': 'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTPS',
283            'Oracle Applications 11i (http)': 'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTP',
284            'Oracle Applications 11i (https)': 'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTPS',
285            'PeopleSoft Portal 9 (http)': 'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTP',
286            'PeopleSoft Portal 9 (https)': 'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTPS',
287            'Rapid Deployment Policy': 'POLICY_TEMPLATE_RAPID_DEPLOYMENT',
288            'SAP NetWeaver 7 (http)': 'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTP',
289            'SAP NetWeaver 7 (https)': 'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTPS',
290            'SharePoint 2003 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2003_HTTP',
291            'SharePoint 2003 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2003_HTTPS',
292            'SharePoint 2007 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2007_HTTP',
293            'SharePoint 2007 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2007_HTTPS',
294            'SharePoint 2010 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2010_HTTP',
295            'SharePoint 2010 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2010_HTTPS'
296        }
297        if self._values['template'] in template_map:
298            return template_map[self._values['template']]
299        else:
300            raise F5ModuleError(
301                "The specified template is not valid for this version of BIG-IP."
302            )
303
304
305class V2Parameters(Parameters):
306    @property
307    def template(self):
308        if self._values['template'] is None:
309            return None
310        template_map = {
311            'ActiveSync v1.0 v2.0 (http)': 'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTP',
312            'ActiveSync v1.0 v2.0 (https)': 'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTPS',
313            'Comprehensive': 'POLICY_TEMPLATE_COMPREHENSIVE',  # v13
314            'Drupal': 'POLICY_TEMPLATE_DRUPAL',  # v13
315            'Fundamental': 'POLICY_TEMPLATE_FUNDAMENTAL',  # v13
316            'Joomla': 'POLICY_TEMPLATE_JOOMLA',  # v13
317            'LotusDomino 6.5 (http)': 'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTP',
318            'LotusDomino 6.5 (https)': 'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTPS',
319            'OWA Exchange 2003 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTP',
320            'OWA Exchange 2003 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTPS',
321            'OWA Exchange 2003 with ActiveSync (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTP',
322            'OWA Exchange 2003 with ActiveSync (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTPS',
323            'OWA Exchange 2007 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTP',
324            'OWA Exchange 2007 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTPS',
325            'OWA Exchange 2007 with ActiveSync (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTP',
326            'OWA Exchange 2007 with ActiveSync (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTPS',
327            'OWA Exchange 2010 (http)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTP',
328            'OWA Exchange 2010 (https)': 'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTPS',
329            'Oracle 10g Portal (http)': 'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTP',
330            'Oracle 10g Portal (https)': 'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTPS',
331            'Oracle Applications 11i (http)': 'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTP',
332            'Oracle Applications 11i (https)': 'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTPS',
333            'PeopleSoft Portal 9 (http)': 'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTP',
334            'PeopleSoft Portal 9 (https)': 'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTPS',
335            'Rapid Deployment Policy': 'POLICY_TEMPLATE_RAPID_DEPLOYMENT',
336            'SAP NetWeaver 7 (http)': 'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTP',
337            'SAP NetWeaver 7 (https)': 'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTPS',
338            'SharePoint 2003 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2003_HTTP',
339            'SharePoint 2003 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2003_HTTPS',
340            'SharePoint 2007 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2007_HTTP',
341            'SharePoint 2007 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2007_HTTPS',
342            'SharePoint 2010 (http)': 'POLICY_TEMPLATE_SHAREPOINT_2010_HTTP',
343            'SharePoint 2010 (https)': 'POLICY_TEMPLATE_SHAREPOINT_2010_HTTPS',
344            'Vulnerability Assessment Baseline': 'POLICY_TEMPLATE_VULNERABILITY_ASSESSMENT',  # v13
345            'Wordpress': 'POLICY_TEMPLATE_WORDPRESS'  # v13
346        }
347        return template_map[self._values['template']]
348
349
350class Changes(Parameters):
351    @property
352    def template(self):
353        if self._values['template'] is None:
354            return None
355        template_map = {
356            'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTP': 'ActiveSync v1.0 v2.0 (http)',
357            'POLICY_TEMPLATE_ACTIVESYNC_V1_0_V2_0_HTTPS': 'ActiveSync v1.0 v2.0 (https)',
358            'POLICY_TEMPLATE_COMPREHENSIVE': 'Comprehensive',
359            'POLICY_TEMPLATE_DRUPAL': 'Drupal',
360            'POLICY_TEMPLATE_FUNDAMENTAL': 'Fundamental',
361            'POLICY_TEMPLATE_JOOMLA': 'Joomla',
362            'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTP': 'LotusDomino 6.5 (http)',
363            'POLICY_TEMPLATE_LOTUSDOMINO_6_5_HTTPS': 'LotusDomino 6.5 (https)',
364            'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTP': 'OWA Exchange 2003 (http)',
365            'POLICY_TEMPLATE_OWA_EXCHANGE_2003_HTTPS': 'OWA Exchange 2003 (https)',
366            'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTP': 'OWA Exchange 2003 with ActiveSync (http)',
367            'POLICY_TEMPLATE_OWA_EXCHANGE_2003_WITH_ACTIVESYNC_HTTPS': 'OWA Exchange 2003 with ActiveSync (https)',
368            'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTP': 'OWA Exchange 2007 (http)',
369            'POLICY_TEMPLATE_OWA_EXCHANGE_2007_HTTPS': 'OWA Exchange 2007 (https)',
370            'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTP': 'OWA Exchange 2007 with ActiveSync (http)',
371            'POLICY_TEMPLATE_OWA_EXCHANGE_2007_WITH_ACTIVESYNC_HTTPS': 'OWA Exchange 2007 with ActiveSync (https)',
372            'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTP': 'OWA Exchange 2010 (http)',
373            'POLICY_TEMPLATE_OWA_EXCHANGE_2010_HTTPS': 'OWA Exchange 2010 (https)',
374            'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTP': 'Oracle 10g Portal (http)',
375            'POLICY_TEMPLATE_ORACLE_10G_PORTAL_HTTPS': 'Oracle 10g Portal (https)',
376            'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTP': 'Oracle Applications 11i (http)',
377            'POLICY_TEMPLATE_ORACLE_APPLICATIONS_11I_HTTPS': 'Oracle Applications 11i (https)',
378            'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTP': 'PeopleSoft Portal 9 (http)',
379            'POLICY_TEMPLATE_PEOPLESOFT_PORTAL_9_HTTPS': 'PeopleSoft Portal 9 (https)',
380            'POLICY_TEMPLATE_RAPID_DEPLOYMENT': 'Rapid Deployment Policy',
381            'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTP': 'SAP NetWeaver 7 (http)',
382            'POLICY_TEMPLATE_SAP_NETWEAVER_7_HTTPS': 'SAP NetWeaver 7 (https)',
383            'POLICY_TEMPLATE_SHAREPOINT_2003_HTTP': 'SharePoint 2003 (http)',
384            'POLICY_TEMPLATE_SHAREPOINT_2003_HTTPS': 'SharePoint 2003 (https)',
385            'POLICY_TEMPLATE_SHAREPOINT_2007_HTTP': 'SharePoint 2007 (http)',
386            'POLICY_TEMPLATE_SHAREPOINT_2007_HTTPS': 'SharePoint 2007 (https)',
387            'POLICY_TEMPLATE_SHAREPOINT_2010_HTTP': 'SharePoint 2010 (http)',
388            'POLICY_TEMPLATE_SHAREPOINT_2010_HTTPS': 'SharePoint 2010 (https)',
389            'POLICY_TEMPLATE_VULNERABILITY_ASSESSMENT': 'Vulnerability Assessment Baseline',
390            'POLICY_TEMPLATE_WORDPRESS': 'Wordpress',
391        }
392        return template_map[self._values['template']]
393
394
395class Difference(object):
396    def __init__(self, want, have=None):
397        self.want = want
398        self.have = have
399
400    def compare(self, param):
401        try:
402            result = getattr(self, param)
403            return result
404        except AttributeError:
405            return self.__default(param)
406
407    def __default(self, param):
408        attr1 = getattr(self.want, param)
409        try:
410            attr2 = getattr(self.have, param)
411            if attr1 != attr2:
412                return attr1
413        except AttributeError:
414            return attr1
415
416    @property
417    def active(self):
418        if self.want.active is True and self.have.active is False:
419            return True
420        if self.want.active is False and self.have.active is True:
421            return False
422
423
424class BaseManager(object):
425    def __init__(self, *args, **kwargs):
426        self.client = kwargs.get('client', None)
427        self.module = kwargs.get('module', None)
428        self.have = None
429        self.changes = Changes()
430
431    def exec_module(self):
432        changed = False
433        result = dict()
434        state = self.want.state
435
436        if state == "present":
437            changed = self.present()
438        elif state == "absent":
439            changed = self.absent()
440
441        changes = self.changes.to_return()
442        result.update(**changes)
443        result.update(dict(changed=changed))
444        return result
445
446    def _set_changed_options(self):
447        changed = {}
448        for key in Parameters.returnables:
449            if getattr(self.want, key) is not None:
450                changed[key] = getattr(self.want, key)
451        if changed:
452            self.changes = Changes(params=changed)
453
454    def should_update(self):
455        result = self._update_changed_options()
456        if result:
457            return True
458        return False
459
460    def _update_changed_options(self):
461        diff = Difference(self.want, self.have)
462        updatables = Parameters.updatables
463        changed = dict()
464        for k in updatables:
465            change = diff.compare(k)
466            if change is None:
467                continue
468            else:
469                if isinstance(change, dict):
470                    changed.update(change)
471                else:
472                    changed[k] = change
473        if changed:
474            self.changes = Changes(params=changed)
475            return True
476        return False
477
478    def present(self):
479        if self.exists():
480            return self.update()
481        else:
482            return self.create()
483
484    def absent(self):
485        if not self.exists():
486            return False
487        else:
488            return self.remove()
489
490    def create(self):
491        if self.want.active is None:
492            self.want.update(dict(active=False))
493        self._set_changed_options()
494        if self.module.check_mode:
495            return True
496
497        if self.want.template is not None:
498            self.create_from_template()
499        if self.want.template is None:
500            self.create_blank()
501        if self.want.active:
502            self.activate()
503            return True
504        else:
505            return True
506
507    def update(self):
508        self.have = self.read_current_from_device()
509        if not self.should_update():
510            return False
511        if self.module.check_mode:
512            return True
513        self.update_on_device()
514        if self.changes.active:
515            self.activate()
516        return True
517
518    def activate(self):
519        self.have = self.read_current_from_device()
520        task_id = self.apply_on_device()
521        if self.wait_for_task(task_id):
522            return True
523        else:
524            raise F5ModuleError('Apply policy task failed.')
525
526    def create_blank(self):
527        self.create_on_device()
528        if self.exists():
529            return True
530        else:
531            raise F5ModuleError(
532                'Failed to create ASM policy: {0}'.format(self.want.name)
533            )
534
535    def remove(self):
536        if self.module.check_mode:
537            return True
538        self.remove_from_device()
539        if self.exists():
540            raise F5ModuleError(
541                'Failed to delete ASM policy: {0}'.format(self.want.name)
542            )
543        return True
544
545    def is_activated(self):
546        if self.want.active is True:
547            return True
548        else:
549            return False
550
551    def exists(self):
552        uri = 'https://{0}:{1}/mgmt/tm/asm/policies/'.format(
553            self.client.provider['server'],
554            self.client.provider['server_port'],
555        )
556
557        query = "?$filter=contains(name,'{0}')+and+contains(partition,'{1}')&$select=name,partition".format(
558            self.want.name, self.want.partition
559        )
560        resp = self.client.api.get(uri + query)
561
562        try:
563            response = resp.json()
564        except ValueError as ex:
565            raise F5ModuleError(str(ex))
566        if 'items' in response and response['items'] != []:
567            return True
568        return False
569
570    def wait_for_task(self, task_id):
571        uri = "https://{0}:{1}/mgmt/tm/asm/tasks/apply-policy/{2}".format(
572            self.client.provider['server'],
573            self.client.provider['server_port'],
574            task_id
575        )
576        while True:
577            resp = self.client.api.get(uri)
578
579            try:
580                response = resp.json()
581            except ValueError as ex:
582                raise F5ModuleError(str(ex))
583
584            if 'code' in response and response['code'] == 400:
585                if 'message' in response:
586                    raise F5ModuleError(response['message'])
587                else:
588                    raise F5ModuleError(resp.content)
589
590            if response['status'] in ['COMPLETED', 'FAILURE']:
591                break
592            time.sleep(1)
593
594        if response['status'] == 'FAILURE':
595            return False
596        if response['status'] == 'COMPLETED':
597            return True
598
599    def _get_policy_id(self):
600        policy_id = None
601        uri = "https://{0}:{1}/mgmt/tm/asm/policies/".format(
602            self.client.provider['server'],
603            self.client.provider['server_port'],
604        )
605        query = "?$filter=contains(name,'{0}')+and+contains(partition,'{1}')&$select=name,id".format(
606            self.want.name, self.want.partition
607        )
608        resp = self.client.api.get(uri + query)
609
610        try:
611            response = resp.json()
612        except ValueError as ex:
613            raise F5ModuleError(str(ex))
614
615        if 'items' in response and response['items'] != []:
616            policy_id = response['items'][0]['id']
617        if not policy_id:
618            raise F5ModuleError("The policy was not found")
619
620        return policy_id
621
622    def update_on_device(self):
623        params = self.changes.api_params()
624        policy_id = self._get_policy_id()
625        uri = "https://{0}:{1}/mgmt/tm/asm/policies/{2}".format(
626            self.client.provider['server'],
627            self.client.provider['server_port'],
628            policy_id
629        )
630        if not params['active']:
631            resp = self.client.api.patch(uri, json=params)
632
633            try:
634                response = resp.json()
635            except ValueError as ex:
636                raise F5ModuleError(str(ex))
637
638            if 'code' in response and response['code'] == 400:
639                if 'message' in response:
640                    raise F5ModuleError(response['message'])
641                else:
642                    raise F5ModuleError(resp.content)
643
644    def read_current_from_device(self):
645        policy_id = self._get_policy_id()
646        uri = "https://{0}:{1}/mgmt/tm/asm/policies/{2}".format(
647            self.client.provider['server'],
648            self.client.provider['server_port'],
649            policy_id
650        )
651        resp = self.client.api.get(uri)
652
653        try:
654            response = resp.json()
655        except ValueError as ex:
656            raise F5ModuleError(str(ex))
657
658        if 'code' in response and response['code'] == 400:
659            if 'message' in response:
660                raise F5ModuleError(response['message'])
661            else:
662                raise F5ModuleError(resp.content)
663
664        response.update((dict(self_link=response['selfLink'])))
665
666        return Parameters(params=response)
667
668    def apply_on_device(self):
669        uri = "https://{0}:{1}/mgmt/tm/asm/tasks/apply-policy/".format(
670            self.client.provider['server'],
671            self.client.provider['server_port'],
672        )
673        params = dict(policyReference={'link': self.have.self_link})
674        resp = self.client.api.post(uri, json=params)
675
676        try:
677            response = resp.json()
678        except ValueError as ex:
679            raise F5ModuleError(str(ex))
680
681        if 'code' in response and response['code'] in [400, 403]:
682            if 'message' in response:
683                raise F5ModuleError(response['message'])
684            else:
685                raise F5ModuleError(resp.content)
686        return response['id']
687
688    def create_from_template_on_device(self):
689        full_name = fq_name(self.want.partition, self.want.name)
690        cmd = 'tmsh create asm policy {0} policy-template {1}'.format(full_name, self.want.template)
691        uri = "https://{0}:{1}/mgmt/tm/util/bash/".format(
692            self.client.provider['server'],
693            self.client.provider['server_port'],
694        )
695        args = dict(
696            command='run',
697            utilCmdArgs='-c "{0}"'.format(cmd)
698        )
699        resp = self.client.api.post(uri, json=args)
700
701        try:
702            response = resp.json()
703            if 'commandResult' in response:
704                if 'Unexpected Error' in response['commandResult']:
705                    raise F5ModuleError(response['commandResult'])
706        except ValueError as ex:
707            raise F5ModuleError(str(ex))
708
709        if 'code' in response and response['code'] == 400:
710            if 'message' in response:
711                raise F5ModuleError(response['message'])
712            else:
713                raise F5ModuleError(resp.content)
714
715    def create_on_device(self):
716        params = self.changes.api_params()
717        params['name'] = self.want.name
718        params['partition'] = self.want.partition
719        # we need to remove active from params as API will raise an error if the active is set to True,
720        # policies can only be activated via apply-policy task endpoint.
721        params.pop('active')
722        uri = "https://{0}:{1}/mgmt/tm/asm/policies/".format(
723            self.client.provider['server'],
724            self.client.provider['server_port'],
725        )
726        resp = self.client.api.post(uri, json=params)
727
728        try:
729            response = resp.json()
730        except ValueError as ex:
731            raise F5ModuleError(str(ex))
732
733        if 'code' in response and response['code'] in [400, 401, 403]:
734            if 'message' in response:
735                raise F5ModuleError(response['message'])
736            else:
737                raise F5ModuleError(resp.content)
738        time.sleep(2)
739        return True
740
741    def remove_from_device(self):
742        policy_id = self._get_policy_id()
743        uri = "https://{0}:{1}/mgmt/tm/asm/policies/{2}".format(
744            self.client.provider['server'],
745            self.client.provider['server_port'],
746            policy_id
747        )
748        response = self.client.api.delete(uri)
749        if response.status in [200, 201]:
750            return True
751        raise F5ModuleError(response.content)
752
753
754class ModuleManager(object):
755    def __init__(self, *args, **kwargs):
756        self.module = kwargs.get('module', None)
757        self.client = F5RestClient(**self.module.params)
758        self.kwargs = kwargs
759
760    def exec_module(self):
761        if not module_provisioned(self.client, 'asm'):
762            raise F5ModuleError(
763                "ASM must be provisioned to use this module."
764            )
765        if self.version_is_less_than_13():
766            manager = self.get_manager('v1')
767        else:
768            manager = self.get_manager('v2')
769        return manager.exec_module()
770
771    def get_manager(self, type):
772        if type == 'v1':
773            return V1Manager(**self.kwargs)
774        elif type == 'v2':
775            return V2Manager(**self.kwargs)
776
777    def version_is_less_than_13(self):
778        version = tmos_version(self.client)
779        if LooseVersion(version) < LooseVersion('13.0.0'):
780            return True
781        else:
782            return False
783
784
785class V1Manager(BaseManager):
786    def __init__(self, *args, **kwargs):
787        module = kwargs.get('module', None)
788        client = F5RestClient(**module.params)
789        super(V1Manager, self).__init__(client=client, module=module)
790        self.want = V1Parameters(params=module.params, client=client)
791
792    def create_from_template(self):
793        self.create_from_template_on_device()
794
795
796class V2Manager(BaseManager):
797    def __init__(self, *args, **kwargs):
798        module = kwargs.get('module', None)
799        client = F5RestClient(**module.params)
800        super(V2Manager, self).__init__(client=client, module=module)
801        self.want = V2Parameters(params=module.params, client=client)
802
803    # TODO Include creating ASM policies from custom templates in v13
804
805    def create_from_template(self):
806        if not self.create_from_template_on_device():
807            return False
808
809
810class ArgumentSpec(object):
811    def __init__(self):
812        self.template_map = [
813            'ActiveSync v1.0 v2.0 (http)',
814            'ActiveSync v1.0 v2.0 (https)',
815            'Comprehensive',
816            'Drupal',
817            'Fundamental',
818            'Joomla',
819            'LotusDomino 6.5 (http)',
820            'LotusDomino 6.5 (https)',
821            'OWA Exchange 2003 (http)',
822            'OWA Exchange 2003 (https)',
823            'OWA Exchange 2003 with ActiveSync (http)',
824            'OWA Exchange 2003 with ActiveSync (https)',
825            'OWA Exchange 2007 (http)',
826            'OWA Exchange 2007 (https)',
827            'OWA Exchange 2007 with ActiveSync (http)',
828            'OWA Exchange 2007 with ActiveSync (https)',
829            'OWA Exchange 2010 (http)',
830            'OWA Exchange 2010 (https)',
831            'Oracle 10g Portal (http)',
832            'Oracle 10g Portal (https)',
833            'Oracle Applications 11i (http)',
834            'Oracle Applications 11i (https)',
835            'PeopleSoft Portal 9 (http)',
836            'PeopleSoft Portal 9 (https)',
837            'Rapid Deployment Policy',
838            'SAP NetWeaver 7 (http)',
839            'SAP NetWeaver 7 (https)',
840            'SharePoint 2003 (http)',
841            'SharePoint 2003 (https)',
842            'SharePoint 2007 (http)',
843            'SharePoint 2007 (https)',
844            'SharePoint 2010 (http)',
845            'SharePoint 2010 (https)',
846            'Vulnerability Assessment Baseline',
847            'Wordpress',
848        ]
849        self.supports_check_mode = True
850        argument_spec = dict(
851            name=dict(
852                required=True,
853            ),
854            template=dict(
855                choices=self.template_map
856            ),
857            active=dict(
858                type='bool',
859                default='no'
860            ),
861            state=dict(
862                default='present',
863                choices=['present', 'absent']
864            ),
865            partition=dict(
866                default='Common',
867                fallback=(env_fallback, ['F5_PARTITION'])
868            )
869        )
870        self.argument_spec = {}
871        self.argument_spec.update(f5_argument_spec)
872        self.argument_spec.update(argument_spec)
873
874
875def main():
876    spec = ArgumentSpec()
877
878    module = AnsibleModule(
879        argument_spec=spec.argument_spec,
880        supports_check_mode=spec.supports_check_mode,
881    )
882
883    try:
884        mm = ModuleManager(module=module)
885        results = mm.exec_module()
886        module.exit_json(**results)
887    except F5ModuleError as ex:
888        module.fail_json(msg=str(ex))
889
890
891if __name__ == '__main__':
892    main()
893