Repository: CS-SI/eodag
Pull request #1050 (2024-03-05 10:18:02)

fix: queryables fixes + small refactor
- refactor: simplify list_queryables in api core
- feat: create a list_queryables in base search plugin
- feat: handle multiple search plugins per provider
- fix[server]: do not modify type of default queryables
- fix[server]: use `collection` and `id` instead of `collections` and `ids`




github-actions[bot]: ## Test Results
    4 files  ±0      4 suites  ±0   5m 1s :stopwatch: -1s
  501 tests ±0    498 :white_check_mark: ±0   3 :zzz: ±0  0 :x: ±0 
2 004 runs  ±0  1 920 :white_check_mark: ±0  84 :zzz: ±0  0 :x: ±0 

Results for commit 88f6ae2a. ± Comparison against base commit f637fdd5.

[test-results]:data:application/gzip;base64,H4sIADHy5mUC/02Myw6DIBBFf8Ww7gIQcejPNDwTUpWGx8r03wutRZfnzJ2zI+cXm9B9YLcBpeJzB1OizD5sFUdMqqin3I7TSY9UtG57Aad6+lf76cJJv1SBu7AxhniYWLbWpBizg/5NIiju6teEvrk0v3xN6rCuPldAAI5LSyWoyRgCo1NcWJglGKeZm5XBI+FCcvT+AJ3F1ZgIAQAA

github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/refactor%2Flist-queryables/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                     11       0  100.00%
cli.py                                         300      47  84.33%   61, 647-686, 788-839, 843
config.py                                      312      27  91.35%   80-82, 91, 99, 103-105, 178, 190, 385-387, 451-454, 501-502, 511-512, 591, 660-665, 667
crunch.py                                        6       6  0.00%    18-24
api/__init__.py                                  1       0  100.00%
api/core.py                                    730      75  89.73%   89-97, 370, 581, 626-629, 667, 770, 774-779, 805, 875, 1026-1031, 1117-1129, 1169, 1171, 1175, 1196-1198, 1202-1213, 1226-1232, 1322-1325, 1354-1374, 1422, 1428-1431, 1440, 1787, 1820-1826, 2089, 2093-2096, 2110-2112, 2147, 2261
api/search_result.py                            44       6  86.36%   33-35, 70, 79, 86, 100
api/product/__init__.py                          5       0  100.00%
api/product/_assets.py                          39       3  92.31%   27, 77, 124
api/product/_product.py                        200      30  85.00%   46-51, 55-57, 161-168, 252-253, 343, 379, 440, 454-457, 470, 490-493, 534-540
api/product/metadata_mapping.py                641      84  86.90%   64-66, 127-129, 230, 262-263, 315-327, 329, 340, 344-360, 399-402, 439, 460-463, 477, 556-557, 581-582, 588-591, 606-607, 771, 871-872, 915-920, 1044, 1058-1078, 1098, 1103, 1213, 1235, 1249, 1262-1281, 1320, 1372, 1410-1414, 1433
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      8       2  75.00%   23, 41
plugins/__init__.py                              1       0  100.00%
plugins/base.py                                 23       3  86.96%   25, 48, 55
plugins/manager.py                             130      13  90.00%   49-51, 93-98, 142, 188-189, 271-272
plugins/apis/__init__.py                         1       0  100.00%
plugins/apis/base.py                            32       8  75.00%   26-29, 94, 98, 165, 210
plugins/apis/cds.py                            204      46  77.45%   72-75, 123-126, 173-185, 241, 306, 314-319, 332-339, 357-359, 383-385, 395-397, 411-412, 457, 460, 470, 482, 515, 517
plugins/apis/ecmwf.py                           94      12  87.23%   48-51, 152-154, 199-200, 226-228
plugins/apis/usgs.py                           165      34  79.39%   59-61, 122, 195, 229, 264-266, 271, 297-298, 303, 331-338, 347-352, 375-381, 383-389, 412
plugins/authentication/__init__.py               7       1  85.71%   31
plugins/authentication/aws_auth.py              20       2  90.00%   25-27
plugins/authentication/base.py                  19       2  89.47%   26, 47
plugins/authentication/generic.py               16       3  81.25%   28, 40, 50
plugins/authentication/header.py                17       1  94.12%   27
plugins/authentication/keycloak.py              82      17  79.27%   32-34, 149-150, 180-201, 227-232
plugins/authentication/oauth.py                 15       8  46.67%   25, 32-34, 38-41
plugins/authentication/openid_connect.py        99      60  39.39%   34-36, 133-145, 149-167, 175-209, 215-224, 233-273, 278-286, 291-292
plugins/authentication/qsauth.py                35       2  94.29%   32, 80
plugins/authentication/sas_auth.py              47       2  95.74%   32, 71
plugins/authentication/token.py                 75       8  89.33%   35-37, 102, 127, 154-157
plugins/crunch/__init__.py                       1       0  100.00%
plugins/crunch/base.py                          10       2  80.00%   25, 38
plugins/crunch/filter_date.py                   62      15  75.81%   30, 53-58, 72, 81, 90, 93, 105-107, 116-118, 125
plugins/crunch/filter_latest_intersect.py       50      10  80.00%   32-34, 51-52, 71, 80-83, 85, 92-95
plugins/crunch/filter_latest_tpl_name.py        33       2  93.94%   28, 86
plugins/crunch/filter_overlap.py                68      17  75.00%   28-30, 33, 82-85, 91, 99, 110-126
plugins/crunch/filter_property.py               33       8  75.76%   29, 60-65, 68-69, 85-89
plugins/download/__init__.py                     1       0  100.00%
plugins/download/aws.py                        478     160  66.53%   75-80, 277, 344-347, 361-365, 408-410, 414, 442-443, 449-453, 483, 551-559, 563, 601-609, 616-618, 659-733, 751-812, 823-828, 844-857, 886, 901-903, 906, 916-924, 932-945, 955-974, 981-993, 1034, 1060, 1105-1107, 1327
plugins/download/base.py                       259      55  78.76%   58-61, 142, 177, 314-315, 335-341, 372-376, 382-383, 427, 430-444, 456, 460, 533-537, 567-568, 576-593, 600-608, 610-614, 661, 684, 706, 714
plugins/download/creodias_s3.py                 17       9  47.06%   44-58
plugins/download/http.py                       402      92  77.11%   69-74, 108, 152, 172-179, 189-202, 235-240, 290, 322, 349-351, 361-369, 428-432, 489, 564-616, 629, 653-655, 685-686, 694, 697, 713, 737, 759-760, 767, 828-834, 884-885, 891, 901, 967, 985-1001
plugins/download/s3rest.py                     111      24  78.38%   53-54, 158, 185, 215-222, 225-227, 231, 244-250, 258-259, 262-266, 289, 309-312
plugins/search/__init__.py                       1       0  100.00%
plugins/search/base.py                          91      11  87.91%   43-44, 91, 95, 121-125, 252, 272
plugins/search/build_search_result.py           56       6  89.29%   67, 112-113, 121, 132, 176
plugins/search/creodias_s3.py                   55       3  94.55%   56, 74, 107
plugins/search/csw.py                          107      83  22.43%   43-45, 57-58, 62-63, 74-122, 128-141, 149-181, 199-240
plugins/search/data_request_search.py          190      59  68.95%   52, 87-90, 106, 118, 137, 143, 149-150, 163-166, 220-221, 225, 235-241, 246, 270, 280-290, 301, 303, 310-311, 313-314, 330-334, 367, 374, 385, 398, 404-416, 421
plugins/search/qssearch.py                     514      54  89.49%   73, 340-341, 435-441, 468-471, 543-544, 603, 618, 671, 692, 695-696, 705, 716, 725, 736-737, 748, 808-813, 817-818, 846, 909, 928-945, 980, 1114-1115, 1136, 1178-1179, 1189-1195, 1232, 1245, 1265, 1353
plugins/search/static_stac_search.py            46       3  93.48%   39-40, 81
rest/__init__.py                                 1       0  100.00%
rest/core.py                                   168      13  92.26%   238, 506-507, 510, 512, 515, 518-520, 572, 579-583
rest/server.py                                 306      54  82.35%   81-82, 106, 129-130, 241-243, 259, 299-300, 312-328, 410-415, 445, 607-614, 645, 688-689, 782-784, 801-806, 836, 838, 842-843, 847-848
rest/stac.py                                   428      93  78.27%   59-61, 229-231, 273, 286-295, 314-320, 365, 402-404, 427, 462-463, 549-594, 639, 820, 885-887, 1106, 1116-1128, 1141-1163, 1177-1222, 1381-1382
rest/types/__init__.py                           1       0  100.00%
rest/types/eodag_search.py                     185       9  95.14%   52-55, 231-235, 288, 291, 359
rest/types/stac_queryables.py                   38       7  81.58%   28, 120-126, 129
rest/types/stac_search.py                      122      10  91.80%   48-51, 167, 182-184, 192, 196
rest/utils/__init__.py                         116      14  87.93%   53, 108-109, 128-130, 180, 190-204, 236
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           31       3  90.32%   78, 90, 92
types/__init__.py                               76       6  92.11%   53, 87, 152, 172, 177, 185
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/queryables.py                             81       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 104
utils/__init__.py                              483      43  91.10%   80-82, 103-105, 183-184, 193-220, 223, 237, 319-323, 399-403, 424-426, 508, 513, 523, 561-562, 958-961, 969-970, 1011-1012, 1092, 1176, 1194, 1367
utils/constraints.py                           120      40  66.67%   81-90, 131, 136, 140, 151, 174, 185-193, 202, 216-232, 241-252
utils/exceptions.py                             36       2  94.44%   23, 89
utils/import_system.py                          30      20  33.33%   27, 67-81, 93-103
utils/logging.py                                29       1  96.55%   123
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/stac_reader.py                            89      27  69.66%   54-55, 62-83, 90-92, 96, 135, 149-152
TOTAL                                         8515    1522  82.13%
```

</details>


### Diff against develop

```
Filename              Stmts    Miss  Cover
------------------  -------  ------  -------
api/core.py             -10      -2  +0.14%
plugins/manager.py        0      +2  -1.54%
TOTAL                   -10       0  -0.02%
```


Results for commit: 88f6ae2a8b5dd183fb69e87a8dfc4f7bd03169a6


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.12-ubuntu-latest/coverage.xml -->
github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports_win/data/refactor%2Flist-queryables/./badge.svg)


## Code Coverage (Windows)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                     11       0  100.00%
cli.py                                         300      47  84.33%   61, 647-686, 788-839, 843
config.py                                      312      28  91.03%   80-82, 91, 99, 103-105, 178, 190, 385-387, 451-454, 501-502, 511-512, 591, 625, 660-665, 667
crunch.py                                        6       6  0.00%    18-24
api/__init__.py                                  1       0  100.00%
api/core.py                                    730      75  89.73%   89-97, 370, 581, 626-629, 667, 770, 774-779, 805, 875, 1026-1031, 1117-1129, 1169, 1171, 1175, 1196-1198, 1202-1213, 1226-1232, 1322-1325, 1354-1374, 1422, 1428-1431, 1440, 1787, 1820-1826, 2089, 2093-2096, 2110-2112, 2147, 2261
api/search_result.py                            44       6  86.36%   33-35, 70, 79, 86, 100
api/product/__init__.py                          5       0  100.00%
api/product/_assets.py                          39       3  92.31%   27, 77, 124
api/product/_product.py                        200      30  85.00%   46-51, 55-57, 161-168, 252-253, 343, 379, 440, 454-457, 470, 490-493, 534-540
api/product/metadata_mapping.py                641      85  86.74%   64-66, 127-129, 230, 262-263, 315-327, 329, 340, 344-360, 399-402, 439, 460-463, 477, 556-557, 581-582, 588-591, 606-607, 771, 871-872, 915-920, 1044, 1058-1078, 1098, 1103, 1213, 1235, 1249, 1262-1281, 1320, 1372, 1395, 1410-1414, 1433
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      8       2  75.00%   23, 41
plugins/__init__.py                              1       0  100.00%
plugins/base.py                                 23       4  82.61%   25, 48, 55, 68
plugins/manager.py                             130      13  90.00%   49-51, 93-98, 142, 188-189, 271-272
plugins/apis/__init__.py                         1       0  100.00%
plugins/apis/base.py                            32       9  71.88%   26-29, 94, 98, 111, 165, 210
plugins/apis/cds.py                            204      53  74.02%   72-75, 123-126, 173-185, 241, 306, 314-319, 332-339, 357-359, 383-385, 395-397, 411-412, 457, 460, 470, 482, 507-522
plugins/apis/ecmwf.py                           94      12  87.23%   48-51, 152-154, 199-200, 226-228
plugins/apis/usgs.py                           165      34  79.39%   59-61, 122, 195, 229, 264-266, 271, 297-298, 303, 331-338, 347-352, 375-381, 383-389, 412
plugins/authentication/__init__.py               7       1  85.71%   31
plugins/authentication/aws_auth.py              20       2  90.00%   25-27
plugins/authentication/base.py                  19       3  84.21%   26, 34, 47
plugins/authentication/generic.py               16       3  81.25%   28, 40, 50
plugins/authentication/header.py                17       1  94.12%   27
plugins/authentication/keycloak.py              82      17  79.27%   32-34, 149-150, 180-201, 227-232
plugins/authentication/oauth.py                 15       8  46.67%   25, 32-34, 38-41
plugins/authentication/openid_connect.py        99      60  39.39%   34-36, 133-145, 149-167, 175-209, 215-224, 233-273, 278-286, 291-292
plugins/authentication/qsauth.py                35       2  94.29%   32, 80
plugins/authentication/sas_auth.py              47       2  95.74%   32, 71
plugins/authentication/token.py                 75       8  89.33%   35-37, 102, 127, 154-157
plugins/crunch/__init__.py                       1       0  100.00%
plugins/crunch/base.py                          10       2  80.00%   25, 38
plugins/crunch/filter_date.py                   62      15  75.81%   30, 53-58, 72, 81, 90, 93, 105-107, 116-118, 125
plugins/crunch/filter_latest_intersect.py       50      35  30.00%   32-34, 48-53, 69-114
plugins/crunch/filter_latest_tpl_name.py        33       2  93.94%   28, 86
plugins/crunch/filter_overlap.py                68      17  75.00%   28-30, 33, 82-85, 91, 99, 110-126
plugins/crunch/filter_property.py               33       8  75.76%   29, 60-65, 68-69, 85-89
plugins/download/__init__.py                     1       0  100.00%
plugins/download/aws.py                        478     160  66.53%   75-80, 277, 344-347, 361-365, 408-410, 414, 442-443, 449-453, 483, 551-559, 563, 601-609, 616-618, 659-733, 751-812, 823-828, 844-857, 886, 901-903, 906, 916-924, 932-945, 955-974, 981-993, 1034, 1060, 1105-1107, 1327
plugins/download/base.py                       259      57  77.99%   58-61, 142, 177, 245-247, 314-315, 335-341, 372-376, 382-383, 427, 430-444, 456, 460, 533-537, 567-568, 576-593, 600-608, 610-614, 661, 684, 706, 714
plugins/download/creodias_s3.py                 17       9  47.06%   44-58
plugins/download/http.py                       402      92  77.11%   69-74, 108, 152, 172-179, 189-202, 235-240, 290, 322, 349-351, 361-369, 428-432, 489, 564-616, 629, 653-655, 685-686, 694, 697, 713, 737, 759-760, 767, 828-834, 884-885, 891, 901, 967, 985-1001
plugins/download/s3rest.py                     111      24  78.38%   53-54, 158, 185, 215-222, 225-227, 231, 244-250, 258-259, 262-266, 289, 309-312
plugins/search/__init__.py                       1       0  100.00%
plugins/search/base.py                          91      11  87.91%   43-44, 91, 95, 121-125, 252, 272
plugins/search/build_search_result.py           56       6  89.29%   67, 112-113, 121, 132, 176
plugins/search/creodias_s3.py                   55       3  94.55%   56, 74, 107
plugins/search/csw.py                          107      83  22.43%   43-45, 57-58, 62-63, 74-122, 128-141, 149-181, 199-240
plugins/search/data_request_search.py          190      59  68.95%   52, 87-90, 106, 118, 137, 143, 149-150, 163-166, 220-221, 225, 235-241, 246, 270, 280-290, 301, 303, 310-311, 313-314, 330-334, 367, 374, 385, 398, 404-416, 421
plugins/search/qssearch.py                     514      78  84.82%   73, 340-341, 435-441, 468-471, 543-544, 603, 618, 671, 692, 695-696, 705, 716, 725, 736-737, 748, 808-813, 817-818, 846, 909, 928-945, 980, 1114-1115, 1136, 1178-1179, 1189-1195, 1232, 1245, 1265, 1313-1382
plugins/search/static_stac_search.py            46       3  93.48%   39-40, 81
rest/__init__.py                                 1       0  100.00%
rest/core.py                                   168      60  64.29%   135, 137, 139, 145-146, 163-171, 178-184, 230-259, 413-440, 458, 505-535, 572, 579-583
rest/server.py                                 306     306  0.00%    18-861
rest/stac.py                                   428     147  65.65%   59-61, 214, 229-231, 273, 286-295, 314-320, 365, 402-404, 427, 462-463, 549-594, 639, 762, 820, 885-887, 904-906, 914-916, 929-931, 945-962, 972-993, 1003-1025, 1033-1050, 1073-1096, 1106, 1116-1128, 1141-1163, 1177-1222, 1375-1401
rest/types/__init__.py                           1       0  100.00%
rest/types/eodag_search.py                     185      18  90.27%   52-55, 231-235, 268-270, 288, 291, 297, 301, 359, 371-374
rest/types/stac_queryables.py                   38      13  65.79%   28, 53-58, 120-126, 129, 133-134
rest/types/stac_search.py                      122      12  90.16%   48-51, 167, 182-184, 192, 196, 240, 243
rest/utils/__init__.py                         116      31  73.28%   53, 79-85, 105, 108-109, 128-130, 147, 173-181, 188-209, 236
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           31       5  83.87%   73-74, 78, 90, 92
types/__init__.py                               76      11  85.53%   53, 87, 125, 152, 162-164, 172, 177, 185, 195
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/queryables.py                             81       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 104
utils/__init__.py                              483      43  91.10%   80-82, 103-105, 183-184, 193-220, 223, 237, 319-323, 399-403, 424-426, 508, 513, 523, 561-562, 958-961, 969-970, 1011-1012, 1092, 1176, 1194, 1367
utils/constraints.py                           120      40  66.67%   81-90, 131, 136, 140, 151, 174, 185-193, 202, 216-232, 241-252
utils/exceptions.py                             36       2  94.44%   23, 89
utils/import_system.py                          30      20  33.33%   27, 67-81, 93-103
utils/logging.py                                29       1  96.55%   123
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/stac_reader.py                            89      27  69.66%   54-55, 62-83, 90-92, 96, 135, 149-152
TOTAL                                         8515    1979  76.76%
```

