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