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