eodag 3.0.0b2__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/__init__.py +6 -8
- eodag/api/core.py +295 -287
- eodag/api/product/__init__.py +10 -4
- eodag/api/product/_assets.py +2 -14
- eodag/api/product/_product.py +16 -30
- eodag/api/product/drivers/__init__.py +7 -2
- eodag/api/product/drivers/base.py +0 -3
- eodag/api/product/metadata_mapping.py +12 -31
- eodag/api/search_result.py +33 -12
- eodag/cli.py +35 -19
- eodag/config.py +455 -155
- eodag/plugins/apis/base.py +13 -7
- eodag/plugins/apis/ecmwf.py +16 -7
- eodag/plugins/apis/usgs.py +68 -16
- eodag/plugins/authentication/aws_auth.py +25 -7
- 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 +183 -167
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +59 -11
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +7 -2
- eodag/plugins/crunch/filter_date.py +8 -11
- eodag/plugins/crunch/filter_latest_intersect.py +5 -7
- eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
- eodag/plugins/crunch/filter_overlap.py +9 -15
- eodag/plugins/crunch/filter_property.py +9 -14
- eodag/plugins/download/aws.py +84 -99
- eodag/plugins/download/base.py +36 -77
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +134 -109
- eodag/plugins/download/s3rest.py +37 -43
- eodag/plugins/manager.py +173 -41
- eodag/plugins/search/__init__.py +9 -9
- eodag/plugins/search/base.py +35 -35
- eodag/plugins/search/build_search_result.py +55 -64
- eodag/plugins/search/cop_marine.py +113 -32
- eodag/plugins/search/creodias_s3.py +20 -8
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +119 -14
- eodag/plugins/search/qssearch.py +619 -197
- eodag/plugins/search/static_stac_search.py +25 -23
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +211 -56
- eodag/resources/providers.yml +1762 -1809
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +134 -119
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +70 -92
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -330
- eodag/rest/stac.py +105 -630
- eodag/rest/types/eodag_search.py +17 -15
- eodag/rest/types/queryables.py +5 -14
- eodag/rest/types/stac_search.py +18 -13
- eodag/rest/utils/rfc3339.py +0 -1
- eodag/types/__init__.py +24 -6
- eodag/types/download_args.py +14 -5
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +10 -11
- eodag/types/whoosh.py +0 -2
- eodag/utils/__init__.py +97 -136
- eodag/utils/constraints.py +0 -8
- eodag/utils/exceptions.py +23 -9
- eodag/utils/import_system.py +0 -4
- eodag/utils/logging.py +37 -80
- eodag/utils/notebook.py +4 -4
- eodag/utils/requests.py +13 -23
- eodag/utils/rest.py +0 -4
- eodag/utils/stac_reader.py +3 -15
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b2.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.0b2.dist-info/RECORD +0 -110
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
eodag/plugins/apis/base.py
CHANGED
|
@@ -24,26 +24,27 @@ from eodag.plugins.search.base import Search
|
|
|
24
24
|
class Api(Search, Download):
|
|
25
25
|
"""Plugins API Base plugin
|
|
26
26
|
|
|
27
|
-
An Api plugin
|
|
27
|
+
An Api plugin inherits the methods from Search and Download plugins.
|
|
28
28
|
|
|
29
29
|
There are three methods that it must implement:
|
|
30
|
+
|
|
30
31
|
- ``query``: search for products
|
|
31
32
|
- ``download``: download a single :class:`~eodag.api.product._product.EOProduct`
|
|
32
33
|
- ``download_all``: download multiple products from a :class:`~eodag.api.search_result.SearchResult`
|
|
33
34
|
|
|
34
35
|
The download methods must:
|
|
35
36
|
|
|
36
|
-
- download data in the ``
|
|
37
|
+
- download data in the ``output_dir`` folder defined in the plugin's
|
|
37
38
|
configuration or passed through kwargs
|
|
38
|
-
- extract products from their archive (if relevant) if ``extract`` is set to True
|
|
39
|
-
(True by default)
|
|
40
|
-
- save a product in an archive/directory (in ``
|
|
39
|
+
- extract products from their archive (if relevant) if ``extract`` is set to ``True``
|
|
40
|
+
(``True`` by default)
|
|
41
|
+
- save a product in an archive/directory (in ``output_dir``) whose name must be
|
|
41
42
|
the product's ``title`` property
|
|
42
43
|
- update the product's ``location`` attribute once its data is downloaded (and
|
|
43
44
|
eventually after it's extracted) to the product's location given as a file URI
|
|
44
45
|
(e.g. 'file:///tmp/product_folder' on Linux or
|
|
45
|
-
'file:///C:/Users/username/AppData/
|
|
46
|
-
- save a *record* file in the directory ``
|
|
46
|
+
'file:///C:/Users/username/AppData/Local/Temp' on Windows)
|
|
47
|
+
- save a *record* file in the directory ``output_dir/.downloaded`` whose name
|
|
47
48
|
is built on the MD5 hash of the product's ``product_type`` and ``properties['id']``
|
|
48
49
|
attributes (``hashlib.md5((product.product_type+"-"+product.properties['id']).encode("utf-8")).hexdigest()``)
|
|
49
50
|
and whose content is the product's ``remote_location`` attribute itself.
|
|
@@ -52,4 +53,9 @@ class Api(Search, Download):
|
|
|
52
53
|
- not try to download a product if its *record* file exists as long as the expected
|
|
53
54
|
product's file/directory. If the *record* file only is found, it must be deleted
|
|
54
55
|
(it certainly indicates that the download didn't complete)
|
|
56
|
+
|
|
57
|
+
:param provider: An EODAG provider name
|
|
58
|
+
:type provider: str
|
|
59
|
+
:param config: An EODAG plugin configuration
|
|
60
|
+
:type config: Dict[str, Any]
|
|
55
61
|
"""
|
eodag/plugins/apis/ecmwf.py
CHANGED
|
@@ -68,10 +68,20 @@ class EcmwfApi(Api, BuildPostSearchResult):
|
|
|
68
68
|
is in query), or on MARS Operational Archive (if ``dataset`` parameter is not in
|
|
69
69
|
query).
|
|
70
70
|
|
|
71
|
-
This class inherits from :class:`~eodag.plugins.apis.base.Api` for compatibility
|
|
72
|
-
:class:`~eodag.plugins.
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
This class inherits from :class:`~eodag.plugins.apis.base.Api` for compatibility and
|
|
72
|
+
:class:`~eodag.plugins.search.build_search_result.BuildPostSearchResult` for the creation
|
|
73
|
+
of the search result.
|
|
74
|
+
|
|
75
|
+
:param provider: provider name
|
|
76
|
+
:param config: Api plugin configuration:
|
|
77
|
+
|
|
78
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): EcmwfApi
|
|
79
|
+
* :attr:`~eodag.config.PluginConfig.api_endpoint` (``str``) (**mandatory**): url of the ecmwf api
|
|
80
|
+
* :attr:`~eodag.config.PluginConfig.metadata_mapping` (``Dict[str, Union[str, list]]``): how
|
|
81
|
+
parameters should be mapped between the provider and eodag; If a string is given, this is
|
|
82
|
+
the mapping parameter returned by provider -> eodag parameter. If a list with 2 elements
|
|
83
|
+
is given, the first one is the mapping eodag parameter -> provider query parameters
|
|
84
|
+
and the second one the mapping provider result parameter -> eodag parameter
|
|
75
85
|
"""
|
|
76
86
|
|
|
77
87
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
@@ -128,7 +138,6 @@ class EcmwfApi(Api, BuildPostSearchResult):
|
|
|
128
138
|
"""Check credentials and returns information needed for auth
|
|
129
139
|
|
|
130
140
|
:returns: {key, url, email} dictionary
|
|
131
|
-
:rtype: dict
|
|
132
141
|
:raises: :class:`~eodag.utils.exceptions.AuthenticationError`
|
|
133
142
|
"""
|
|
134
143
|
# Get credentials from eodag or using ecmwf conf
|
|
@@ -165,8 +174,8 @@ class EcmwfApi(Api, BuildPostSearchResult):
|
|
|
165
174
|
"""Download data from ECMWF MARS"""
|
|
166
175
|
product_format = product.properties.get("format", "grib")
|
|
167
176
|
product_extension = ECMWF_MARS_KNOWN_FORMATS.get(product_format, product_format)
|
|
168
|
-
kwargs["
|
|
169
|
-
"
|
|
177
|
+
kwargs["output_extension"] = kwargs.get(
|
|
178
|
+
"output_extension", f".{product_extension}"
|
|
170
179
|
)
|
|
171
180
|
|
|
172
181
|
# Prepare download
|
eodag/plugins/apis/usgs.py
CHANGED
|
@@ -68,7 +68,30 @@ logger = logging.getLogger("eodag.apis.usgs")
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
class UsgsApi(Api):
|
|
71
|
-
"""A plugin that enables to query and download data on the USGS catalogues
|
|
71
|
+
"""A plugin that enables to query and download data on the USGS catalogues
|
|
72
|
+
|
|
73
|
+
:param provider: provider name
|
|
74
|
+
:param config: Api plugin configuration:
|
|
75
|
+
|
|
76
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): UsgsApi
|
|
77
|
+
* :attr:`~eodag.config.PluginConfig.pagination` (:class:`~eodag.config.PluginConfig.Pagination`)
|
|
78
|
+
(**mandatory**): object containing parameters for pagination; should contain the attribute
|
|
79
|
+
:attr:`~eodag.config.PluginConfig.Pagination.total_items_nb_key_path`
|
|
80
|
+
which is indicating the key for the number of total items in the provider result
|
|
81
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
|
|
82
|
+
should be verified in the download request; default: ``True``
|
|
83
|
+
* :attr:`~eodag.config.PluginConfig.need_auth` (``bool``): if authentication is required
|
|
84
|
+
for search; default: ``False``
|
|
85
|
+
* :attr:`~eodag.config.PluginConfig.extract` (``bool``): if the content of the downloaded
|
|
86
|
+
file should be extracted; default: ``True``
|
|
87
|
+
* :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): if the product has to
|
|
88
|
+
be ordered to download it; default: ``False``
|
|
89
|
+
* :attr:`~eodag.config.PluginConfig.metadata_mapping` (``Dict[str, Union[str, list]]``): how
|
|
90
|
+
parameters should be mapped between the provider and eodag; If a string is given, this is
|
|
91
|
+
the mapping parameter returned by provider -> eodag parameter. If a list with 2 elements
|
|
92
|
+
is given, the first one is the mapping eodag parameter -> provider query parameters
|
|
93
|
+
and the second one the mapping provider result parameter -> eodag parameter
|
|
94
|
+
"""
|
|
72
95
|
|
|
73
96
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
74
97
|
super(UsgsApi, self).__init__(provider, config)
|
|
@@ -128,13 +151,14 @@ class UsgsApi(Api):
|
|
|
128
151
|
raise NoMatchingProductType(
|
|
129
152
|
"Cannot search on USGS without productType specified"
|
|
130
153
|
)
|
|
131
|
-
if kwargs.get("
|
|
154
|
+
if kwargs.get("sort_by"):
|
|
132
155
|
raise ValidationError("USGS does not support sorting feature")
|
|
133
156
|
|
|
134
157
|
self.authenticate()
|
|
135
158
|
|
|
136
159
|
product_type_def_params = self.config.products.get( # type: ignore
|
|
137
|
-
product_type,
|
|
160
|
+
product_type,
|
|
161
|
+
self.config.products[GENERIC_PRODUCT_TYPE], # type: ignore
|
|
138
162
|
)
|
|
139
163
|
usgs_dataset = format_dict_items(product_type_def_params, **kwargs)["dataset"]
|
|
140
164
|
start_date = kwargs.pop("startTimeFromAscendingNode", None)
|
|
@@ -172,11 +196,39 @@ class UsgsApi(Api):
|
|
|
172
196
|
max_results=items_per_page,
|
|
173
197
|
starting_number=(1 + (page - 1) * items_per_page),
|
|
174
198
|
)
|
|
175
|
-
logger.info(
|
|
176
|
-
f"Sending search request for {usgs_dataset} with {api_search_kwargs}"
|
|
177
|
-
)
|
|
178
199
|
|
|
179
|
-
|
|
200
|
+
# search by id
|
|
201
|
+
if searched_id := kwargs.get("id"):
|
|
202
|
+
dataset_filters = api.dataset_filters(usgs_dataset)
|
|
203
|
+
# ip pattern set as parameter queryable (first element of param conf list)
|
|
204
|
+
id_pattern = self.config.metadata_mapping["id"][0]
|
|
205
|
+
# loop on matching dataset_filters until one returns expected results
|
|
206
|
+
for dataset_filter in dataset_filters["data"]:
|
|
207
|
+
if id_pattern in dataset_filter["searchSql"]:
|
|
208
|
+
logger.debug(
|
|
209
|
+
f"Try using {dataset_filter['searchSql']} dataset filter to search by id on {usgs_dataset}"
|
|
210
|
+
)
|
|
211
|
+
full_api_search_kwargs = {
|
|
212
|
+
"where": {
|
|
213
|
+
"filter_id": dataset_filter["id"],
|
|
214
|
+
"value": searched_id,
|
|
215
|
+
},
|
|
216
|
+
**api_search_kwargs,
|
|
217
|
+
}
|
|
218
|
+
logger.info(
|
|
219
|
+
f"Sending search request for {usgs_dataset} with {full_api_search_kwargs}"
|
|
220
|
+
)
|
|
221
|
+
results = api.scene_search(
|
|
222
|
+
usgs_dataset, **full_api_search_kwargs
|
|
223
|
+
)
|
|
224
|
+
if len(results["data"]["results"]) == 1:
|
|
225
|
+
# search by id using this dataset_filter succeeded
|
|
226
|
+
break
|
|
227
|
+
else:
|
|
228
|
+
logger.info(
|
|
229
|
+
f"Sending search request for {usgs_dataset} with {api_search_kwargs}"
|
|
230
|
+
)
|
|
231
|
+
results = api.scene_search(usgs_dataset, **api_search_kwargs)
|
|
180
232
|
|
|
181
233
|
# update results with storage info from download_options()
|
|
182
234
|
results_by_entity_id = {
|
|
@@ -227,7 +279,7 @@ class UsgsApi(Api):
|
|
|
227
279
|
f"Product type {usgs_dataset} may not exist on USGS EE catalog"
|
|
228
280
|
)
|
|
229
281
|
api.logout()
|
|
230
|
-
raise RequestError(e)
|
|
282
|
+
raise RequestError.from_error(e) from e
|
|
231
283
|
|
|
232
284
|
api.logout()
|
|
233
285
|
|
|
@@ -257,13 +309,13 @@ class UsgsApi(Api):
|
|
|
257
309
|
)
|
|
258
310
|
progress_callback = ProgressCallback(disable=True)
|
|
259
311
|
|
|
260
|
-
|
|
312
|
+
output_extension = cast(
|
|
261
313
|
str,
|
|
262
314
|
self.config.products.get( # type: ignore
|
|
263
315
|
product.product_type, self.config.products[GENERIC_PRODUCT_TYPE] # type: ignore
|
|
264
|
-
).get("
|
|
316
|
+
).get("output_extension", ".tar.gz"),
|
|
265
317
|
)
|
|
266
|
-
kwargs["
|
|
318
|
+
kwargs["output_extension"] = kwargs.get("output_extension", output_extension)
|
|
267
319
|
|
|
268
320
|
fs_path, record_filename = self._prepare_download(
|
|
269
321
|
product,
|
|
@@ -375,8 +427,8 @@ class UsgsApi(Api):
|
|
|
375
427
|
|
|
376
428
|
# Check downloaded file format
|
|
377
429
|
if (
|
|
378
|
-
kwargs["
|
|
379
|
-
) or (kwargs["
|
|
430
|
+
kwargs["output_extension"] == ".tar.gz" and tarfile.is_tarfile(fs_path)
|
|
431
|
+
) or (kwargs["output_extension"] == ".zip" and zipfile.is_zipfile(fs_path)):
|
|
380
432
|
product_path = self._finalize(
|
|
381
433
|
fs_path,
|
|
382
434
|
progress_callback=progress_callback,
|
|
@@ -388,7 +440,7 @@ class UsgsApi(Api):
|
|
|
388
440
|
logger.info(
|
|
389
441
|
"Downloaded product detected as a tar File, but was was expected to be a zip file"
|
|
390
442
|
)
|
|
391
|
-
new_fs_path = fs_path[: fs_path.index(
|
|
443
|
+
new_fs_path = fs_path[: fs_path.index(output_extension)] + ".tar.gz"
|
|
392
444
|
shutil.move(fs_path, new_fs_path)
|
|
393
445
|
product.location = path_to_uri(new_fs_path)
|
|
394
446
|
return new_fs_path
|
|
@@ -396,7 +448,7 @@ class UsgsApi(Api):
|
|
|
396
448
|
logger.info(
|
|
397
449
|
"Downloaded product detected as a zip File, but was was expected to be a tar file"
|
|
398
450
|
)
|
|
399
|
-
new_fs_path = fs_path[: fs_path.index(
|
|
451
|
+
new_fs_path = fs_path[: fs_path.index(output_extension)] + ".zip"
|
|
400
452
|
shutil.move(fs_path, new_fs_path)
|
|
401
453
|
product.location = path_to_uri(new_fs_path)
|
|
402
454
|
return new_fs_path
|
|
@@ -404,7 +456,7 @@ class UsgsApi(Api):
|
|
|
404
456
|
logger.warning(
|
|
405
457
|
"Downloaded product is not a tar or a zip File. Please check its file type before using it"
|
|
406
458
|
)
|
|
407
|
-
new_fs_path = fs_path[: fs_path.index(
|
|
459
|
+
new_fs_path = fs_path[: fs_path.index(output_extension)]
|
|
408
460
|
shutil.move(fs_path, new_fs_path)
|
|
409
461
|
product.location = path_to_uri(new_fs_path)
|
|
410
462
|
return new_fs_path
|
|
@@ -30,13 +30,23 @@ if TYPE_CHECKING:
|
|
|
30
30
|
class AwsAuth(Authentication):
|
|
31
31
|
"""AWS authentication plugin
|
|
32
32
|
|
|
33
|
-
Authentication will use the first valid method within the following ones
|
|
33
|
+
Authentication will use the first valid method within the following ones depending on which
|
|
34
|
+
parameters are available in the configuration:
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
* auth anonymously using no-sign-request
|
|
37
|
+
* auth using ``aws_profile``
|
|
38
|
+
* auth using ``aws_access_key_id`` and ``aws_secret_access_key``
|
|
39
|
+
(optionally ``aws_session_token``)
|
|
40
|
+
* auth using current environment (AWS environment variables and/or ``~/aws/*``),
|
|
39
41
|
will be skipped if AWS credentials are filled in eodag conf
|
|
42
|
+
|
|
43
|
+
:param provider: provider name
|
|
44
|
+
:param config: Authentication plugin configuration:
|
|
45
|
+
|
|
46
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): AwsAuth
|
|
47
|
+
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``) (mandatory for ``creodias_s3``):
|
|
48
|
+
which error code is returned in case of an authentication error
|
|
49
|
+
|
|
40
50
|
"""
|
|
41
51
|
|
|
42
52
|
s3_client: S3Client
|
|
@@ -45,13 +55,13 @@ class AwsAuth(Authentication):
|
|
|
45
55
|
super(AwsAuth, self).__init__(provider, config)
|
|
46
56
|
self.aws_access_key_id = None
|
|
47
57
|
self.aws_secret_access_key = None
|
|
58
|
+
self.aws_session_token = None
|
|
48
59
|
self.profile_name = None
|
|
49
60
|
|
|
50
61
|
def authenticate(self) -> Dict[str, str]:
|
|
51
62
|
"""Authenticate
|
|
52
63
|
|
|
53
64
|
:returns: dict containing AWS/boto3 non-empty credentials
|
|
54
|
-
:rtype: dict
|
|
55
65
|
"""
|
|
56
66
|
credentials = getattr(self.config, "credentials", {}) or {}
|
|
57
67
|
self.aws_access_key_id = credentials.get(
|
|
@@ -60,7 +70,15 @@ class AwsAuth(Authentication):
|
|
|
60
70
|
self.aws_secret_access_key = credentials.get(
|
|
61
71
|
"aws_secret_access_key", self.aws_secret_access_key
|
|
62
72
|
)
|
|
73
|
+
self.aws_session_token = credentials.get(
|
|
74
|
+
"aws_session_token", self.aws_session_token
|
|
75
|
+
)
|
|
63
76
|
self.profile_name = credentials.get("aws_profile", self.profile_name)
|
|
64
77
|
|
|
65
|
-
auth_keys = [
|
|
78
|
+
auth_keys = [
|
|
79
|
+
"aws_access_key_id",
|
|
80
|
+
"aws_secret_access_key",
|
|
81
|
+
"aws_session_token",
|
|
82
|
+
"profile_name",
|
|
83
|
+
]
|
|
66
84
|
return {k: getattr(self, k) for k in auth_keys if getattr(self, k)}
|
|
@@ -27,7 +27,16 @@ if TYPE_CHECKING:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Authentication(PluginTopic):
|
|
30
|
-
"""Plugins authentication Base plugin
|
|
30
|
+
"""Plugins authentication Base plugin
|
|
31
|
+
|
|
32
|
+
:param provider: provider name
|
|
33
|
+
:param config: Authentication plugin configuration:
|
|
34
|
+
|
|
35
|
+
* :attr:`~eodag.config.PluginConfig.matching_url` (``str``): URL pattern to match with search plugin endpoint or
|
|
36
|
+
download link
|
|
37
|
+
* :attr:`~eodag.config.PluginConfig.matching_conf` (``Dict[str, Any]``): Part of the search or download plugin
|
|
38
|
+
configuration that needs authentication and helps identifying it
|
|
39
|
+
"""
|
|
31
40
|
|
|
32
41
|
def authenticate(self) -> Union[AuthBase, Dict[str, str]]:
|
|
33
42
|
"""Authenticate"""
|
|
@@ -29,7 +29,18 @@ if TYPE_CHECKING:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class GenericAuth(Authentication):
|
|
32
|
-
"""GenericAuth authentication plugin
|
|
32
|
+
"""GenericAuth authentication plugin (authentication using ``username`` and ``password``)
|
|
33
|
+
|
|
34
|
+
The mandatory parameters that have to be added in the eodag config are username and password.
|
|
35
|
+
|
|
36
|
+
:param provider: provider name
|
|
37
|
+
:param config: Authentication plugin configuration:
|
|
38
|
+
|
|
39
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): GenericAuth
|
|
40
|
+
* :attr:`~eodag.config.PluginConfig.method` (``str``): specifies if digest authentication
|
|
41
|
+
(``digest``) or basic authentication (``basic``) should be used; default: ``basic``
|
|
42
|
+
|
|
43
|
+
"""
|
|
33
44
|
|
|
34
45
|
def authenticate(self) -> AuthBase:
|
|
35
46
|
"""Authenticate"""
|
|
@@ -48,6 +59,6 @@ class GenericAuth(Authentication):
|
|
|
48
59
|
)
|
|
49
60
|
else:
|
|
50
61
|
raise MisconfiguredError(
|
|
51
|
-
f"Cannot authenticate with {self.provider}
|
|
52
|
-
f"
|
|
62
|
+
f"Cannot authenticate with {self.provider}",
|
|
63
|
+
f"Method {method} is not supported; it must be one of 'digest' or 'basic'.",
|
|
53
64
|
)
|
|
@@ -34,7 +34,14 @@ class HTTPHeaderAuth(Authentication):
|
|
|
34
34
|
This plugin enables implementation of custom HTTP authentication scheme (other than Basic, Digest, Token
|
|
35
35
|
negotiation et al.) using HTTP headers.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
:param provider: provider name
|
|
38
|
+
:param config: Authentication plugin configuration:
|
|
39
|
+
|
|
40
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): HTTPHeaderAuth
|
|
41
|
+
* :attr:`~eodag.config.PluginConfig.headers` (``Dict[str, str]``): dictionary containing
|
|
42
|
+
all keys/value pairs that should be added to the headers
|
|
43
|
+
|
|
44
|
+
Below an example for the configuration in the providers config file is shown::
|
|
38
45
|
|
|
39
46
|
provider:
|
|
40
47
|
...
|
|
@@ -47,9 +54,9 @@ class HTTPHeaderAuth(Authentication):
|
|
|
47
54
|
...
|
|
48
55
|
...
|
|
49
56
|
|
|
50
|
-
As you can see in the sample above, the maintainer of
|
|
51
|
-
authentication process as-is, by giving their names (e.g.
|
|
52
|
-
|
|
57
|
+
As you can see in the sample above, the maintainer of ``provider`` define the headers that will be used in the
|
|
58
|
+
authentication process as-is, by giving their names (e.g. ``Authorization``) and their value (e.g
|
|
59
|
+
``"Something {userinput}"``) as regular Python string templates that enable passing in the user input necessary to
|
|
53
60
|
compute its identity. The user input awaited in the header value string must be present in the user config file.
|
|
54
61
|
In the sample above, the plugin await for user credentials to be specified as::
|
|
55
62
|
|
|
@@ -72,6 +79,7 @@ class HTTPHeaderAuth(Authentication):
|
|
|
72
79
|
X-Another-Special-Header: "YYY"
|
|
73
80
|
...
|
|
74
81
|
...
|
|
82
|
+
|
|
75
83
|
"""
|
|
76
84
|
|
|
77
85
|
def authenticate(self) -> HeaderAuth:
|
|
@@ -41,19 +41,38 @@ logger = logging.getLogger("eodag.auth.keycloak")
|
|
|
41
41
|
class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
42
42
|
"""Authentication plugin using Keycloak and OpenId Connect.
|
|
43
43
|
|
|
44
|
-
This plugin
|
|
44
|
+
This plugin requests a token which is added to a query-string or a header for authentication.
|
|
45
|
+
|
|
46
|
+
:param provider: provider name
|
|
47
|
+
:param config: Authentication plugin configuration:
|
|
48
|
+
|
|
49
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): KeycloakOIDCPasswordAuth
|
|
50
|
+
* :attr:`~eodag.config.PluginConfig.oidc_config_url` (``str``) (**mandatory**):
|
|
51
|
+
The url to get the OIDC Provider's endpoints
|
|
52
|
+
* :attr:`~eodag.config.PluginConfig.client_id` (``str``) (**mandatory**): keycloak client id
|
|
53
|
+
* :attr:`~eodag.config.PluginConfig.client_secret` (``str``) (**mandatory**): keycloak
|
|
54
|
+
client secret, set to null if no secret is used
|
|
55
|
+
* :attr:`~eodag.config.PluginConfig.token_provision` (``str``) (**mandatory**): if the
|
|
56
|
+
token should be added to the query string (``qs``) or to the header (``header``)
|
|
57
|
+
* :attr:`~eodag.config.PluginConfig.token_qs_key` (``str``): (**mandatory if token_provision=qs**)
|
|
58
|
+
key of the param added to the query string
|
|
59
|
+
* :attr:`~eodag.config.PluginConfig.allowed_audiences` (``List[str]``) (**mandatory**):
|
|
60
|
+
The allowed audiences that have to be present in the user token.
|
|
61
|
+
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
|
|
62
|
+
returned in case of an authentication error
|
|
63
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
|
|
64
|
+
should be verified in the token request; default: ``True``
|
|
45
65
|
|
|
46
66
|
Using :class:`~eodag.plugins.download.http.HTTPDownload` a download link
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
``http://example.com?foo=bar`` will become
|
|
68
|
+
``http://example.com?foo=bar&my-token=obtained-token`` if associated to the following
|
|
49
69
|
configuration::
|
|
50
70
|
|
|
51
71
|
provider:
|
|
52
72
|
...
|
|
53
73
|
auth:
|
|
54
74
|
plugin: KeycloakOIDCPasswordAuth
|
|
55
|
-
|
|
56
|
-
realm: 'the-realm'
|
|
75
|
+
oidc_config_url: 'https://somewhere/auth/realms/realm/.well-known/openid-configuration'
|
|
57
76
|
client_id: 'SOME_ID'
|
|
58
77
|
client_secret: '01234-56789'
|
|
59
78
|
token_provision: qs
|
|
@@ -62,15 +81,14 @@ class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
|
62
81
|
...
|
|
63
82
|
|
|
64
83
|
If configured to send the token through the header, the download request header will
|
|
65
|
-
be updated with
|
|
84
|
+
be updated with ``Authorization: "Bearer obtained-token"`` if associated to the
|
|
66
85
|
following configuration::
|
|
67
86
|
|
|
68
87
|
provider:
|
|
69
88
|
...
|
|
70
89
|
auth:
|
|
71
90
|
plugin: KeycloakOIDCPasswordAuth
|
|
72
|
-
|
|
73
|
-
realm: 'the-realm'
|
|
91
|
+
oidc_config_url: 'https://somewhere/auth/realms/realm/.well-known/openid-configuration'
|
|
74
92
|
client_id: 'SOME_ID'
|
|
75
93
|
client_secret: '01234-56789'
|
|
76
94
|
token_provision: header
|
|
@@ -79,8 +97,12 @@ class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
|
79
97
|
"""
|
|
80
98
|
|
|
81
99
|
GRANT_TYPE = "password"
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
REQUIRED_PARAMS = [
|
|
101
|
+
"oidc_config_url",
|
|
102
|
+
"client_id",
|
|
103
|
+
"client_secret",
|
|
104
|
+
"token_provision",
|
|
105
|
+
]
|
|
84
106
|
|
|
85
107
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
86
108
|
super(KeycloakOIDCPasswordAuth, self).__init__(provider, config)
|
|
@@ -101,10 +123,9 @@ class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
|
101
123
|
Makes authentication request
|
|
102
124
|
"""
|
|
103
125
|
self.validate_config_credentials()
|
|
104
|
-
|
|
105
|
-
self.token_info["access_token"] = access_token
|
|
126
|
+
self._get_access_token()
|
|
106
127
|
return CodeAuthorizedAuth(
|
|
107
|
-
self.
|
|
128
|
+
self.access_token,
|
|
108
129
|
self.config.token_provision,
|
|
109
130
|
key=getattr(self.config, "token_qs_key", None),
|
|
110
131
|
)
|
|
@@ -117,15 +138,14 @@ class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
|
117
138
|
"grant_type": self.GRANT_TYPE,
|
|
118
139
|
}
|
|
119
140
|
credentials = {k: v for k, v in self.config.credentials.items()}
|
|
141
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
120
142
|
try:
|
|
121
143
|
response = self.session.post(
|
|
122
|
-
self.
|
|
123
|
-
auth_base_uri=self.config.auth_base_uri.rstrip("/"),
|
|
124
|
-
realm=self.config.realm,
|
|
125
|
-
),
|
|
144
|
+
self.token_endpoint,
|
|
126
145
|
data=dict(req_data, **credentials),
|
|
127
146
|
headers=USER_AGENT,
|
|
128
147
|
timeout=HTTP_REQ_TIMEOUT,
|
|
148
|
+
verify=ssl_verify,
|
|
129
149
|
)
|
|
130
150
|
response.raise_for_status()
|
|
131
151
|
except requests.exceptions.Timeout as exc:
|
|
@@ -140,17 +160,16 @@ class KeycloakOIDCPasswordAuth(OIDCRefreshTokenBase):
|
|
|
140
160
|
"client_id": self.config.client_id,
|
|
141
161
|
"client_secret": self.config.client_secret,
|
|
142
162
|
"grant_type": "refresh_token",
|
|
143
|
-
"refresh_token": self.
|
|
163
|
+
"refresh_token": self.refresh_token,
|
|
144
164
|
}
|
|
165
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
145
166
|
try:
|
|
146
167
|
response = self.session.post(
|
|
147
|
-
self.
|
|
148
|
-
auth_base_uri=self.config.auth_base_uri.rstrip("/"),
|
|
149
|
-
realm=self.config.realm,
|
|
150
|
-
),
|
|
168
|
+
self.token_endpoint,
|
|
151
169
|
data=req_data,
|
|
152
170
|
headers=USER_AGENT,
|
|
153
171
|
timeout=HTTP_REQ_TIMEOUT,
|
|
172
|
+
verify=ssl_verify,
|
|
154
173
|
)
|
|
155
174
|
response.raise_for_status()
|
|
156
175
|
except requests.RequestException as e:
|
|
@@ -26,7 +26,17 @@ if TYPE_CHECKING:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class OAuth(Authentication):
|
|
29
|
-
"""OAuth authentication plugin
|
|
29
|
+
"""OAuth authentication plugin
|
|
30
|
+
|
|
31
|
+
The mandatory parameters that have to be added in the eodag config are ``aws_access_key_id``
|
|
32
|
+
and ``aws_secret_access_key``.
|
|
33
|
+
|
|
34
|
+
:param provider: provider name
|
|
35
|
+
:param config: Authentication plugin configuration:
|
|
36
|
+
|
|
37
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): OAuth
|
|
38
|
+
|
|
39
|
+
"""
|
|
30
40
|
|
|
31
41
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
32
42
|
super(OAuth, self).__init__(provider, config)
|