1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""LDAP methods module."""
4from hvac import exceptions, utils
5from hvac.api.vault_api_base import VaultApiBase
6
7DEFAULT_MOUNT_POINT = "ldap"
8
9
10class Ldap(VaultApiBase):
11    """LDAP Auth Method (API).
12
13    Reference: https://www.vaultproject.io/api/auth/ldap/index.html
14    """
15
16    def configure(
17        self,
18        user_dn=None,
19        group_dn=None,
20        url=None,
21        case_sensitive_names=None,
22        starttls=None,
23        tls_min_version=None,
24        tls_max_version=None,
25        insecure_tls=None,
26        certificate=None,
27        bind_dn=None,
28        bind_pass=None,
29        user_attr=None,
30        discover_dn=None,
31        deny_null_bind=True,
32        upn_domain=None,
33        group_filter=None,
34        group_attr=None,
35        use_token_groups=None,
36        token_ttl=None,
37        token_max_ttl=None,
38        mount_point=DEFAULT_MOUNT_POINT,
39    ):
40        """
41        Configure the LDAP auth method.
42
43        Supported methods:
44            POST: /auth/{mount_point}/config. Produces: 204 (empty body)
45
46        :param user_dn: Base DN under which to perform user search. Example: ou=Users,dc=example,dc=com
47        :type user_dn: str | unicode
48        :param group_dn: LDAP search base to use for group membership search. This can be the root containing either
49            groups or users. Example: ou=Groups,dc=example,dc=com
50        :type group_dn: str | unicode
51        :param url: The LDAP server to connect to. Examples: ldap://ldap.myorg.com, ldaps://ldap.myorg.com:636.
52            Multiple URLs can be specified with commas, e.g. ldap://ldap.myorg.com,ldap://ldap2.myorg.com; these will be
53            tried in-order.
54        :type url: str | unicode
55        :param case_sensitive_names: If set, user and group names assigned to policies within the backend will be case
56            sensitive. Otherwise, names will be normalized to lower case. Case will still be preserved when sending the
57            username to the LDAP server at login time; this is only for matching local user/group definitions.
58        :type case_sensitive_names: bool
59        :param starttls: If true, issues a StartTLS command after establishing an unencrypted connection.
60        :type starttls: bool
61        :param tls_min_version: Minimum TLS version to use. Accepted values are tls10, tls11 or tls12.
62        :type tls_min_version: str | unicode
63        :param tls_max_version: Maximum TLS version to use. Accepted values are tls10, tls11 or tls12.
64        :type tls_max_version: str | unicode
65        :param insecure_tls: If true, skips LDAP server SSL certificate verification - insecure, use with caution!
66        :type insecure_tls: bool
67        :param certificate: CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded.
68        :type certificate: str | unicode
69        :param bind_dn: Distinguished name of object to bind when performing user search. Example:
70            cn=vault,ou=Users,dc=example,dc=com
71        :type bind_dn: str | unicode
72        :param bind_pass:  Password to use along with binddn when performing user search.
73        :type bind_pass: str | unicode
74        :param user_attr: Attribute on user attribute object matching the username passed when authenticating. Examples:
75            sAMAccountName, cn, uid
76        :type user_attr: str | unicode
77        :param discover_dn: Use anonymous bind to discover the bind DN of a user.
78        :type discover_dn: bool
79        :param deny_null_bind: This option prevents users from bypassing authentication when providing an empty password.
80        :type deny_null_bind: bool
81        :param upn_domain: The userPrincipalDomain used to construct the UPN string for the authenticating user. The
82            constructed UPN will appear as [username]@UPNDomain. Example: example.com, which will cause vault to bind as
83            username@example.com.
84        :type upn_domain: str | unicode
85        :param group_filter: Go template used when constructing the group membership query. The template can access the
86            following context variables: [UserDN, Username]. The default is
87            `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))`, which is compatible with several
88            common directory schemas. To support nested group resolution for Active Directory, instead use the following
89            query: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}})).
90        :type group_filter: str | unicode
91        :param group_attr: LDAP attribute to follow on objects returned by groupfilter in order to enumerate user group
92            membership. Examples: for groupfilter queries returning group objects, use: cn. For queries returning user
93            objects, use: memberOf. The default is cn.
94        :type group_attr: str | unicode
95        :param use_token_groups: If true, groups are resolved through Active Directory tokens. This may speed up nested
96            group membership resolution in large directories.
97        :type use_token_groups: bool
98        :param token_ttl: The incremental lifetime for generated tokens.
99        :type token_ttl: str | unicode
100        :param token_max_ttl: The maximum lifetime for generated tokens.
101        :type token_max_ttl: str | unicode
102        :param mount_point: The "path" the method/backend was mounted on.
103        :type mount_point: str | unicode
104        :return: The response of the configure request.
105        :rtype: requests.Response
106        """
107        params = utils.remove_nones(
108            {
109                "url": url,
110                "userdn": user_dn,
111                "groupdn": group_dn,
112                "case_sensitive_names": case_sensitive_names,
113                "starttls": starttls,
114                "tls_min_version": tls_min_version,
115                "tls_max_version": tls_max_version,
116                "insecure_tls": insecure_tls,
117                "certificate": certificate,
118                "userattr": user_attr,
119                "discoverdn": discover_dn,
120                "deny_null_bind": deny_null_bind,
121                "groupfilter": group_filter,
122                "groupattr": group_attr,
123                "upndomain": upn_domain,
124                "binddn": bind_dn,
125                "bindpass": bind_pass,
126                "certificate": certificate,
127                "use_token_groups": use_token_groups,
128                "token_ttl": token_ttl,
129                "token_max_ttl": token_max_ttl,
130            }
131        )
132
133        api_path = utils.format_url(
134            "/v1/auth/{mount_point}/config", mount_point=mount_point
135        )
136        return self._adapter.post(
137            url=api_path,
138            json=params,
139        )
140
141    def read_configuration(self, mount_point=DEFAULT_MOUNT_POINT):
142        """
143        Retrieve the LDAP configuration for the auth method.
144
145        Supported methods:
146            GET: /auth/{mount_point}/config. Produces: 200 application/json
147
148        :param mount_point: The "path" the method/backend was mounted on.
149        :type mount_point: str | unicode
150        :return: The JSON response of the read_configuration request.
151        :rtype: dict
152        """
153        api_path = utils.format_url(
154            "/v1/auth/{mount_point}/config", mount_point=mount_point
155        )
156        return self._adapter.get(
157            url=api_path,
158        )
159
160    def create_or_update_group(
161        self, name, policies=None, mount_point=DEFAULT_MOUNT_POINT
162    ):
163        """
164        Create or update LDAP group policies.
165
166        Supported methods:
167            POST: /auth/{mount_point}/groups/{name}. Produces: 204 (empty body)
168
169
170        :param name: The name of the LDAP group
171        :type name: str | unicode
172        :param policies: List of policies associated with the group. This parameter is transformed to a comma-delimited
173            string before being passed to Vault.
174        :type policies: list
175        :param mount_point: The "path" the method/backend was mounted on.
176        :type mount_point: str | unicode
177        :return: The response of the create_or_update_group request.
178        :rtype: requests.Response
179        """
180        if policies is not None and not isinstance(policies, list):
181            error_msg = '"policies" argument must be an instance of list or None, "{policies_type}" provided.'.format(
182                policies_type=type(policies),
183            )
184            raise exceptions.ParamValidationError(error_msg)
185
186        params = {}
187        if policies is not None:
188            params["policies"] = ",".join(policies)
189        api_path = utils.format_url(
190            "/v1/auth/{mount_point}/groups/{name}",
191            mount_point=mount_point,
192            name=name,
193        )
194        return self._adapter.post(
195            url=api_path,
196            json=params,
197        )
198
199    def list_groups(self, mount_point=DEFAULT_MOUNT_POINT):
200        """
201        List existing LDAP existing groups that have been created in this auth method.
202
203        Supported methods:
204            LIST: /auth/{mount_point}/groups. Produces: 200 application/json
205
206
207        :param mount_point: The "path" the method/backend was mounted on.
208        :type mount_point: str | unicode
209        :return: The JSON response of the list_groups request.
210        :rtype: dict
211        """
212        api_path = utils.format_url(
213            "/v1/auth/{mount_point}/groups", mount_point=mount_point
214        )
215        return self._adapter.list(
216            url=api_path,
217        )
218
219    def read_group(self, name, mount_point=DEFAULT_MOUNT_POINT):
220        """
221        Read policies associated with a LDAP group.
222
223        Supported methods:
224            GET: /auth/{mount_point}/groups/{name}. Produces: 200 application/json
225
226
227        :param name: The name of the LDAP group
228        :type name: str | unicode
229        :param mount_point: The "path" the method/backend was mounted on.
230        :type mount_point: str | unicode
231        :return: The JSON response of the read_group request.
232        :rtype: dict
233        """
234        params = {
235            "name": name,
236        }
237        api_path = utils.format_url(
238            "/v1/auth/{mount_point}/groups/{name}",
239            mount_point=mount_point,
240            name=name,
241        )
242        return self._adapter.get(
243            url=api_path,
244            json=params,
245        )
246
247    def delete_group(self, name, mount_point=DEFAULT_MOUNT_POINT):
248        """
249        Delete a LDAP group and policy association.
250
251        Supported methods:
252            DELETE: /auth/{mount_point}/groups/{name}. Produces: 204 (empty body)
253
254
255        :param name: The name of the LDAP group
256        :type name: str | unicode
257        :param mount_point: The "path" the method/backend was mounted on.
258        :type mount_point: str | unicode
259        :return: The response of the delete_group request.
260        :rtype: requests.Response
261        """
262        api_path = utils.format_url(
263            "/v1/auth/{mount_point}/groups/{name}",
264            mount_point=mount_point,
265            name=name,
266        )
267        return self._adapter.delete(
268            url=api_path,
269        )
270
271    def create_or_update_user(
272        self, username, policies=None, groups=None, mount_point=DEFAULT_MOUNT_POINT
273    ):
274        """
275        Create or update LDAP users policies and group associations.
276
277        Supported methods:
278            POST: /auth/{mount_point}/users/{username}. Produces: 204 (empty body)
279
280
281        :param username: The username of the LDAP user
282        :type username: str | unicode
283        :param policies: List of policies associated with the user. This parameter is transformed to a comma-delimited
284            string before being passed to Vault.
285        :type policies: str | unicode
286        :param groups: List of groups associated with the user. This parameter is transformed to a comma-delimited
287            string before being passed to Vault.
288        :type groups: str | unicode
289        :param mount_point: The "path" the method/backend was mounted on.
290        :type mount_point: str | unicode
291        :return: The response of the create_or_update_user request.
292        :rtype: requests.Response
293        """
294        list_required_params = {
295            "policies": policies,
296            "groups": groups,
297        }
298        for param_name, param_arg in list_required_params.items():
299            if param_arg is not None and not isinstance(param_arg, list):
300                error_msg = '"{param_name}" argument must be an instance of list or None, "{param_type}" provided.'.format(
301                    param_name=param_name,
302                    param_type=type(param_arg),
303                )
304                raise exceptions.ParamValidationError(error_msg)
305
306        params = {}
307        if policies is not None:
308            params["policies"] = ",".join(policies)
309        if groups is not None:
310            params["groups"] = ",".join(groups)
311        api_path = utils.format_url(
312            "/v1/auth/{mount_point}/users/{username}",
313            mount_point=mount_point,
314            username=username,
315        )
316        return self._adapter.post(
317            url=api_path,
318            json=params,
319        )
320
321    def list_users(self, mount_point=DEFAULT_MOUNT_POINT):
322        """
323        List existing users in the method.
324
325        Supported methods:
326            LIST: /auth/{mount_point}/users. Produces: 200 application/json
327
328
329        :param mount_point: The "path" the method/backend was mounted on.
330        :type mount_point: str | unicode
331        :return: The JSON response of the list_users request.
332        :rtype: dict
333        """
334        api_path = utils.format_url(
335            "/v1/auth/{mount_point}/users", mount_point=mount_point
336        )
337        return self._adapter.list(
338            url=api_path,
339        )
340
341    def read_user(self, username, mount_point=DEFAULT_MOUNT_POINT):
342        """
343        Read policies associated with a LDAP user.
344
345        Supported methods:
346            GET: /auth/{mount_point}/users/{username}. Produces: 200 application/json
347
348
349        :param username: The username of the LDAP user
350        :type username: str | unicode
351        :param mount_point: The "path" the method/backend was mounted on.
352        :type mount_point: str | unicode
353        :return: The JSON response of the read_user request.
354        :rtype: dict
355        """
356        api_path = utils.format_url(
357            "/v1/auth/{mount_point}/users/{username}",
358            mount_point=mount_point,
359            username=username,
360        )
361        return self._adapter.get(
362            url=api_path,
363        )
364
365    def delete_user(self, username, mount_point=DEFAULT_MOUNT_POINT):
366        """
367        Delete a LDAP user and policy association.
368
369        Supported methods:
370            DELETE: /auth/{mount_point}/users/{username}. Produces: 204 (empty body)
371
372
373        :param username: The username of the LDAP user
374        :type username: str | unicode
375        :param mount_point: The "path" the method/backend was mounted on.
376        :type mount_point: str | unicode
377        :return: The response of the delete_user request.
378        :rtype: requests.Response
379        """
380        api_path = utils.format_url(
381            "/v1/auth/{mount_point}/users/{username}",
382            mount_point=mount_point,
383            username=username,
384        )
385        return self._adapter.delete(
386            url=api_path,
387        )
388
389    def login(
390        self, username, password, use_token=True, mount_point=DEFAULT_MOUNT_POINT
391    ):
392        """
393        Log in with LDAP credentials.
394
395        Supported methods:
396            POST: /auth/{mount_point}/login/{username}. Produces: 200 application/json
397
398
399        :param username: The username of the LDAP user
400        :type username: str | unicode
401        :param password: The password for the LDAP user
402        :type password: str | unicode
403        :param use_token: if True, uses the token in the response received from the auth request to set the "token"
404            attribute on the the :py:meth:`hvac.adapters.Adapter` instance under the _adapater Client attribute.
405        :type use_token: bool
406        :param mount_point: The "path" the method/backend was mounted on.
407        :type mount_point: str | unicode
408        :return: The response of the login_with_user request.
409        :rtype: requests.Response
410        """
411        params = {
412            "password": password,
413        }
414        api_path = utils.format_url(
415            "/v1/auth/{mount_point}/login/{username}",
416            mount_point=mount_point,
417            username=username,
418        )
419        return self._adapter.login(
420            url=api_path,
421            use_token=use_token,
422            json=params,
423        )
424