1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""Multi-factor authentication methods module."""
4from hvac.api.vault_api_base import VaultApiBase
5from hvac import exceptions, utils
6
7SUPPORTED_MFA_TYPES = [
8    "duo",
9]
10SUPPORTED_AUTH_METHODS = ["ldap", "okta", "radius", "userpass"]
11
12
13class Mfa(VaultApiBase):
14    """Multi-factor authentication Auth Method (API).
15
16    .. warning::
17        This class's methods correspond to a legacy / unsupported set of Vault API routes. Please see the reference link
18        for additional context.
19
20    Reference: https://www.vaultproject.io/docs/auth/mfa.html
21    """
22
23    def configure(self, mount_point, mfa_type="duo", force=False):
24        """Configure MFA for a supported method.
25
26        This endpoint allows you to turn on multi-factor authentication with a given backend.
27        Currently only Duo is supported.
28
29        Supported methods:
30            POST: /auth/{mount_point}/mfa_config. Produces: 204 (empty body)
31
32        :param mount_point: The "path" the method/backend was mounted on.
33        :type mount_point: str | unicode
34        :param mfa_type: Enables MFA with given backend (available: duo)
35        :type mfa_type: str | unicode
36        :param force: If True, make the "mfa_config" request regardless of circumstance. If False (the default), verify
37            the provided mount_point is available and one of the types of methods supported by this feature.
38        :type force: bool
39        :return: The response of the configure MFA request.
40        :rtype: requests.Response
41        """
42        if mfa_type != "duo" and not force:
43            # The situation described via this exception is not likely to change in the future.
44            # However we provided that flexibility here just in case.
45            error_msg = 'Unsupported mfa_type argument provided "{arg}", supported types: "{mfa_types}"'
46            raise exceptions.ParamValidationError(
47                error_msg.format(
48                    mfa_types=",".join(SUPPORTED_MFA_TYPES),
49                    arg=mfa_type,
50                )
51            )
52        params = {
53            "type": mfa_type,
54        }
55
56        api_path = utils.format_url(
57            "/v1/auth/{mount_point}/mfa_config", mount_point=mount_point
58        )
59        return self._adapter.post(
60            url=api_path,
61            json=params,
62        )
63
64    def read_configuration(self, mount_point):
65        """Read the MFA configuration.
66
67        Supported methods:
68            GET: /auth/{mount_point}/mfa_config. Produces: 200 application/json
69
70
71        :param mount_point: The "path" the method/backend was mounted on.
72        :type mount_point: str | unicode
73        :return: The JSON response of the read_configuration request.
74        :rtype: dict
75        """
76        api_path = utils.format_url(
77            "/v1/auth/{mount_point}/mfa_config",
78            mount_point=mount_point,
79        )
80        return self._adapter.get(url=api_path)
81
82    def configure_duo_access(self, mount_point, host, integration_key, secret_key):
83        """Configure the access keys and host for Duo API connections.
84
85        To authenticate users with Duo, the backend needs to know what host to connect to and must authenticate with an
86        integration key and secret key. This endpoint is used to configure that information.
87
88        Supported methods:
89            POST: /auth/{mount_point}/duo/access. Produces: 204 (empty body)
90
91        :param mount_point: The "path" the method/backend was mounted on.
92        :type mount_point: str | unicode
93        :param host: Duo API host
94        :type host: str | unicode
95        :param integration_key: Duo integration key
96        :type integration_key: Duo secret key
97        :param secret_key: The "path" the method/backend was mounted on.
98        :type secret_key: str | unicode
99        :return: The response of the configure_duo_access request.
100        :rtype: requests.Response
101        """
102        params = {
103            "host": host,
104            "ikey": integration_key,
105            "skey": secret_key,
106        }
107        api_path = utils.format_url(
108            "/v1/auth/{mount_point}/duo/access",
109            mount_point=mount_point,
110        )
111        return self._adapter.post(
112            url=api_path,
113            json=params,
114        )
115
116    def configure_duo_behavior(
117        self, mount_point, push_info=None, user_agent=None, username_format="%s"
118    ):
119        """Configure Duo second factor behavior.
120
121        This endpoint allows you to configure how the original auth method username maps to the Duo username by
122        providing a template format string.
123
124        Supported methods:
125            POST: /auth/{mount_point}/duo/config. Produces: 204 (empty body)
126
127
128        :param mount_point: The "path" the method/backend was mounted on.
129        :type mount_point: str | unicode
130        :param push_info: A string of URL-encoded key/value pairs that provides additional context about the
131            authentication attempt in the Duo Mobile app
132        :type push_info: str | unicode
133        :param user_agent: User agent to connect to Duo (default "")
134        :type user_agent: str | unicode
135        :param username_format: Format string given auth method username as argument to create Duo username
136            (default '%s')
137        :type username_format: str | unicode
138        :return: The response of the configure_duo_behavior request.
139        :rtype: requests.Response
140        """
141        params = {
142            "username_format": username_format,
143        }
144        if push_info is not None:
145            params["push_info"] = push_info
146        if user_agent is not None:
147            params["user_agent"] = user_agent
148        api_path = utils.format_url(
149            "/v1/auth/{mount_point}/duo/config",
150            mount_point=mount_point,
151        )
152        return self._adapter.post(
153            url=api_path,
154            json=params,
155        )
156
157    def read_duo_behavior_configuration(self, mount_point):
158        """Read the Duo second factor behavior configuration.
159
160        Supported methods:
161            GET: /auth/{mount_point}/duo/config. Produces: 200 application/json
162
163
164        :param mount_point: The "path" the method/backend was mounted on.
165        :type mount_point: str | unicode
166        :return: The JSON response of the read_duo_behavior_configuration request.
167        :rtype: dict
168        """
169        api_path = utils.format_url(
170            "/v1/auth/{mount_point}/duo/config",
171            mount_point=mount_point,
172        )
173        return self._adapter.get(url=api_path)
174