</details>


### Diff against develop

```
Filename              Stmts    Miss  Cover
------------------  -------  ------  -------
api/core.py             -10      -6  +0.68%
plugins/manager.py        0      +2  -1.54%
TOTAL                   -10      -4  +0.02%
```


Results for commit: 88f6ae2a8b5dd183fb69e87a8dfc4f7bd03169a6


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.12-windows-latest/coverage.xml -->

Patch:
diff --git a/eodag/api/core.py b/eodag/api/core.py
index 92716831..83515ef2 100644
--- a/eodag/api/core.py
+++ b/eodag/api/core.py
@@ -23,18 +23,7 @@ import re
 import shutil
 import tempfile
 from operator import itemgetter
-from typing import (
-    TYPE_CHECKING,
-    AbstractSet,
-    Any,
-    Dict,
-    Iterator,
-    List,
-    Optional,
-    Set,
-    Tuple,
-    Union,
-)
+from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Tuple, Union
 
 import geojson
 import pkg_resources
@@ -46,10 +35,7 @@ from whoosh.fields import Schema
 from whoosh.index import create_in, exists_in, open_dir
 from whoosh.qparser import QueryParser
 
-from eodag.api.product.metadata_mapping import (
-    NOT_MAPPED,
-    mtd_cfg_as_conversion_and_querypath,
-)
+from eodag.api.product.metadata_mapping import mtd_cfg_as_conversion_and_querypath
 from eodag.api.search_result import SearchResult
 from eodag.config import (
     SimpleYamlProxyConfig,
@@ -65,7 +51,7 @@ from eodag.config import (
 from eodag.plugins.manager import PluginManager
 from eodag.plugins.search.build_search_result import BuildPostSearchResult
 from eodag.types import model_fields_to_annotated
-from eodag.types.queryables import CommonQueryables, Queryables
+from eodag.types.queryables import CommonQueryables
 from eodag.utils import (
     DEFAULT_DOWNLOAD_TIMEOUT,
     DEFAULT_DOWNLOAD_WAIT,
@@ -78,7 +64,6 @@ from eodag.utils import (
     _deprecated,
     copy_deepcopy,
     deepcopy,
-    get_args,
     get_geometry_from_various,
     makedirs,
     obj_md5sum,
@@ -2024,6 +2009,7 @@ class EODataAccessGateway:
         provider: Optional[str] = None,
         productType: Optional[str] = None,
         timeout: int = HTTP_REQ_TIMEOUT,
+        ssl_verify: bool = True,
         **kwargs: Any,
     ) -> SearchResult:
         """Loads STAC items from a geojson file / STAC catalog or collection, and convert to SearchResult.
@@ -2057,6 +2043,7 @@ class EODataAccessGateway:
             recursive=recursive,
             max_connections=max_connections,
             timeout=timeout,
+            ssl_verify=ssl_verify,
         )
         feature_collection = geojson.FeatureCollection(features)
 
@@ -2166,9 +2153,7 @@ class EODataAccessGateway:
         return self._plugins_manager.get_crunch_plugin(name, **plugin_conf)
 
     def list_queryables(
-        self,
-        provider: Optional[str] = None,
-        **kwargs: Any,
+        self, provider: Optional[str] = None, **kwargs: Any
     ) -> Dict[str, Annotated[Any, FieldInfo]]:
         """Fetch the queryable properties for a given product type and/or provider.
 
@@ -2177,113 +2162,49 @@ class EODataAccessGateway:
         :param kwargs: additional filters for queryables (`productType` or other search
                        arguments)
         :type kwargs: Any
+
+        :raises UnsupportedProductType: If the specified product type is not available for the
+                                        provider.
+
         :returns: A dict containing the EODAG queryable properties, associating
                   parameters to their annotated type
         :rtype: Dict[str, Annotated[Any, FieldInfo]]
         """
-        # unknown product type
         available_product_types = [
-            pt["ID"] for pt in self.list_product_types(fetch_providers=False)
+            pt["ID"]
+            for pt in self.list_product_types(provider=provider, fetch_providers=False)
         ]
-        product_type = kwargs.get("productType", None)
-        if product_type is not None and product_type not in available_product_types:
-            self.fetch_product_types_list()
+        product_type = kwargs.get("productType")
 
-        # dictionary of the queryable properties of the providers supporting the given product type
-        providers_available_queryables: Dict[
-            str, Dict[str, Annotated[Any, FieldInfo]]
-        ] = dict()
-
-        if provider is None and product_type is None:
-            return model_fields_to_annotated(CommonQueryables.model_fields)
-        elif provider is None:
-            for plugin in self._plugins_manager.get_search_plugins(
-                product_type, provider
-            ):
-                providers_available_queryables[plugin.provider] = self.list_queryables(
-                    provider=plugin.provider, **kwargs
-                )
-
-            # return providers queryables intersection
-            queryables_keys: AbstractSet[str] = set()
-            for queryables in providers_available_queryables.values():
-                queryables_keys = (
-                    queryables_keys & queryables.keys()
-                    if queryables_keys
-                    else queryables.keys()
+        if product_type:
+            try:
+                kwargs["productType"] = product_type = self.get_product_type_from_alias(
+                    product_type
                 )
-            return {
-                k: v
-                for k, v in providers_available_queryables.popitem()[1].items()
-                if k in queryables_keys
-            }
+            except NoMatchingProductType as e:
+                raise UnsupportedProductType(f"{product_type} is not available") from e
 
-        all_queryables = copy_deepcopy(
-            model_fields_to_annotated(Queryables.model_fields)
-        )
+        if product_type and product_type not in available_product_types:
+            self.fetch_product_types_list()
 
-        try:
-            plugin = next(
-                self._plugins_manager.get_search_plugins(product_type, provider)
-            )
-        except StopIteration:
-            # return default queryables if no plugin is found
+        if not provider and not product_type:
             return model_fields_to_annotated(CommonQueryables.model_fields)
 
-        providers_available_queryables[plugin.provider] = dict()
+        providers_queryables: Dict[str, Dict[str, Annotated[Any, FieldInfo]]] = {}
 
-        # unknown product type: try again after fetch_product_types_list()
-        if (
-            product_type
-            and product_type not in plugin.config.products.keys()
-            and provider is None
-        ):
-            raise UnsupportedProductType(product_type)
-        elif product_type and product_type not in plugin.config.products.keys():
-            raise UnsupportedProductType(
-                f"{product_type} is not available for provider {provider}"
+        for plugin in self._plugins_manager.get_search_plugins(product_type, provider):
+            providers_queryables[plugin.provider] = plugin.list_queryables(
+                filters=kwargs, product_type=product_type
             )
 
-        metadata_mapping = deepcopy(getattr(plugin.config, "metadata_mapping", {}))
-
-        # product_type-specific metadata-mapping
-        metadata_mapping.update(
-            getattr(plugin.config, "products", {})
-            .get(product_type, {})
-            .get("metadata_mapping", {})
-        )
-
-        # default values
-        default_values = deepcopy(
-            getattr(plugin.config, "products", {}).get(product_type, {})
+        queryable_keys: Set[str] = set.intersection(  # type: ignore
+            *[set(q.keys()) for q in providers_queryables.values()]
         )
-        default_values.pop("metadata_mapping", None)
-        kwargs = dict(default_values, **kwargs)
-
-        # remove not mapped parameters or non-queryables
-        for param in list(metadata_mapping.keys()):
-            if NOT_MAPPED in metadata_mapping[param] or not isinstance(
-                metadata_mapping[param], list
-            ):
-                del metadata_mapping[param]
-
-        for key, value in all_queryables.items():
-            annotated_args = get_args(value)
-            if len(annotated_args) < 1:
-                continue
-            field_info = annotated_args[1]
-            if not isinstance(field_info, FieldInfo):
-                continue
-            if key in kwargs:
-                field_info.default = kwargs[key]
-            if field_info.is_required() or (
-                (field_info.alias or key) in metadata_mapping
-            ):
-                providers_available_queryables[plugin.provider][key] = value
-
-        provider_queryables = plugin.discover_queryables(**kwargs) or dict()
-        # use EODAG configured queryables by default
-        provider_queryables.update(providers_available_queryables[provider])
+        queryables = {
+            k: v
+            for k, v in list(providers_queryables.values())[0].items()
+            if k in queryable_keys
+        }
 
         # always keep at least CommonQueryables
         common_queryables = copy_deepcopy(CommonQueryables.model_fields)
@@ -2291,9 +2212,9 @@ class EODataAccessGateway:
             if key in kwargs:
                 queryable.default = kwargs[key]
 
-        provider_queryables.update(model_fields_to_annotated(common_queryables))
+        queryables.update(model_fields_to_annotated(common_queryables))
 
-        return provider_queryables
+        return queryables
 
     def available_sortables(self) -> Dict[str, Optional[ProviderSortables]]:
         """For each provider, gives its available sortable parameter(s) and its maximum
diff --git a/eodag/config.py b/eodag/config.py
index 32dcc7f0..bf91dd49 100644
--- a/eodag/config.py
+++ b/eodag/config.py
@@ -281,6 +281,7 @@ class PluginConfig(yaml.YAMLObject):
     max_connections: int  # StaticStacSearch
     timeout: float  # StaticStacSearch
     s3_bucket: str  # CreodiasS3Search
+    ssl_verify: bool
 
     # download -------------------------------------------------------------------------
     base_uri: str
diff --git a/eodag/plugins/apis/cds.py b/eodag/plugins/apis/cds.py
index d6a1d285..b245b17b 100644
--- a/eodag/plugins/apis/cds.py
+++ b/eodag/plugins/apis/cds.py
@@ -471,6 +471,11 @@ class CdsApi(Api, HTTPDownload, BuildPostSearchResult):
                 f"Cannot change dataset from {provider_product_type} to {user_provider_product_type}"
             )
 
+        # defaults
+        default_queryables = self._get_defaults_as_queryables(product_type)
+        # remove dataset from queryables
+        default_queryables.pop("dataset", None)
+
         non_empty_kwargs = {k: v for k, v in kwargs.items() if v}
 
         if "{" in constraints_file_url:
@@ -479,12 +484,7 @@ class CdsApi(Api, HTTPDownload, BuildPostSearchResult):
             )
         constraints = fetch_constraints(constraints_file_url, self)
         if not constraints:
-            return {}
-
-        # defaults
-        default_queryables = self.get_defaults_as_queryables(product_type)
-        # remove dataset from queryables
-        default_queryables.pop("dataset", None)
+            return default_queryables
 
         constraint_params: Dict[str, Dict[str, Set[Any]]] = {}
         if len(kwargs) == 0:
diff --git a/eodag/plugins/apis/usgs.py b/eodag/plugins/apis/usgs.py
index 5bddeafb..7760902a 100644
--- a/eodag/plugins/apis/usgs.py
+++ b/eodag/plugins/apis/usgs.py
@@ -313,6 +313,7 @@ class UsgsApi(Api):
         req_url = req_urls[0]
         progress_callback.reset()
         logger.debug(f"Downloading {req_url}")
+        ssl_verify = getattr(self.config, "ssl_verify", True)
 
         @self._download_retry(product, wait, timeout)
         def download_request(
@@ -327,6 +328,7 @@ class UsgsApi(Api):
                     stream=True,
                     headers=USER_AGENT,
                     timeout=wait * 60,
+                    verify=ssl_verify,
                 ) as stream:
                     try:
                         stream.raise_for_status()
diff --git a/eodag/plugins/authentication/qsauth.py b/eodag/plugins/authentication/qsauth.py
index 724fdf64..26c92367 100644
--- a/eodag/plugins/authentication/qsauth.py
+++ b/eodag/plugins/authentication/qsauth.py
@@ -67,6 +67,8 @@ class HttpQueryStringAuth(Authentication):
         auth = QueryStringAuth(**self.config.credentials)
 
         auth_uri = getattr(self.config, "auth_uri", None)
+        ssl_verify = getattr(self.config, "ssl_verify", True)
+
         if auth_uri:
             try:
                 response = requests.get(
@@ -74,6 +76,7 @@ class HttpQueryStringAuth(Authentication):
                     timeout=HTTP_REQ_TIMEOUT,
                     headers=USER_AGENT,
                     auth=auth,
+                    verify=ssl_verify,
                 )
                 response.raise_for_status()
             except requests.exceptions.Timeout as exc:
diff --git a/eodag/plugins/authentication/sas_auth.py b/eodag/plugins/authentication/sas_auth.py
index c0d0e61b..a40488d6 100644
--- a/eodag/plugins/authentication/sas_auth.py
+++ b/eodag/plugins/authentication/sas_auth.py
@@ -43,11 +43,13 @@ class RequestsSASAuth(AuthBase):
         auth_uri: str,
         signed_url_key: str,
         headers: Optional[Dict[str, str]] = None,
+        ssl_verify: bool = True,
     ) -> None:
         self.auth_uri = auth_uri
         self.signed_url_key = signed_url_key
         self.headers = headers
         self.signed_urls: Dict[str, str] = {}
