diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e603ad..8439d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ This is the changelog for [Authress SDK](readme.md). ## 3.1 ## * [Breaking] Throw validation error on setting a property that doesn't exist in any of the Authress DTO Models. +* Optimize JWKs fetching using the keyId ## 3.0 ## * [Breaking] Added type checking everywhere - This means most models have breaking changes. diff --git a/authress/__init__.py b/authress/__init__.py index 66b4fcb..53649c6 100644 --- a/authress/__init__.py +++ b/authress/__init__.py @@ -9,6 +9,7 @@ from authress.authress_client import AuthressClient from authress.http_client import HttpClient from authress.rest import ApiException +from authress.utils.service_client_token_provider import ServiceClientTokenProvider # import apis into sdk package from authress.api.access_records_api import AccessRecordsApi diff --git a/authress/api/access_records_api.py b/authress/api/access_records_api.py index 1a5df0e..3a23bf5 100644 --- a/authress/api/access_records_api.py +++ b/authress/api/access_records_api.py @@ -37,7 +37,6 @@ from authress.models.access_request_response import AccessRequestResponse from authress.models.claim_request import ClaimRequest -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -53,8 +52,6 @@ class AccessRecordsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/accounts_api.py b/authress/api/accounts_api.py index d74d467..3f00420 100644 --- a/authress/api/accounts_api.py +++ b/authress/api/accounts_api.py @@ -37,7 +37,6 @@ from authress.models.identity_collection import IdentityCollection from authress.models.identity_request import IdentityRequest -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -53,8 +52,6 @@ class AccountsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/applications_api.py b/authress/api/applications_api.py index f737b2d..27a3c90 100644 --- a/authress/api/applications_api.py +++ b/authress/api/applications_api.py @@ -30,7 +30,6 @@ from authress.models.application_delegation import ApplicationDelegation -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -46,8 +45,6 @@ class ApplicationsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/connections_api.py b/authress/api/connections_api.py index 9530553..ee4395d 100644 --- a/authress/api/connections_api.py +++ b/authress/api/connections_api.py @@ -32,7 +32,6 @@ from authress.models.connection_collection import ConnectionCollection from authress.models.user_connection_credentials import UserConnectionCredentials -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -48,8 +47,6 @@ class ConnectionsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/extensions_api.py b/authress/api/extensions_api.py index ea73cf7..98d2271 100644 --- a/authress/api/extensions_api.py +++ b/authress/api/extensions_api.py @@ -36,7 +36,6 @@ from authress.models.o_auth_token_request import OAuthTokenRequest from authress.models.o_auth_token_response import OAuthTokenResponse -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -52,8 +51,6 @@ class ExtensionsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/groups_api.py b/authress/api/groups_api.py index 4fe9b78..ea4487a 100644 --- a/authress/api/groups_api.py +++ b/authress/api/groups_api.py @@ -33,7 +33,6 @@ from authress.models.group import Group from authress.models.group_collection import GroupCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -49,8 +48,6 @@ class GroupsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/invites_api.py b/authress/api/invites_api.py index 0fce3fa..b0bf77d 100644 --- a/authress/api/invites_api.py +++ b/authress/api/invites_api.py @@ -31,7 +31,6 @@ from authress.models.account import Account from authress.models.invite import Invite -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -47,8 +46,6 @@ class InvitesApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/resource_permissions_api.py b/authress/api/resource_permissions_api.py index 5a5b5de..847bb97 100644 --- a/authress/api/resource_permissions_api.py +++ b/authress/api/resource_permissions_api.py @@ -34,7 +34,6 @@ from authress.models.permissioned_resource_collection import PermissionedResourceCollection from authress.models.resource_users_collection import ResourceUsersCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -50,8 +49,6 @@ class ResourcePermissionsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/roles_api.py b/authress/api/roles_api.py index 33defee..31a98bc 100644 --- a/authress/api/roles_api.py +++ b/authress/api/roles_api.py @@ -28,7 +28,6 @@ from authress.models.role import Role from authress.models.role_collection import RoleCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -44,8 +43,6 @@ class RolesApi(object): """ def __init__(self, api_client=None) -> None: - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/service_clients_api.py b/authress/api/service_clients_api.py index 616a24a..4798c9f 100644 --- a/authress/api/service_clients_api.py +++ b/authress/api/service_clients_api.py @@ -34,7 +34,6 @@ from authress.models.client_access_key import ClientAccessKey from authress.models.client_collection import ClientCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -50,8 +49,6 @@ class ServiceClientsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/tenants_api.py b/authress/api/tenants_api.py index 3ebeb9a..3bcdc92 100644 --- a/authress/api/tenants_api.py +++ b/authress/api/tenants_api.py @@ -27,7 +27,6 @@ from authress.models.tenant_collection import TenantCollection from authress.models.tenant_user import TenantUser -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -43,8 +42,6 @@ class TenantsApi: """ def __init__(self, api_client=None) -> None: - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/token_verifier.py b/authress/api/token_verifier.py index 7a7e82b..b60f145 100644 --- a/authress/api/token_verifier.py +++ b/authress/api/token_verifier.py @@ -11,7 +11,11 @@ from authress.utils import service_client_token_provider, PackageVersionProvider class TokenVerifier(object): - def __init__(self): + def __init__(self, http_client=None): + self.http_client = http_client + if http_client is None: + self.http_client = rest.RESTClientObject() + self.keyMap = dict() self.package_version_provider = PackageVersionProvider() @@ -72,14 +76,12 @@ def get_public_key(self, jwkKeyListUrl, kid): self.keyMap[hashKey] = self.get_key_uncached(jwkKeyListUrl, kid) return self.keyMap[hashKey] - def get_key_uncached(self, jwkKeyListUrl, kid): - rest_client = rest.RESTClientObject() - + def get_key_uncached(self, jwkKeyListUrl, kid): version = self.package_version_provider.get_version() headers = { 'User-Agent': f'Authress SDK; Python; {version};' } - result = rest_client.get_request(jwkKeyListUrl, headers=headers) + result = self.http_client.request_with_retries('GET', jwkKeyListUrl, headers=headers) for index, key in enumerate(json.loads(result.data)['keys']): if key['kid'] == kid: diff --git a/authress/api/user_permissions_api.py b/authress/api/user_permissions_api.py index 9e79fda..a811caf 100644 --- a/authress/api/user_permissions_api.py +++ b/authress/api/user_permissions_api.py @@ -34,7 +34,6 @@ from authress.models.user_resources_collection import UserResourcesCollection from authress.models.user_role_collection import UserRoleCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -50,8 +49,6 @@ class UserPermissionsApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/api/users_api.py b/authress/api/users_api.py index de1fc9a..5ca254c 100644 --- a/authress/api/users_api.py +++ b/authress/api/users_api.py @@ -34,7 +34,6 @@ from authress.models.user_identity import UserIdentity from authress.models.user_identity_collection import UserIdentityCollection -from authress.http_client import HttpClient from authress.api_response import ApiResponse from authress.exceptions import ( # noqa: F401 ApiTypeError, @@ -50,8 +49,6 @@ class UsersApi(object): """ def __init__(self, api_client=None): - if api_client is None: - api_client = HttpClient.get_default() self.api_client = api_client @validate_arguments diff --git a/authress/authress_client.py b/authress/authress_client.py index 50e01c8..ed2f83c 100644 --- a/authress/authress_client.py +++ b/authress/authress_client.py @@ -23,11 +23,12 @@ from authress.api import token_verifier class AuthressClient(object): - def __init__(self, authress_api_url=None, service_client_access_key=None): + def __init__(self, authress_api_url=None, service_client_access_key=None, user_agent=None): self._host = authress_api_url if authress_api_url.startswith('http') else f"https://{authress_api_url}" self._host = re.sub(r'/+$', '', self._host) - self._token_verifier = token_verifier.TokenVerifier() - self._http_client = HttpClient(host=self._host, access_key=service_client_access_key) + + self._http_client = HttpClient(host=self._host, access_key=service_client_access_key, user_agent=user_agent) + self._token_verifier = token_verifier.TokenVerifier(http_client=self._http_client) def set_token(self, token: str): self._http_client.set_token(token) diff --git a/authress/http_client.py b/authress/http_client.py index ab7924b..2d74365 100644 --- a/authress/http_client.py +++ b/authress/http_client.py @@ -48,7 +48,7 @@ class HttpClient(object): } _pool = None - def __init__(self, host=None, access_key=None): + def __init__(self, host=None, access_key=None, user_agent=None): self.host = host if host is not None and host.startswith('http') else f"https://{host}" self.access_key = access_key self.pool_threads = 1 @@ -60,7 +60,7 @@ def __init__(self, host=None, access_key=None): self.service_client_token_provider = service_client_token_provider.ServiceClientTokenProvider(self.access_key, self.host) version = PackageVersionProvider().get_version() - self.default_headers['User-Agent'] = f'Authress SDK; Python; {version};' + self.default_headers['User-Agent'] = f'Authress SDK; Python; {version}; {user_agent or ""}' def set_token(self, token): self.default_headers['Authorization'] = f'Bearer {token.replace("Bearer", "").strip()}' diff --git a/test/test_service_client_token_provider.py b/test/test_service_client_token_provider.py index cbae872..d726842 100644 --- a/test/test_service_client_token_provider.py +++ b/test/test_service_client_token_provider.py @@ -6,7 +6,6 @@ from authress.models import * from authress.utils import ServiceClientTokenProvider, JwtManager from authress import AuthressClient -from authress.http_client import HttpClient import unittest from unittest.mock import patch @@ -27,10 +26,10 @@ def test_get_token(self): access_key = 'eyJrZXlJZCI6ImNjYjFjZGJmLTM0NzYtNGNiNy05Njc1LTVlMzNmYjI5NTNjMyIsInByaXZhdGVLZXkiOiItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRFFidWFBd0V6VkJHZnZcbjBEL2JKSjRHa2JoV2oyRy9lYTF3UUZNeWxiK0ZzTDM1dGJvVHNIUGdvbUtGMDNNQkphWmRBVHhwcnhYa2xvYWNcblFhYkE4eXcyQ2lFbitHNjlMcUFnUVlLSmZzL1psWEo4MVJ0TkR0TkZRUTdPS0xpSGJrU1I1cFU3R0lKNENQZTZcbkxHM3UzUkVUbFFndmlhV2M2bzVOUkRMWTBIbzhya2w0Yk8rZjMycXg2SGV6dzBsUnZOK1I4L24vZUxLbCtGVlpcbk1yYzFkcmh5NDdtVU80ZnJKWW1LSzg2ZGZacVk4RVh2aGpjaFZzdHhSTXdtMlRDbUtWT2xsQWUrandmNTR5Q2NcbnUzcTNsRUxzcnhXTlRsVC9mRHBQQSt6MDh2MFdDa2ozdWo5SEh3REE1VjhZaUtJOEFheWRLSG9nYnA5eHp1amdcbnZwQmVacnRuQWdNQkFBRUNnZ0VBZnRpL0Z1UHcza0tNTG5vQ0lvK3FURDBxZmlOTVRZYnpjamp6YVBtUlVQODZcbjNsa21JUTFsdC9PYkdlNlJNc1dDOVY3bk1Ub0lqTkMrb3lHaEpoUFhlQnU2Q2VVN0g0N2NqRVRSK0hOZ2N2NXNcbmFtUVc5VkpzYU4wcThYUCt1UXoyVmdTS0ZTalpYY3UzVjJucWpVK2tNTktsNUtoVVRhYkJhMnh4dFZsS3l0bjlcbld6QnIzZExLVXBwYzdoZXFaa2diSHE2amZXd3h2Vk56cmhkNUZ1Tm5EeWI2R3QrUXhzYi83dmdhdnRsNmtXM0hcbnU5ZUtGcjJhdER4VHhaTDArRVIxWjVyV21MNzdUK3owQitYVkZJZ05ia2FlSURBZjEzRjBPSzE3YjJ4NmlSSjVcbnJCRTdCc0ZhMGVuOXBjSWN2UUxhbGFMREVOL1d2YWd4dnowSWZ4M1R3UUtCZ1FEbjVMSGhFczBzeWxYVUdrVlFcbmhFU1ZacGh5QzdFRHBZYm44UW5DNkI3ZDFtWkR1d1JuSkMyZXVxT1lNMFlVcWlGT3Q5RW9UMllNQy9jT2pUVTFcbndnMm9GQ0hqWUI4cGptNHVkb3B3MHRHcGRyb1piQVNkOHkzR1RuNitMK25tVGVzNjYxTGN6ekhSekhrTnh5OUJcbkNVNTRzWXhnK1M5bnhSTjdEVERxeTE2M1dRS0JnUURtR2Q1Snl4VnNyS3ZVSmFVbDM5ekZRcTh3cnpTZ0xZVVBcbjF0a1dHYWhIanFvRjYvSnM2YUZnZlYraXl5THk1dm03WEN0dUw2RGtEQW93NnVpS1NiRlhicnVCYW5GSDBnWktcbkRaVmVQcU9mbTVIYWJWalB1VmdPdW5HWHBOMnZ4QTdwNkg0SkMxOVkvUkg0MkY5bHE4aUtkejZXWHJYMjNPRHFcbjQrcHZtdzF3dndLQmdRQ0YyajlHNExobjR6OFptRFJzWG56TUZCVm90eER0UHUyWkVrd0ZJa0UyNFp2VCtxNTJcbjdxNGFramIrRXBLZ09QZlMzVTJ3eSt2bWhqMk1PN3Y4Rk5BWE5jKzkxRzBJYXJ0MHZGMzY4K1dyd09sNDVSM2hcbklrNUl5bVJrV1huVXd5TkZ0akgxWE8rdjN5djg1UDJFdDkrQTBWTnJZa3FYeG0wUk9UTUVSSEdldVFLQmdEZmdcbnNrMkRSc21rU1BuMHhsMGpOdTZrV2Z6ZG4wOENudHlRMVJqNzFCVEVmVitBdzlkVkNQNXdrOGZwd3F2d0VWZEJcbmM3NkhURy8weUlqR2t2LzZFMW5qSngrdlpLRUhUTVd3OU1QMVBERG5TNDBhbnNXYkFkcFp4bm9IN0ZuaHA2bC9cbjd4TnRNcE5lcVgyZnRkTHYyM3hjcHROSFhyTDdRcGRvRDZkWXBQUHJBb0dCQU5CN2QyME5kY1EzaTBmWGJ6dGhcbk1RUFIwK3NEVkViMUZjSUdXbDdPeXNvYy9UZ2prT3NhVDRTL2hXODg1RGR5ZnZHbjdpRmpyMDBPQVVyVjE5NlRcbmFwdDJNS0EvWVdWeG9Ud2kwZCs0UHZ5Mnk3SXBnMk9tcEE0bVliYnBXQ0NPS3dtczlEQ0E4MVVGeEJiMHdUbTdcbjlXVStVbGZMWDAvcGNkSFNEZkExbXVjZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiYXVkaWVuY2UiOiIyMmJiYzUwMi0zYjdhLTRjNTQtOGE2ZS1jMDRhM2NhNGRmNWYuYWNjb3VudHMuYXV0aHJlc3MuaW8iLCJjbGllbnRJZCI6IjIzYjRiY2Q1LWMwYzEtNDYwMi05NGU1LThkYTgyNzNkMGRiMCJ9' - http_client = HttpClient("", access_key) - token1 = http_client._get_client_token() + service_client_token_provider = ServiceClientTokenProvider(access_key, "") + token1 = service_client_token_provider.get_client_token() time.sleep(2) - token2 = http_client._get_client_token() + token2 = service_client_token_provider.get_client_token() assert token1 == token2 pass @@ -42,10 +41,10 @@ def test_get_token_for_eddsa(self): access_key = 'CLIENT.KEY.ACCOUNT.MC4CAQAwBQYDK2VwBCIEIIM7npIckfT431rYzEeF+hCqvHogpOllmVSgINwqQv+g' - http_client = HttpClient("", access_key) - token1 = http_client._get_client_token() + service_client_token_provider = ServiceClientTokenProvider(access_key, "") + token1 = service_client_token_provider.get_client_token() time.sleep(2) - token2 = http_client._get_client_token() + token2 = service_client_token_provider.get_client_token() assert token1 == token2 pass @@ -55,8 +54,8 @@ def test_get_token_without_access_key(self): Ignores access keys that are None """ - http_client = HttpClient("") - token1 = http_client._get_client_token() + service_client_token_provider = ServiceClientTokenProvider("") + token1 = service_client_token_provider.get_client_token() assert token1 == None pass diff --git a/test/test_token_verifier.py b/test/test_token_verifier.py index 3d9e38a..cdc078d 100644 --- a/test/test_token_verifier.py +++ b/test/test_token_verifier.py @@ -29,7 +29,7 @@ def test_get_token_for_eddsa(self): token_verifier_instance.get_key_uncached = mock_get_key_uncached identity = token_verifier_instance.verify_token(authressCustomDomain=f"https://{customDomain}", token=access_key) - mock_get_key_uncached.assert_called_once_with(f"https://{customDomain}/v1/clients/CLIENT/.well-known/openid-configuration/jwks", "KEY") + mock_get_key_uncached.assert_called_once_with(f"https://{customDomain}/v1/clients/CLIENT/.well-known/openid-configuration/jwks?kid=KEY", "KEY") assert identity['iss'] == f'https://{customDomain}/v1/clients/CLIENT' assert identity['sub'] == "CLIENT"