1#!/usr/bin/python
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019 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_device_group
27short_description: Configure device groups 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 device_group category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.5
33version_added: "2.9"
34author:
35    - Miguel Angel Munoz (@mamunozgonzalez)
36    - Nicolas Thomas (@thomnico)
37notes:
38    - Requires fortiosapi library developed by Fortinet
39    - Run as a local_action in your playbook
40requirements:
41    - fortiosapi>=0.9.8
42options:
43    host:
44        description:
45            - FortiOS or FortiGate IP address.
46        type: str
47        required: false
48    username:
49        description:
50            - FortiOS or FortiGate username.
51        type: str
52        required: false
53    password:
54        description:
55            - FortiOS or FortiGate password.
56        type: str
57        default: ""
58    vdom:
59        description:
60            - Virtual domain, among those defined previously. A vdom is a
61              virtual instance of the FortiGate that can be configured and
62              used as a different unit.
63        type: str
64        default: root
65    https:
66        description:
67            - Indicates if the requests towards FortiGate must use HTTPS protocol.
68        type: bool
69        default: true
70    ssl_verify:
71        description:
72            - Ensures FortiGate certificate must be verified by a proper CA.
73        type: bool
74        default: true
75    state:
76        description:
77            - Indicates whether to create or remove the object.
78        type: str
79        required: true
80        choices:
81            - present
82            - absent
83    user_device_group:
84        description:
85            - Configure device groups.
86        default: null
87        type: dict
88        suboptions:
89            comment:
90                description:
91                    - Comment.
92                type: str
93            member:
94                description:
95                    - Device group member.
96                type: list
97                suboptions:
98                    name:
99                        description:
100                            - Device name. Source user.device.alias user.device-category.name.
101                        required: true
102                        type: str
103            name:
104                description:
105                    - Device group name.
106                required: true
107                type: str
108            tagging:
109                description:
110                    - Config object tagging.
111                type: list
112                suboptions:
113                    category:
114                        description:
115                            - Tag category. Source system.object-tagging.category.
116                        type: str
117                    name:
118                        description:
119                            - Tagging entry name.
120                        required: true
121                        type: str
122                    tags:
123                        description:
124                            - Tags.
125                        type: list
126                        suboptions:
127                            name:
128                                description:
129                                    - Tag name. Source system.object-tagging.tags.name.
130                                required: true
131                                type: str
132'''
133
134EXAMPLES = '''
135- hosts: localhost
136  vars:
137   host: "192.168.122.40"
138   username: "admin"
139   password: ""
140   vdom: "root"
141   ssl_verify: "False"
142  tasks:
143  - name: Configure device groups.
144    fortios_user_device_group:
145      host:  "{{ host }}"
146      username: "{{ username }}"
147      password: "{{ password }}"
148      vdom:  "{{ vdom }}"
149      https: "False"
150      state: "present"
151      user_device_group:
152        comment: "Comment."
153        member:
154         -
155            name: "default_name_5 (source user.device.alias user.device-category.name)"
156        name: "default_name_6"
157        tagging:
158         -
159            category: "<your_own_value> (source system.object-tagging.category)"
160            name: "default_name_9"
161            tags:
162             -
163                name: "default_name_11 (source system.object-tagging.tags.name)"
164'''
165
166RETURN = '''
167build:
168  description: Build number of the fortigate image
169  returned: always
170  type: str
171  sample: '1547'
172http_method:
173  description: Last method used to provision the content into FortiGate
174  returned: always
175  type: str
176  sample: 'PUT'
177http_status:
178  description: Last result given by FortiGate on last operation applied
179  returned: always
180  type: str
181  sample: "200"
182mkey:
183  description: Master key (id) used in the last call to FortiGate
184  returned: success
185  type: str
186  sample: "id"
187name:
188  description: Name of the table used to fulfill the request
189  returned: always
190  type: str
191  sample: "urlfilter"
192path:
193  description: Path of the table used to fulfill the request
194  returned: always
195  type: str
196  sample: "webfilter"
197revision:
198  description: Internal revision number
199  returned: always
200  type: str
201  sample: "17.0.2.10658"
202serial:
203  description: Serial number of the unit
204  returned: always
205  type: str
206  sample: "FGVMEVYYQT3AB5352"
207status:
208  description: Indication of the operation's result
209  returned: always
210  type: str
211  sample: "success"
212vdom:
213  description: Virtual domain used
214  returned: always
215  type: str
216  sample: "root"
217version:
218  description: Version of the FortiGate
219  returned: always
220  type: str
221  sample: "v5.6.3"
222
223'''
224
225from ansible.module_utils.basic import AnsibleModule
226from ansible.module_utils.connection import Connection
227from ansible.module_utils.network.fortios.fortios import FortiOSHandler
228from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
229
230
231def login(data, fos):
232    host = data['host']
233    username = data['username']
234    password = data['password']
235    ssl_verify = data['ssl_verify']
236
237    fos.debug('on')
238    if 'https' in data and not data['https']:
239        fos.https('off')
240    else:
241        fos.https('on')
242
243    fos.login(host, username, password, verify=ssl_verify)
244
245
246def filter_user_device_group_data(json):
247    option_list = ['comment', 'member', 'name',
248                   'tagging']
249    dictionary = {}
250
251    for attribute in option_list:
252        if attribute in json and json[attribute] is not None:
253            dictionary[attribute] = json[attribute]
254
255    return dictionary
256
257
258def underscore_to_hyphen(data):
259    if isinstance(data, list):
260        for elem in data:
261            elem = underscore_to_hyphen(elem)
262    elif isinstance(data, dict):
263        new_data = {}
264        for k, v in data.items():
265            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
266        data = new_data
267
268    return data
269
270
271def user_device_group(data, fos):
272    vdom = data['vdom']
273    state = data['state']
274    user_device_group_data = data['user_device_group']
275    filtered_data = underscore_to_hyphen(filter_user_device_group_data(user_device_group_data))
276
277    if state == "present":
278        return fos.set('user',
279                       'device-group',
280                       data=filtered_data,
281                       vdom=vdom)
282
283    elif state == "absent":
284        return fos.delete('user',
285                          'device-group',
286                          mkey=filtered_data['name'],
287                          vdom=vdom)
288
289
290def is_successful_status(status):
291    return status['status'] == "success" or \
292        status['http_method'] == "DELETE" and status['http_status'] == 404
293
294
295def fortios_user(data, fos):
296
297    if data['user_device_group']:
298        resp = user_device_group(data, fos)
299
300    return not is_successful_status(resp), \
301        resp['status'] == "success", \
302        resp
303
304
305def main():
306    fields = {
307        "host": {"required": False, "type": "str"},
308        "username": {"required": False, "type": "str"},
309        "password": {"required": False, "type": "str", "default": "", "no_log": True},
310        "vdom": {"required": False, "type": "str", "default": "root"},
311        "https": {"required": False, "type": "bool", "default": True},
312        "ssl_verify": {"required": False, "type": "bool", "default": True},
313        "state": {"required": True, "type": "str",
314                  "choices": ["present", "absent"]},
315        "user_device_group": {
316            "required": False, "type": "dict", "default": None,
317            "options": {
318                "comment": {"required": False, "type": "str"},
319                "member": {"required": False, "type": "list",
320                           "options": {
321                               "name": {"required": True, "type": "str"}
322                           }},
323                "name": {"required": True, "type": "str"},
324                "tagging": {"required": False, "type": "list",
325                            "options": {
326                                "category": {"required": False, "type": "str"},
327                                "name": {"required": True, "type": "str"},
328                                "tags": {"required": False, "type": "list",
329                                         "options": {
330                                             "name": {"required": True, "type": "str"}
331                                         }}
332                            }}
333
334            }
335        }
336    }
337
338    module = AnsibleModule(argument_spec=fields,
339                           supports_check_mode=False)
340
341    # legacy_mode refers to using fortiosapi instead of HTTPAPI
342    legacy_mode = 'host' in module.params and module.params['host'] is not None and \
343                  'username' in module.params and module.params['username'] is not None and \
344                  'password' in module.params and module.params['password'] is not None
345
346    if not legacy_mode:
347        if module._socket_path:
348            connection = Connection(module._socket_path)
349            fos = FortiOSHandler(connection)
350
351            is_error, has_changed, result = fortios_user(module.params, fos)
352        else:
353            module.fail_json(**FAIL_SOCKET_MSG)
354    else:
355        try:
356            from fortiosapi import FortiOSAPI
357        except ImportError:
358            module.fail_json(msg="fortiosapi module is required")
359
360        fos = FortiOSAPI()
361
362        login(module.params, fos)
363        is_error, has_changed, result = fortios_user(module.params, fos)
364        fos.logout()
365
366    if not is_error:
367        module.exit_json(changed=has_changed, meta=result)
368    else:
369        module.fail_json(msg="Error in repo", meta=result)
370
371
372if __name__ == '__main__':
373    main()
374