diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d7785..bba68ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## ?.?.? - Unreleased + +* Removed support for file system credentials caching. + ## 4.0.0 - 2026-01-22 * Added support for Python 3.14. diff --git a/okdata/sdk/auth/auth.py b/okdata/sdk/auth/auth.py index 0285f80..3b4b535 100644 --- a/okdata/sdk/auth/auth.py +++ b/okdata/sdk/auth/auth.py @@ -9,7 +9,6 @@ ) from okdata.sdk.auth.credentials.password_grant import TokenServiceProvider from okdata.sdk.exceptions import ApiAuthenticateError -from okdata.sdk.file_cache import FileCache log = logging.getLogger() @@ -25,7 +24,15 @@ class Authenticate: _expires_at = None _refresh_expires_at = None + # TODO: Remove keyword argument `file_cache` in a later release. def __init__(self, config, token_provider=None, file_cache=None): + if file_cache is not None: + log.warning( + "Keyword argument `file_cache` to " + "`okdata.sdk.auth.auth.Authenticate` is deprecated and will " + "be removed in a later release of okdata-sdk." + ) + self.token_provider = token_provider if not self.token_provider: try: @@ -36,10 +43,6 @@ def __init__(self, config, token_provider=None, file_cache=None): except StopIteration: log.info("No valid auth strategies available") - self.file_cache = file_cache - if not self.file_cache: - self.file_cache = FileCache(config) - def _resolve_token_provider(self, config): # Add more TokenProviders to accept different login methods strategies = [ClientCredentialsProvider, TokenServiceProvider] @@ -69,15 +72,6 @@ def login(self, force=False): if not self.token_provider: return - cached = self.file_cache.read_credentials() - if cached: - self._access_token = cached["access_token"] - self._refresh_token = cached.get("refresh_token") - if expires_at := cached.get("expires_at"): - self._expires_at = datetime.fromisoformat(expires_at) - if refresh_expires_at := cached.get("refresh_expires_at"): - self._refresh_expires_at = datetime.fromisoformat(refresh_expires_at) - if self._access_token and not _is_expired(self._expires_at): log.info("Token not expired, skipping") return @@ -109,7 +103,6 @@ def refresh_access_token(self): ) self._access_token = tokens["access_token"] - self.file_cache.write_credentials(credentials=str(self)) def __repr__(self): return json.dumps( diff --git a/okdata/sdk/file_cache.py b/okdata/sdk/file_cache.py deleted file mode 100644 index a555cf5..0000000 --- a/okdata/sdk/file_cache.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -import logging - -from okdata.sdk.io_utils import write_to_okdata_cache, read_from_okdata_cache - -log = logging.getLogger() - - -class FileCache: - def __init__(self, config): - self.credentials_cache_enabled = config.get("cacheCredentials") - self.env = config.get("env") - - def write_credentials(self, credentials: str): - if self.credentials_cache_enabled: - filename = f"client_credentials-{self.env}.json" - log.debug(f"Writing credentials to cache: {filename}") - write_to_okdata_cache(content=credentials, filename=filename) - else: - log.debug("Skipping write_credentials: cache is not enabled") - - def read_credentials(self): - if self.credentials_cache_enabled: - filename = f"client_credentials-{self.env}.json" - credentials = read_from_okdata_cache(filename=filename) - if credentials: - try: - log.debug(f"Reading credentials from cache: {filename}") - return json.loads(credentials) - except ValueError as ve: - log.debug(f"Could not read credentials from cache: {ve}") - else: - log.debug("Skipping write_credentials: cache is not enabled") - return None diff --git a/okdata/sdk/io_utils.py b/okdata/sdk/io_utils.py index e12e725..22878bd 100644 --- a/okdata/sdk/io_utils.py +++ b/okdata/sdk/io_utils.py @@ -1,23 +1,8 @@ -import os import errno +import os from pathlib import Path -def write_to_okdata_cache(content, filename, failure_count=0): - okdata_cache_path = Path(f"{os.environ['HOME']}/.okdata/cache") - - if failure_count == 2: - print(f"Could not write credentials to {okdata_cache_path}/{filename}") - return - - if okdata_cache_path.exists(): - with open(f"{okdata_cache_path}/{filename}", "w+") as file: - file.write(content) - else: - create_dir(okdata_cache_path) - write_to_okdata_cache(content, filename, failure_count + 1) - - def create_dir(path): try: os.makedirs(path) @@ -26,18 +11,6 @@ def create_dir(path): raise -def read_from_okdata_cache(filename): - okdata_cache_path = Path(f"{os.environ['HOME']}/.okdata/cache") - - try: - file = open(f"{okdata_cache_path}/{filename}", "r") - except IOError: - return None - else: - with file: - return file.read() - - def write_file_content(file_name, path, content): if not Path(path).exists(): create_dir(path) diff --git a/tests/auth/auth_test.py b/tests/auth/auth_test.py index 176db73..717139a 100644 --- a/tests/auth/auth_test.py +++ b/tests/auth/auth_test.py @@ -3,18 +3,14 @@ import re import pytest +from freezegun import freeze_time from okdata.sdk.auth.auth import Authenticate from okdata.sdk.auth.credentials.client_credentials import ClientCredentialsProvider from okdata.sdk.config import Config from okdata.sdk.exceptions import ApiAuthenticateError -from freezegun import freeze_time - from tests.auth.client_credentials_test_utils import ( - expired_time, - from_cache_expired_token, - from_cache_not_expired_token, - not_expired_time, + not_expired_token, utc_now, ) from tests.test_utils import ( @@ -29,33 +25,12 @@ token_endpoint = "https://login.oslo.kommune.no/auth/realms/api-catalog/protocol/openid-connect/token" -@pytest.fixture(scope="function") -def mock_home_dir(monkeypatch, tmp_path): - monkeypatch.setenv("HOME", str(tmp_path)) - - @freeze_time(utc_now) class TestAuthenticate: - def test_authenticate_cache_disabled(self, requests_mock, mock_home_dir): - client_credentials_provider = ClientCredentialsProvider(config) - auth = Authenticate(config=config, token_provider=client_credentials_provider) - - auth.file_cache.credentials_cache_enabled = False - - response = json.dumps(client_credentials_response) - matcher = re.compile(token_endpoint) - requests_mock.register_uri("POST", matcher, text=response, status_code=200) - - auth.login() - assert auth._access_token == client_credentials_response["access_token"] - assert auth._refresh_token == client_credentials_response["refresh_token"] - - def test_authenticat_no_cache(self, requests_mock, mock_home_dir): + def test_authenticate(self, requests_mock): client_credentials_provider = ClientCredentialsProvider(config) auth = Authenticate(config=config, token_provider=client_credentials_provider) - auth.file_cache.credentials_cache_enabled = True - response = json.dumps(client_credentials_response) matcher = re.compile(token_endpoint) requests_mock.register_uri("POST", matcher, text=response, status_code=200) @@ -64,99 +39,19 @@ def test_authenticat_no_cache(self, requests_mock, mock_home_dir): assert auth._access_token == client_credentials_response["access_token"] assert auth._refresh_token == client_credentials_response["refresh_token"] - def test_authenticate_cached_credentials(self, mock_home_dir): - client_credentials_provider = ClientCredentialsProvider(config) - auth = Authenticate(config=config, token_provider=client_credentials_provider) - - auth.file_cache.credentials_cache_enabled = True - cached_credentials = { - "provider": "ClientCredentialsProvider", - "access_token": from_cache_not_expired_token, - "refresh_token": from_cache_not_expired_token, - "expires_at": not_expired_time.isoformat(), - "refresh_expires_at": not_expired_time.isoformat(), - } - - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - auth.login() - assert auth._access_token == cached_credentials["access_token"] - assert auth._refresh_token == cached_credentials["refresh_token"] - - def test_authenticate_refresh_credentials(self, requests_mock, mock_home_dir): + def test_authenticate_refresh_credentials(self, requests_mock): client_credentials_provider = ClientCredentialsProvider(config) auth = Authenticate(config=config, token_provider=client_credentials_provider) - auth.file_cache.credentials_cache_enabled = True - - cached_credentials = { - "provider": "ClientCredentialsProvider", - "access_token": from_cache_not_expired_token, - "refresh_token": from_cache_not_expired_token, - "expires_at": not_expired_time.isoformat(), - "refresh_expires_at": not_expired_time.isoformat(), - } - - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - response = json.dumps(client_credentials_response) matcher = re.compile(token_endpoint) requests_mock.register_uri("POST", matcher, text=response, status_code=200) auth.login() - assert auth._access_token == cached_credentials["access_token"] - assert auth._refresh_token == cached_credentials["refresh_token"] - - def test_authenticate_expired_tokens(self, requests_mock, mock_home_dir): - client_credentials_provider = ClientCredentialsProvider(config) - auth = Authenticate(config=config, token_provider=client_credentials_provider) - - auth.file_cache.credentials_cache_enabled = True - - cached_credentials = { - "provider": "TokenServiceProvider", - "access_token": from_cache_expired_token, - "refresh_token": from_cache_expired_token, - "expires_at": expired_time.isoformat(), - "refresh_expires_at": expired_time.isoformat(), - } + assert auth._access_token == not_expired_token + assert auth._refresh_token == not_expired_token - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - - response = json.dumps(client_credentials_response) - matcher = re.compile(token_endpoint) - requests_mock.register_uri("POST", matcher, text=response, status_code=200) - - auth.login() - print(from_cache_not_expired_token) - print(from_cache_expired_token) - assert auth._access_token == client_credentials_response["access_token"] - assert auth._refresh_token == client_credentials_response["access_token"] - - def test_authenticate_expired_access_token(self, requests_mock, mock_home_dir): - client_credentials_provider = ClientCredentialsProvider(config) - auth = Authenticate(config=config, token_provider=client_credentials_provider) - - auth.file_cache.credentials_cache_enabled = True - - cached_credentials = { - "provider": "TokenServiceProvider", - "access_token": from_cache_expired_token, - "refresh_token": from_cache_not_expired_token, - "expires_at": expired_time.isoformat(), - "refresh_expires_at": not_expired_time.isoformat(), - } - - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - - response = json.dumps(client_credentials_response) - matcher = re.compile(token_endpoint) - requests_mock.register_uri("POST", matcher, text=response, status_code=200) - - auth.login() - assert auth._access_token == from_cache_not_expired_token - assert auth._refresh_token == cached_credentials["refresh_token"] - - def test_authenticate_fail(self, requests_mock, mock_home_dir): + def test_authenticate_fail(self, requests_mock): client_credentials_provider = ClientCredentialsProvider( config, client_id="wrong_id" ) @@ -168,63 +63,18 @@ def test_authenticate_fail(self, requests_mock, mock_home_dir): matcher = re.compile(token_endpoint) requests_mock.register_uri("POST", matcher, text=response, status_code=200) - try: + with pytest.raises(ApiAuthenticateError): auth.login() - except ApiAuthenticateError: - assert True - def test_refresh_inactive_session(self, requests_mock, mock_home_dir): + def test_refresh_no_refresh_token(self, requests_mock): client_credentials_provider = ClientCredentialsProvider(config) auth = Authenticate(config=config, token_provider=client_credentials_provider) - auth.file_cache.credentials_cache_enabled = True - - cached_credentials = { - "provider": "TokenServiceProvider", - "access_token": from_cache_expired_token, - "refresh_token": from_cache_not_expired_token, - "expires_at": expired_time.isoformat(), - "refresh_expires_at": not_expired_time.isoformat(), - } - - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - - error_msg = { - "error": "invalid_grant", - "error_description": "Session not active", - } - refresh_response = {"text": json.dumps(error_msg), "status_code": 400} - login_response = { - "text": json.dumps(client_credentials_response), - "status_code": 200, - } - matcher = re.compile(token_endpoint) - requests_mock.register_uri("POST", matcher, [refresh_response, login_response]) - - auth.login() - - assert auth._access_token == from_cache_not_expired_token - assert auth._refresh_token == cached_credentials["refresh_token"] - - def test_refresh_no_refresh_token(self, requests_mock, mock_home_dir): - client_credentials_provider = ClientCredentialsProvider(config) - auth = Authenticate(config=config, token_provider=client_credentials_provider) - - auth.file_cache.credentials_cache_enabled = True - - cached_credentials = { - "provider": "TokenServiceProvider", - "access_token": from_cache_expired_token, - "expires_at": expired_time.isoformat(), - } - - auth.file_cache.write_credentials(json.dumps(cached_credentials)) - response = json.dumps(client_credentials_response_no_refresh) matcher = re.compile(token_endpoint) requests_mock.register_uri("POST", matcher, text=response, status_code=200) auth.login() - assert auth._access_token == from_cache_not_expired_token + assert auth._access_token == not_expired_token assert auth._refresh_token is None diff --git a/tests/auth/client_credentials_test_utils.py b/tests/auth/client_credentials_test_utils.py index 5a9cce0..c55eca1 100644 --- a/tests/auth/client_credentials_test_utils.py +++ b/tests/auth/client_credentials_test_utils.py @@ -3,25 +3,16 @@ from dateutil import parser utc_now = parser.parse("2019-11-01T10:00:30+00:00") -not_expired_time = parser.parse("2019-11-01T10:00:41+00:00") -expired_time = parser.parse("2019-11-01T10:00:39+00:00") +_not_expired_time = parser.parse("2019-11-01T10:00:41+00:00") +_expired_time = parser.parse("2019-11-01T10:00:39+00:00") -not_expired = {"exp": datetime.timestamp(not_expired_time)} +_not_expired = {"exp": datetime.timestamp(_not_expired_time)} +_expired = {"exp": datetime.timestamp(_expired_time)} -expired = {"exp": datetime.timestamp(expired_time)} - -not_expired_token = jwt.encode(not_expired, "secret", algorithm="HS256") -expired_token = jwt.encode(expired, "secret", algorithm="HS256") +not_expired_token = jwt.encode(_not_expired, "secret", algorithm="HS256") +expired_token = jwt.encode(_expired, "secret", algorithm="HS256") default_test_client_credentials = { "access_token": not_expired_token, "refresh_token": not_expired_token, } - -from_cache_not_expired = {"exp": not_expired_time, "source": "cache"} -from_cache_expired = {"exp": expired_time, "source": "cache"} - -from_cache_not_expired_token = jwt.encode( - from_cache_not_expired, "secret", algorithm="HS256" -) -from_cache_expired_token = jwt.encode(from_cache_expired, "secret", algorithm="HS256") diff --git a/tests/auth/credentials/password_grant_test.py b/tests/auth/credentials/password_grant_test.py index 7560060..7fd9801 100644 --- a/tests/auth/credentials/password_grant_test.py +++ b/tests/auth/credentials/password_grant_test.py @@ -4,7 +4,7 @@ import pytest from okdata.sdk.auth.credentials.password_grant import TokenServiceProvider from okdata.sdk.config import Config -from tests.auth.client_credentials_test_utils import from_cache_not_expired_token +from tests.auth.client_credentials_test_utils import not_expired_token logging.basicConfig(level=logging.INFO) @@ -15,8 +15,8 @@ def token_service_provider(requests_mock): config.config["tokenService"] = "http://localhost/token" response = json.dumps( { - "access_token": from_cache_not_expired_token, - "refresh_token": from_cache_not_expired_token, + "access_token": not_expired_token, + "refresh_token": not_expired_token, } ) requests_mock.register_uri( @@ -27,4 +27,4 @@ def token_service_provider(requests_mock): def test_token_service_get_token(token_service_provider): tokens = token_service_provider.new_token() - assert tokens["access_token"] == from_cache_not_expired_token + assert tokens["access_token"] == not_expired_token diff --git a/tests/data/dataset_test.py b/tests/data/dataset_test.py index c4d1696..da048ce 100644 --- a/tests/data/dataset_test.py +++ b/tests/data/dataset_test.py @@ -7,12 +7,9 @@ from okdata.sdk.auth.auth import Authenticate from okdata.sdk.config import Config from okdata.sdk.data.dataset import Dataset -from okdata.sdk.file_cache import FileCache config = Config() -file_cache = FileCache(config) -file_cache.credentials_cache_enabled = False -auth_default = Authenticate(config, file_cache=file_cache) +auth_default = Authenticate(config) class TestDataset: diff --git a/tests/data/upload_test.py b/tests/data/upload_test.py index 3fbcd0c..13f2c11 100644 --- a/tests/data/upload_test.py +++ b/tests/data/upload_test.py @@ -2,17 +2,13 @@ import re from unittest.mock import patch, mock_open -from okdata.sdk.data.upload import Upload from okdata.sdk.auth.auth import Authenticate from okdata.sdk.config import Config -from okdata.sdk.file_cache import FileCache +from okdata.sdk.data.upload import Upload from tests.auth.client_credentials_test_utils import default_test_client_credentials config = Config() -file_cache = FileCache(config) -file_cache.credentials_cache_enabled = False -auth_default = Authenticate(config, file_cache=file_cache) - +auth_default = Authenticate(config) auth_default.client_credentials = default_test_client_credentials diff --git a/tests/file_cache_test.py b/tests/file_cache_test.py deleted file mode 100644 index 7bec717..0000000 --- a/tests/file_cache_test.py +++ /dev/null @@ -1,23 +0,0 @@ -import json - -from okdata.sdk.file_cache import FileCache -from okdata.sdk.config import Config - -config = Config() - - -def test_write_client_credentials(): - fc = FileCache(config) - fc.credentials_cache_enabled = True - cc = {"access_token": "yo", "refresh_token": "bro"} - fc.write_credentials(json.dumps(cc)) - assert cc == fc.read_credentials() - - -def test_disable_cache(): - fc = FileCache(config) - fc.credentials_cache_enabled = False - cc = {"access_token": "yo-bro", "refresh_token": "zup-dawg"} - fc.write_credentials(json.dumps(cc)) - - assert fc.read_credentials() is None diff --git a/tests/status/test_status.py b/tests/status/test_status.py index cd6fee8..4b0750b 100644 --- a/tests/status/test_status.py +++ b/tests/status/test_status.py @@ -2,15 +2,12 @@ import re from requests.exceptions import HTTPError -from okdata.sdk.status import Status from okdata.sdk.auth.auth import Authenticate from okdata.sdk.config import Config -from okdata.sdk.file_cache import FileCache +from okdata.sdk.status import Status config = Config() -file_cache = FileCache(config) -file_cache.credentials_cache_enabled = False -auth_default = Authenticate(config, file_cache=file_cache) +auth_default = Authenticate(config) get_status_response = [{"trace_event_id": "my-id"}, {"trace_event_id": "my-other-id"}] diff --git a/tests/test_utils.py b/tests/test_utils.py index 2f64edf..c7a79ac 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,4 @@ -from tests.auth.client_credentials_test_utils import from_cache_not_expired_token +from tests.auth.client_credentials_test_utils import not_expired_token well_known_response = { "issuer": "https://localhost/auth/realms/api-catalog", @@ -162,10 +162,10 @@ client_credentials_response = { - "access_token": from_cache_not_expired_token, + "access_token": not_expired_token, "expires_in": 300, "refresh_expires_in": 1800, - "refresh_token": from_cache_not_expired_token, + "refresh_token": not_expired_token, "token_type": "bearer", "not-before-policy": 1563194597, "session_state": "28957acd-9d5d-44f1-9f3f-7900b63fb010", @@ -173,7 +173,7 @@ } client_credentials_response_no_refresh = { - "access_token": from_cache_not_expired_token, + "access_token": not_expired_token, "expires_in": 300, "token_type": "bearer", "not-before-policy": 1563194597,