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_fsso_polling
27short_description: Configure FSSO active directory servers for polling mode 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 fsso_polling 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_fsso_polling:
76        description:
77            - Configure FSSO active directory servers for polling mode.
78        default: null
79        type: dict
80        suboptions:
81            adgrp:
82                description:
83                    - LDAP Group Info.
84                type: list
85                suboptions:
86                    name:
87                        description:
88                            - Name.
89                        required: true
90                        type: str
91            default_domain:
92                description:
93                    - Default domain managed by this Active Directory server.
94                type: str
95            id:
96                description:
97                    - Active Directory server ID.
98                required: true
99                type: int
100            ldap_server:
101                description:
102                    - LDAP server name used in LDAP connection strings. Source user.ldap.name.
103                type: str
104            logon_history:
105                description:
106                    - Number of hours of logon history to keep, 0 means keep all history.
107                type: int
108            password:
109                description:
110                    - Password required to log into this Active Directory server
111                type: str
112            polling_frequency:
113                description:
114                    - Polling frequency (every 1 to 30 seconds).
115                type: int
116            port:
117                description:
118                    - Port to communicate with this Active Directory server.
119                type: int
120            server:
121                description:
122                    - Host name or IP address of the Active Directory server.
123                type: str
124            smb_ntlmv1_auth:
125                description:
126                    - Enable/disable support of NTLMv1 for Samba authentication.
127                type: str
128                choices:
129                    - enable
130                    - disable
131            smbv1:
132                description:
133                    - Enable/disable support of SMBv1 for Samba.
134                type: str
135                choices:
136                    - enable
137                    - disable
138            status:
139                description:
140                    - Enable/disable polling for the status of this Active Directory server.
141                type: str
142                choices:
143                    - enable
144                    - disable
145            user:
146                description:
147                    - User name required to log into this Active Directory server.
148                type: str
149'''
150
151EXAMPLES = '''
152- hosts: fortigates
153  collections:
154    - fortinet.fortios
155  connection: httpapi
156  vars:
157   vdom: "root"
158   ansible_httpapi_use_ssl: yes
159   ansible_httpapi_validate_certs: no
160   ansible_httpapi_port: 443
161  tasks:
162  - name: Configure FSSO active directory servers for polling mode.
163    fortios_user_fsso_polling:
164      vdom:  "{{ vdom }}"
165      state: "present"
166      access_token: "<your_own_value>"
167      user_fsso_polling:
168        adgrp:
169         -
170            name: "default_name_4"
171        default_domain: "<your_own_value>"
172        id:  "6"
173        ldap_server: "<your_own_value> (source user.ldap.name)"
174        logon_history: "8"
175        password: "<your_own_value>"
176        polling_frequency: "10"
177        port: "11"
178        server: "192.168.100.40"
179        smb_ntlmv1_auth: "enable"
180        smbv1: "enable"
181        status: "enable"
182        user: "<your_own_value>"
183
184'''
185
186RETURN = '''
187build:
188  description: Build number of the fortigate image
189  returned: always
190  type: str
191  sample: '1547'
192http_method:
193  description: Last method used to provision the content into FortiGate
194  returned: always
195  type: str
196  sample: 'PUT'
197http_status:
198  description: Last result given by FortiGate on last operation applied
199  returned: always
200  type: str
201  sample: "200"
202mkey:
203  description: Master key (id) used in the last call to FortiGate
204  returned: success
205  type: str
206  sample: "id"
207name:
208  description: Name of the table used to fulfill the request
209  returned: always
210  type: str
211  sample: "urlfilter"
212path:
213  description: Path of the table used to fulfill the request
214  returned: always
215  type: str
216  sample: "webfilter"
217revision:
218  description: Internal revision number
219  returned: always
220  type: str
221  sample: "17.0.2.10658"
222serial:
223  description: Serial number of the unit
224  returned: always
225  type: str
226  sample: "FGVMEVYYQT3AB5352"
227status:
228  description: Indication of the operation's result
229  returned: always
230  type: str
231  sample: "success"
232vdom:
233  description: Virtual domain used
234  returned: always
235  type: str
236  sample: "root"
237version:
238  description: Version of the FortiGate
239  returned: always
240  type: str
241  sample: "v5.6.3"
242
243'''
244from ansible.module_utils.basic import AnsibleModule
245from ansible.module_utils.connection import Connection
246from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
247from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
248from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
249from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
250from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
251from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
252from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
253
254
255def filter_user_fsso_polling_data(json):
256    option_list = ['adgrp', 'default_domain', 'id',
257                   'ldap_server', 'logon_history', 'password',
258                   'polling_frequency', 'port', 'server',
259                   'smb_ntlmv1_auth', 'smbv1', 'status',
260                   'user']
261    dictionary = {}
262
263    for attribute in option_list:
264        if attribute in json and json[attribute] is not None:
265            dictionary[attribute] = json[attribute]
266
267    return dictionary
268
269
270def underscore_to_hyphen(data):
271    if isinstance(data, list):
272        for i, elem in enumerate(data):
273            data[i] = underscore_to_hyphen(elem)
274    elif isinstance(data, dict):
275        new_data = {}
276        for k, v in data.items():
277            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
278        data = new_data
279
280    return data
281
282
283def user_fsso_polling(data, fos, check_mode=False):
284
285    vdom = data['vdom']
286
287    state = data['state']
288
289    user_fsso_polling_data = data['user_fsso_polling']
290    filtered_data = underscore_to_hyphen(filter_user_fsso_polling_data(user_fsso_polling_data))
291
292    # check_mode starts from here
293    if check_mode:
294        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
295        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
296        is_existed = current_data and current_data.get('http_status') == 200 \
297            and isinstance(current_data.get('results'), list) \
298            and len(current_data['results']) > 0
299
300        # 2. if it exists and the state is 'present' then compare current settings with desired
301        if state == 'present' or state is True:
302            if mkey is None:
303                return False, True, filtered_data
304
305            # if mkey exists then compare each other
306            # record exits and they're matched or not
307            if is_existed:
308                is_same = is_same_comparison(
309                    serialize(current_data['results'][0]), serialize(filtered_data))
310                return False, not is_same, filtered_data
311
312            # record does not exist
313            return False, True, filtered_data
314
315        if state == 'absent':
316            if mkey is None:
317                return False, False, filtered_data
318
319            if is_existed:
320                return False, True, filtered_data
321            return False, False, filtered_data
322
323        return True, False, {'reason: ': 'Must provide state parameter'}
324
325    if state == "present" or state is True:
326        return fos.set('user',
327                       'fsso-polling',
328                       data=filtered_data,
329                       vdom=vdom)
330
331    elif state == "absent":
332        return fos.delete('user',
333                          'fsso-polling',
334                          mkey=filtered_data['id'],
335                          vdom=vdom)
336    else:
337        fos._module.fail_json(msg='state must be present or absent!')
338
339
340def is_successful_status(status):
341    return status['status'] == "success" or \
342        status['http_method'] == "DELETE" and status['http_status'] == 404
343
344
345def fortios_user(data, fos, check_mode):
346
347    if data['user_fsso_polling']:
348        resp = user_fsso_polling(data, fos, check_mode)
349    else:
350        fos._module.fail_json(msg='missing task body: %s' % ('user_fsso_polling'))
351    if check_mode:
352        return resp
353    return not is_successful_status(resp), \
354        resp['status'] == "success" and \
355        (resp['revision_changed'] if 'revision_changed' in resp else True), \
356        resp
357
358
359versioned_schema = {
360    "type": "list",
361    "children": {
362        "status": {
363            "type": "string",
364            "options": [
365                {
366                    "value": "enable",
367                    "revisions": {
368                        "v6.0.0": True,
369                        "v7.0.0": True,
370                        "v6.0.5": True,
371                        "v6.4.4": True,
372                        "v6.4.0": True,
373                        "v6.4.1": True,
374                        "v6.2.0": True,
375                        "v6.2.3": True,
376                        "v6.2.5": True,
377                        "v6.2.7": True,
378                        "v6.0.11": True
379                    }
380                },
381                {
382                    "value": "disable",
383                    "revisions": {
384                        "v6.0.0": True,
385                        "v7.0.0": True,
386                        "v6.0.5": 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                        "v6.0.11": True
395                    }
396                }
397            ],
398            "revisions": {
399                "v6.0.0": True,
400                "v7.0.0": True,
401                "v6.0.5": True,
402                "v6.4.4": True,
403                "v6.4.0": True,
404                "v6.4.1": True,
405                "v6.2.0": True,
406                "v6.2.3": True,
407                "v6.2.5": True,
408                "v6.2.7": True,
409                "v6.0.11": True
410            }
411        },
412        "smbv1": {
413            "type": "string",
414            "options": [
415                {
416                    "value": "enable",
417                    "revisions": {
418                        "v7.0.0": True,
419                        "v6.4.4": True,
420                        "v6.4.0": True,
421                        "v6.4.1": True,
422                        "v6.2.0": True,
423                        "v6.2.3": True,
424                        "v6.2.5": True,
425                        "v6.2.7": True
426                    }
427                },
428                {
429                    "value": "disable",
430                    "revisions": {
431                        "v7.0.0": True,
432                        "v6.4.4": True,
433                        "v6.4.0": True,
434                        "v6.4.1": True,
435                        "v6.2.0": True,
436                        "v6.2.3": True,
437                        "v6.2.5": True,
438                        "v6.2.7": True
439                    }
440                }
441            ],
442            "revisions": {
443                "v7.0.0": True,
444                "v6.4.4": True,
445                "v6.4.0": True,
446                "v6.4.1": True,
447                "v6.2.0": True,
448                "v6.2.3": True,
449                "v6.2.5": True,
450                "v6.2.7": True
451            }
452        },
453        "password": {
454            "type": "string",
455            "revisions": {
456                "v6.0.0": True,
457                "v7.0.0": True,
458                "v6.0.5": True,
459                "v6.4.4": True,
460                "v6.4.0": True,
461                "v6.4.1": True,
462                "v6.2.0": True,
463                "v6.2.3": True,
464                "v6.2.5": True,
465                "v6.2.7": True,
466                "v6.0.11": True
467            }
468        },
469        "polling_frequency": {
470            "type": "integer",
471            "revisions": {
472                "v6.0.0": True,
473                "v7.0.0": True,
474                "v6.0.5": True,
475                "v6.4.4": True,
476                "v6.4.0": True,
477                "v6.4.1": True,
478                "v6.2.0": True,
479                "v6.2.3": True,
480                "v6.2.5": True,
481                "v6.2.7": True,
482                "v6.0.11": True
483            }
484        },
485        "ldap_server": {
486            "type": "string",
487            "revisions": {
488                "v6.0.0": True,
489                "v7.0.0": True,
490                "v6.0.5": True,
491                "v6.4.4": True,
492                "v6.4.0": True,
493                "v6.4.1": True,
494                "v6.2.0": True,
495                "v6.2.3": True,
496                "v6.2.5": True,
497                "v6.2.7": True,
498                "v6.0.11": True
499            }
500        },
501        "port": {
502            "type": "integer",
503            "revisions": {
504                "v6.0.0": True,
505                "v7.0.0": True,
506                "v6.0.5": True,
507                "v6.4.4": True,
508                "v6.4.0": True,
509                "v6.4.1": True,
510                "v6.2.0": True,
511                "v6.2.3": True,
512                "v6.2.5": True,
513                "v6.2.7": True,
514                "v6.0.11": True
515            }
516        },
517        "default_domain": {
518            "type": "string",
519            "revisions": {
520                "v6.0.0": True,
521                "v7.0.0": True,
522                "v6.0.5": True,
523                "v6.4.4": True,
524                "v6.4.0": True,
525                "v6.4.1": True,
526                "v6.2.0": True,
527                "v6.2.3": True,
528                "v6.2.5": True,
529                "v6.2.7": True,
530                "v6.0.11": True
531            }
532        },
533        "user": {
534            "type": "string",
535            "revisions": {
536                "v6.0.0": True,
537                "v7.0.0": True,
538                "v6.0.5": True,
539                "v6.4.4": True,
540                "v6.4.0": True,
541                "v6.4.1": True,
542                "v6.2.0": True,
543                "v6.2.3": True,
544                "v6.2.5": True,
545                "v6.2.7": True,
546                "v6.0.11": True
547            }
548        },
549        "adgrp": {
550            "type": "list",
551            "children": {
552                "name": {
553                    "type": "string",
554                    "revisions": {
555                        "v6.0.0": True,
556                        "v7.0.0": True,
557                        "v6.0.5": True,
558                        "v6.4.4": True,
559                        "v6.4.0": True,
560                        "v6.4.1": True,
561                        "v6.2.0": True,
562                        "v6.2.3": True,
563                        "v6.2.5": True,
564                        "v6.2.7": True,
565                        "v6.0.11": True
566                    }
567                }
568            },
569            "revisions": {
570                "v6.0.0": True,
571                "v7.0.0": True,
572                "v6.0.5": True,
573                "v6.4.4": True,
574                "v6.4.0": True,
575                "v6.4.1": True,
576                "v6.2.0": True,
577                "v6.2.3": True,
578                "v6.2.5": True,
579                "v6.2.7": True,
580                "v6.0.11": True
581            }
582        },
583        "id": {
584            "type": "integer",
585            "revisions": {
586                "v6.0.0": True,
587                "v7.0.0": True,
588                "v6.0.5": True,
589                "v6.4.4": True,
590                "v6.4.0": True,
591                "v6.4.1": True,
592                "v6.2.0": True,
593                "v6.2.3": True,
594                "v6.2.5": True,
595                "v6.2.7": True,
596                "v6.0.11": True
597            }
598        },
599        "logon_history": {
600            "type": "integer",
601            "revisions": {
602                "v6.0.0": True,
603                "v7.0.0": True,
604                "v6.0.5": True,
605                "v6.4.4": True,
606                "v6.4.0": True,
607                "v6.4.1": True,
608                "v6.2.0": True,
609                "v6.2.3": True,
610                "v6.2.5": True,
611                "v6.2.7": True,
612                "v6.0.11": True
613            }
614        },
615        "server": {
616            "type": "string",
617            "revisions": {
618                "v6.0.0": True,
619                "v7.0.0": True,
620                "v6.0.5": True,
621                "v6.4.4": True,
622                "v6.4.0": True,
623                "v6.4.1": True,
624                "v6.2.0": True,
625                "v6.2.3": True,
626                "v6.2.5": True,
627                "v6.2.7": True,
628                "v6.0.11": True
629            }
630        },
631        "smb_ntlmv1_auth": {
632            "type": "string",
633            "options": [
634                {
635                    "value": "enable",
636                    "revisions": {
637                        "v7.0.0": True,
638                        "v6.4.4": True,
639                        "v6.4.0": True,
640                        "v6.4.1": True,
641                        "v6.2.0": True,
642                        "v6.2.3": True,
643                        "v6.2.5": True,
644                        "v6.2.7": True
645                    }
646                },
647                {
648                    "value": "disable",
649                    "revisions": {
650                        "v7.0.0": True,
651                        "v6.4.4": True,
652                        "v6.4.0": True,
653                        "v6.4.1": True,
654                        "v6.2.0": True,
655                        "v6.2.3": True,
656                        "v6.2.5": True,
657                        "v6.2.7": True
658                    }
659                }
660            ],
661            "revisions": {
662                "v7.0.0": True,
663                "v6.4.4": True,
664                "v6.4.0": True,
665                "v6.4.1": True,
666                "v6.2.0": True,
667                "v6.2.3": True,
668                "v6.2.5": True,
669                "v6.2.7": True
670            }
671        }
672    },
673    "revisions": {
674        "v6.0.0": True,
675        "v7.0.0": True,
676        "v6.0.5": True,
677        "v6.4.4": True,
678        "v6.4.0": True,
679        "v6.4.1": True,
680        "v6.2.0": True,
681        "v6.2.3": True,
682        "v6.2.5": True,
683        "v6.2.7": True,
684        "v6.0.11": True
685    }
686}
687
688
689def main():
690    module_spec = schema_to_module_spec(versioned_schema)
691    mkeyname = 'id'
692    fields = {
693        "access_token": {"required": False, "type": "str", "no_log": True},
694        "enable_log": {"required": False, "type": bool},
695        "vdom": {"required": False, "type": "str", "default": "root"},
696        "state": {"required": True, "type": "str",
697                  "choices": ["present", "absent"]},
698        "user_fsso_polling": {
699            "required": False, "type": "dict", "default": None,
700            "options": {
701            }
702        }
703    }
704    for attribute_name in module_spec['options']:
705        fields["user_fsso_polling"]['options'][attribute_name] = module_spec['options'][attribute_name]
706        if mkeyname and mkeyname == attribute_name:
707            fields["user_fsso_polling"]['options'][attribute_name]['required'] = True
708
709    check_legacy_fortiosapi()
710    module = AnsibleModule(argument_spec=fields,
711                           supports_check_mode=True)
712
713    versions_check_result = None
714    if module._socket_path:
715        connection = Connection(module._socket_path)
716        if 'access_token' in module.params:
717            connection.set_option('access_token', module.params['access_token'])
718
719        if 'enable_log' in module.params:
720            connection.set_option('enable_log', module.params['enable_log'])
721        else:
722            connection.set_option('enable_log', False)
723        fos = FortiOSHandler(connection, module, mkeyname)
724        versions_check_result = check_schema_versioning(fos, versioned_schema, "user_fsso_polling")
725
726        is_error, has_changed, result = fortios_user(module.params, fos, module.check_mode)
727
728    else:
729        module.fail_json(**FAIL_SOCKET_MSG)
730
731    if versions_check_result and versions_check_result['matched'] is False:
732        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
733
734    if not is_error:
735        if versions_check_result and versions_check_result['matched'] is False:
736            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
737        else:
738            module.exit_json(changed=has_changed, meta=result)
739    else:
740        if versions_check_result and versions_check_result['matched'] is False:
741            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
742        else:
743            module.fail_json(msg="Error in repo", meta=result)
744
745
746if __name__ == '__main__':
747    main()
748