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