+        self.ssl_verify = ssl_verify
 
     def __call__(self, request: PreparedRequest) -> PreparedRequest:
         """Perform the actual authentication"""
@@ -63,7 +65,10 @@ class RequestsSASAuth(AuthBase):
             logger.debug(f"Signed URL request: {req_signed_url}")
             try:
                 response = requests.get(
-                    req_signed_url, headers=self.headers, timeout=HTTP_REQ_TIMEOUT
+                    req_signed_url,
+                    headers=self.headers,
+                    timeout=HTTP_REQ_TIMEOUT,
+                    verify=self.ssl_verify,
                 )
                 response.raise_for_status()
                 signed_url = response.json().get(self.signed_url_key)
@@ -95,6 +100,7 @@ class SASAuth(Authentication):
 
         # update headers with subscription key if exists
         apikey = getattr(self.config, "credentials", {}).get("apikey", None)
+        ssl_verify = getattr(self.config, "ssl_verify", True)
         if apikey:
             headers_update = format_dict_items(self.config.headers, apikey=apikey)
             headers.update(headers_update)
@@ -103,4 +109,5 @@ class SASAuth(Authentication):
             auth_uri=self.config.auth_uri,
             signed_url_key=self.config.signed_url_key,
             headers=headers,
+            ssl_verify=ssl_verify,
         )
diff --git a/eodag/plugins/download/aws.py b/eodag/plugins/download/aws.py
index f6d78409..e553d990 100644
--- a/eodag/plugins/download/aws.py
+++ b/eodag/plugins/download/aws.py
@@ -439,6 +439,8 @@ class AwsDownload(Download):
         product_conf = getattr(self.config, "products", {}).get(
             product.product_type, {}
         )
+        ssl_verify = getattr(self.config, "ssl_verify", True)
+
         if build_safe and "fetch_metadata" in product_conf.keys():
             fetch_format = product_conf["fetch_metadata"]["fetch_format"]
             update_metadata = product_conf["fetch_metadata"]["update_metadata"]
@@ -448,7 +450,10 @@ class AwsDownload(Download):
             logger.info("Fetching extra metadata from %s" % fetch_url)
             try:
                 resp = requests.get(
-                    fetch_url, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
+                    fetch_url,
+                    headers=USER_AGENT,
+                    timeout=HTTP_REQ_TIMEOUT,
+                    verify=ssl_verify,
                 )
             except requests.exceptions.Timeout as exc:
                 raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
