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