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_antivirus_notification
27short_description: Configure AntiVirus notification lists 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 antivirus feature and notification 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    antivirus_notification:
76        description:
77            - Configure AntiVirus notification lists.
78        default: null
79        type: dict
80        suboptions:
81            comment:
82                description:
83                    - Optional comments.
84                type: str
85            entries:
86                description:
87                    - modify this antivirus notification list
88                type: list
89                suboptions:
90                    name:
91                        description:
92                            - virus name or prefix
93                        required: true
94                        type: str
95                    prefix:
96                        description:
97                            - enable prefix matches based on this name
98                        type: str
99                        choices:
100                            - enable
101                            - disable
102                    status:
103                        description:
104                            - enable this entry for notifications
105                        type: str
106                        choices:
107                            - enable
108                            - disable
109            id:
110                description:
111                    - ID.
112                required: true
113                type: int
114            name:
115                description:
116                    - Name of table.
117                type: str
118'''
119
120EXAMPLES = '''
121- hosts: fortigates
122  collections:
123    - fortinet.fortios
124  connection: httpapi
125  vars:
126   vdom: "root"
127   ansible_httpapi_use_ssl: yes
128   ansible_httpapi_validate_certs: no
129   ansible_httpapi_port: 443
130  tasks:
131  - name: Configure AntiVirus notification lists.
132    fortios_antivirus_notification:
133      vdom:  "{{ vdom }}"
134      state: "present"
135      access_token: "<your_own_value>"
136      antivirus_notification:
137        comment: "Optional comments."
138        entries:
139         -
140            name: "default_name_5"
141            prefix: "enable"
142            status: "enable"
143        id:  "8"
144        name: "default_name_9"
145
146'''
147
148RETURN = '''
149build:
150  description: Build number of the fortigate image
151  returned: always
152  type: str
153  sample: '1547'
154http_method:
155  description: Last method used to provision the content into FortiGate
156  returned: always
157  type: str
158  sample: 'PUT'
159http_status:
160  description: Last result given by FortiGate on last operation applied
161  returned: always
162  type: str
163  sample: "200"
164mkey:
165  description: Master key (id) used in the last call to FortiGate
166  returned: success
167  type: str
168  sample: "id"
169name:
170  description: Name of the table used to fulfill the request
171  returned: always
172  type: str
173  sample: "urlfilter"
174path:
175  description: Path of the table used to fulfill the request
176  returned: always
177  type: str
178  sample: "webfilter"
179revision:
180  description: Internal revision number
181  returned: always
182  type: str
183  sample: "17.0.2.10658"
184serial:
185  description: Serial number of the unit
186  returned: always
187  type: str
188  sample: "FGVMEVYYQT3AB5352"
189status:
190  description: Indication of the operation's result
191  returned: always
192  type: str
193  sample: "success"
194vdom:
195  description: Virtual domain used
196  returned: always
197  type: str
198  sample: "root"
199version:
200  description: Version of the FortiGate
201  returned: always
202  type: str
203  sample: "v5.6.3"
204
205'''
206from ansible.module_utils.basic import AnsibleModule
207from ansible.module_utils.connection import Connection
208from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
209from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
210from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
211from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
212from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
213from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
214from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
215
216
217def filter_antivirus_notification_data(json):
218    option_list = ['comment', 'entries', 'id',
219                   'name']
220    dictionary = {}
221
222    for attribute in option_list:
223        if attribute in json and json[attribute] is not None:
224            dictionary[attribute] = json[attribute]
225
226    return dictionary
227
228
229def underscore_to_hyphen(data):
230    if isinstance(data, list):
231        for i, elem in enumerate(data):
232            data[i] = underscore_to_hyphen(elem)
233    elif isinstance(data, dict):
234        new_data = {}
235        for k, v in data.items():
236            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
237        data = new_data
238
239    return data
240
241
242def antivirus_notification(data, fos, check_mode=False):
243
244    vdom = data['vdom']
245
246    state = data['state']
247
248    antivirus_notification_data = data['antivirus_notification']
249    filtered_data = underscore_to_hyphen(filter_antivirus_notification_data(antivirus_notification_data))
250
251    # check_mode starts from here
252    if check_mode:
253        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
254        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
255        is_existed = current_data and current_data.get('http_status') == 200 \
256            and isinstance(current_data.get('results'), list) \
257            and len(current_data['results']) > 0
258
259        # 2. if it exists and the state is 'present' then compare current settings with desired
260        if state == 'present' or state is True:
261            if mkey is None:
262                return False, True, filtered_data
263
264            # if mkey exists then compare each other
265            # record exits and they're matched or not
266            if is_existed:
267                is_same = is_same_comparison(
268                    serialize(current_data['results'][0]), serialize(filtered_data))
269                return False, not is_same, filtered_data
270
271            # record does not exist
272            return False, True, filtered_data
273
274        if state == 'absent':
275            if mkey is None:
276                return False, False, filtered_data
277
278            if is_existed:
279                return False, True, filtered_data
280            return False, False, filtered_data
281
282        return True, False, {'reason: ': 'Must provide state parameter'}
283
284    if state == "present" or state is True:
285        return fos.set('antivirus',
286                       'notification',
287                       data=filtered_data,
288                       vdom=vdom)
289
290    elif state == "absent":
291        return fos.delete('antivirus',
292                          'notification',
293                          mkey=filtered_data['id'],
294                          vdom=vdom)
295    else:
296        fos._module.fail_json(msg='state must be present or absent!')
297
298
299def is_successful_status(status):
300    return status['status'] == "success" or \
301        status['http_method'] == "DELETE" and status['http_status'] == 404
302
303
304def fortios_antivirus(data, fos, check_mode):
305
306    if data['antivirus_notification']:
307        resp = antivirus_notification(data, fos, check_mode)
308    else:
309        fos._module.fail_json(msg='missing task body: %s' % ('antivirus_notification'))
310    if check_mode:
311        return resp
312    return not is_successful_status(resp), \
313        resp['status'] == "success" and \
314        (resp['revision_changed'] if 'revision_changed' in resp else True), \
315        resp
316
317
318versioned_schema = {
319    "type": "list",
320    "children": {
321        "comment": {
322            "type": "string",
323            "revisions": {
324                "v6.0.0": True,
325                "v6.0.5": True,
326                "v6.2.0": True,
327                "v6.2.3": True,
328                "v6.2.5": True,
329                "v6.2.7": True,
330                "v6.0.11": True
331            }
332        },
333        "entries": {
334            "type": "list",
335            "children": {
336                "status": {
337                    "type": "string",
338                    "options": [
339                        {
340                            "value": "enable",
341                            "revisions": {
342                                "v6.0.0": True,
343                                "v6.0.5": True,
344                                "v6.2.0": True,
345                                "v6.2.3": True,
346                                "v6.2.5": True,
347                                "v6.2.7": True,
348                                "v6.0.11": True
349                            }
350                        },
351                        {
352                            "value": "disable",
353                            "revisions": {
354                                "v6.0.0": True,
355                                "v6.0.5": True,
356                                "v6.2.0": True,
357                                "v6.2.3": True,
358                                "v6.2.5": True,
359                                "v6.2.7": True,
360                                "v6.0.11": True
361                            }
362                        }
363                    ],
364                    "revisions": {
365                        "v6.0.0": True,
366                        "v6.0.5": True,
367                        "v6.2.0": True,
368                        "v6.2.3": True,
369                        "v6.2.5": True,
370                        "v6.2.7": True,
371                        "v6.0.11": True
372                    }
373                },
374                "prefix": {
375                    "type": "string",
376                    "options": [
377                        {
378                            "value": "enable",
379                            "revisions": {
380                                "v6.0.0": True,
381                                "v6.0.5": True,
382                                "v6.2.0": True,
383                                "v6.2.3": True,
384                                "v6.2.5": True,
385                                "v6.2.7": True,
386                                "v6.0.11": True
387                            }
388                        },
389                        {
390                            "value": "disable",
391                            "revisions": {
392                                "v6.0.0": True,
393                                "v6.0.5": True,
394                                "v6.2.0": True,
395                                "v6.2.3": True,
396                                "v6.2.5": True,
397                                "v6.2.7": True,
398                                "v6.0.11": True
399                            }
400                        }
401                    ],
402                    "revisions": {
403                        "v6.0.0": True,
404                        "v6.0.5": True,
405                        "v6.2.0": True,
406                        "v6.2.3": True,
407                        "v6.2.5": True,
408                        "v6.2.7": True,
409                        "v6.0.11": True
410                    }
411                },
412                "name": {
413                    "type": "string",
414                    "revisions": {
415                        "v6.0.0": True,
416                        "v6.0.5": True,
417                        "v6.2.0": True,
418                        "v6.2.3": True,
419                        "v6.2.5": True,
420                        "v6.2.7": True,
421                        "v6.0.11": True
422                    }
423                }
424            },
425            "revisions": {
426                "v6.0.0": True,
427                "v6.0.5": True,
428                "v6.2.0": True,
429                "v6.2.3": True,
430                "v6.2.5": True,
431                "v6.2.7": True,
432                "v6.0.11": True
433            }
434        },
435        "id": {
436            "type": "integer",
437            "revisions": {
438                "v6.0.0": True,
439                "v6.0.5": True,
440                "v6.2.0": True,
441                "v6.2.3": True,
442                "v6.2.5": True,
443                "v6.2.7": True,
444                "v6.0.11": True
445            }
446        },
447        "name": {
448            "type": "string",
449            "revisions": {
450                "v6.0.0": True,
451                "v6.0.5": True,
452                "v6.2.0": True,
453                "v6.2.3": True,
454                "v6.2.5": True,
455                "v6.2.7": True,
456                "v6.0.11": True
457            }
458        }
459    },
460    "revisions": {
461        "v6.0.0": True,
462        "v6.0.5": True,
463        "v6.2.0": True,
464        "v6.2.3": True,
465        "v6.2.5": True,
466        "v6.2.7": True,
467        "v6.0.11": True
468    }
469}
470
471
472def main():
473    module_spec = schema_to_module_spec(versioned_schema)
474    mkeyname = 'id'
475    fields = {
476        "access_token": {"required": False, "type": "str", "no_log": True},
477        "enable_log": {"required": False, "type": bool},
478        "vdom": {"required": False, "type": "str", "default": "root"},
479        "state": {"required": True, "type": "str",
480                  "choices": ["present", "absent"]},
481        "antivirus_notification": {
482            "required": False, "type": "dict", "default": None,
483            "options": {
484            }
485        }
486    }
487    for attribute_name in module_spec['options']:
488        fields["antivirus_notification"]['options'][attribute_name] = module_spec['options'][attribute_name]
489        if mkeyname and mkeyname == attribute_name:
490            fields["antivirus_notification"]['options'][attribute_name]['required'] = True
491
492    check_legacy_fortiosapi()
493    module = AnsibleModule(argument_spec=fields,
494                           supports_check_mode=True)
495
496    versions_check_result = None
497    if module._socket_path:
498        connection = Connection(module._socket_path)
499        if 'access_token' in module.params:
500            connection.set_option('access_token', module.params['access_token'])
501
502        if 'enable_log' in module.params:
503            connection.set_option('enable_log', module.params['enable_log'])
504        else:
505            connection.set_option('enable_log', False)
506        fos = FortiOSHandler(connection, module, mkeyname)
507        versions_check_result = check_schema_versioning(fos, versioned_schema, "antivirus_notification")
508
509        is_error, has_changed, result = fortios_antivirus(module.params, fos, module.check_mode)
510
511    else:
512        module.fail_json(**FAIL_SOCKET_MSG)
513
514    if versions_check_result and versions_check_result['matched'] is False:
515        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
516
517    if not is_error:
518        if versions_check_result and versions_check_result['matched'] is False:
519            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
520        else:
521            module.exit_json(changed=has_changed, meta=result)
522    else:
523        if versions_check_result and versions_check_result['matched'] is False:
524            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
525        else:
526            module.fail_json(msg="Error in repo", meta=result)
527
528
529if __name__ == '__main__':
530    main()
531