4949 BadParametersError ,
5050 Forbidden ,
5151 HTTPError ,
52+ InvalidConfiguration ,
5253 InvalidCredential ,
5354 InvalidKey ,
5455 InvalidRegion ,
6061 ResourceExpiredError ,
6162 ResourceNotFoundError ,
6263)
64+ from .oauth2 import OAuth2
6365
64- #: Mapping between OVH API region names and corresponding endpoints
66+ # Mapping between OVH API region names and corresponding endpoints
6567ENDPOINTS = {
6668 "ovh-eu" : "https://eu.api.ovh.com/1.0" ,
6769 "ovh-us" : "https://api.us.ovhcloud.com/1.0" ,
7274 "soyoustart-ca" : "https://ca.api.soyoustart.com/1.0" ,
7375}
7476
75- #: Default timeout for each request. 180 seconds connect, 180 seconds read.
77+ # Default timeout for each request. 180 seconds connect, 180 seconds read.
7678TIMEOUT = 180
7779
80+ # OAuth2 token provider URLs
81+ OAUTH2_TOKEN_URLS = {
82+ "ovh-eu" : "https://www.ovh.com/auth/oauth2/token" ,
83+ "ovh-ca" : "https://ca.ovh.com/auth/oauth2/token" ,
84+ "ovh-us" : "https://us.ovhcloud.com/auth/oauth2/token" ,
85+ }
86+
7887
7988class Client :
8089 """
@@ -116,18 +125,24 @@ def __init__(
116125 consumer_key = None ,
117126 timeout = TIMEOUT ,
118127 config_file = None ,
128+ client_id = None ,
129+ client_secret = None ,
119130 ):
120131 """
121132 Creates a new Client. No credential check is done at this point.
122133
123- The ``application_key`` identifies your application while
124- ``application_secret`` authenticates it. On the other hand, the
125- ``consumer_key`` uniquely identifies your application's end user without
126- requiring his personal password.
134+ When using OAuth2 authentication, ``client_id`` and ``client_secret``
135+ will be used to initiate a Client Credential OAuth2 flow.
136+
137+ When using the OVHcloud authentication method, the ``application_key``
138+ identifies your application while ``application_secret`` authenticates
139+ it. On the other hand, the ``consumer_key`` uniquely identifies your
140+ application's end user without requiring his personal password.
127141
128- If any of ``endpoint``, ``application_key``, ``application_secret``
129- or ``consumer_key`` is not provided, this client will attempt to locate
130- from them from environment, ~/.ovh.cfg or /etc/ovh.cfg.
142+ If any of ``endpoint``, ``application_key``, ``application_secret``,
143+ ``consumer_key``, ``client_id`` or ``client_secret`` is not provided,
144+ this client will attempt to locate from them from environment,
145+ ``~/.ovh.cfg`` or ``/etc/ovh.cfg``.
131146
132147 See :py:mod:`ovh.config` for more information on supported
133148 configuration mechanisms.
@@ -139,9 +154,11 @@ def __init__(
139154 180 seconds for connection and 180 seconds for read.
140155
141156 :param str endpoint: API endpoint to use. Valid values in ``ENDPOINTS``
142- :param str application_key: Application key as provided by OVH
143- :param str application_secret: Application secret key as provided by OVH
157+ :param str application_key: Application key as provided by OVHcloud
158+ :param str application_secret: Application secret key as provided by OVHcloud
144159 :param str consumer_key: uniquely identifies
160+ :param str client_id: OAuth2 client ID
161+ :param str client_secret: OAuth2 client secret
145162 :param tuple timeout: Connection and read timeout for each request
146163 :param float timeout: Same timeout for both connection and read
147164 :raises InvalidRegion: if ``endpoint`` can't be found in ``ENDPOINTS``.
@@ -175,6 +192,50 @@ def __init__(
175192 consumer_key = configuration .get (endpoint , "consumer_key" )
176193 self ._consumer_key = consumer_key
177194
195+ # load OAuth2 data
196+ if client_id is None :
197+ client_id = configuration .get (endpoint , "client_id" )
198+ self ._client_id = client_id
199+
200+ if client_secret is None :
201+ client_secret = configuration .get (endpoint , "client_secret" )
202+ self ._client_secret = client_secret
203+
204+ # configuration validation
205+ if bool (self ._client_id ) is not bool (self ._client_secret ):
206+ raise InvalidConfiguration ("Invalid OAuth2 config, both client_id and client_secret must be given" )
207+
208+ if bool (self ._application_key ) is not bool (self ._application_secret ):
209+ raise InvalidConfiguration (
210+ "Invalid authentication config, both application_key and application_secret must be given"
211+ )
212+
213+ if self ._client_id is not None and self ._application_key is not None :
214+ raise InvalidConfiguration (
215+ "Can't use both application_key/application_secret and OAuth2 client_id/client_secret"
216+ )
217+ if self ._client_id is None and self ._application_key is None :
218+ raise InvalidConfiguration (
219+ "Missing authentication information, you need to provide at least an application_key/application_secret"
220+ " or a client_id/client_secret"
221+ )
222+ if self ._client_id and endpoint not in OAUTH2_TOKEN_URLS :
223+ raise InvalidConfiguration (
224+ "OAuth2 authentication is not compatible with endpoint "
225+ + endpoint
226+ + " (it can only be used with ovh-eu, ovh-ca and ovh-us)"
227+ )
228+
229+ # when in OAuth2 mode, instantiate the oauthlib client
230+ if self ._client_id :
231+ self ._oauth2 = OAuth2 (
232+ client_id = self ._client_id ,
233+ client_secret = self ._client_secret ,
234+ token_url = OAUTH2_TOKEN_URLS [endpoint ],
235+ )
236+ else :
237+ self ._oauth2 = None
238+
178239 # lazy load time delta
179240 self ._time_delta = None
180241
@@ -524,7 +585,6 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
524585
525586 if headers is None :
526587 headers = {}
527- headers ["X-Ovh-Application" ] = self ._application_key
528588
529589 # include payload
530590 if data is not None :
@@ -533,6 +593,9 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
533593
534594 # sign request. Never sign 'time' or will recurse infinitely
535595 if need_auth :
596+ if self ._oauth2 :
597+ return self ._oauth2 .session .request (method , target , headers = headers , data = body , timeout = self ._timeout )
598+
536599 if not self ._application_secret :
537600 raise InvalidKey ("Invalid ApplicationSecret '%s'" % self ._application_secret )
538601
@@ -551,4 +614,5 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
551614 headers ["X-Ovh-Timestamp" ] = now
552615 headers ["X-Ovh-Signature" ] = "$1$" + signature .hexdigest ()
553616
617+ headers ["X-Ovh-Application" ] = self ._application_key
554618 return self ._session .request (method , target , headers = headers , data = body , timeout = self ._timeout )
0 commit comments