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_system_geoip_country
27short_description: Define geoip country name-ID table 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 system feature and geoip_country 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    system_geoip_country:
76        description:
77            - Define geoip country name-ID table.
78        default: null
79        type: dict
80        suboptions:
81            id:
82                description:
83                    - Country ID.
84                required: true
85                type: str
86            name:
87                description:
88                    - Country name.
89                type: str
90'''
91
92EXAMPLES = '''
93- hosts: fortigates
94  collections:
95    - fortinet.fortios
96  connection: httpapi
97  vars:
98   vdom: "root"
99   ansible_httpapi_use_ssl: yes
100   ansible_httpapi_validate_certs: no
101   ansible_httpapi_port: 443
102  tasks:
103  - name: Define geoip country name-ID table.
104    fortios_system_geoip_country:
105      vdom:  "{{ vdom }}"
106      state: "present"
107      access_token: "<your_own_value>"
108      system_geoip_country:
109        id:  "3"
110        name: "default_name_4"
111
112'''
113
114RETURN = '''
115build:
116  description: Build number of the fortigate image
117  returned: always
118  type: str
119  sample: '1547'
120http_method:
121  description: Last method used to provision the content into FortiGate
122  returned: always
123  type: str
124  sample: 'PUT'
125http_status:
126  description: Last result given by FortiGate on last operation applied
127  returned: always
128  type: str
129  sample: "200"
130mkey:
131  description: Master key (id) used in the last call to FortiGate
132  returned: success
133  type: str
134  sample: "id"
135name:
136  description: Name of the table used to fulfill the request
137  returned: always
138  type: str
139  sample: "urlfilter"
140path:
141  description: Path of the table used to fulfill the request
142  returned: always
143  type: str
144  sample: "webfilter"
145revision:
146  description: Internal revision number
147  returned: always
148  type: str
149  sample: "17.0.2.10658"
150serial:
151  description: Serial number of the unit
152  returned: always
153  type: str
154  sample: "FGVMEVYYQT3AB5352"
155status:
156  description: Indication of the operation's result
157  returned: always
158  type: str
159  sample: "success"
160vdom:
161  description: Virtual domain used
162  returned: always
163  type: str
164  sample: "root"
165version:
166  description: Version of the FortiGate
167  returned: always
168  type: str
169  sample: "v5.6.3"
170
171'''
172from ansible.module_utils.basic import AnsibleModule
173from ansible.module_utils.connection import Connection
174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
176from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
177from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
178from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
179from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
180from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
181
182
183def filter_system_geoip_country_data(json):
184    option_list = ['id', 'name']
185    dictionary = {}
186
187    for attribute in option_list:
188        if attribute in json and json[attribute] is not None:
189            dictionary[attribute] = json[attribute]
190
191    return dictionary
192
193
194def underscore_to_hyphen(data):
195    if isinstance(data, list):
196        for i, elem in enumerate(data):
197            data[i] = underscore_to_hyphen(elem)
198    elif isinstance(data, dict):
199        new_data = {}
200        for k, v in data.items():
201            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
202        data = new_data
203
204    return data
205
206
207def system_geoip_country(data, fos, check_mode=False):
208
209    vdom = data['vdom']
210
211    state = data['state']
212
213    system_geoip_country_data = data['system_geoip_country']
214    filtered_data = underscore_to_hyphen(filter_system_geoip_country_data(system_geoip_country_data))
215
216    # check_mode starts from here
217    if check_mode:
218        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
219        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
220        is_existed = current_data and current_data.get('http_status') == 200 \
221            and isinstance(current_data.get('results'), list) \
222            and len(current_data['results']) > 0
223
224        # 2. if it exists and the state is 'present' then compare current settings with desired
225        if state == 'present' or state is True:
226            if mkey is None:
227                return False, True, filtered_data
228
229            # if mkey exists then compare each other
230            # record exits and they're matched or not
231            if is_existed:
232                is_same = is_same_comparison(
233                    serialize(current_data['results'][0]), serialize(filtered_data))
234                return False, not is_same, filtered_data
235
236            # record does not exist
237            return False, True, filtered_data
238
239        if state == 'absent':
240            if mkey is None:
241                return False, False, filtered_data
242
243            if is_existed:
244                return False, True, filtered_data
245            return False, False, filtered_data
246
247        return True, False, {'reason: ': 'Must provide state parameter'}
248
249    if state == "present" or state is True:
250        return fos.set('system',
251                       'geoip-country',
252                       data=filtered_data,
253                       vdom=vdom)
254
255    elif state == "absent":
256        return fos.delete('system',
257                          'geoip-country',
258                          mkey=filtered_data['id'],
259                          vdom=vdom)
260    else:
261        fos._module.fail_json(msg='state must be present or absent!')
262
263
264def is_successful_status(status):
265    return status['status'] == "success" or \
266        status['http_method'] == "DELETE" and status['http_status'] == 404
267
268
269def fortios_system(data, fos, check_mode):
270
271    if data['system_geoip_country']:
272        resp = system_geoip_country(data, fos, check_mode)
273    else:
274        fos._module.fail_json(msg='missing task body: %s' % ('system_geoip_country'))
275    if check_mode:
276        return resp
277    return not is_successful_status(resp), \
278        resp['status'] == "success" and \
279        (resp['revision_changed'] if 'revision_changed' in resp else True), \
280        resp
281
282
283versioned_schema = {
284    "type": "list",
285    "children": {
286        "id": {
287            "type": "string",
288            "revisions": {
289                "v7.0.0": True,
290                "v6.4.4": True,
291                "v6.4.0": True,
292                "v6.4.1": True,
293                "v6.2.0": True,
294                "v6.2.5": True,
295                "v6.2.7": True
296            }
297        },
298        "name": {
299            "type": "string",
300            "revisions": {
301                "v7.0.0": True,
302                "v6.4.4": True,
303                "v6.4.0": True,
304                "v6.4.1": True,
305                "v6.2.0": True,
306                "v6.2.5": True,
307                "v6.2.7": True
308            }
309        }
310    },
311    "revisions": {
312        "v7.0.0": True,
313        "v6.4.4": True,
314        "v6.4.0": True,
315        "v6.4.1": True,
316        "v6.2.0": True,
317        "v6.2.5": True,
318        "v6.2.7": True
319    }
320}
321
322
323def main():
324    module_spec = schema_to_module_spec(versioned_schema)
325    mkeyname = 'id'
326    fields = {
327        "access_token": {"required": False, "type": "str", "no_log": True},
328        "enable_log": {"required": False, "type": bool},
329        "vdom": {"required": False, "type": "str", "default": "root"},
330        "state": {"required": True, "type": "str",
331                  "choices": ["present", "absent"]},
332        "system_geoip_country": {
333            "required": False, "type": "dict", "default": None,
334            "options": {
335            }
336        }
337    }
338    for attribute_name in module_spec['options']:
339        fields["system_geoip_country"]['options'][attribute_name] = module_spec['options'][attribute_name]
340        if mkeyname and mkeyname == attribute_name:
341            fields["system_geoip_country"]['options'][attribute_name]['required'] = True
342
343    check_legacy_fortiosapi()
344    module = AnsibleModule(argument_spec=fields,
345                           supports_check_mode=True)
346
347    versions_check_result = None
348    if module._socket_path:
349        connection = Connection(module._socket_path)
350        if 'access_token' in module.params:
351            connection.set_option('access_token', module.params['access_token'])
352
353        if 'enable_log' in module.params:
354            connection.set_option('enable_log', module.params['enable_log'])
355        else:
356            connection.set_option('enable_log', False)
357        fos = FortiOSHandler(connection, module, mkeyname)
358        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_geoip_country")
359
360        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
361
362    else:
363        module.fail_json(**FAIL_SOCKET_MSG)
364
365    if versions_check_result and versions_check_result['matched'] is False:
366        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
367
368    if not is_error:
369        if versions_check_result and versions_check_result['matched'] is False:
370            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
371        else:
372            module.exit_json(changed=has_changed, meta=result)
373    else:
374        if versions_check_result and versions_check_result['matched'] is False:
375            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
376        else:
377            module.fail_json(msg="Error in repo", meta=result)
378
379
380if __name__ == '__main__':
381    main()
382