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