49
49
BadParametersError ,
50
50
Forbidden ,
51
51
HTTPError ,
52
+ InvalidConfiguration ,
52
53
InvalidCredential ,
53
54
InvalidKey ,
54
55
InvalidRegion ,
60
61
ResourceExpiredError ,
61
62
ResourceNotFoundError ,
62
63
)
64
+ from .oauth2 import OAuth2
63
65
64
- #: Mapping between OVH API region names and corresponding endpoints
66
+ # Mapping between OVH API region names and corresponding endpoints
65
67
ENDPOINTS = {
66
68
"ovh-eu" : "https://eu.api.ovh.com/1.0" ,
67
69
"ovh-us" : "https://api.us.ovhcloud.com/1.0" ,
72
74
"soyoustart-ca" : "https://ca.api.soyoustart.com/1.0" ,
73
75
}
74
76
75
- #: Default timeout for each request. 180 seconds connect, 180 seconds read.
77
+ # Default timeout for each request. 180 seconds connect, 180 seconds read.
76
78
TIMEOUT = 180
77
79
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
+
78
87
79
88
class Client :
80
89
"""
@@ -116,18 +125,24 @@ def __init__(
116
125
consumer_key = None ,
117
126
timeout = TIMEOUT ,
118
127
config_file = None ,
128
+ client_id = None ,
129
+ client_secret = None ,
119
130
):
120
131
"""
121
132
Creates a new Client. No credential check is done at this point.
122
133
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.
127
141
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``.
131
146
132
147
See :py:mod:`ovh.config` for more information on supported
133
148
configuration mechanisms.
@@ -139,9 +154,11 @@ def __init__(
139
154
180 seconds for connection and 180 seconds for read.
140
155
141
156
: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
144
159
:param str consumer_key: uniquely identifies
160
+ :param str client_id: OAuth2 client ID
161
+ :param str client_secret: OAuth2 client secret
145
162
:param tuple timeout: Connection and read timeout for each request
146
163
:param float timeout: Same timeout for both connection and read
147
164
:raises InvalidRegion: if ``endpoint`` can't be found in ``ENDPOINTS``.
@@ -175,6 +192,50 @@ def __init__(
175
192
consumer_key = configuration .get (endpoint , "consumer_key" )
176
193
self ._consumer_key = consumer_key
177
194
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
+
178
239
# lazy load time delta
179
240
self ._time_delta = None
180
241
@@ -524,7 +585,6 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
524
585
525
586
if headers is None :
526
587
headers = {}
527
- headers ["X-Ovh-Application" ] = self ._application_key
528
588
529
589
# include payload
530
590
if data is not None :
@@ -533,6 +593,9 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
533
593
534
594
# sign request. Never sign 'time' or will recurse infinitely
535
595
if need_auth :
596
+ if self ._oauth2 :
597
+ return self ._oauth2 .session .request (method , target , headers = headers , data = body , timeout = self ._timeout )
598
+
536
599
if not self ._application_secret :
537
600
raise InvalidKey ("Invalid ApplicationSecret '%s'" % self ._application_secret )
538
601
@@ -551,4 +614,5 @@ def raw_call(self, method, path, data=None, need_auth=True, headers=None):
551
614
headers ["X-Ovh-Timestamp" ] = now
552
615
headers ["X-Ovh-Signature" ] = "$1$" + signature .hexdigest ()
553
616
617
+ headers ["X-Ovh-Application" ] = self ._application_key
554
618
return self ._session .request (method , target , headers = headers , data = body , timeout = self ._timeout )
0 commit comments