diff --git a/eodag/plugins/download/http.py b/eodag/plugins/download/http.py
index 27480b54..762f0c87 100644
--- a/eodag/plugins/download/http.py
+++ b/eodag/plugins/download/http.py
@@ -158,6 +158,7 @@ class HTTPDownload(Download):
         :type kwargs: Union[str, bool, dict]
         """
         order_method = getattr(self.config, "order_method", "GET").lower()
+        ssl_verify = getattr(self.config, "ssl_verify", True)
         OrderKwargs = TypedDict(
             "OrderKwargs", {"json": Dict[str, Union[Any, List[str]]]}, total=False
         )
@@ -174,13 +175,13 @@ class HTTPDownload(Download):
         else:
             order_url = product.properties["orderLink"]
             order_kwargs = {}
-
         with requests.request(
             method=order_method,
             url=order_url,
             auth=auth,
             timeout=HTTP_REQ_TIMEOUT,
             headers=dict(getattr(self.config, "order_headers", {}), **USER_AGENT),
+            verify=ssl_verify,
             **order_kwargs,
         ) as response:
             try:
@@ -253,6 +254,9 @@ class HTTPDownload(Download):
             "StatusKwargs", {"json": Dict[str, Union[Any, List[str]]]}, total=False
         )
         status_kwargs: StatusKwargs = {}
+
+        ssl_verify = getattr(self.config, "ssl_verify", True)
+
         if status_method == "post":
             # separate url & parameters
             parts = urlparse(str(product.properties["orderStatusLink"]))
@@ -273,6 +277,7 @@ class HTTPDownload(Download):
             headers=dict(
                 getattr(self.config, "order_status_headers", {}), **USER_AGENT
             ),
+            verify=ssl_verify,
             **status_kwargs,
         ) as response:
             try:
@@ -326,6 +331,7 @@ class HTTPDownload(Download):
                         product.properties["searchLink"],
                         timeout=HTTP_REQ_TIMEOUT,
                         headers=USER_AGENT,
+                        verify=ssl_verify,
                     )
                     response.raise_for_status()
                     if (
@@ -833,6 +839,7 @@ class HTTPDownload(Download):
         flatten_top_dirs = product_conf.get(
             "flatten_top_dirs", getattr(self.config, "flatten_top_dirs", False)
         )
+        ssl_verify = getattr(self.config, "ssl_verify", True)
 
         # loop for assets download
         for asset in assets_values:
@@ -849,6 +856,7 @@ class HTTPDownload(Download):
                 params=params,
                 headers=USER_AGENT,
                 timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+                verify=ssl_verify,
             ) as stream:
                 try:
                     stream.raise_for_status()
@@ -1041,6 +1049,7 @@ class HTTPDownload(Download):
     ) -> int:
         total_size = 0
 
+        ssl_verify = getattr(self.config, "ssl_verify", True)
         # loop for assets size & filename
         for asset in assets_values:
             if not asset["href"].startswith("file:"):
@@ -1082,6 +1091,7 @@ class HTTPDownload(Download):
                         params=params,
                         headers=USER_AGENT,
                         timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+                        verify=ssl_verify,
                     ) as stream:
                         # size from GET header / Content-length
                         asset.size = int(stream.headers.get("Content-length", 0))
diff --git a/eodag/plugins/download/s3rest.py b/eodag/plugins/download/s3rest.py
index dd9eced7..11187100 100644
--- a/eodag/plugins/download/s3rest.py
+++ b/eodag/plugins/download/s3rest.py
@@ -178,8 +178,15 @@ class S3RestDownload(Download):
 
             # get nodes/files list contained in the bucket
             logger.debug("Retrieving product content from %s", nodes_list_url)
+
+            ssl_verify = getattr(self.config, "ssl_verify", True)
+
             bucket_contents = requests.get(
-                nodes_list_url, auth=auth, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
+                nodes_list_url,
+                auth=auth,
+                headers=USER_AGENT,
+                timeout=HTTP_REQ_TIMEOUT,
+                verify=ssl_verify,
             )
             try:
                 bucket_contents.raise_for_status()
@@ -310,6 +317,7 @@ class S3RestDownload(Download):
                     auth=auth,
                     headers=USER_AGENT,
                     timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+                    verify=ssl_verify,
                 ) as stream:
                     try:
                         stream.raise_for_status()
diff --git a/eodag/plugins/search/base.py b/eodag/plugins/search/base.py
index 538df976..d5d7cace 100644
--- a/eodag/plugins/search/base.py
+++ b/eodag/plugins/search/base.py
@@ -18,30 +18,36 @@
 from __future__ import annotations
 
 import logging
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
+from typing import TYPE_CHECKING
 
 import orjson
-from pydantic.fields import Field
+from pydantic.fields import Field, FieldInfo
 
 from eodag.api.product.metadata_mapping import (
     DEFAULT_METADATA_MAPPING,
+    NOT_MAPPED,
     mtd_cfg_as_conversion_and_querypath,
 )
 from eodag.plugins.base import PluginTopic
+from eodag.types import model_fields_to_annotated
+from eodag.types.queryables import Queryables
 from eodag.types.search_args import SortByList
 from eodag.utils import (
     DEFAULT_ITEMS_PER_PAGE,
     DEFAULT_PAGE,
     GENERIC_PRODUCT_TYPE,
     Annotated,
+    copy_deepcopy,
     deepcopy,
     format_dict_items,
+    get_args,
     update_nested_dict,
 )
 from eodag.utils.exceptions import ValidationError
 
 if TYPE_CHECKING:
-    from pydantic.fields import FieldInfo
+    from typing import Any, Dict, List, Optional, Tuple, Union
+
     from requests.auth import AuthBase
 
     from eodag.api.product import EOProduct
@@ -116,13 +122,15 @@ class Search(PluginTopic):
         :returns: fetched queryable parameters dict
         :rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
         """
-        return None
+        raise NotImplementedError(
+            f"discover_queryables is not implemeted for plugin {self.__class__.__name__}"
+        )
 
-    def get_defaults_as_queryables(
+    def _get_defaults_as_queryables(
         self, product_type: str
     ) -> Dict[str, Annotated[Any, FieldInfo]]:
         """
-        Return given product type defaut settings as queryables
+        Return given product type default settings as queryables
 
         :param product_type: given product type
         :type product_type: str
@@ -317,3 +325,62 @@ class Search(PluginTopic):
             except orjson.JSONDecodeError:
                 sort_by_qs += parsed_sort_by_tpl
         return (sort_by_qs, sort_by_qp)
+
+    def list_queryables(
+        self,
+        filters: Dict[str, Any],
+        product_type: Optional[str] = None,
+    ) -> Dict[str, Annotated[Any, FieldInfo]]:
+        """
+        Get queryables
+
+        :param filters: Additional filters for queryables.
+        :type filters: Dict[str, Any]
+        :param product_type: (optional) The product type.
+        :type product_type: Optional[str]
+
+        :return: A dictionary containing the queryable properties, associating parameters to their
+                annotated type.
+        :rtype: Dict[str, Annotated[Any, FieldInfo]]
+        """
+        default_values: Dict[str, Any] = deepcopy(
+            getattr(self.config, "products", {}).get(product_type, {})
+        )
+        default_values.pop("metadata_mapping", None)
+
+        queryables: Dict[str, Annotated[Any, FieldInfo]] = {}
+        try:
+            queryables = self.discover_queryables(**{**default_values, **filters}) or {}
+        except NotImplementedError:
+            pass
+
+        metadata_mapping: Dict[str, Any] = deepcopy(
+            getattr(self.config, "metadata_mapping", {})
+        )
+        metadata_mapping.update(
+            getattr(self.config, "products", {})
+            .get(product_type, {})
+            .get("metadata_mapping", {})
+        )
+
+        for param in list(metadata_mapping.keys()):
+            if NOT_MAPPED in metadata_mapping[param] or not isinstance(
+                metadata_mapping[param], list
+            ):
+                del metadata_mapping[param]
+
+        eoadag_queryables = copy_deepcopy(
+            model_fields_to_annotated(Queryables.model_fields)
+        )
+        for k, v in eoadag_queryables.items():
+            field_info = get_args(v)[1] if len(get_args(v)) > 1 else None
+            if not isinstance(field_info, FieldInfo):
+                continue
+            if k in filters:
+                field_info.default = filters[k]
+            if field_info.is_required() or (
+                (field_info.alias or k) in metadata_mapping
+            ):
+                queryables[k] = v
+
+        return queryables
diff --git a/eodag/plugins/search/data_request_search.py b/eodag/plugins/search/data_request_search.py
index 9a864a59..de680cbe 100644
--- a/eodag/plugins/search/data_request_search.py
+++ b/eodag/plugins/search/data_request_search.py
@@ -260,6 +260,7 @@ class DataRequestSearch(Search):
         self, product_type: str, eodag_product_type: str, **kwargs: Any
     ) -> str:
         headers = getattr(self.auth, "headers", USER_AGENT)
+        ssl_verify = getattr(self.config.ssl_verify, "ssl_verify", True)
         try:
             url = self.config.data_request_url
             request_body = format_query_params(
@@ -269,7 +270,11 @@ class DataRequestSearch(Search):
                 f"Sending search job request to {url} with {str(request_body)}"
             )
             request_job = requests.post(
-                url, json=request_body, headers=headers, timeout=HTTP_REQ_TIMEOUT
+                url,
+                json=request_body,
+                headers=headers,
+                timeout=HTTP_REQ_TIMEOUT,
+                verify=ssl_verify,
             )
             request_job.raise_for_status()
         except requests.exceptions.Timeout as exc:
@@ -300,9 +305,14 @@ class DataRequestSearch(Search):
         logger.debug("checking status of request job %s", data_request_id)
         status_url = self.config.status_url + data_request_id
         headers = getattr(self.auth, "headers", USER_AGENT)
+        ssl_verify = getattr(self.config, "ssl_verify", True)
+
         try:
             status_resp = requests.get(
-                status_url, headers=headers, timeout=HTTP_REQ_TIMEOUT
+                status_url,
+                headers=headers,
+                timeout=HTTP_REQ_TIMEOUT,
+                verify=ssl_verify,
             )
             status_resp.raise_for_status()
         except requests.exceptions.Timeout as exc:
@@ -331,9 +341,12 @@ class DataRequestSearch(Search):
         url = self.config.result_url.format(
             jobId=data_request_id, items_per_page=items_per_page, page=page
         )
+        ssl_verify = getattr(self.config, "ssl_verify", True)
         headers = getattr(self.auth, "headers", USER_AGENT)
         try:
-            return requests.get(url, headers=headers, timeout=HTTP_REQ_TIMEOUT).json()
+            return requests.get(
+                url, headers=headers, timeout=HTTP_REQ_TIMEOUT, verify=ssl_verify
+            ).json()
         except requests.exceptions.Timeout as exc:
             raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
         except requests.RequestException:
diff --git a/eodag/plugins/search/qssearch.py b/eodag/plugins/search/qssearch.py
index ea153faf..6819c755 100644
--- a/eodag/plugins/search/qssearch.py
+++ b/eodag/plugins/search/qssearch.py
@@ -69,6 +69,7 @@ from eodag.utils import (
     dict_items_recursive_apply,
     format_dict_items,
     get_args,
+    get_ssl_context,
     quote,
     string_to_jsonpath,
     update_nested_dict,
@@ -875,6 +876,9 @@ class QueryStringSearch(Search):
     ) -> Response:
         try:
             timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
+            ssl_verify = getattr(self.config, "ssl_verify", True)
+
+            ssl_ctx = get_ssl_context(ssl_verify)
             # auth if needed
             kwargs: Dict[str, Any] = {}
             if (
@@ -885,6 +889,7 @@ class QueryStringSearch(Search):
                 kwargs["auth"] = self.auth
             # requests auto quote url params, without any option to prevent it
             # use urllib instead of requests if req must be sent unquoted
+
             if hasattr(self.config, "dont_quote"):
                 # keep unquoted desired params
                 base_url, params = url.split("?") if "?" in url else (url, "")
@@ -902,7 +907,7 @@ class QueryStringSearch(Search):
                 if info_message:
                     logger.info(info_message.replace(url, prep.url))
                 urllib_req = Request(prep.url, headers=USER_AGENT)
-                urllib_response = urlopen(urllib_req, timeout=timeout)
+                urllib_response = urlopen(urllib_req, timeout=timeout, context=ssl_ctx)
                 # build Response
                 adapter = HTTPAdapter()
                 response = cast(Response, adapter.build_response(prep, urllib_response))
@@ -910,7 +915,11 @@ class QueryStringSearch(Search):
                 if info_message:
                     logger.info(info_message)
                 response = requests.get(
-                    url, timeout=timeout, headers=USER_AGENT, **kwargs
+                    url,
+                    timeout=timeout,
+                    headers=USER_AGENT,
+                    verify=ssl_verify,
+                    **kwargs,
                 )
                 response.raise_for_status()
         except requests.exceptions.Timeout as exc:
@@ -981,13 +990,17 @@ class ODataV4Search(QueryStringSearch):
 
         if getattr(self.config, "per_product_metadata_query", False):
             final_result = []
+            ssl_verify = getattr(self.config, "ssl_verify", True)
             # Query the products entity set for basic metadata about the product
             for entity in super(ODataV4Search, self).do_search(*args, **kwargs):
                 metadata_url = self.get_metadata_search_url(entity)
                 try:
                     logger.debug("Sending metadata request: %s", metadata_url)
                     response = requests.get(
-                        metadata_url, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
+                        metadata_url,
+                        headers=USER_AGENT,
+                        timeout=HTTP_REQ_TIMEOUT,
+                        verify=ssl_verify,
                     )
                     response.raise_for_status()
                 except requests.exceptions.Timeout as exc:
@@ -1231,6 +1244,7 @@ class PostJsonSearch(QueryStringSearch):
         exception_message: Optional[str] = None,
     ) -> Response:
         timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
+        ssl_verify = getattr(self.config, "ssl_verify", True)
         try:
             # auth if needed
             RequestsKwargs = TypedDict(
@@ -1255,6 +1269,7 @@ class PostJsonSearch(QueryStringSearch):
                 json=self.query_params,
                 headers=USER_AGENT,
                 timeout=timeout,
+                verify=ssl_verify,
                 **kwargs,
             )
             response.raise_for_status()
diff --git a/eodag/plugins/search/static_stac_search.py b/eodag/plugins/search/static_stac_search.py
index 502b5016..10a22ebf 100644
--- a/eodag/plugins/search/static_stac_search.py
+++ b/eodag/plugins/search/static_stac_search.py
@@ -71,6 +71,7 @@ class StaticStacSearch(StacSearch):
         super(StaticStacSearch, self).__init__(provider, config)
         self.config.__dict__.setdefault("max_connections", 100)
         self.config.__dict__.setdefault("timeout", HTTP_REQ_TIMEOUT)
+        self.config.__dict__.setdefault("ssl_verify", True)
 
     def discover_product_types(self) -> Dict[str, Any]:
         """Fetch product types is disabled for `StaticStacSearch`
@@ -95,6 +96,7 @@ class StaticStacSearch(StacSearch):
             recursive=True,
             max_connections=self.config.max_connections,
             timeout=self.config.timeout,
+            ssl_verify=self.config.ssl_verify,
         )
         nb_features = len(features)
         feature_collection = geojson.FeatureCollection(features)
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index 23ad297d..7d0a821d 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -16,7 +16,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ---
-!provider
+!provider # usgs
   name: usgs
   priority: 0
   description: U.S geological survey catalog for Landsat products
