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_endpoint_control_forticlient_registration_sync
27short_description: Configure FortiClient registration synchronization settings 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 endpoint_control feature and forticlient_registration_sync 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    endpoint_control_forticlient_registration_sync:
76        description:
77            - Configure FortiClient registration synchronization settings.
78        default: null
79        type: dict
80        suboptions:
81            peer_ip:
82                description:
83                    - IP address of the peer FortiGate for endpoint license synchronization.
84                type: str
85            peer_name:
86                description:
87                    - Peer name.
88                type: str
89'''
90
91EXAMPLES = '''
92- hosts: fortigates
93  collections:
94    - fortinet.fortios
95  connection: httpapi
96  vars:
97   vdom: "root"
98   ansible_httpapi_use_ssl: yes
99   ansible_httpapi_validate_certs: no
100   ansible_httpapi_port: 443
101  tasks:
102  - name: Configure FortiClient registration synchronization settings.
103    fortios_endpoint_control_forticlient_registration_sync:
104      vdom:  "{{ vdom }}"
105      state: "present"
106      access_token: "<your_own_value>"
107      endpoint_control_forticlient_registration_sync:
108        peer_ip: "<your_own_value>"
109        peer_name: "<your_own_value>"
110
111'''
112
113RETURN = '''
114build:
115  description: Build number of the fortigate image
116  returned: always
117  type: str
118  sample: '1547'
119http_method:
120  description: Last method used to provision the content into FortiGate
121  returned: always
122  type: str
123  sample: 'PUT'
124http_status:
125  description: Last result given by FortiGate on last operation applied
126  returned: always
127  type: str
128  sample: "200"
129mkey:
130  description: Master key (id) used in the last call to FortiGate
131  returned: success
132  type: str
133  sample: "id"
134name:
135  description: Name of the table used to fulfill the request
136  returned: always
137  type: str
138  sample: "urlfilter"
139path:
140  description: Path of the table used to fulfill the request
141  returned: always
142  type: str
143  sample: "webfilter"
144revision:
145  description: Internal revision number
146  returned: always
147  type: str
148  sample: "17.0.2.10658"
149serial:
150  description: Serial number of the unit
151  returned: always
152  type: str
153  sample: "FGVMEVYYQT3AB5352"
154status:
155  description: Indication of the operation's result
156  returned: always
157  type: str
158  sample: "success"
159vdom:
160  description: Virtual domain used
161  returned: always
162  type: str
163  sample: "root"
164version:
165  description: Version of the FortiGate
166  returned: always
167  type: str
168  sample: "v5.6.3"
169
170'''
171from ansible.module_utils.basic import AnsibleModule
172from ansible.module_utils.connection import Connection
173from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
176from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
177from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
178from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
179from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
180
181
182def filter_endpoint_control_forticlient_registration_sync_data(json):
183    option_list = ['peer_ip', 'peer_name']
184    dictionary = {}
185
186    for attribute in option_list:
187        if attribute in json and json[attribute] is not None:
188            dictionary[attribute] = json[attribute]
189
190    return dictionary
191
192
193def underscore_to_hyphen(data):
194    if isinstance(data, list):
195        for i, elem in enumerate(data):
196            data[i] = underscore_to_hyphen(elem)
197    elif isinstance(data, dict):
198        new_data = {}
199        for k, v in data.items():
200            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
201        data = new_data
202
203    return data
204
205
206def endpoint_control_forticlient_registration_sync(data, fos, check_mode=False):
207
208    vdom = data['vdom']
209
210    state = data['state']
211
212    endpoint_control_forticlient_registration_sync_data = data['endpoint_control_forticlient_registration_sync']
213    filtered_data = underscore_to_hyphen(filter_endpoint_control_forticlient_registration_sync_data(endpoint_control_forticlient_registration_sync_data))
214
215    # check_mode starts from here
216    if check_mode:
217        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
218        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
219        is_existed = current_data and current_data.get('http_status') == 200 \
220            and isinstance(current_data.get('results'), list) \
221            and len(current_data['results']) > 0
222
223        # 2. if it exists and the state is 'present' then compare current settings with desired
224        if state == 'present' or state is True:
225            if mkey is None:
226                return False, True, filtered_data
227
228            # if mkey exists then compare each other
229            # record exits and they're matched or not
230            if is_existed:
231                is_same = is_same_comparison(
232                    serialize(current_data['results'][0]), serialize(filtered_data))
233                return False, not is_same, filtered_data
234
235            # record does not exist
236            return False, True, filtered_data
237
238        if state == 'absent':
239            if mkey is None:
240                return False, False, filtered_data
241
242            if is_existed:
243                return False, True, filtered_data
244            return False, False, filtered_data
245
246        return True, False, {'reason: ': 'Must provide state parameter'}
247
248    if state == "present" or state is True:
249        return fos.set('endpoint-control',
250                       'forticlient-registration-sync',
251                       data=filtered_data,
252                       vdom=vdom)
253
254    elif state == "absent":
255        return fos.delete('endpoint-control',
256                          'forticlient-registration-sync',
257                          mkey=filtered_data['peer-name'],
258                          vdom=vdom)
259    else:
260        fos._module.fail_json(msg='state must be present or absent!')
261
262
263def is_successful_status(status):
264    return status['status'] == "success" or \
265        status['http_method'] == "DELETE" and status['http_status'] == 404
266
267
268def fortios_endpoint_control(data, fos, check_mode):
269
270    if data['endpoint_control_forticlient_registration_sync']:
271        resp = endpoint_control_forticlient_registration_sync(data, fos, check_mode)
272    else:
273        fos._module.fail_json(msg='missing task body: %s' % ('endpoint_control_forticlient_registration_sync'))
274    if check_mode:
275        return resp
276    return not is_successful_status(resp), \
277        resp['status'] == "success" and \
278        (resp['revision_changed'] if 'revision_changed' in resp else True), \
279        resp
280
281
282versioned_schema = {
283    "type": "list",
284    "children": {
285        "peer_ip": {
286            "type": "string",
287            "revisions": {
288                "v6.0.11": True,
289                "v6.0.0": True,
290                "v6.0.5": True
291            }
292        },
293        "peer_name": {
294            "type": "string",
295            "revisions": {
296                "v6.0.11": True,
297                "v6.0.0": True,
298                "v6.0.5": True
299            }
300        }
301    },
302    "revisions": {
303        "v6.0.11": True,
304        "v6.0.0": True,
305        "v6.0.5": True
306    }
307}
308
309
310def main():
311    module_spec = schema_to_module_spec(versioned_schema)
312    mkeyname = 'peer-name'
313    fields = {
314        "access_token": {"required": False, "type": "str", "no_log": True},
315        "enable_log": {"required": False, "type": bool},
316        "vdom": {"required": False, "type": "str", "default": "root"},
317        "state": {"required": True, "type": "str",
318                  "choices": ["present", "absent"]},
319        "endpoint_control_forticlient_registration_sync": {
320            "required": False, "type": "dict", "default": None,
321            "options": {
322            }
323        }
324    }
325    for attribute_name in module_spec['options']:
326        fields["endpoint_control_forticlient_registration_sync"]['options'][attribute_name] = module_spec['options'][attribute_name]
327        if mkeyname and mkeyname == attribute_name:
328            fields["endpoint_control_forticlient_registration_sync"]['options'][attribute_name]['required'] = True
329
330    check_legacy_fortiosapi()
331    module = AnsibleModule(argument_spec=fields,
332                           supports_check_mode=True)
333
334    versions_check_result = None
335    if module._socket_path:
336        connection = Connection(module._socket_path)
337        if 'access_token' in module.params:
338            connection.set_option('access_token', module.params['access_token'])
339
340        if 'enable_log' in module.params:
341            connection.set_option('enable_log', module.params['enable_log'])
342        else:
343            connection.set_option('enable_log', False)
344        fos = FortiOSHandler(connection, module, mkeyname)
345        versions_check_result = check_schema_versioning(fos, versioned_schema, "endpoint_control_forticlient_registration_sync")
346
347        is_error, has_changed, result = fortios_endpoint_control(module.params, fos, module.check_mode)
348
349    else:
350        module.fail_json(**FAIL_SOCKET_MSG)
351
352    if versions_check_result and versions_check_result['matched'] is False:
353        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
354
355    if not is_error:
356        if versions_check_result and versions_check_result['matched'] is False:
357            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
358        else:
359            module.exit_json(changed=has_changed, meta=result)
360    else:
361        if versions_check_result and versions_check_result['matched'] is False:
362            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
363        else:
364            module.fail_json(msg="Error in repo", meta=result)
365
366
367if __name__ == '__main__':
368    main()
369