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