1#!/usr/bin/python
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019 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_webfilter_content
27short_description: Configure Web filter banned word 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 webfilter feature and content category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.5
33version_added: "2.8"
34author:
35    - Miguel Angel Munoz (@mamunozgonzalez)
36    - Nicolas Thomas (@thomnico)
37notes:
38    - Requires fortiosapi library developed by Fortinet
39    - Run as a local_action in your playbook
40requirements:
41    - fortiosapi>=0.9.8
42options:
43    host:
44        description:
45            - FortiOS or FortiGate IP address.
46        type: str
47        required: false
48    username:
49        description:
50            - FortiOS or FortiGate username.
51        type: str
52        required: false
53    password:
54        description:
55            - FortiOS or FortiGate password.
56        type: str
57        default: ""
58    vdom:
59        description:
60            - Virtual domain, among those defined previously. A vdom is a
61              virtual instance of the FortiGate that can be configured and
62              used as a different unit.
63        type: str
64        default: root
65    https:
66        description:
67            - Indicates if the requests towards FortiGate must use HTTPS protocol.
68        type: bool
69        default: true
70    ssl_verify:
71        description:
72            - Ensures FortiGate certificate must be verified by a proper CA.
73        type: bool
74        default: true
75        version_added: 2.9
76    state:
77        description:
78            - Indicates whether to create or remove the object.
79              This attribute was present already in previous version in a deeper level.
80              It has been moved out to this outer level.
81        type: str
82        required: false
83        choices:
84            - present
85            - absent
86        version_added: 2.9
87    webfilter_content:
88        description:
89            - Configure Web filter banned word table.
90        default: null
91        type: dict
92        suboptions:
93            state:
94                description:
95                    - B(Deprecated)
96                    - Starting with Ansible 2.9 we recommend using the top-level 'state' parameter.
97                    - HORIZONTALLINE
98                    - Indicates whether to create or remove the object.
99                type: str
100                required: false
101                choices:
102                    - present
103                    - absent
104            comment:
105                description:
106                    - Optional comments.
107                type: str
108            entries:
109                description:
110                    - Configure banned word entries.
111                type: list
112                suboptions:
113                    action:
114                        description:
115                            - Block or exempt word when a match is found.
116                        type: str
117                        choices:
118                            - block
119                            - exempt
120                    lang:
121                        description:
122                            - Language of banned word.
123                        type: str
124                        choices:
125                            - western
126                            - simch
127                            - trach
128                            - japanese
129                            - korean
130                            - french
131                            - thai
132                            - spanish
133                            - cyrillic
134                    name:
135                        description:
136                            - Banned word.
137                        required: true
138                        type: str
139                    pattern_type:
140                        description:
141                            - "Banned word pattern type: wildcard pattern or Perl regular expression."
142                        type: str
143                        choices:
144                            - wildcard
145                            - regexp
146                    score:
147                        description:
148                            - Score, to be applied every time the word appears on a web page (0 - 4294967295).
149                        type: int
150                    status:
151                        description:
152                            - Enable/disable banned word.
153                        type: str
154                        choices:
155                            - enable
156                            - disable
157            id:
158                description:
159                    - ID.
160                required: true
161                type: int
162            name:
163                description:
164                    - Name of table.
165                type: str
166'''
167
168EXAMPLES = '''
169- hosts: localhost
170  vars:
171   host: "192.168.122.40"
172   username: "admin"
173   password: ""
174   vdom: "root"
175   ssl_verify: "False"
176  tasks:
177  - name: Configure Web filter banned word table.
178    fortios_webfilter_content:
179      host:  "{{ host }}"
180      username: "{{ username }}"
181      password: "{{ password }}"
182      vdom:  "{{ vdom }}"
183      https: "False"
184      state: "present"
185      webfilter_content:
186        comment: "Optional comments."
187        entries:
188         -
189            action: "block"
190            lang: "western"
191            name: "default_name_7"
192            pattern_type: "wildcard"
193            score: "9"
194            status: "enable"
195        id:  "11"
196        name: "default_name_12"
197'''
198
199RETURN = '''
200build:
201  description: Build number of the fortigate image
202  returned: always
203  type: str
204  sample: '1547'
205http_method:
206  description: Last method used to provision the content into FortiGate
207  returned: always
208  type: str
209  sample: 'PUT'
210http_status:
211  description: Last result given by FortiGate on last operation applied
212  returned: always
213  type: str
214  sample: "200"
215mkey:
216  description: Master key (id) used in the last call to FortiGate
217  returned: success
218  type: str
219  sample: "id"
220name:
221  description: Name of the table used to fulfill the request
222  returned: always
223  type: str
224  sample: "urlfilter"
225path:
226  description: Path of the table used to fulfill the request
227  returned: always
228  type: str
229  sample: "webfilter"
230revision:
231  description: Internal revision number
232  returned: always
233  type: str
234  sample: "17.0.2.10658"
235serial:
236  description: Serial number of the unit
237  returned: always
238  type: str
239  sample: "FGVMEVYYQT3AB5352"
240status:
241  description: Indication of the operation's result
242  returned: always
243  type: str
244  sample: "success"
245vdom:
246  description: Virtual domain used
247  returned: always
248  type: str
249  sample: "root"
250version:
251  description: Version of the FortiGate
252  returned: always
253  type: str
254  sample: "v5.6.3"
255
256'''
257
258from ansible.module_utils.basic import AnsibleModule
259from ansible.module_utils.connection import Connection
260from ansible.module_utils.network.fortios.fortios import FortiOSHandler
261from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
262
263
264def login(data, fos):
265    host = data['host']
266    username = data['username']
267    password = data['password']
268    ssl_verify = data['ssl_verify']
269
270    fos.debug('on')
271    if 'https' in data and not data['https']:
272        fos.https('off')
273    else:
274        fos.https('on')
275
276    fos.login(host, username, password, verify=ssl_verify)
277
278
279def filter_webfilter_content_data(json):
280    option_list = ['comment', 'entries', 'id',
281                   'name']
282    dictionary = {}
283
284    for attribute in option_list:
285        if attribute in json and json[attribute] is not None:
286            dictionary[attribute] = json[attribute]
287
288    return dictionary
289
290
291def underscore_to_hyphen(data):
292    if isinstance(data, list):
293        for elem in data:
294            elem = underscore_to_hyphen(elem)
295    elif isinstance(data, dict):
296        new_data = {}
297        for k, v in data.items():
298            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
299        data = new_data
300
301    return data
302
303
304def webfilter_content(data, fos):
305    vdom = data['vdom']
306    if 'state' in data and data['state']:
307        state = data['state']
308    elif 'state' in data['webfilter_content'] and data['webfilter_content']:
309        state = data['webfilter_content']['state']
310    else:
311        state = True
312    webfilter_content_data = data['webfilter_content']
313    filtered_data = underscore_to_hyphen(filter_webfilter_content_data(webfilter_content_data))
314
315    if state == "present":
316        return fos.set('webfilter',
317                       'content',
318                       data=filtered_data,
319                       vdom=vdom)
320
321    elif state == "absent":
322        return fos.delete('webfilter',
323                          'content',
324                          mkey=filtered_data['id'],
325                          vdom=vdom)
326
327
328def is_successful_status(status):
329    return status['status'] == "success" or \
330        status['http_method'] == "DELETE" and status['http_status'] == 404
331
332
333def fortios_webfilter(data, fos):
334
335    if data['webfilter_content']:
336        resp = webfilter_content(data, fos)
337
338    return not is_successful_status(resp), \
339        resp['status'] == "success", \
340        resp
341
342
343def main():
344    fields = {
345        "host": {"required": False, "type": "str"},
346        "username": {"required": False, "type": "str"},
347        "password": {"required": False, "type": "str", "default": "", "no_log": True},
348        "vdom": {"required": False, "type": "str", "default": "root"},
349        "https": {"required": False, "type": "bool", "default": True},
350        "ssl_verify": {"required": False, "type": "bool", "default": True},
351        "state": {"required": False, "type": "str",
352                  "choices": ["present", "absent"]},
353        "webfilter_content": {
354            "required": False, "type": "dict", "default": None,
355            "options": {
356                "state": {"required": False, "type": "str",
357                          "choices": ["present", "absent"]},
358                "comment": {"required": False, "type": "str"},
359                "entries": {"required": False, "type": "list",
360                            "options": {
361                                "action": {"required": False, "type": "str",
362                                           "choices": ["block", "exempt"]},
363                                "lang": {"required": False, "type": "str",
364                                         "choices": ["western", "simch", "trach",
365                                                     "japanese", "korean", "french",
366                                                     "thai", "spanish", "cyrillic"]},
367                                "name": {"required": True, "type": "str"},
368                                "pattern_type": {"required": False, "type": "str",
369                                                 "choices": ["wildcard", "regexp"]},
370                                "score": {"required": False, "type": "int"},
371                                "status": {"required": False, "type": "str",
372                                           "choices": ["enable", "disable"]}
373                            }},
374                "id": {"required": True, "type": "int"},
375                "name": {"required": False, "type": "str"}
376
377            }
378        }
379    }
380
381    module = AnsibleModule(argument_spec=fields,
382                           supports_check_mode=False)
383
384    # legacy_mode refers to using fortiosapi instead of HTTPAPI
385    legacy_mode = 'host' in module.params and module.params['host'] is not None and \
386                  'username' in module.params and module.params['username'] is not None and \
387                  'password' in module.params and module.params['password'] is not None
388
389    if not legacy_mode:
390        if module._socket_path:
391            connection = Connection(module._socket_path)
392            fos = FortiOSHandler(connection)
393
394            is_error, has_changed, result = fortios_webfilter(module.params, fos)
395        else:
396            module.fail_json(**FAIL_SOCKET_MSG)
397    else:
398        try:
399            from fortiosapi import FortiOSAPI
400        except ImportError:
401            module.fail_json(msg="fortiosapi module is required")
402
403        fos = FortiOSAPI()
404
405        login(module.params, fos)
406        is_error, has_changed, result = fortios_webfilter(module.params, fos)
407        fos.logout()
408
409    if not is_error:
410        module.exit_json(changed=has_changed, meta=result)
411    else:
412        module.fail_json(msg="Error in repo", meta=result)
413
414
415if __name__ == '__main__':
416    main()
417