1#!/usr/local/bin/python3.8
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019-2020 Fortinet, Inc.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18__metaclass__ = type
19
20ANSIBLE_METADATA = {'status': ['preview'],
21                    'supported_by': 'community',
22                    'metadata_version': '1.1'}
23
24DOCUMENTATION = '''
25---
26module: fortios_firewall_internet_service_custom_group
27short_description: Configure custom Internet Service group in Fortinet's FortiOS and FortiGate.
28description:
29    - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the
30      user to set and modify firewall feature and internet_service_custom_group category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.0
33version_added: "2.10"
34author:
35    - Link Zheng (@chillancezen)
36    - Jie Xue (@JieX19)
37    - Hongbin Lu (@fgtdev-hblu)
38    - Frank Shen (@frankshen01)
39    - Miguel Angel Munoz (@mamunozgonzalez)
40    - Nicolas Thomas (@thomnico)
41notes:
42    - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks
43
44requirements:
45    - ansible>=2.9.0
46options:
47    access_token:
48        description:
49            - Token-based authentication.
50              Generated from GUI of Fortigate.
51        type: str
52        required: false
53    enable_log:
54        description:
55            - Enable/Disable logging for task.
56        type: bool
57        required: false
58        default: false
59    vdom:
60        description:
61            - Virtual domain, among those defined previously. A vdom is a
62              virtual instance of the FortiGate that can be configured and
63              used as a different unit.
64        type: str
65        default: root
66
67    state:
68        description:
69            - Indicates whether to create or remove the object.
70        type: str
71        required: true
72        choices:
73            - present
74            - absent
75    firewall_internet_service_custom_group:
76        description:
77            - Configure custom Internet Service group.
78        default: null
79        type: dict
80        suboptions:
81            comment:
82                description:
83                    - Comment.
84                type: str
85            member:
86                description:
87                    - Custom Internet Service group members.
88                type: list
89                suboptions:
90                    name:
91                        description:
92                            - Group member name. Source firewall.internet-service-custom.name.
93                        required: true
94                        type: str
95            name:
96                description:
97                    - Custom Internet Service group name.
98                required: true
99                type: str
100'''
101
102EXAMPLES = '''
103- hosts: fortigates
104  collections:
105    - fortinet.fortios
106  connection: httpapi
107  vars:
108   vdom: "root"
109   ansible_httpapi_use_ssl: yes
110   ansible_httpapi_validate_certs: no
111   ansible_httpapi_port: 443
112  tasks:
113  - name: Configure custom Internet Service group.
114    fortios_firewall_internet_service_custom_group:
115      vdom:  "{{ vdom }}"
116      state: "present"
117      access_token: "<your_own_value>"
118      firewall_internet_service_custom_group:
119        comment: "Comment."
120        member:
121         -
122            name: "default_name_5 (source firewall.internet-service-custom.name)"
123        name: "default_name_6"
124
125'''
126
127RETURN = '''
128build:
129  description: Build number of the fortigate image
130  returned: always
131  type: str
132  sample: '1547'
133http_method:
134  description: Last method used to provision the content into FortiGate
135  returned: always
136  type: str
137  sample: 'PUT'
138http_status:
139  description: Last result given by FortiGate on last operation applied
140  returned: always
141  type: str
142  sample: "200"
143mkey:
144  description: Master key (id) used in the last call to FortiGate
145  returned: success
146  type: str
147  sample: "id"
148name:
149  description: Name of the table used to fulfill the request
150  returned: always
151  type: str
152  sample: "urlfilter"
153path:
154  description: Path of the table used to fulfill the request
155  returned: always
156  type: str
157  sample: "webfilter"
158revision:
159  description: Internal revision number
160  returned: always
161  type: str
162  sample: "17.0.2.10658"
163serial:
164  description: Serial number of the unit
165  returned: always
166  type: str
167  sample: "FGVMEVYYQT3AB5352"
168status:
169  description: Indication of the operation's result
170  returned: always
171  type: str
172  sample: "success"
173vdom:
174  description: Virtual domain used
175  returned: always
176  type: str
177  sample: "root"
178version:
179  description: Version of the FortiGate
180  returned: always
181  type: str
182  sample: "v5.6.3"
183
184'''
185from ansible.module_utils.basic import AnsibleModule
186from ansible.module_utils.connection import Connection
187from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
188from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
189from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
190from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
191from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
192from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
193from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
194
195
196def filter_firewall_internet_service_custom_group_data(json):
197    option_list = ['comment', 'member', 'name']
198    dictionary = {}
199
200    for attribute in option_list:
201        if attribute in json and json[attribute] is not None:
202            dictionary[attribute] = json[attribute]
203
204    return dictionary
205
206
207def underscore_to_hyphen(data):
208    if isinstance(data, list):
209        for i, elem in enumerate(data):
210            data[i] = underscore_to_hyphen(elem)
211    elif isinstance(data, dict):
212        new_data = {}
213        for k, v in data.items():
214            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
215        data = new_data
216
217    return data
218
219
220def firewall_internet_service_custom_group(data, fos, check_mode=False):
221
222    vdom = data['vdom']
223
224    state = data['state']
225
226    firewall_internet_service_custom_group_data = data['firewall_internet_service_custom_group']
227    filtered_data = underscore_to_hyphen(filter_firewall_internet_service_custom_group_data(firewall_internet_service_custom_group_data))
228
229    # check_mode starts from here
230    if check_mode:
231        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
232        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
233        is_existed = current_data and current_data.get('http_status') == 200 \
234            and isinstance(current_data.get('results'), list) \
235            and len(current_data['results']) > 0
236
237        # 2. if it exists and the state is 'present' then compare current settings with desired
238        if state == 'present' or state is True:
239            if mkey is None:
240                return False, True, filtered_data
241
242            # if mkey exists then compare each other
243            # record exits and they're matched or not
244            if is_existed:
245                is_same = is_same_comparison(
246                    serialize(current_data['results'][0]), serialize(filtered_data))
247                return False, not is_same, filtered_data
248
249            # record does not exist
250            return False, True, filtered_data
251
252        if state == 'absent':
253            if mkey is None:
254                return False, False, filtered_data
255
256            if is_existed:
257                return False, True, filtered_data
258            return False, False, filtered_data
259
260        return True, False, {'reason: ': 'Must provide state parameter'}
261
262    if state == "present" or state is True:
263        return fos.set('firewall',
264                       'internet-service-custom-group',
265                       data=filtered_data,
266                       vdom=vdom)
267
268    elif state == "absent":
269        return fos.delete('firewall',
270                          'internet-service-custom-group',
271                          mkey=filtered_data['name'],
272                          vdom=vdom)
273    else:
274        fos._module.fail_json(msg='state must be present or absent!')
275
276
277def is_successful_status(status):
278    return status['status'] == "success" or \
279        status['http_method'] == "DELETE" and status['http_status'] == 404
280
281
282def fortios_firewall(data, fos, check_mode):
283
284    if data['firewall_internet_service_custom_group']:
285        resp = firewall_internet_service_custom_group(data, fos, check_mode)
286    else:
287        fos._module.fail_json(msg='missing task body: %s' % ('firewall_internet_service_custom_group'))
288    if check_mode:
289        return resp
290    return not is_successful_status(resp), \
291        resp['status'] == "success" and \
292        (resp['revision_changed'] if 'revision_changed' in resp else True), \
293        resp
294
295
296versioned_schema = {
297    "type": "list",
298    "children": {
299        "comment": {
300            "type": "string",
301            "revisions": {
302                "v6.0.0": True,
303                "v7.0.0": True,
304                "v6.0.5": True,
305                "v6.4.4": True,
306                "v6.4.0": True,
307                "v6.4.1": True,
308                "v6.2.0": True,
309                "v6.2.3": True,
310                "v6.2.5": True,
311                "v6.2.7": True,
312                "v6.0.11": True
313            }
314        },
315        "member": {
316            "type": "list",
317            "children": {
318                "name": {
319                    "type": "string",
320                    "revisions": {
321                        "v6.0.0": True,
322                        "v7.0.0": True,
323                        "v6.0.5": True,
324                        "v6.4.4": True,
325                        "v6.4.0": True,
326                        "v6.4.1": True,
327                        "v6.2.0": True,
328                        "v6.2.3": True,
329                        "v6.2.5": True,
330                        "v6.2.7": True,
331                        "v6.0.11": True
332                    }
333                }
334            },
335            "revisions": {
336                "v6.0.0": True,
337                "v7.0.0": True,
338                "v6.0.5": True,
339                "v6.4.4": True,
340                "v6.4.0": True,
341                "v6.4.1": True,
342                "v6.2.0": True,
343                "v6.2.3": True,
344                "v6.2.5": True,
345                "v6.2.7": True,
346                "v6.0.11": True
347            }
348        },
349        "name": {
350            "type": "string",
351            "revisions": {
352                "v6.0.0": True,
353                "v7.0.0": True,
354                "v6.0.5": True,
355                "v6.4.4": True,
356                "v6.4.0": True,
357                "v6.4.1": True,
358                "v6.2.0": True,
359                "v6.2.3": True,
360                "v6.2.5": True,
361                "v6.2.7": True,
362                "v6.0.11": True
363            }
364        }
365    },
366    "revisions": {
367        "v6.0.0": True,
368        "v7.0.0": True,
369        "v6.0.5": True,
370        "v6.4.4": True,
371        "v6.4.0": True,
372        "v6.4.1": True,
373        "v6.2.0": True,
374        "v6.2.3": True,
375        "v6.2.5": True,
376        "v6.2.7": True,
377        "v6.0.11": True
378    }
379}
380
381
382def main():
383    module_spec = schema_to_module_spec(versioned_schema)
384    mkeyname = 'name'
385    fields = {
386        "access_token": {"required": False, "type": "str", "no_log": True},
387        "enable_log": {"required": False, "type": bool},
388        "vdom": {"required": False, "type": "str", "default": "root"},
389        "state": {"required": True, "type": "str",
390                  "choices": ["present", "absent"]},
391        "firewall_internet_service_custom_group": {
392            "required": False, "type": "dict", "default": None,
393            "options": {
394            }
395        }
396    }
397    for attribute_name in module_spec['options']:
398        fields["firewall_internet_service_custom_group"]['options'][attribute_name] = module_spec['options'][attribute_name]
399        if mkeyname and mkeyname == attribute_name:
400            fields["firewall_internet_service_custom_group"]['options'][attribute_name]['required'] = True
401
402    check_legacy_fortiosapi()
403    module = AnsibleModule(argument_spec=fields,
404                           supports_check_mode=True)
405
406    versions_check_result = None
407    if module._socket_path:
408        connection = Connection(module._socket_path)
409        if 'access_token' in module.params:
410            connection.set_option('access_token', module.params['access_token'])
411
412        if 'enable_log' in module.params:
413            connection.set_option('enable_log', module.params['enable_log'])
414        else:
415            connection.set_option('enable_log', False)
416        fos = FortiOSHandler(connection, module, mkeyname)
417        versions_check_result = check_schema_versioning(fos, versioned_schema, "firewall_internet_service_custom_group")
418
419        is_error, has_changed, result = fortios_firewall(module.params, fos, module.check_mode)
420
421    else:
422        module.fail_json(**FAIL_SOCKET_MSG)
423
424    if versions_check_result and versions_check_result['matched'] is False:
425        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
426
427    if not is_error:
428        if versions_check_result and versions_check_result['matched'] is False:
429            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
430        else:
431            module.exit_json(changed=has_changed, meta=result)
432    else:
433        if versions_check_result and versions_check_result['matched'] is False:
434            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
435        else:
436            module.fail_json(msg="Error in repo", meta=result)
437
438
439if __name__ == '__main__':
440    main()
441