eodag 3.0.0b3__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eodag/api/core.py +189 -125
- eodag/api/product/metadata_mapping.py +12 -3
- eodag/api/search_result.py +29 -3
- eodag/cli.py +35 -19
- eodag/config.py +412 -116
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +14 -4
- eodag/plugins/apis/usgs.py +25 -2
- eodag/plugins/authentication/aws_auth.py +14 -5
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +57 -10
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +4 -3
- eodag/plugins/download/aws.py +39 -22
- eodag/plugins/download/base.py +11 -11
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +86 -52
- eodag/plugins/download/s3rest.py +20 -18
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +33 -14
- eodag/plugins/search/build_search_result.py +55 -51
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +20 -5
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +532 -152
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +187 -56
- eodag/resources/providers.yml +1610 -1701
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +61 -51
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -325
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +13 -8
- eodag/rest/types/queryables.py +1 -2
- eodag/rest/types/stac_search.py +11 -2
- eodag/types/__init__.py +15 -3
- eodag/types/download_args.py +1 -1
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +3 -3
- eodag/utils/__init__.py +77 -57
- eodag/utils/exceptions.py +23 -9
- eodag/utils/logging.py +37 -77
- eodag/utils/requests.py +1 -3
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/METADATA +11 -12
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
|
@@ -20,22 +20,18 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
22
|
import string
|
|
23
|
-
from datetime import datetime
|
|
23
|
+
from datetime import datetime, timedelta, timezone
|
|
24
24
|
from random import SystemRandom
|
|
25
|
-
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
26
26
|
|
|
27
|
+
import jwt
|
|
27
28
|
import requests
|
|
28
29
|
from lxml import etree
|
|
29
30
|
from requests.auth import AuthBase
|
|
30
31
|
|
|
31
32
|
from eodag.plugins.authentication import Authentication
|
|
32
33
|
from eodag.utils import HTTP_REQ_TIMEOUT, USER_AGENT, parse_qs, repeatfunc, urlparse
|
|
33
|
-
from eodag.utils.exceptions import
|
|
34
|
-
AuthenticationError,
|
|
35
|
-
MisconfiguredError,
|
|
36
|
-
RequestError,
|
|
37
|
-
TimeOutError,
|
|
38
|
-
)
|
|
34
|
+
from eodag.utils.exceptions import AuthenticationError, MisconfiguredError, TimeOutError
|
|
39
35
|
|
|
40
36
|
if TYPE_CHECKING:
|
|
41
37
|
from requests import PreparedRequest, Response
|
|
@@ -47,93 +43,121 @@ logger = logging.getLogger("eodag.auth.openid_connect")
|
|
|
47
43
|
|
|
48
44
|
|
|
49
45
|
class OIDCRefreshTokenBase(Authentication):
|
|
50
|
-
"""OIDC refresh token base class, to be used through specific OIDC flows plugins
|
|
46
|
+
"""OIDC refresh token base class, to be used through specific OIDC flows plugins;
|
|
47
|
+
Common mechanism to handle refresh token from all OIDC auth plugins;
|
|
48
|
+
|
|
49
|
+
Plugins inheriting from this base class must implement the methods ``_request_new_token()`` and
|
|
50
|
+
``_get_token_with_refresh_token()``. Depending on the implementation of these methods they can have
|
|
51
|
+
different configuration parameters.
|
|
51
52
|
|
|
52
|
-
Common mechanism to handle refresh token from all OIDC auth plugins.
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
jwks_client: jwt.PyJWKClient
|
|
56
|
+
|
|
57
|
+
access_token: str
|
|
58
|
+
access_token_expiration: datetime
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
refresh_token_expiration: float
|
|
60
|
+
refresh_token: str
|
|
61
|
+
refresh_token_expiration: datetime
|
|
62
|
+
|
|
63
|
+
token_endpoint: str
|
|
64
|
+
authorization_endpoint: str
|
|
64
65
|
|
|
65
66
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
66
67
|
super(OIDCRefreshTokenBase, self).__init__(provider, config)
|
|
67
68
|
self.session = requests.Session()
|
|
68
|
-
|
|
69
|
-
self.
|
|
69
|
+
|
|
70
|
+
self.access_token = ""
|
|
71
|
+
self.access_token_expiration = datetime.min
|
|
72
|
+
|
|
73
|
+
self.refresh_token = ""
|
|
74
|
+
self.refresh_token_expiration = datetime.min
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
response = requests.get(self.config.oidc_config_url)
|
|
78
|
+
response.raise_for_status()
|
|
79
|
+
auth_config = response.json()
|
|
80
|
+
except requests.HTTPError as e:
|
|
81
|
+
raise MisconfiguredError(
|
|
82
|
+
f"Cannot obtain OIDC endpoints from {self.config.oidc_config_url}"
|
|
83
|
+
f"Request returned {e.response.text}."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
self.jwks_client = jwt.PyJWKClient(auth_config["jwks_uri"])
|
|
87
|
+
self.token_endpoint = auth_config["token_endpoint"]
|
|
88
|
+
self.authorization_endpoint = auth_config["authorization_endpoint"]
|
|
89
|
+
self.algorithms = auth_config["id_token_signing_alg_values_supported"]
|
|
90
|
+
|
|
91
|
+
def decode_jwt_token(self, token: str) -> Dict[str, Any]:
|
|
92
|
+
"""Decode JWT token."""
|
|
93
|
+
try:
|
|
94
|
+
key = self.jwks_client.get_signing_key_from_jwt(token).key
|
|
95
|
+
if getattr(self.config, "allowed_audiences", None):
|
|
96
|
+
return jwt.decode(
|
|
97
|
+
token,
|
|
98
|
+
key,
|
|
99
|
+
algorithms=self.algorithms,
|
|
100
|
+
# NOTE: Audience validation MUST match audience claim if set in token
|
|
101
|
+
# (https://pyjwt.readthedocs.io/en/stable/changelog.html?highlight=audience#id40)
|
|
102
|
+
audience=self.config.allowed_audiences,
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
return jwt.decode(
|
|
106
|
+
token,
|
|
107
|
+
key,
|
|
108
|
+
algorithms=self.algorithms,
|
|
109
|
+
)
|
|
110
|
+
except (jwt.exceptions.InvalidTokenError, jwt.exceptions.DecodeError) as e:
|
|
111
|
+
raise AuthenticationError(e)
|
|
70
112
|
|
|
71
113
|
def _get_access_token(self) -> str:
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# Refresh token available but expired
|
|
77
|
-
or (
|
|
78
|
-
"refresh_token" in self.token_info
|
|
79
|
-
and self.token_info["refresh_token_expiration"] > 0
|
|
80
|
-
and (current_time - self.token_info["token_time"]).seconds
|
|
81
|
-
>= self.token_info["refresh_token_expiration"]
|
|
114
|
+
now = datetime.now(timezone.utc)
|
|
115
|
+
if self.access_token and now < self.access_token_expiration:
|
|
116
|
+
logger.debug(
|
|
117
|
+
f"Existing access_token is still valid until {self.access_token_expiration.isoformat()}."
|
|
82
118
|
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
119
|
+
return self.access_token
|
|
120
|
+
|
|
121
|
+
elif self.refresh_token and now < self.refresh_token_expiration:
|
|
122
|
+
response = self._get_token_with_refresh_token()
|
|
123
|
+
logger.debug(
|
|
124
|
+
"access_token expired, fetching new access_token using refresh_token"
|
|
88
125
|
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
):
|
|
108
|
-
# Use refresh token
|
|
109
|
-
res = self._get_token_with_refresh_token()
|
|
110
|
-
self.token_info["refresh_token"] = res["refresh_token"]
|
|
111
|
-
self.token_info["refresh_time"] = current_time
|
|
112
|
-
return res["access_token"]
|
|
126
|
+
else:
|
|
127
|
+
logger.debug("access_token expired or not available yet, new token request")
|
|
128
|
+
response = self._request_new_token()
|
|
129
|
+
|
|
130
|
+
self.access_token = response[getattr(self.config, "token_key", "access_token")]
|
|
131
|
+
self.access_token_expiration = datetime.fromtimestamp(
|
|
132
|
+
self.decode_jwt_token(self.access_token)["exp"], timezone.utc
|
|
133
|
+
)
|
|
134
|
+
self.refresh_token = response.get(
|
|
135
|
+
getattr(self.config, "refresh_token_key", "refresh_token"), ""
|
|
136
|
+
)
|
|
137
|
+
if self.refresh_token and response.get("refresh_expires_in", "0"):
|
|
138
|
+
self.refresh_token_expiration = now + timedelta(
|
|
139
|
+
seconds=int(response["refresh_expires_in"])
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
# refresh token does not expire but will be changed at each request
|
|
143
|
+
self.refresh_token_expiration = now + timedelta(days=1000)
|
|
113
144
|
|
|
114
|
-
|
|
115
|
-
return self.token_info["access_token"]
|
|
145
|
+
return self.access_token
|
|
116
146
|
|
|
117
147
|
def _request_new_token(self) -> Dict[str, str]:
|
|
118
|
-
"""Fetch the access token with a new
|
|
148
|
+
"""Fetch the access token with a new authentication"""
|
|
119
149
|
raise NotImplementedError(
|
|
120
150
|
"Incomplete OIDC refresh token retrieval mechanism implementation"
|
|
121
151
|
)
|
|
122
152
|
|
|
123
153
|
def _request_new_token_error(self, e: requests.RequestException) -> Dict[str, str]:
|
|
124
154
|
"""Handle RequestException raised by `self._request_new_token()`"""
|
|
125
|
-
if self.
|
|
155
|
+
if self.access_token:
|
|
126
156
|
# try using already retrieved token if authenticate() fails (OTP use-case)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
else:
|
|
133
|
-
return {
|
|
134
|
-
"access_token": self.token_info["access_token"],
|
|
135
|
-
"expires_in": "0",
|
|
136
|
-
}
|
|
157
|
+
return {
|
|
158
|
+
"access_token": self.access_token,
|
|
159
|
+
"expires_in": self.access_token_expiration.isoformat(),
|
|
160
|
+
}
|
|
137
161
|
response_text = getattr(e.response, "text", "").strip()
|
|
138
162
|
# check if error is identified as auth_error in provider conf
|
|
139
163
|
auth_errors = getattr(self.config, "auth_error_code", [None])
|
|
@@ -145,8 +169,9 @@ class OIDCRefreshTokenBase(Authentication):
|
|
|
145
169
|
and e.response.status_code in auth_errors
|
|
146
170
|
):
|
|
147
171
|
raise AuthenticationError(
|
|
148
|
-
"
|
|
149
|
-
|
|
172
|
+
f"Please check your credentials for {self.provider}.",
|
|
173
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
174
|
+
response_text,
|
|
150
175
|
)
|
|
151
176
|
# other error
|
|
152
177
|
else:
|
|
@@ -175,6 +200,7 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
175
200
|
adds an authentication layer on top of oauth 2.0. This plugin implements the
|
|
176
201
|
`authorization code flow <http://openid.net/specs/openid-connect-core-1_0.html#Authentication>`_
|
|
177
202
|
option of this specification.
|
|
203
|
+
|
|
178
204
|
The particularity of this plugin is that it proceeds to a headless (not involving the user)
|
|
179
205
|
interaction with the OpenID provider (if necessary) to authenticate a
|
|
180
206
|
registered user with its username and password on the server and then granting to eodag the
|
|
@@ -184,83 +210,60 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
184
210
|
The headless interaction is fully configurable, and rely on XPATH to retrieve all the necessary
|
|
185
211
|
information.
|
|
186
212
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
:param provider: provider name
|
|
214
|
+
:param config: Authentication plugin configuration:
|
|
215
|
+
|
|
216
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): OIDCAuthorizationCodeFlowAuth
|
|
217
|
+
* :attr:`~eodag.config.PluginConfig.redirect_uri` (``str``) (**mandatory**): The callback
|
|
218
|
+
url that will handle the code given by the OIDC provider
|
|
219
|
+
* :attr:`~eodag.config.PluginConfig.oidc_config_url` (``str``) (**mandatory**):
|
|
220
|
+
The url to get the OIDC Provider's endpoints
|
|
221
|
+
* :attr:`~eodag.config.PluginConfig.client_id` (``str``) (**mandatory**): The OIDC provider's
|
|
222
|
+
client ID of the eodag provider
|
|
223
|
+
* :attr:`~eodag.config.PluginConfig.user_consent_needed` (``bool``) (mandatory): Whether
|
|
224
|
+
a user consent is needed during the authentication
|
|
225
|
+
* :attr:`~eodag.config.PluginConfig.token_exchange_post_data_method` (``str``) (**mandatory**):
|
|
226
|
+
One of: ``json``, ``data`` or ``params``. This is the way to pass the data to the
|
|
227
|
+
POST request that is made to the token server. They correspond to the recognised keywords
|
|
228
|
+
arguments of the Python `requests <http://docs.python-requests.org/>`_ library
|
|
229
|
+
* :attr:`~eodag.config.PluginConfig.token_key` (``str``): The key pointing
|
|
230
|
+
to the token in the json response to the POST request to the token server
|
|
231
|
+
* :attr:`~eodag.config.PluginConfig.token_provision` (``str``) (**mandatory**): One of
|
|
232
|
+
``qs`` or ``header``. This is how the token obtained will be used to authenticate the
|
|
233
|
+
user on protected requests. If ``qs`` is chosen, then ``token_qs_key`` is mandatory
|
|
234
|
+
* :attr:`~eodag.config.PluginConfig.login_form_xpath` (``str``) (**mandatory**): The
|
|
235
|
+
xpath to the HTML form element representing the user login form
|
|
236
|
+
* :attr:`~eodag.config.PluginConfig.authentication_uri_source` (``str``) (**mandatory**): Where
|
|
237
|
+
to look for the authentication_uri. One of ``config`` (in the configuration) or ``login-form``
|
|
238
|
+
(use the 'action' URL found in the login form retrieved with login_form_xpath). If the
|
|
239
|
+
value is ``config``, authentication_uri config param is mandatory
|
|
240
|
+
* :attr:`~eodag.config.PluginConfig.authentication_uri` (``str``): (**mandatory if
|
|
241
|
+
authentication_uri_source=config**) The URL of the authentication backend of the OIDC provider
|
|
242
|
+
* :attr:`~eodag.config.PluginConfig.user_consent_form_xpath` (``str``): The xpath to
|
|
243
|
+
the user consent form. The form is searched in the content of the response to the authorization request
|
|
244
|
+
* :attr:`~eodag.config.PluginConfig.user_consent_form_data` (``Dict[str, str]``): The data that
|
|
245
|
+
will be passed with the POST request on the form 'action' URL. The data are given as
|
|
246
|
+
key value pairs, the keys representing the data key and the value being either a 'constant'
|
|
247
|
+
string value, or a string of the form 'xpath(<path-to-a-value-to-be-retrieved>)' and representing a
|
|
248
|
+
value to be retrieved in the user consent form. The xpath must resolve directly to a
|
|
249
|
+
string value, not to an HTML element. Example: ``xpath(//input[@name="sessionDataKeyConsent"]/@value)``
|
|
250
|
+
* :attr:`~eodag.config.PluginConfig.additional_login_form_data` (``Dict[str, str]``): A mapping
|
|
251
|
+
giving additional data to be passed to the login POST request. The value follows
|
|
252
|
+
the same rules as with user_consent_form_data
|
|
253
|
+
* :attr:`~eodag.config.PluginConfig.exchange_url_error_pattern` (``Dict[str, str]``): Key/value
|
|
254
|
+
pairs of patterns/messages. If exchange_url contains the given pattern, the associated
|
|
255
|
+
message will be sent in an AuthenticationError
|
|
256
|
+
* :attr:`~eodag.config.PluginConfig.client_secret` (``str``): The OIDC provider's client
|
|
257
|
+
secret of the eodag provider
|
|
258
|
+
* :attr:`~eodag.config.PluginConfig.token_exchange_params` (``Dict[str, str]``): mandatory
|
|
259
|
+
keys for the dict: redirect_uri, client_id; A mapping between OIDC url query string
|
|
260
|
+
and token handler query string params (only necessary if they are not the same as for OIDC).
|
|
261
|
+
This is eodag provider dependant
|
|
262
|
+
* :attr:`~eodag.config.PluginConfig.token_qs_key` (``str``): (mandatory when token_provision=qs)
|
|
263
|
+
Refers to the name of the query param to be used in the query request
|
|
264
|
+
* :attr:`~eodag.config.PluginConfig.refresh_token_key` (``str``): The key pointing to
|
|
265
|
+
the refresh_token in the json response to the POST request to the token server
|
|
216
266
|
|
|
217
|
-
# (mandatory) The xpath to the HTML form element representing the user login form
|
|
218
|
-
login_form_xpath:
|
|
219
|
-
|
|
220
|
-
# (mandatory) Where to look for the authentication_uri. One of 'config' (in the configuration) or 'login-form'
|
|
221
|
-
# (use the 'action' URL found in the login form retrieved with login_form_xpath). If the value is 'config',
|
|
222
|
-
# authentication_uri config param is mandatory
|
|
223
|
-
authentication_uri_source:
|
|
224
|
-
|
|
225
|
-
# (optional) The URL of the authentication backend of the OIDC provider
|
|
226
|
-
authentication_uri:
|
|
227
|
-
|
|
228
|
-
# (optional) The xpath to the user consent form. The form is searched in the content of the response
|
|
229
|
-
# to the authorization request
|
|
230
|
-
user_consent_form_xpath:
|
|
231
|
-
|
|
232
|
-
# (optional) The data that will be passed with the POST request on the form 'action' URL. The data are
|
|
233
|
-
# given as a key value pairs, the keys representing the data key and the value being either
|
|
234
|
-
# a 'constant' string value, or a string of the form 'xpath(<path-to-a-value-to-be-retrieved>)'
|
|
235
|
-
# and representing a value to be retrieved in the user consent form. The xpath must resolve
|
|
236
|
-
# directly to a string value, not to an HTML element. Example:
|
|
237
|
-
# `xpath(//input[@name="sessionDataKeyConsent"]/@value)`
|
|
238
|
-
user_consent_form_data:
|
|
239
|
-
|
|
240
|
-
# (optional) A mapping giving additional data to be passed to the login POST request. The value follows the
|
|
241
|
-
# same rules as with user_consent_form_data
|
|
242
|
-
additional_login_form_data:
|
|
243
|
-
|
|
244
|
-
# (optional) Key/value pairs of patterns/messages. If exchange_url contains the given pattern, the associated
|
|
245
|
-
message will be sent in an AuthenticationError
|
|
246
|
-
exchange_url_error_pattern:
|
|
247
|
-
|
|
248
|
-
# (optional) The OIDC provider's client secret of the eodag provider
|
|
249
|
-
client_secret:
|
|
250
|
-
|
|
251
|
-
# (optional) A mapping between OIDC url query string and token handler query string
|
|
252
|
-
# params (only necessary if they are not the same as for OIDC). This is eodag provider
|
|
253
|
-
# dependant
|
|
254
|
-
token_exchange_params:
|
|
255
|
-
redirect_uri:
|
|
256
|
-
client_id:
|
|
257
|
-
|
|
258
|
-
# (optional) Only necessary when 'token_provision' is 'qs'. Refers to the name of the query param to be
|
|
259
|
-
# used in the query request
|
|
260
|
-
token_qs_key:
|
|
261
|
-
|
|
262
|
-
# (optional) The key pointing to the refresh_token in the json response to the POST request to the token server
|
|
263
|
-
refresh_token_key:
|
|
264
267
|
"""
|
|
265
268
|
|
|
266
269
|
SCOPE = "openid"
|
|
@@ -287,17 +290,17 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
287
290
|
|
|
288
291
|
def authenticate(self) -> CodeAuthorizedAuth:
|
|
289
292
|
"""Authenticate"""
|
|
290
|
-
self.
|
|
293
|
+
self._get_access_token()
|
|
291
294
|
|
|
292
295
|
return CodeAuthorizedAuth(
|
|
293
|
-
self.
|
|
296
|
+
self.access_token,
|
|
294
297
|
self.config.token_provision,
|
|
295
298
|
key=getattr(self.config, "token_qs_key", None),
|
|
296
299
|
)
|
|
297
300
|
|
|
298
301
|
def _request_new_token(self) -> Dict[str, str]:
|
|
299
|
-
"""Fetch the access token with a new
|
|
300
|
-
logger.debug("Fetching access token from %s", self.
|
|
302
|
+
"""Fetch the access token with a new authentication"""
|
|
303
|
+
logger.debug("Fetching access token from %s", self.token_endpoint)
|
|
301
304
|
state = self.compute_state()
|
|
302
305
|
authentication_response = self.authenticate_user(state)
|
|
303
306
|
exchange_url = authentication_response.url
|
|
@@ -326,20 +329,22 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
326
329
|
def _get_token_with_refresh_token(self) -> Dict[str, str]:
|
|
327
330
|
"""Fetch the access token with the refresh token"""
|
|
328
331
|
logger.debug(
|
|
329
|
-
"Fetching access token with refresh token from %s", self.
|
|
332
|
+
"Fetching access token with refresh token from %s.", self.token_endpoint
|
|
330
333
|
)
|
|
331
334
|
token_data: Dict[str, Any] = {
|
|
332
|
-
"refresh_token": self.
|
|
335
|
+
"refresh_token": self.refresh_token,
|
|
333
336
|
"grant_type": "refresh_token",
|
|
334
337
|
}
|
|
335
338
|
token_data = self._prepare_token_post_data(token_data)
|
|
336
339
|
post_request_kwargs: Any = {
|
|
337
340
|
self.config.token_exchange_post_data_method: token_data
|
|
338
341
|
}
|
|
342
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
339
343
|
try:
|
|
340
344
|
token_response = self.session.post(
|
|
341
|
-
self.
|
|
345
|
+
self.token_endpoint,
|
|
342
346
|
timeout=HTTP_REQ_TIMEOUT,
|
|
347
|
+
verify=ssl_verify,
|
|
343
348
|
**post_request_kwargs,
|
|
344
349
|
)
|
|
345
350
|
token_response.raise_for_status()
|
|
@@ -363,11 +368,13 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
363
368
|
"state": state,
|
|
364
369
|
"redirect_uri": self.config.redirect_uri,
|
|
365
370
|
}
|
|
371
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
366
372
|
authorization_response = self.session.get(
|
|
367
|
-
self.
|
|
373
|
+
self.authorization_endpoint,
|
|
368
374
|
params=params,
|
|
369
375
|
headers=USER_AGENT,
|
|
370
376
|
timeout=HTTP_REQ_TIMEOUT,
|
|
377
|
+
verify=ssl_verify,
|
|
371
378
|
)
|
|
372
379
|
|
|
373
380
|
login_document = etree.HTML(authorization_response.text)
|
|
@@ -392,7 +399,7 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
392
399
|
self.config.login_form_xpath.rstrip("/") + "/@action"
|
|
393
400
|
)
|
|
394
401
|
if not auth_uri or not auth_uri[0]:
|
|
395
|
-
raise
|
|
402
|
+
raise MisconfiguredError(
|
|
396
403
|
f"Could not get auth_uri from {self.config.login_form_xpath}"
|
|
397
404
|
)
|
|
398
405
|
auth_uri = auth_uri[0]
|
|
@@ -401,7 +408,11 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
401
408
|
if not auth_uri:
|
|
402
409
|
raise MisconfiguredError("authentication_uri is missing")
|
|
403
410
|
return self.session.post(
|
|
404
|
-
auth_uri,
|
|
411
|
+
auth_uri,
|
|
412
|
+
data=login_data,
|
|
413
|
+
headers=USER_AGENT,
|
|
414
|
+
timeout=HTTP_REQ_TIMEOUT,
|
|
415
|
+
verify=ssl_verify,
|
|
405
416
|
)
|
|
406
417
|
|
|
407
418
|
def grant_user_consent(self, authentication_response: Response) -> Response:
|
|
@@ -415,11 +426,13 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
415
426
|
key: self._constant_or_xpath_extracted(value, user_consent_form)
|
|
416
427
|
for key, value in self.config.user_consent_form_data.items()
|
|
417
428
|
}
|
|
429
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
418
430
|
return self.session.post(
|
|
419
|
-
self.
|
|
431
|
+
self.authorization_endpoint,
|
|
420
432
|
data=user_consent_data,
|
|
421
433
|
headers=USER_AGENT,
|
|
422
434
|
timeout=HTTP_REQ_TIMEOUT,
|
|
435
|
+
verify=ssl_verify,
|
|
423
436
|
)
|
|
424
437
|
|
|
425
438
|
def _prepare_token_post_data(self, token_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -467,10 +480,12 @@ class OIDCAuthorizationCodeFlowAuth(OIDCRefreshTokenBase):
|
|
|
467
480
|
post_request_kwargs: Any = {
|
|
468
481
|
self.config.token_exchange_post_data_method: token_exchange_data
|
|
469
482
|
}
|
|
483
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
470
484
|
r = self.session.post(
|
|
471
|
-
self.
|
|
485
|
+
self.token_endpoint,
|
|
472
486
|
headers=USER_AGENT,
|
|
473
487
|
timeout=HTTP_REQ_TIMEOUT,
|
|
488
|
+
verify=ssl_verify,
|
|
474
489
|
**post_request_kwargs,
|
|
475
490
|
)
|
|
476
491
|
return r
|
|
@@ -36,9 +36,17 @@ class HttpQueryStringAuth(Authentication):
|
|
|
36
36
|
"""An Authentication plugin using HTTP query string parameters.
|
|
37
37
|
|
|
38
38
|
This plugin sends credentials as query-string parameters.
|
|
39
|
+
|
|
40
|
+
:param provider: provider name
|
|
41
|
+
:param config: Authentication plugin configuration:
|
|
42
|
+
|
|
43
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): HttpQueryStringAuth
|
|
44
|
+
* :attr:`~eodag.config.PluginConfig.auth_uri` (``str``): used to check the credentials
|
|
45
|
+
given in the configuration
|
|
46
|
+
|
|
39
47
|
Using :class:`~eodag.plugins.download.http.HTTPDownload` a download link
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
``http://example.com?foo=bar`` will become
|
|
49
|
+
``http://example.com?foo=bar&apikey=XXX&otherkey=YYY`` if associated to the following
|
|
42
50
|
configuration::
|
|
43
51
|
|
|
44
52
|
provider:
|
|
@@ -56,7 +64,7 @@ class HttpQueryStringAuth(Authentication):
|
|
|
56
64
|
...
|
|
57
65
|
...
|
|
58
66
|
|
|
59
|
-
If
|
|
67
|
+
If ``auth_uri`` is specified (optional), it will be used to check credentials through
|
|
60
68
|
:meth:`~eodag.plugins.authentication.query_string.HttpQueryStringAuth.authenticate`
|
|
61
69
|
"""
|
|
62
70
|
|
|
@@ -82,7 +90,7 @@ class HttpQueryStringAuth(Authentication):
|
|
|
82
90
|
except requests.exceptions.Timeout as exc:
|
|
83
91
|
raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
|
|
84
92
|
except RequestException as e:
|
|
85
|
-
raise AuthenticationError(
|
|
93
|
+
raise AuthenticationError("Could no authenticate", str(e)) from e
|
|
86
94
|
|
|
87
95
|
return auth
|
|
88
96
|
|
|
@@ -75,7 +75,7 @@ class RequestsSASAuth(AuthBase):
|
|
|
75
75
|
except requests.exceptions.Timeout as exc:
|
|
76
76
|
raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
|
|
77
77
|
except (requests.RequestException, JSONDecodeError, KeyError) as e:
|
|
78
|
-
raise AuthenticationError(
|
|
78
|
+
raise AuthenticationError("Could no get signed url", str(e)) from e
|
|
79
79
|
else:
|
|
80
80
|
self.signed_urls[req_signed_url] = signed_url
|
|
81
81
|
|
|
@@ -85,7 +85,24 @@ class RequestsSASAuth(AuthBase):
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class SASAuth(Authentication):
|
|
88
|
-
"""SASAuth authentication plugin
|
|
88
|
+
"""SASAuth authentication plugin
|
|
89
|
+
|
|
90
|
+
An apiKey that is added in the headers can be given in the credentials in the config file.
|
|
91
|
+
|
|
92
|
+
:param provider: provider name
|
|
93
|
+
:param config: Authentication plugin configuration:
|
|
94
|
+
|
|
95
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): SASAuth
|
|
96
|
+
* :attr:`~eodag.config.PluginConfig.auth_uri` (``str``) (**mandatory**): url used to
|
|
97
|
+
get the signed url
|
|
98
|
+
* :attr:`~eodag.config.PluginConfig.signed_url_key` (``str``) (**mandatory**): key to
|
|
99
|
+
get the signed url
|
|
100
|
+
* :attr:`~eodag.config.PluginConfig.headers` (``Dict[str, str]``) (**mandatory if
|
|
101
|
+
apiKey is used**): headers to be added to the requests
|
|
102
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be
|
|
103
|
+
verified in the requests; default: ``True``
|
|
104
|
+
|
|
105
|
+
"""
|
|
89
106
|
|
|
90
107
|
def validate_config_credentials(self) -> None:
|
|
91
108
|
"""Validate configured credentials"""
|