@@ -83,7 +83,7 @@
       dataset: '{productType}'
 
 ---
-!provider
+!provider # aws_eos
   name: aws_eos
   priority: 0
   description: EOS search for Amazon public datasets
@@ -96,6 +96,7 @@
     need_auth: true
     auth_error_code: 403
     results_entry: 'results'
+    ssl_verify: true
     pagination:
       next_page_query_obj: '{{"limit":{items_per_page},"page":{page}}}'
       total_items_nb_key_path: '$.meta.found'
@@ -499,6 +500,7 @@
   download: !plugin
     type: AwsDownload
     requester_pays: True
+    ssl_verify: true
     products:
       CBERS4_MUX_L2:
         default_bucket: 'cbers-pds'
@@ -535,9 +537,10 @@
           - productPath
   auth: !plugin
     type: AwsAuth
+    ssl_verify: true
 
 ---
-!provider
+!provider # theia
   name: theia
   priority: 0
   description: French National Space Agency (CNES) catalog for Sentinel 2 products, Pleiades and Landsat products
@@ -548,6 +551,7 @@
     type: QueryStringSearch
     api_endpoint: 'https://theia.cnes.fr/atdistrib/resto2/api/collections/{collection}/search.json'
     need_auth: false
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -707,14 +711,16 @@
     archive_depth: 2
     order_enabled: true
     auth_error_code: 403
+    ssl_verify: true
     dl_url_params:
       issuerId: theia
   auth: !plugin
     type: TokenAuth
     auth_uri: 'https://theia.cnes.fr/atdistrib/services/authenticate/'
+    ssl_verify: true
 
 ---
-!provider
+!provider # peps
   name: peps
   priority: 1
   description: |
@@ -727,6 +733,7 @@
     type: QueryStringSearch
     api_endpoint: 'https://peps.cnes.fr/resto/api/collections/{collection}/search.json'
     need_auth: false
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -861,13 +868,15 @@
     archive_depth: 2
     order_enabled: true
     auth_error_code: 401
+    ssl_verify: true
     dl_url_params:
       issuerId: peps
   auth: !plugin
     type: GenericAuth
     auth_uri: 'https://peps.cnes.fr/resto/api/users/connect'
+    ssl_verify: true
 ---
-!provider
+!provider # creodias
   name: creodias
   priority: 0
   description: CloudFerro DIAS
@@ -879,6 +888,7 @@
     api_endpoint: 'http://datahub.creodias.eu/resto/api/collections/{collection}/search.json'
     need_auth: false
     timeout: 60
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}&exactCount=1'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -1014,6 +1024,7 @@
     extract: true
     order_enabled: false
     archive_depth: 2
+    ssl_verify: true
   auth: !plugin
     type: KeycloakOIDCPasswordAuth
     auth_base_uri: 'https://identity.cloudferro.com/auth'
@@ -1023,6 +1034,7 @@
     token_provision: qs
     token_qs_key: 'token'
     auth_error_code: 401
+    ssl_verify: true
   products:
       # S1
     S1_SAR_RAW:
@@ -1228,7 +1240,7 @@
       productType: '{productType}'
       collection: '{collection}'
 ---
-!provider
+!provider # onda
   name: onda
   priority: 0
   description: Serco DIAS
@@ -1240,6 +1252,7 @@
     api_endpoint: 'https://catalogue.onda-dias.eu/dias-catalogue/Products'
     timeout: 60
     need_auth: false
+    ssl_verify: true
     dont_quote:
       - '['
       - ']'
@@ -1528,13 +1541,15 @@
     auth_error_code: 401
     order_enabled: true
     order_method: 'POST'
+    ssl_verify: true
     order_headers:
       Content-Type: application/json
   auth: !plugin
     type: GenericAuth
+    ssl_verify: true
 
 ---
-!provider
+!provider # astraea_eod
   name: astraea_eod
   priority: 0
   roles:
@@ -1545,6 +1560,7 @@
     type: StacSearch
     api_endpoint: https://eod-catalog-svc-prod.astraea.earth/search
     need_auth: false
+    ssl_verify: true
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
@@ -1627,6 +1643,7 @@
     type: AwsDownload
     requester_pays: True
     flatten_top_dirs: True
+    ssl_verify: true
     products:
       S1_SAR_GRD:
         default_bucket: 'sentinel-s1-l1c'
@@ -1659,9 +1676,10 @@
           - tilePath
   auth: !plugin
     type: AwsAuth
+    ssl_verify: true
 
 ---
-!provider
+!provider # usgs_satapi_aws
   name: usgs_satapi_aws
   priority: 0
   roles:
@@ -1672,6 +1690,7 @@
     type: StacSearch
     api_endpoint: https://landsatlook.usgs.gov/stac-server/search
     need_auth: false
+    ssl_verify: true
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
@@ -1716,11 +1735,13 @@
     type: AwsDownload
     requester_pays: True
     flatten_top_dirs: True
+    ssl_verify: true
   auth: !plugin
     type: AwsAuth
+    ssl_verify: true
 
 ---
-!provider
+!provider # earth_search
   name: earth_search
   priority: 0
   roles:
@@ -1731,6 +1752,7 @@
     type: StacSearch
     api_endpoint: https://earth-search.aws.element84.com/v1/search
     need_auth: false
+    ssl_verify: true
     discover_product_types:
       results_entry: '$.collections[?id!="sentinel-s2-l2a-cogs"]'
     discover_queryables:
@@ -1810,6 +1832,7 @@
     type: AwsDownload
     requester_pays: True
     flatten_top_dirs: True
+    ssl_verify: true
     products:
       S2_MSI_L1C:
         default_bucket: 'sentinel-s2-l1c'
@@ -1819,9 +1842,10 @@
           - tilePath
   auth: !plugin
     type: AwsAuth
+    ssl_verify: true
 
 ---
-!provider
+!provider # earth_search_cog
   name: earth_search_cog
   priority: 0
   roles:
@@ -1832,6 +1856,7 @@
     type: StacSearch
     api_endpoint: https://earth-search.aws.element84.com/v1/search
     need_auth: false
+    ssl_verify: true
     discover_product_types:
       fetch_url: null
     discover_queryables:
@@ -1872,8 +1897,9 @@
   download: !plugin
     type: HTTPDownload
     base_uri: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com'
+    ssl_verify: true
 ---
-!provider
+!provider # earth_search_gcs
   name: earth_search_gcs
   priority: 0
   roles:
@@ -1884,6 +1910,7 @@
     type: StacSearch
     api_endpoint: https://earth-search.aws.element84.com/v0/search
     need_auth: false
+    ssl_verify: true
     discover_product_types:
       fetch_url: null
     discover_queryables:
@@ -1933,13 +1960,15 @@
     type: AwsDownload
     base_uri: https://storage.googleapis.com
     ignore_assets: True
+    ssl_verify: true
     products:
       S2_MSI_L1C:
         default_bucket: 'gcp-public-data-sentinel-2'
   auth: !plugin
     type: AwsAuth
+    ssl_verify: true
 ---
-!provider
+!provider # ecmwf
   name: ecmwf
   priority: 0
   description: ECMWF archive products
@@ -1950,6 +1979,7 @@
     type: EcmwfApi
     api_endpoint: https://api.ecmwf.int/v1
     extract: false
+    ssl_verify: true
     metadata_mapping:
       productType: '$.productType'
       title: '$.id'
