diff --git a/.gitignore b/.gitignore index b3d340206b2dbe7e2dfa3a583bcc3793b8006962..b44d1332963c68308ef9274c9ab090dcf8ae17a4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ dist/ *venv/ .vscode/ _version.py +*.xml +.coverage +htmlcov diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 741ca8aa049dc553f77b89d803723671c06b86ca..517425cc82790d5a10b8cfa1561c6f3dad1e8c82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,30 +35,46 @@ pylint: - pip install pylint - pylint dinamis_sdk tests - -OAuth2 Tests: +Tests: extends: .tests_base script: - - python tests/test_spot_6_7_drs.py - - python tests/test_super_s2.py - - python tests/test_push.py - - python tests/test_headers.py authorization + - pip install coverage -API key Tests: - extends: .tests_base - script: + - echo "Starting OAuth2 tests" + - coverage run -a tests/test_spot_6_7_drs.py + - coverage run -a tests/test_super_s2.py + - coverage run -a tests/test_push.py + - coverage run -a tests/test_misc.py + - coverage run -a tests/test_headers.py authorization + + - echo "Starting API key tests" - dinamis_cli register - - python tests/test_headers.py access-key + - coverage run -a tests/test_headers.py access-key # ensure that we une only API key from now - mv /root/.config/dinamis_sdk_auth/.jwt /root/.config/dinamis_sdk_auth/.jwt_ - - python tests/test_spot_6_7_drs.py - - python tests/test_super_s2.py - - python tests/test_push.py + - coverage run -a tests/test_misc.py + - coverage run -a tests/test_spot_6_7_drs.py + - coverage run -a tests/test_super_s2.py + - coverage run -a tests/test_push.py # Test API key from environment variables - export DINAMIS_SDK_ACCESS_KEY=$(cat /root/.config/dinamis_sdk_auth/.apikey | cut -d'"' -f4) - export DINAMIS_SDK_SECRET_KEY=$(cat /root/.config/dinamis_sdk_auth/.apikey | cut -d'"' -f8) - rm /root/.config/dinamis_sdk_auth/.apikey # ensure that we use env. vars. - - python tests/test_spot_6_7_drs.py + - coverage run -a tests/test_spot_6_7_drs.py # bring back oauth2 credentials so we can revoke the API key - mv /root/.config/dinamis_sdk_auth/.jwt_ /root/.config/dinamis_sdk_auth/.jwt - dinamis_cli revoke ${DINAMIS_SDK_ACCESS_KEY} + + - coverage report + - coverage xml + - coverage html + coverage: '/^TOTAL.+?(\d+\%)$/' + artifacts: + paths: + - htmlcov/ + when: always + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + diff --git a/dinamis_sdk/__init__.py b/dinamis_sdk/__init__.py index 3d58f795aaa26390f9bc93f49d3293134ecec30a..2d20a647fbe350d940e8d28a2b7bd05fda3a488b 100644 --- a/dinamis_sdk/__init__.py +++ b/dinamis_sdk/__init__.py @@ -14,7 +14,7 @@ from dinamis_sdk.signing import ( ) # noqa from .oauth2 import OAuth2Session # noqa from .upload import push -from .http import get_headers +from .http import get_headers, get_userinfo try: __version__ = version("dinamis_sdk") diff --git a/dinamis_sdk/http.py b/dinamis_sdk/http.py index 37c0ac3c21b4244f2621aca333c231b4c8735494..9ab1160f7e17dd67526f5ba15271b0b812dd30fd 100644 --- a/dinamis_sdk/http.py +++ b/dinamis_sdk/http.py @@ -1,12 +1,12 @@ """HTTP connections with various methods.""" -from typing import Dict +from typing import Dict, Any from ast import literal_eval from pydantic import BaseModel, ConfigDict from .utils import get_logger_for, create_session -from .oauth2 import OAuth2Session +from .oauth2 import OAuth2Session, retrieve_token_endpoint from .model import ApiKey -from .settings import ENV, SIGNING_ENDPOINT +from .settings import ENV log = get_logger_for(__name__) @@ -16,7 +16,7 @@ class BareConnectionMethod(BaseModel): """Bare connection method, no extra headers.""" model_config = ConfigDict(arbitrary_types_allowed=True) - endpoint: str = SIGNING_ENDPOINT + endpoint: str = ENV.dinamis_sdk_signing_endpoint def get_headers(self) -> Dict[str, str]: """Get the headers.""" @@ -40,6 +40,13 @@ class OAuth2ConnectionMethod(BareConnectionMethod): """Return the headers.""" return {"authorization": f"bearer {self.oauth2_session.get_access_token()}"} + def get_userinfo(self): + """Get the userinfo endpoint.""" + openapi_url = retrieve_token_endpoint().replace("/token", "/userinfo") + + _session = create_session() + return _session.get(openapi_url, timeout=10, headers=self.get_headers()).json() + class ApiKeyConnectionMethod(BareConnectionMethod): """API key connection method.""" @@ -50,6 +57,10 @@ class ApiKeyConnectionMethod(BareConnectionMethod): """Return the headers.""" return self.api_key.to_dict() + def get_userinfo(self): + """User info method for API key. Not available.""" + raise NotImplementedError("No userinfo available with API key method.") + class HTTPSession: """HTTP session class.""" @@ -78,9 +89,9 @@ class HTTPSession: def prepare_connection_method(self): """Set the connection method.""" # Custom server without authentication method - if ENV.dinamis_sdk_bypass_auth_api: + if ENV.dinamis_sdk_digning_disable_auth: self._method = BareConnectionMethod( - endpoint=ENV.dinamis_sdk_bypass_auth_api + endpoint=ENV.dinamis_sdk_signing_endpoint ) # API key method @@ -110,6 +121,11 @@ class HTTPSession: session = HTTPSession() -def get_headers(): - """Return the headers.""" +def get_headers() -> dict[str, Any]: + """Return the headers needed to authenticate on the system.""" return session.get_method().get_headers() + + +def get_userinfo() -> dict[str, str]: + """Return userinfo.""" + return OAuth2ConnectionMethod().get_userinfo() diff --git a/dinamis_sdk/oauth2.py b/dinamis_sdk/oauth2.py index 5469da4a861c84a8d60a6a9aae81904a22813c6a..92338cbc7e999c6787fcead6c754fe6399f1389b 100644 --- a/dinamis_sdk/oauth2.py +++ b/dinamis_sdk/oauth2.py @@ -8,14 +8,14 @@ from typing import Dict import qrcode # type: ignore from .utils import create_session, get_logger_for from .model import JWT, DeviceGrantResponse -from .settings import SIGNING_ENDPOINT +from .settings import ENV log = get_logger_for(__name__) def retrieve_token_endpoint(): """Retrieve the token endpoint from the s3 signing endpoint.""" - openapi_url = SIGNING_ENDPOINT + "openapi.json" + openapi_url = ENV.dinamis_sdk_signing_endpoint + "openapi.json" log.debug("Fetching OAuth2 endpoint from openapi url %s", openapi_url) _session = create_session() res = _session.get( @@ -55,6 +55,16 @@ class GrantMethodBase: "scope": "openid offline_access", } + def get_userinfo(self): + """Get the userinfo endpoint.""" + if not self._token_endpoint: + self._token_endpoint = retrieve_token_endpoint() + openapi_url = retrieve_token_endpoint().replace("/token", "/userinfo") + + _session = create_session() + res = _session.get(openapi_url, timeout=10, headers=self.headers) + return res.json() + def refresh_token(self, old_jwt: JWT) -> JWT: """Refresh the token.""" log.debug("Refreshing token") diff --git a/dinamis_sdk/settings.py b/dinamis_sdk/settings.py index 63c722ec40f01a14b34cfee2c966e98a7ad0cc47..be2d4a7708642274730017324e852155b325f392 100644 --- a/dinamis_sdk/settings.py +++ b/dinamis_sdk/settings.py @@ -8,25 +8,34 @@ from .utils import get_logger_for log = get_logger_for(__name__) +# Constants +APP_NAME = "dinamis_sdk_auth" +MAX_URLS = 64 +S3_STORAGE_DOMAIN = "meso.umontpellier.fr" +DEFAULT_SIGNING_ENDPOINT = ( + "https://s3-signing-cdos.apps.okd.crocc.meso.umontpellier.fr/" +) + class Settings(BaseSettings): """Environment variables.""" dinamis_sdk_ttl_margin: NonNegativeInt = 1800 dinamis_sdk_url_duration: NonNegativeInt = 0 - dinamis_sdk_bypass_auth_api: str = "" # Endpoint with no authentication. dinamis_sdk_config_dir: str = "" dinamis_sdk_access_key: str = "" dinamis_sdk_secret_key: str = "" dinamis_sdk_retry_total: PositiveInt = 10 dinamis_sdk_retry_backoff_factor: PositiveFloat = 0.8 + dinamis_sdk_digning_disable_auth: bool = False + dinamis_sdk_signing_endpoint: str = DEFAULT_SIGNING_ENDPOINT + + def model_post_init(self, __context): + """Signing endpoint validation module.""" + if not self.dinamis_sdk_signing_endpoint.endswith("/"): + self.dinamis_sdk_signing_endpoint = self.dinamis_sdk_signing_endpoint + "/" -# Constants -APP_NAME = "dinamis_sdk_auth" -MAX_URLS = 64 -S3_STORAGE_DOMAIN = "meso.umontpellier.fr" -SIGNING_ENDPOINT = "https://s3-signing-cdos.apps.okd.crocc.meso.umontpellier.fr/" ENV = Settings() diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 0000000000000000000000000000000000000000..2adceede88e5fc62b9f91a0cff803f98df005d0d --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,11 @@ +"""Misc test module.""" + +import dinamis_sdk + + +def test_userinfo(): + """Test userinfo method.""" + print(dinamis_sdk.get_userinfo()) + + +test_userinfo() diff --git a/tests/test_spot_6_7_drs.py b/tests/test_spot_6_7_drs.py index 90ff1c387d7a830adc16c528e27fc37ad5e22ca4..e7568c9cb8d7abf32198d5e2d10515a4007776fe 100755 --- a/tests/test_spot_6_7_drs.py +++ b/tests/test_spot_6_7_drs.py @@ -1,11 +1,12 @@ """Spot 6/7 STAC items retrieval test.""" -import requests +import time import pystac_client import dinamis_sdk +start = time.time() api = pystac_client.Client.open( "https://stacapi-cdos.apps.okd.crocc.meso.umontpellier.fr", modifier=dinamis_sdk.sign_inplace, @@ -19,5 +20,9 @@ urls = [item.assets["src_xs"].href for item in res.items()] print(f"{len(urls)} items found") assert len(urls) > 1000 -response = requests.get(urls[0], timeout=10) -assert response.status_code == 200 +assert "Amz" in urls[0] + +print(urls[0]) + +elapsed = time.time() - start +print(f"Took {round(elapsed, 2)} s")