@@ -2162,7 +2192,7 @@
     GENERIC_PRODUCT_TYPE:
       dataset: '{productType}'
 ---
-!provider
+!provider # cop_ads
   name: cop_ads
   priority: 0
   description: Copernicus Atmosphere Data Store
@@ -2175,6 +2205,7 @@
     extract: false
     flatten_top_dirs: True
     constraints_file_url: "https://datastore.copernicus-climate.eu/cams/published-forms/camsprod/{dataset}/constraints.json"
+    ssl_verify: true
     metadata_mapping:
       productType: '$.productType'
       title: '$.id'
@@ -2568,7 +2599,7 @@
       format: grib
       time: '00:00'
 ---
-!provider
+!provider # cop_cds
   name: cop_cds
   priority: 0
   description: Copernicus Climate Data Store
@@ -2579,6 +2610,7 @@
     type: CdsApi
     api_endpoint: https://cds.climate.copernicus.eu/api/v2
     extract: false
+    ssl_verify: true
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
@@ -3148,7 +3180,7 @@
       time: '00:00'
       format: netcdf
 ---
-!provider
+!provider # sara
   name: sara
   priority: 0
   description: Sentinel Australasia Regional Access
@@ -3160,6 +3192,7 @@
     # but can be very slow if not enough metdata is provided.
     api_endpoint: 'https://copernicus.nci.org.au/sara.server/1.0/api/collections/{collection}/search.json'
     need_auth: false
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -3423,11 +3456,13 @@
     archive_depth: 2
     order_enabled: true
     auth_error_code: 403
+    ssl_verify: true
   auth: !plugin
     type: GenericAuth
     method: basic
+    ssl_verify: true
 ---
-!provider
+!provider # meteoblue
   name: meteoblue
   priority: 0
   roles:
@@ -3438,6 +3473,7 @@
     type: BuildPostSearchResult
     api_endpoint: 'https://my.meteoblue.com/dataset/query'
     need_auth: true
+    ssl_verify: true
     pagination:
       next_page_query_obj: '{{"checkOnly":true}}'
     discover_metadata:
@@ -3497,6 +3533,7 @@
     extract: False
     order_enabled: true
     order_method: 'POST'
+    ssl_verify: true
     order_on_response:
       metadata_mapping:
         order_id: '$.id'
@@ -3511,8 +3548,9 @@
   auth: !plugin
     type: HttpQueryStringAuth
     auth_uri: 'http://my.meteoblue.com/dataset/meta?dataset=NEMSAUTO'
+    ssl_verify: true
 ---
-!provider
+!provider # cop_dataspace
   name: cop_dataspace
   priority: 0
   description: Copernicus Data Space Ecosystem
@@ -3524,6 +3562,7 @@
     api_endpoint: 'http://catalogue.dataspace.copernicus.eu/resto/api/collections/{collection}/search.json'
     need_auth: false
     timeout: 60
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}&exactCount=1'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -3623,6 +3662,7 @@
     extract: true
     order_enabled: false
     archive_depth: 2
+    ssl_verify: true
   auth: !plugin
     type: KeycloakOIDCPasswordAuth
     auth_base_uri: 'https://identity.dataspace.copernicus.eu/auth'
@@ -3632,6 +3672,7 @@
     token_provision: qs
     token_qs_key: 'token'
     auth_error_code: 401
+    ssl_verify: true
   products:
     # S2
     S2_MSI_L1C:
@@ -3816,7 +3857,7 @@
       productType: '{productType}'
       collection: '{collection}'
 ---
-!provider
+!provider # planetary_computer
   name: planetary_computer
   priority: 0
   roles:
@@ -3827,6 +3868,7 @@
     type: StacSearch
     api_endpoint: https://planetarycomputer.microsoft.com/api/stac/v1/search
     need_auth: false
+    ssl_verify: true
     pagination:
       max_items_per_page: 1000
     sort:
@@ -3862,14 +3904,16 @@
     base_uri: 'https://planetarycomputer.microsoft.com/api/stac/download'
     flatten_top_dirs: True
     auth_error_code: 403
+    ssl_verify: true
   auth: !plugin
     type: SASAuth
     auth_uri: 'https://planetarycomputer.microsoft.com/api/sas/v1/sign?href={url}'
     signed_url_key: href
+    ssl_verify: true
     headers:
       Ocp-Apim-Subscription-Key: "{apikey}"
 ---
-!provider
+!provider # hydroweb_next
   name: hydroweb_next
   priority: 0
   roles:
@@ -3881,6 +3925,7 @@
     api_endpoint: https://hydroweb.next.theia-land.fr/api/v1/rs-catalog/stac/search
     need_auth: true
     auth_error_code: 401
+    ssl_verify: true
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
@@ -3910,12 +3955,14 @@
     base_uri: https://hydroweb.next.theia-land.fr
     flatten_top_dirs: true
     auth_error_code: 401
+    ssl_verify: true
   auth: !plugin
     type: HTTPHeaderAuth
+    ssl_verify: true
     headers:
       X-API-Key: "{apikey}"
 ---
-!provider
+!provider # wekeo
   name: wekeo
   priority: 0
   roles:
@@ -3935,6 +3982,7 @@
     results_entry: content
     two_passes_id_search: true
     dates_required: true
+    ssl_verify: true
     pagination:
       start_page: 0
       max_items_per_page: 20
@@ -3967,6 +4015,7 @@
     token_type: json
     token_key: access_token
     request_method: GET
+    ssl_verify: true
     headers:
       Authorization: Bearer $token
   products:
@@ -6838,6 +6887,7 @@
     auth_error_code: 401
     order_enabled: true
     order_method: 'POST'
+    ssl_verify: true
     order_on_response:
       metadata_mapping:
         order_id: '$.orderId'
@@ -6854,7 +6904,7 @@
         outputs_extension: .tar
 
 ---
-!provider
+!provider # creodias_s3
   name: creodias_s3
   priority: 0
   description: CloudFerro DIAS data through S3 protocol
@@ -6867,6 +6917,7 @@
     s3_endpoint: 'https://eodata.cloudferro.com'
     need_auth: true
     timeout: 60
+    ssl_verify: true
     pagination:
       next_page_url_tpl: '{url}?{search}&maxRecords={items_per_page}&page={page}&exactCount=1'
       total_items_nb_key_path: '$.properties.totalResults'
@@ -7001,9 +7052,11 @@
     flatten_top_dirs: True
     base_uri: 'https://eodata.cloudferro.com'
     s3_bucket: 'eodata'
+    ssl_verify: true
   auth: !plugin
     type: AwsAuth
     auth_error_code: 403
+    ssl_verify: true
   products:
     # S1
     S1_SAR_RAW:
diff --git a/eodag/rest/core.py b/eodag/rest/core.py
index 9ac51394..0f853ea9 100644
--- a/eodag/rest/core.py
+++ b/eodag/rest/core.py
@@ -36,7 +36,7 @@ from eodag.plugins.crunch.filter_latest_tpl_name import FilterLatestByName
 from eodag.plugins.crunch.filter_overlap import FilterOverlap
 from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
 from eodag.rest.types.eodag_search import EODAGSearch
-from eodag.rest.types.stac_queryables import StacQueryableProperty
+from eodag.rest.types.stac_queryables import StacQueryableProperty, StacQueryables
 from eodag.rest.types.stac_search import SearchPostRequest
 from eodag.rest.utils import (
     Cruncher,
@@ -46,7 +46,12 @@ from eodag.rest.utils import (
     get_next_link,
 )
 from eodag.rest.utils.rfc3339 import rfc3339_str_to_datetime
-from eodag.utils import StreamResponse, _deprecated, dict_items_recursive_apply
+from eodag.utils import (
+    StreamResponse,
+    _deprecated,
+    deepcopy,
+    dict_items_recursive_apply,
+)
 from eodag.utils.exceptions import (
     MisconfiguredError,
     NoMatchingProductType,
@@ -487,9 +492,12 @@ def get_stac_extension_oseo(url: str) -> Dict[str, str]:
     )
 
 
-def fetch_collection_queryable_properties(
-    collection_id: Optional[str] = None, provider: Optional[str] = None, **kwargs: Any
-) -> Dict[str, StacQueryableProperty]:
+def get_queryables(
+    request: Request,
+    collection: Optional[str] = None,
+    provider: Optional[str] = None,
+    **kwargs: Any,
+) -> StacQueryables:
     """Fetch the queryable properties for a collection.
 
     :param collection_id: The ID of the collection.
@@ -502,17 +510,10 @@ def fetch_collection_queryable_properties(
     :returns: A set containing the STAC standardized queryable properties for a collection.
     :rtype Dict[str, StacQueryableProperty]: set
     """
-    if not collection_id and "collections" in kwargs:
-        collection_ids = kwargs.pop("collections").split(",")
-        collection_id = collection_ids[0]
-
-    if collection_id and "productType" in kwargs:
+    if collection and "productType" in kwargs:
         kwargs.pop("productType")
     elif "productType" in kwargs:
-        collection_id = kwargs.pop("productType")
-
-    if "ids" in kwargs:
-        kwargs["id"] = kwargs.pop("ids")
+        collection = kwargs.pop("productType")
 
     if "datetime" in kwargs:
         dates = get_datetime(kwargs)
@@ -520,19 +521,39 @@ def fetch_collection_queryable_properties(
         kwargs["end"] = dates[1]
 
     python_queryables = eodag_api.list_queryables(
-        provider=provider, productType=collection_id, **kwargs
+        provider=provider, productType=collection, **kwargs
     )
     python_queryables.pop("start")
     python_queryables.pop("end")
 
-    stac_queryables: Dict[str, StacQueryableProperty] = dict()
+    # productType and id are already default in stac collection and id
+    python_queryables.pop("productType", None)
+    python_queryables.pop("id", None)
+
+    stac_queryables: Dict[str, StacQueryableProperty] = deepcopy(
+        StacQueryables.default_properties
+    )
     for param, queryable in python_queryables.items():
         stac_param = EODAGSearch.to_stac(param)
+        # only keep "datetime" queryable for dates
+        if stac_param in stac_queryables or stac_param in (
+            "start_datetime",
+            "end_datetime",
+        ):
+            continue
+
         stac_queryables[
             stac_param
         ] = StacQueryableProperty.from_python_field_definition(stac_param, queryable)
 
-    return stac_queryables
+    if collection:
+        stac_queryables.pop("collection")
+
+    return StacQueryables(
+        q_id=request.state.url,
+        additional_properties=bool(not collection),
+        properties=stac_queryables,
+    )
 
 
 @_deprecated(
diff --git a/eodag/rest/server.py b/eodag/rest/server.py
index 05777a62..3aedb6a9 100755
--- a/eodag/rest/server.py
+++ b/eodag/rest/server.py
@@ -49,8 +49,8 @@ from eodag.config import load_stac_api_config
 from eodag.rest.core import (
     download_stac_item,
     eodag_api_init,
-    fetch_collection_queryable_properties,
     get_detailled_collections_list,
+    get_queryables,
     get_stac_api_version,
     get_stac_catalogs,
     get_stac_collection_by_id,
@@ -60,7 +60,6 @@ from eodag.rest.core import (
     search_stac_items,
 )
 from eodag.rest.types.eodag_search import EODAGSearch
-from eodag.rest.types.stac_queryables import StacQueryables
 from eodag.rest.types.stac_search import SearchPostRequest, sortby2list
 from eodag.rest.utils import format_pydantic_error, str2json, str2list
 from eodag.utils import parse_header, update_nested_dict
@@ -511,17 +510,12 @@ def list_collection_queryables(
     :rtype: Any
     """
     logger.debug(f"URL: {request.url}")
-    kwargs = dict(request.query_params)
-    provider = kwargs.pop("provider", None)
+    additional_params = dict(request.query_params)
+    provider = additional_params.pop("provider", None)
 
-    queryables = StacQueryables(q_id=request.state.url, additional_properties=False)
-
-    collection_queryables = fetch_collection_queryable_properties(
-        collection_id, provider, **kwargs
+    queryables = get_queryables(
+        request, collection=collection_id, provider=provider, **additional_params
     )
-    for key, collection_queryable in collection_queryables.items():
-        queryables[key] = collection_queryable
-    queryables.properties.pop("collections")
 
     return jsonable_encoder(queryables)
 
@@ -739,14 +733,9 @@ def list_queryables(request: Request, provider: Optional[str] = None) -> Any:
     :rtype: Any
     """
     logger.debug(f"URL: {request.url}")
-    query_params = request.query_params.items()
-    additional_params = dict(query_params)
+    additional_params = dict(request.query_params.items())
     additional_params.pop("provider", None)
-    queryables = StacQueryables(q_id=request.state.url)
-    if provider:
-        queryables.properties.update(
-            fetch_collection_queryable_properties(None, provider, **additional_params)
-        )
+    queryables = get_queryables(request, provider=provider, **additional_params)
 
     return jsonable_encoder(queryables)
 
diff --git a/eodag/rest/types/stac_queryables.py b/eodag/rest/types/stac_queryables.py
index 36cc9a32..41dcb841 100644
--- a/eodag/rest/types/stac_queryables.py
+++ b/eodag/rest/types/stac_queryables.py
@@ -17,7 +17,7 @@
 # limitations under the License.
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Union
 
 from pydantic import BaseModel, Field
 
@@ -87,48 +87,28 @@ class StacQueryables(BaseModel):
     description: str = Field(
         default="Queryable names for the EODAG STAC API Item Search filter."
     )
-    properties: Dict[str, StacQueryableProperty] = Field(
-        default={
-            "ids": StacQueryableProperty(
-                description="ID",
-                ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id",
-            ),
-            "collections": StacQueryableProperty(
-                description="Collection",
-                ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
-            ),
-            "geometry": StacQueryableProperty(
-                description="Geometry",
-                ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
-            ),
-            "datetime": StacQueryableProperty(
-                description="Datetime - use parameters year, month, day, time instead if available",
-                ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
-            ),
-        }
-    )
+    default_properties: ClassVar[Dict[str, StacQueryableProperty]] = {
+        "id": StacQueryableProperty(
+            description="ID",
+            ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id",
+        ),
+        "collection": StacQueryableProperty(
+            description="Collection",
+            ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
+        ),
+        "geometry": StacQueryableProperty(
+            description="Geometry",
+            ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
+        ),
+        "datetime": StacQueryableProperty(
+            description="Datetime - use parameters year, month, day, time instead if available",
+            ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
+        ),
+    }
+    properties: Dict[str, StacQueryableProperty] = Field()
     additional_properties: bool = Field(
         default=True, serialization_alias="additionalProperties"
     )
 
-    def get_properties(self) -> Dict[str, StacQueryableProperty]:
-        """Get the queryable properties.
-
-        :returns: A dictionary containing queryable properties.
-        :rtype: typing.Dict[str, StacQueryableProperty]
-        """
-        properties = {}
-        for key, property in self.properties.items():
-            property = StacQueryableProperty(
-                description=property.description, type=property.type
-            )
-            properties[key] = property
-        return properties
-
     def __contains__(self, name: str) -> bool:
         return name in self.properties
-
-    def __setitem__(self, name: str, qprop: StacQueryableProperty) -> None:
-        # only keep "datetime" queryable for dates
-        if name not in ("start_datetime", "end_datetime"):
-            self.properties[name] = qprop
diff --git a/eodag/utils/__init__.py b/eodag/utils/__init__.py
index 99bd705a..bbf35793 100644
--- a/eodag/utils/__init__.py
+++ b/eodag/utils/__init__.py
@@ -33,6 +33,7 @@ import mimetypes
 import os
 import re
 import shutil
+import ssl
 import string
 import sys
 import types
@@ -1437,3 +1438,22 @@ def guess_file_type(file: str) -> Optional[str]:
     mimetypes.add_type("text/xml", ".xsd")
     mime_type, _ = mimetypes.guess_type(file, False)
     return mime_type
+
+
+def get_ssl_context(ssl_verify: bool) -> ssl.SSLContext:
+
+    """
+    Returns an SSL context based on ssl_verify argument.
+    :param ssl_verify: ssl_verify parameter
+    :type ssl_verify: bool
+    :returns: An SSL context object.
+    :rtype: ssl.SSLContext
+    """
+    ctx = ssl.create_default_context()
+    if not ssl_verify:
+        ctx.check_hostname = False
+        ctx.verify_mode = ssl.CERT_NONE
+    else:
+        ctx.check_hostname = True
+        ctx.verify_mode = ssl.CERT_REQUIRED
+    return ctx
diff --git a/eodag/utils/stac_reader.py b/eodag/utils/stac_reader.py
index ac2772af..92f5dc9d 100644
--- a/eodag/utils/stac_reader.py
+++ b/eodag/utils/stac_reader.py
@@ -28,7 +28,7 @@ import concurrent.futures
 import orjson
 import pystac
 
-from eodag.utils import HTTP_REQ_TIMEOUT
+from eodag.utils import HTTP_REQ_TIMEOUT, get_ssl_context
 from eodag.utils.exceptions import STACOpenerError
 
 logger = logging.getLogger("eodag.utils.stac_reader")
@@ -38,10 +38,11 @@ class _TextOpener:
     """Exhaust read methods for pystac.StacIO in the order defined
     in the openers list"""
 
-    def __init__(self, timeout: int) -> None:
+    def __init__(self, timeout: int, ssl_verify: bool) -> None:
         self.openers = [self.read_local_json, self.read_http_remote_json]
         # Only used by read_http_remote_json
         self.timeout = timeout
+        self.ssl_verify = ssl_verify
 
     @staticmethod
     def read_local_json(url: str, as_json: bool = False) -> Any:
@@ -59,8 +60,10 @@ class _TextOpener:
 
     def read_http_remote_json(self, url: str, as_json: bool = False) -> Any:
         """Read JSON remote HTTP file"""
+        ssl_ctx = get_ssl_context(self.ssl_verify)
+
         try:
-            res = urlopen(url, timeout=self.timeout)
+            res = urlopen(url, timeout=self.timeout, context=ssl_ctx)
             content_type = res.getheader("Content-Type")
             if content_type is None:
                 encoding = "utf-8"
@@ -102,6 +105,7 @@ def fetch_stac_items(
     recursive: bool = False,
     max_connections: int = 100,
     timeout: int = HTTP_REQ_TIMEOUT,
+    ssl_verify: bool = True,
 ) -> List[Dict[str, Any]]:
     """Fetch STAC item from a single item file or items from a catalog.
 
@@ -113,13 +117,15 @@ def fetch_stac_items(
     :type max_connections: int
     :param timeout: (optional) Timeout in seconds for each internal HTTP request
     :type timeout: float
+    :param ssl_verify: (optional) SSL Verification for HTTP request
+    :type ssl_verify: bool
     :returns: The items found in `stac_path`
     :rtype: :class:`list`
     """
 
     # URI opener used by PySTAC internally, instantiated here
     # to retrieve the timeout.
-    _text_opener = _TextOpener(timeout)
+    _text_opener = _TextOpener(timeout, ssl_verify)
     pystac.StacIO.read_text = _text_opener
 
     stac_obj = pystac.read_file(stac_path)


Test patch:
diff --git a/tests/context.py b/tests/context.py
index 72b07403..68ee4418 100644
--- a/tests/context.py
+++ b/tests/context.py
@@ -88,6 +88,7 @@ from eodag.utils import (
     cached_parse,
     sanitize,
     parse_header,
+    get_ssl_context,
 )
 from eodag.utils.exceptions import (
     AddressNotFound,
diff --git a/tests/integration/test_core_config.py b/tests/integration/test_core_config.py
index ce09a795..7efc6858 100644
--- a/tests/integration/test_core_config.py
+++ b/tests/integration/test_core_config.py
@@ -255,6 +255,7 @@ class TestCoreProductTypesConfig(TestCase):
             auth=mock.ANY,
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
 
         mock_urlopen.return_value.json.return_value = {}
diff --git a/tests/units/test_auth_plugins.py b/tests/units/test_auth_plugins.py
index 048525c6..38932cec 100644
--- a/tests/units/test_auth_plugins.py
+++ b/tests/units/test_auth_plugins.py
@@ -251,6 +251,7 @@ class TestAuthPluginHttpQueryStringAuth(BaseAuthPluginTest):
             timeout=HTTP_REQ_TIMEOUT,
             headers=USER_AGENT,
             auth=auth,
+            verify=True,
         )
 
         # check auth query string
diff --git a/tests/units/test_core.py b/tests/units/test_core.py
index b6eaa26f..403e26ed 100644
--- a/tests/units/test_core.py
+++ b/tests/units/test_core.py
@@ -27,6 +27,7 @@ import unittest
 import uuid
 from pathlib import Path
 from tempfile import TemporaryDirectory
+from unittest.mock import Mock
 
 from pkg_resources import resource_filename
 from shapely import wkt
@@ -1129,8 +1130,8 @@ class TestCore(TestCoreBase):
         "eodag.api.core.EODataAccessGateway.fetch_product_types_list", autospec=True
     )
     def test_list_queryables(
-        self, mock_discover_queryables, mock_fetch_product_types_list
-    ):
+        self, mock_discover_queryables: Mock, mock_fetch_product_types_list: Mock
+    ) -> None:
         """list_queryables must return queryables list adapted to provider and product-type"""
         with self.assertRaises(UnsupportedProvider):
             self.dag.list_queryables(provider="not_supported_provider")
@@ -1167,7 +1168,7 @@ class TestCore(TestCoreBase):
                 self.assertEqual(str(expected_longer_result[key]), str(queryable))
 
     @mock.patch("eodag.plugins.apis.cds.CdsApi.discover_queryables", autospec=True)
-    def test_list_queryables_with_constraints(self, mock_discover_queryables):
+    def test_list_queryables_with_constraints(self, mock_discover_queryables: Mock):
         plugin = next(
             self.dag._plugins_manager.get_search_plugins(
                 provider="cop_cds", product_type="ERA5_SL"
diff --git a/tests/units/test_download_plugins.py b/tests/units/test_download_plugins.py
index dfd05175..818124e2 100644
--- a/tests/units/test_download_plugins.py
+++ b/tests/units/test_download_plugins.py
@@ -521,6 +521,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             params=plugin.config.dl_url_params,
             headers=USER_AGENT,
             timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+            verify=True,
         )
         mock_requests_session.assert_not_called()
         # re-enable product download
@@ -550,9 +551,42 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             params=plugin.config.dl_url_params,
             headers=USER_AGENT,
             timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+            verify=True,
         )
         mock_requests_session.assert_not_called()
 
+    @mock.patch("eodag.plugins.download.http.requests.head", autospec=True)
+    @mock.patch("eodag.plugins.download.http.requests.get", autospec=True)
+    def test_plugins_download_http_ignore_assets_without_ssl(
+        self, mock_requests_get, mock_requests_head
+    ):
+        """HTTPDownload.download() must ignore assets if configured to"""
+
+        plugin = self.get_download_plugin(self.product)
+        self.product.location = (
+            self.product.remote_location
+        ) = "http://somewhere/download_from_location"
+        self.product.properties["id"] = "someproduct"
+        self.product.assets.clear()
+        self.product.assets.update({"foo": {"href": "http://somewhere/download_asset"}})
+        mock_requests_get.return_value.__enter__.return_value.iter_content.side_effect = lambda *x, **y: io.BytesIO(
+            b"some content"
+        )
+        # download asset if ssl_verify = False
+        plugin.config.ssl_verify = False
+
+        plugin.download(self.product, outputs_prefix=self.output_dir)
+        mock_requests_get.assert_called_once_with(
+            self.product.assets["foo"]["href"],
+            stream=True,
+            auth=None,
+            params=plugin.config.dl_url_params,
+            headers=USER_AGENT,
+            timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+            verify=False,
+        )
+        del plugin.config.ssl_verify
+
     @mock.patch("eodag.plugins.download.http.requests.head", autospec=True)
     @mock.patch("eodag.plugins.download.http.requests.get", autospec=True)
     def test_plugins_download_http_assets_filename_from_href(
@@ -596,6 +630,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             params=plugin.config.dl_url_params,
             headers=USER_AGENT,
             timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
+            verify=True,
         )
         # Check if the HEAD request has been called once for size & filename request
         mock_requests_head.assert_called_once_with(
@@ -973,6 +1008,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             auth=auth,
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
 
     @mock.patch("eodag.plugins.download.http.requests.request", autospec=True)
@@ -995,6 +1031,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             auth=auth,
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
         # orderLink with query query args
         mock_request.reset_mock()
@@ -1007,6 +1044,7 @@ class TestDownloadPluginHttp(BaseDownloadPluginTest):
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
             json={"foo": ["bar"]},
+            verify=True,
         )
 
     def test_plugins_download_http_order_status(self):
@@ -1431,6 +1469,7 @@ class TestDownloadPluginAws(BaseDownloadPluginTest):
             self.product.properties["tileInfo"],
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
         self.assertEqual(mock_get_authenticated_objects.call_count, 2)
         mock_get_authenticated_objects.assert_any_call(
@@ -1511,6 +1550,7 @@ class TestDownloadPluginAws(BaseDownloadPluginTest):
             self.product.properties["tileInfo"],
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
         mock_get_authenticated_objects.assert_called_once_with(
             plugin, "example", "", {}
diff --git a/tests/units/test_http_server.py b/tests/units/test_http_server.py
index 82b33732..9571d73f 100644
--- a/tests/units/test_http_server.py
+++ b/tests/units/test_http_server.py
@@ -1136,6 +1136,7 @@ class RequestTestCase(unittest.TestCase):
             url="https://planetarycomputer.microsoft.com/api/stac/v1/search/../queryables",
             timeout=HTTP_REQ_TIMEOUT,
             headers=USER_AGENT,
+            verify=True,
         )
         # TODO: with an unsupported provider
 
@@ -1178,6 +1179,7 @@ class RequestTestCase(unittest.TestCase):
             "sentinel-1-grd/queryables",
             timeout=HTTP_REQ_TIMEOUT,
             headers=USER_AGENT,
+            verify=True,
         )
         # returned queryables
         self.assertListEqual(
@@ -1194,7 +1196,7 @@ class RequestTestCase(unittest.TestCase):
         )
         self.assertListEqual(
             list(res_no_provider["properties"].keys()),
-            ["ids", "geometry", "datetime"],
+            ["id", "geometry", "datetime"],
         )
         self.assertIn("geometry", res_no_provider["properties"])
         self.assertNotIn("s1:processing_level", res_no_provider["properties"])
@@ -1211,6 +1213,7 @@ class RequestTestCase(unittest.TestCase):
             "sentinel-1-grd/queryables",
             timeout=HTTP_REQ_TIMEOUT,
             headers=USER_AGENT,
+            verify=True,
         )
 
         self.assertListEqual(
@@ -1255,9 +1258,9 @@ class RequestTestCase(unittest.TestCase):
         )
         self.assertEqual(10, len(res["properties"]))
         self.assertIn("year", res["properties"])
-        self.assertIn("ids", res["properties"])
+        self.assertIn("id", res["properties"])
         self.assertIn("geometry", res["properties"])
-        self.assertNotIn("collections", res["properties"])
+        self.assertNotIn("collection", res["properties"])
 
     def test_cql_post_search(self):
         self._request_valid(
diff --git a/tests/units/test_search_plugins.py b/tests/units/test_search_plugins.py
index 711979c6..4e99e4ca 100644
--- a/tests/units/test_search_plugins.py
+++ b/tests/units/test_search_plugins.py
@@ -17,6 +17,7 @@
 # limitations under the License.
 import json
 import re
+import ssl
 import unittest
 from datetime import datetime, timedelta, timezone
 from pathlib import Path
@@ -907,6 +908,47 @@ class TestSearchPluginODataV4Search(BaseSearchPluginTest):
         # products count non extracted from search results as count endpoint is specified
         self.assertFalse(hasattr(self.onda_search_plugin, "total_items_nb"))
 
+    @mock.patch("eodag.plugins.search.qssearch.get_ssl_context", autospec=True)
+    @mock.patch("eodag.plugins.search.qssearch.Request", autospec=True)
+    @mock.patch("eodag.plugins.search.qssearch.urlopen", autospec=True)
+    @mock.patch("eodag.plugins.search.qssearch.cast", autospec=True)
+    def test_plugins_search_odatav4search_with_ssl_context(
+        self, mock_cast, mock_urlopen, mock_request, mock_get_ssl_context
+    ):
+        """A query with a ODataV4Search (here onda) must return tuple with a list of EOProduct and a number of available products"""  # noqa
+        self.onda_search_plugin.config.ssl_verify = False
+        mock_cast.return_value.json.return_value = 2
+
+        ssl_ctx = ssl.create_default_context()
+        ssl_ctx.check_hostname = False
+        ssl_ctx.verify_mode = ssl.CERT_NONE
+
+        mock_request.return_value = mock.Mock()
+
+        # Mocking return value of get_ssl_context
+        mock_get_ssl_context.return_value = ssl_ctx
+
+        self.onda_search_plugin.query(
+            page=1,
+            items_per_page=2,
+            auth=self.onda_auth_plugin,
+            # custom query argument that must be mapped using discovery_metata.search_param
+            foo="bar",
+            **self.search_criteria_s2_msi_l1c,
+        )
+
+        mock_get_ssl_context.assert_called_with(False)
+
+        # # Asserting that get_ssl_context has been called
+        self.assertEqual(mock_get_ssl_context.call_count, 2)
+
+        # Asserting that urlopen has been called with the correct arguments
+        mock_urlopen.assert_called_with(
+            mock_request.return_value, timeout=60, context=ssl_ctx
+        )
+
+        del self.onda_search_plugin.config.ssl_verify
+
     @mock.patch("eodag.plugins.search.qssearch.requests.get", autospec=True)
     @mock.patch(
         "eodag.plugins.search.qssearch.QueryStringSearch._request", autospec=True
@@ -959,7 +1001,7 @@ class TestSearchPluginODataV4Search(BaseSearchPluginTest):
             products[1].properties["uid"],
         )
         mock_requests_get.assert_called_with(
-            metadata_url, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
+            metadata_url, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT, verify=True
         )
         # we check that two requests have been called, one per product
         self.assertEqual(mock_requests_get.call_count, 2)
@@ -1319,6 +1361,7 @@ class TestSearchPluginBuildPostSearchResult(BaseSearchPluginTest):
             headers=USER_AGENT,
             timeout=HTTP_REQ_TIMEOUT,
             auth=self.search_plugin.auth,
+            verify=True,
         )
         self.assertEqual(estimate, 1)
         self.assertIsInstance(products[0], EOProduct)
@@ -1363,6 +1406,7 @@ class TestSearchPluginDataRequestSearch(BaseSearchPluginTest):
             json={"datasetId": "EO:DEM:DAT:COP-DEM_GLO-30-DGED__2022_1"},
             headers=getattr(self.search_plugin.auth, "headers", ""),
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
         keywords = {
             "format": "GeoTiff100mt",
@@ -1385,6 +1429,7 @@ class TestSearchPluginDataRequestSearch(BaseSearchPluginTest):
             },
             headers=getattr(self.search_plugin.auth, "headers", ""),
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
 
     @mock.patch("eodag.plugins.search.data_request_search.requests.get", autospec=True)
@@ -1395,6 +1440,7 @@ class TestSearchPluginDataRequestSearch(BaseSearchPluginTest):
             self.search_plugin.config.status_url + "123",
             headers=getattr(self.search_plugin.auth, "headers", ""),
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
         assert successful
         mock_requests_get.return_value = MockResponse(
@@ -1412,8 +1458,24 @@ class TestSearchPluginDataRequestSearch(BaseSearchPluginTest):
             ),
             headers=getattr(self.search_plugin.auth, "headers", ""),
             timeout=HTTP_REQ_TIMEOUT,
+            verify=True,
         )
 
+    @mock.patch("eodag.plugins.search.data_request_search.requests.get", autospec=True)
+    def test_plugins_get_result_data_ssl_verify_false(self, mock_requests_get):
+        self.search_plugin.config.ssl_verify = False
+        self.search_plugin._get_result_data("123", items_per_page=5, page=1)
+        mock_requests_get.assert_called_with(
+            self.search_plugin.config.result_url.format(
+                jobId="123", items_per_page=5, page=0
+            ),
+            headers=getattr(self.search_plugin.auth, "headers", ""),
+            timeout=HTTP_REQ_TIMEOUT,
+            verify=False,
+        )
+
+        del self.search_plugin.config.ssl_verify
+
     def test_plugins_search_datareq_distinct_product_type_mtd_mapping(self):
         """The metadata mapping for data_request_search should not mix specific product-types metadata-mapping"""
         geojson_geometry = self.search_criteria_s2_msi_l1c["geometry"].__geo_interface__
diff --git a/tests/units/test_utils.py b/tests/units/test_utils.py
index df75f956..3e596b23 100644
--- a/tests/units/test_utils.py
+++ b/tests/units/test_utils.py
@@ -19,6 +19,7 @@
 import copy
 import logging
 import os
+import ssl
 import sys
 import unittest
 from contextlib import closing
@@ -33,6 +34,7 @@ from tests.context import (
     deepcopy,
     flatten_top_directories,
     get_bucket_name_and_prefix,
+    get_ssl_context,
     get_timestamp,
     merge_mappings,
     path_to_uri,
@@ -92,6 +94,16 @@ class TestUtils(unittest.TestCase):
         with self.assertRaises(ValueError):
             uri_to_path("not_a_uri")
 
+    def test_ssl_context(self):
+
+        ssl_ctx = get_ssl_context(False)
+        self.assertEqual(ssl_ctx.verify_mode, ssl.CERT_NONE)
+        self.assertEqual(ssl_ctx.check_hostname, False)
+
+        ssl_ctx = get_ssl_context(True)
+        self.assertEqual(ssl_ctx.verify_mode, ssl.CERT_REQUIRED)
+        self.assertEqual(ssl_ctx.check_hostname, True)
+
     def test_path_to_uri(self):
         if sys.platform == "win32":
             self.assertEqual(path_to_uri(r"C:\tmp\file.txt"), "file:///C:/tmp/file.txt")