From 3e7f054c5caaa12a6978128f808ded39a87a7a63 Mon Sep 17 00:00:00 2001 From: pehala Date: Wed, 18 Mar 2020 14:53:55 +0100 Subject: [PATCH 1/5] Remove AIO functionality completely - I am not sure about how AIO works to properly rewrite it --- setup.py | 12 -- src/keycloak/aio/__init__.py | 28 --- src/keycloak/aio/abc.py | 28 --- src/keycloak/aio/authz.py | 99 ---------- src/keycloak/aio/client.py | 78 -------- src/keycloak/aio/mixins.py | 31 ---- src/keycloak/aio/openid_connect.py | 14 -- src/keycloak/aio/realm.py | 76 -------- src/keycloak/aio/uma.py | 11 -- src/keycloak/aio/well_known.py | 35 ---- tests/keycloak/aio/__init__.py | 0 tests/keycloak/aio/admin/__init__.py | 0 tests/keycloak/aio/admin/test_roles.py | 96 ---------- tests/keycloak/aio/admin/test_users.py | 50 ------ tests/keycloak/aio/test_admin.py | 22 --- tests/keycloak/aio/test_authz.py | 43 ----- tests/keycloak/aio/test_client.py | 209 ---------------------- tests/keycloak/aio/test_openid_connect.py | 164 ----------------- tests/keycloak/aio/test_realm.py | 135 -------------- tests/keycloak/aio/test_uma.py | 195 -------------------- 20 files changed, 1326 deletions(-) delete mode 100644 src/keycloak/aio/__init__.py delete mode 100644 src/keycloak/aio/abc.py delete mode 100644 src/keycloak/aio/authz.py delete mode 100644 src/keycloak/aio/client.py delete mode 100644 src/keycloak/aio/mixins.py delete mode 100644 src/keycloak/aio/openid_connect.py delete mode 100644 src/keycloak/aio/realm.py delete mode 100644 src/keycloak/aio/uma.py delete mode 100644 src/keycloak/aio/well_known.py delete mode 100644 tests/keycloak/aio/__init__.py delete mode 100644 tests/keycloak/aio/admin/__init__.py delete mode 100644 tests/keycloak/aio/admin/test_roles.py delete mode 100644 tests/keycloak/aio/admin/test_users.py delete mode 100644 tests/keycloak/aio/test_admin.py delete mode 100644 tests/keycloak/aio/test_authz.py delete mode 100644 tests/keycloak/aio/test_client.py delete mode 100644 tests/keycloak/aio/test_openid_connect.py delete mode 100644 tests/keycloak/aio/test_realm.py delete mode 100644 tests/keycloak/aio/test_uma.py diff --git a/setup.py b/setup.py index 6af962a..ef09e6a 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,8 @@ import os -import sys from setuptools import find_packages, setup VERSION = '0.2.3-dev' -AIO_COMPATIBLE = sys.version_info >= (3, 5, 3) with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: README = readme.read() @@ -19,13 +17,6 @@ 'mock>=2.0', ] -if AIO_COMPATIBLE: - TESTS_REQUIRE += [ - 'asynctest', - ] -else: - EXCLUDED_PACKAGES.append('keycloak.aio') - setup( name='python-keycloak-client', version=VERSION, @@ -41,9 +32,6 @@ 'Sphinx==1.4.4', 'sphinx-autobuild==0.6.0', ], - 'aio': [ - 'aiohttp>=3.4.4,<4; python_full_version>="3.5.3"' - ] }, setup_requires=[ 'pytest-runner>=4.0,<5' diff --git a/src/keycloak/aio/__init__.py b/src/keycloak/aio/__init__.py deleted file mode 100644 index a0bcc11..0000000 --- a/src/keycloak/aio/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -try: - import aiohttp # noqa: F401 -except ImportError: - raise ImportWarning('Please install extras_require "aio" ' - 'for using this module') - -from .abc import * # noqa: F403 -from .authz import * # noqa: F403 -from .client import * # noqa: F403 -from .mixins import * # noqa: F403 -from .openid_connect import * # noqa: F403 -from .realm import * # noqa: F403 -from .uma import * # noqa: F403 -from .well_known import * # noqa: F403 -from .. import admin - -__all__ = ( - abc.__all__ # noqa: F405 - + admin.__all__ - + authz.__all__ # noqa: F405 - + client.__all__ # noqa: F405 - + mixins.__all__ # noqa: F405 - + openid_connect.__all__ # noqa: F405 - + realm.__all__ # noqa: F405 - + uma.__all__ # noqa: F405 - + well_known.__all__ # noqa: F405 - + ('admin',) -) diff --git a/src/keycloak/aio/abc.py b/src/keycloak/aio/abc.py deleted file mode 100644 index ff1c6fe..0000000 --- a/src/keycloak/aio/abc.py +++ /dev/null @@ -1,28 +0,0 @@ -__all__ = ( - 'AsyncInit', -) - - -class AsyncInit(object): - async def __async_init__(self): - raise NotImplementedError() - - async def close(self): - pass - - def __enter__(self): - raise TypeError("Use async with instead") - - def __exit__(self, exc_type, exc_val, exc_tb): - # __exit__ should exist in pair with __enter__ but never executed - pass # pragma: no cover - - def __await__(self): - return self.__async_init__().__await__() - - async def __aenter__(self): - await self.__async_init__() - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - await self.close() diff --git a/src/keycloak/aio/authz.py b/src/keycloak/aio/authz.py deleted file mode 100644 index 2c76027..0000000 --- a/src/keycloak/aio/authz.py +++ /dev/null @@ -1,99 +0,0 @@ -from keycloak.aio.mixins import WellKnownMixin -from keycloak.authz import ( - KeycloakAuthz as SyncKeycloakAuthz, - PATH_WELL_KNOWN, - urlencode, -) -from keycloak.exceptions import KeycloakClientError - -__all__ = ( - 'KeycloakAuthz', -) - - -class KeycloakAuthz(WellKnownMixin, SyncKeycloakAuthz): - def get_path_well_known(self): - return PATH_WELL_KNOWN - - async def get_permissions(self, token, resource_scopes_tuples=None, - submit_request=False, ticket=None): - """ - Request permissions for user from keycloak server. - - https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_protection_permission_api_papi - - :param str token: client access token - :param Iterable[Tuple[str, str]] resource_scopes_tuples: - list of tuples (resource, scope) - :param boolean submit_request: submit request if not allowed to access? - :param str ticket: Permissions ticket - rtype: dict - """ - headers = { - "Authorization": "Bearer %s" % token, - 'Content-type': 'application/x-www-form-urlencoded', - } - - data = [ - ('grant_type', 'urn:ietf:params:oauth:grant-type:uma-ticket'), - ('audience', self._client_id), - ('response_include_resource_name', True), - ] - - if resource_scopes_tuples: - for atuple in resource_scopes_tuples: - data.append(('permission', '#'.join(atuple))) - data.append(('submit_request', submit_request)) - elif ticket: - data.append(('ticket', ticket)) - - authz_info = {} - - try: - response = await self._realm.client.post( - self.well_known['token_endpoint'], - data=urlencode(data), - headers=headers, - ) - - error = response.get('error') - if error: - self.logger.warning( - '%s: %s', - error, - response.get('error_description') - ) - else: - token = response.get('refresh_token') - decoded_token = self._decode_token(token.split('.')[1]) - authz_info = decoded_token.get('authorization', {}) - except KeycloakClientError as error: - self.logger.warning(str(error)) - return authz_info - - async def eval_permissions(self, token, resource_scopes_tuples=None, - submit_request=False): - """ - Evaluates if user has permission for all the resource scope - combinations. - - :param str token: client access token - :param Iterable[Tuple[str, str]] resource_scopes_tuples: resource to - access - :param boolean submit_request: submit request if not allowed to access? - rtype: boolean - """ - permissions = await self.get_permissions( - token=token, - resource_scopes_tuples=resource_scopes_tuples, - submit_request=submit_request - ) - - res = [] - for permission in permissions.get('permissions', []): - for scope in permission.get('scopes', []): - ptuple = (permission.get('rsname'), scope) - if ptuple in resource_scopes_tuples: - res.append(ptuple) - - return res == resource_scopes_tuples diff --git a/src/keycloak/aio/client.py b/src/keycloak/aio/client.py deleted file mode 100644 index c1f3588..0000000 --- a/src/keycloak/aio/client.py +++ /dev/null @@ -1,78 +0,0 @@ -import asyncio -from functools import partial -from typing import Any - -import aiohttp - -from keycloak.aio.abc import AsyncInit -from keycloak.client import KeycloakClient as SyncKeycloakClient -from keycloak.exceptions import KeycloakClientError - -__all__ = ( - 'KeycloakClient', -) - - -class KeycloakClient(AsyncInit, SyncKeycloakClient): - _lock = None - _loop = None - _session_factory = None - - def __init__(self, server_url, *, headers, logger=None, loop=None, - session_factory=aiohttp.client.ClientSession, - **session_params): - - super().__init__(server_url, headers=headers, logger=logger) - - self._lock = asyncio.Lock() - self._loop = loop or asyncio.get_event_loop() - - session_params['loop'] = self._loop - session_params['headers'] = self._headers - self._session_factory = partial(session_factory, **session_params) - - @property - def loop(self): - return self._loop - - @property - def session(self): - if not self._session: - raise RuntimeError - return self._session - - async def _handle_response(self, req_ctx) -> Any: - """ - :param aiohttp.client._RequestContextManager req_ctx - :return: - """ - async with req_ctx as response: - try: - response.raise_for_status() - except aiohttp.client.ClientResponseError as cre: - text = await response.text(errors='replace') - self.logger.debug('{cre}; ' - 'Request info: {cre.request_info}; ' - 'Response headers: {cre.headers}; ' - 'Response status: {cre.status}; ' - 'Content: {text}'.format(cre=cre, text=text)) - raise KeycloakClientError(original_exc=cre) - - try: - result = await response.json(content_type=None) - except ValueError: - result = await response.read() - - return result - - async def __async_init__(self) -> 'KeycloakClient': - async with self._lock: - if self._session is None: - self._session = self._session_factory() - await self._session.__aenter__() - return self - - async def close(self) -> None: - if self._session is not None: - await self._session.close() - self._session = None diff --git a/src/keycloak/aio/mixins.py b/src/keycloak/aio/mixins.py deleted file mode 100644 index 6089861..0000000 --- a/src/keycloak/aio/mixins.py +++ /dev/null @@ -1,31 +0,0 @@ -from keycloak.aio.abc import AsyncInit -from keycloak.aio.well_known import KeycloakWellKnown -from keycloak.mixins import WellKnownMixin as SyncWellKnownMixin - -__all__ = ( - 'WellKnownMixin', -) - - -class WellKnownMixin(AsyncInit, SyncWellKnownMixin): - def get_path_well_known(self): - raise NotImplementedError() - - @property - def well_known(self): - if self._well_known is None: - raise RuntimeError - return self._well_known - - async def __async_init__(self) -> 'WellKnownMixin': - async with self._realm._lock: - if self._well_known is None: - p = self.get_path_well_known().format(self._realm.realm_name) - self._well_known = await KeycloakWellKnown( - realm=self._realm, - path=self._realm.client.get_full_url(p) - ) - return self - - async def close(self): - await self._well_known.close() diff --git a/src/keycloak/aio/openid_connect.py b/src/keycloak/aio/openid_connect.py deleted file mode 100644 index ffa7e38..0000000 --- a/src/keycloak/aio/openid_connect.py +++ /dev/null @@ -1,14 +0,0 @@ -from keycloak.aio.mixins import WellKnownMixin -from keycloak.openid_connect import ( - KeycloakOpenidConnect as SyncKeycloakOpenidConnect, - PATH_WELL_KNOWN, -) - -__all__ = ( - 'KeycloakOpenidConnect', -) - - -class KeycloakOpenidConnect(WellKnownMixin, SyncKeycloakOpenidConnect): - def get_path_well_known(self): - return PATH_WELL_KNOWN diff --git a/src/keycloak/aio/realm.py b/src/keycloak/aio/realm.py deleted file mode 100644 index dfb02f0..0000000 --- a/src/keycloak/aio/realm.py +++ /dev/null @@ -1,76 +0,0 @@ -import asyncio - -from keycloak.aio.abc import AsyncInit -from keycloak.aio.authz import KeycloakAuthz -from keycloak.aio.client import KeycloakClient -from keycloak.aio.openid_connect import KeycloakOpenidConnect -from keycloak.aio.uma import KeycloakUMA -from keycloak.realm import KeycloakRealm as SyncKeycloakRealm - -__all__ = ( - 'KeycloakRealm', -) - - -class KeycloakRealm(AsyncInit, SyncKeycloakRealm): - _lock = None - _loop = None - - def __init__(self, *args, loop=None, **kwargs): - self.client_class = kwargs.pop('client_class', KeycloakClient) - super().__init__(*args, **kwargs) - self._lock = asyncio.Lock() - self._loop = loop or asyncio.get_event_loop() - - @property - def client(self): - """ - Get Keycloak client - - :rtype: keycloak.aio.client.KeycloakClient - """ - if self._client is None: - raise RuntimeError - return self._client - - def open_id_connect(self, client_id, client_secret): - """ - Get OpenID Connect client - - :param str client_id: - :param str client_secret: - :rtype: keycloak.aio.openid_connect.KeycloakOpenidConnect - """ - return KeycloakOpenidConnect(realm=self, client_id=client_id, - client_secret=client_secret) - - def authz(self, client_id): - """ - Get async Authz client - - :param str client_id: - :rtype: keycloak.aio.authz.KeycloakAuthz - """ - return KeycloakAuthz(realm=self, client_id=client_id) - - def uma(self): - """ - Get UMA client - :return: keycloak.aio.uma.KeycloakUMA - """ - return KeycloakUMA(realm=self) - - async def __async_init__(self) -> 'KeycloakRealm': - async with self._lock: - if self._client is None: - self._client = await self.client_class( - server_url=self._server_url, - headers=self._headers, - loop=self._loop - ) - return self - - async def close(self): - if self._client is not None: - await self._client.close() - self._client = None diff --git a/src/keycloak/aio/uma.py b/src/keycloak/aio/uma.py deleted file mode 100644 index a4c5a47..0000000 --- a/src/keycloak/aio/uma.py +++ /dev/null @@ -1,11 +0,0 @@ -from keycloak.aio.mixins import WellKnownMixin -from keycloak.uma import KeycloakUMA as SyncKeycloakUMA, PATH_WELL_KNOWN - -__all__ = ( - 'KeycloakUMA', -) - - -class KeycloakUMA(WellKnownMixin, SyncKeycloakUMA): - def get_path_well_known(self): - return PATH_WELL_KNOWN diff --git a/src/keycloak/aio/well_known.py b/src/keycloak/aio/well_known.py deleted file mode 100644 index c8b79f0..0000000 --- a/src/keycloak/aio/well_known.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio - -from keycloak.aio.abc import AsyncInit -from ..well_known import KeycloakWellKnown as SyncKeycloakWellKnown - -__all__ = ( - 'KeycloakWellKnown', -) - - -class KeycloakWellKnown(AsyncInit, SyncKeycloakWellKnown): - _lock = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._lock = asyncio.Lock() - - @property - def contents(self): - if self._contents is None: - raise RuntimeError - return self._contents - - @contents.setter - def contents(self, content): - self._contents = content - - async def __async_init__(self) -> 'KeycloakWellKnown': - async with self._lock: - if self._contents is None: - self._contents = await self._realm.client.get(self._path) - return self - - async def close(self): - pass diff --git a/tests/keycloak/aio/__init__.py b/tests/keycloak/aio/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/keycloak/aio/admin/__init__.py b/tests/keycloak/aio/admin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/keycloak/aio/admin/test_roles.py b/tests/keycloak/aio/admin/test_roles.py deleted file mode 100644 index fd2fea7..0000000 --- a/tests/keycloak/aio/admin/test_roles.py +++ /dev/null @@ -1,96 +0,0 @@ -import asynctest - -try: - import aiohttp -except ImportError: - aiohttp = None -else: - from keycloak.admin import KeycloakAdmin - from keycloak.aio.realm import KeycloakRealm - from keycloak.client import KeycloakClient - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakAdminRolesTestCase(asynctest.TestCase): - def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.realm.client = asynctest.MagicMock(spec_set=KeycloakClient)() - self.realm.client.get = asynctest.CoroutineMock() - self.realm.client.post = asynctest.CoroutineMock() - self.realm.client.put = asynctest.CoroutineMock() - self.realm.client.delete = asynctest.CoroutineMock() - self.realm.realm_name = 'realm-name' - self.client_id = 'client-id' - self.admin = KeycloakAdmin(realm=self.realm) - self.admin.set_token('some-token') - - async def tearDown(self): - await self.realm.close() - - async def test_create(self): - await self.admin \ - .realms.by_name('realm-name') \ - .clients.by_id('#123') \ - .roles.create( - name='my-role-name', - description='my-description', - id='my-id', - client_role='my-client-role', - composite=False, - composites='my-composites', - container_id='my-container-id', - scope_param_required=True - ) - - self.realm.client.get_full_url.assert_called_once_with( - '/auth/admin/realms/realm-name/clients/#123/roles' - ) - self.realm.client.post.assert_awaited_once_with( - url=self.realm.client.get_full_url.return_value, - data='{"clientRole": "my-client-role", ' - '"composite": false, ' - '"composites": "my-composites", ' - '"containerId": "my-container-id", ' - '"description": "my-description", ' - '"id": "my-id", ' - '"name": "my-role-name", ' - '"scopeParamRequired": true}', - headers={ - 'Authorization': 'Bearer some-token', - 'Content-Type': 'application/json' - } - ) - - async def test_update(self): - await self.admin \ - .realms.by_name('realm-name') \ - .clients.by_id('#123') \ - .roles.by_name('role-name') \ - .update( - name='my-role-name', - description='my-description', - id='my-id', - client_role='my-client-role', - composite=False, - composites='my-composites', - container_id='my-container-id', - scope_param_required=True - ) - self.realm.client.get_full_url.assert_called_once_with( - '/auth/admin/realms/realm-name/clients/#123/roles/role-name' - ) - self.realm.client.put.assert_awaited_once_with( - url=self.realm.client.get_full_url.return_value, - data='{"clientRole": "my-client-role", ' - '"composite": false, ' - '"composites": "my-composites", ' - '"containerId": "my-container-id", ' - '"description": "my-description", ' - '"id": "my-id", "name": ' - '"my-role-name", ' - '"scopeParamRequired": true}', - headers={ - 'Authorization': 'Bearer some-token', - 'Content-Type': 'application/json' - } - ) diff --git a/tests/keycloak/aio/admin/test_users.py b/tests/keycloak/aio/admin/test_users.py deleted file mode 100644 index d3c2e66..0000000 --- a/tests/keycloak/aio/admin/test_users.py +++ /dev/null @@ -1,50 +0,0 @@ -import asynctest - -try: - import aiohttp -except ImportError: - aiohttp = None -else: - from keycloak.admin import KeycloakAdmin - from keycloak.client import KeycloakClient - from keycloak.realm import KeycloakRealm - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakAdminUsersTestCase(asynctest.TestCase): - - def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.realm.client = asynctest.MagicMock(spec_set=KeycloakClient)() - self.realm.client.get = asynctest.CoroutineMock() - self.realm.client.post = asynctest.CoroutineMock() - self.realm.client.put = asynctest.CoroutineMock() - self.realm.client.delete = asynctest.CoroutineMock() - self.admin = KeycloakAdmin(realm=self.realm) - self.admin.set_token('some-token') - - async def test_create(self): - await self.admin.realms.by_name('realm-name').users.create( - username='my-username', - credentials={'some': 'value'}, - first_name='my-first-name', - last_name='my-last-name', - email='my-email', - enabled=True - ) - self.realm.client.get_full_url.assert_called_once_with( - '/auth/admin/realms/realm-name/users' - ) - self.realm.client.post.assert_awaited_once_with( - url=self.realm.client.get_full_url.return_value, - data='{"credentials": {"some": "value"}, ' - '"email": "my-email", ' - '"enabled": true, ' - '"firstName": "my-first-name", ' - '"lastName": "my-last-name", ' - '"username": "my-username"}', - headers={ - 'Authorization': 'Bearer some-token', - 'Content-Type': 'application/json' - } - ) diff --git a/tests/keycloak/aio/test_admin.py b/tests/keycloak/aio/test_admin.py deleted file mode 100644 index b1bde71..0000000 --- a/tests/keycloak/aio/test_admin.py +++ /dev/null @@ -1,22 +0,0 @@ -import asynctest - -try: - import aiohttp # noqa: F401 -except ImportError: - aiohttp = None -else: - from keycloak.admin import KeycloakAdmin - from keycloak.admin.realm import Realms - from keycloak.aio.realm import KeycloakRealm - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakAdminTestCase(asynctest.TestCase): - - def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.admin = KeycloakAdmin(realm=self.realm) - - def test_realm(self): - realm = self.admin.realms - self.assertIsInstance(realm, Realms) diff --git a/tests/keycloak/aio/test_authz.py b/tests/keycloak/aio/test_authz.py deleted file mode 100644 index 2138dbf..0000000 --- a/tests/keycloak/aio/test_authz.py +++ /dev/null @@ -1,43 +0,0 @@ -import asynctest - -try: - import aiohttp # noqa: F401 -except ImportError: - aiohttp = None -else: - from keycloak.aio.authz import KeycloakAuthz - from keycloak.aio.client import KeycloakClient - from keycloak.aio.realm import KeycloakRealm - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakAuthzTestCase(asynctest.TestCase): - async def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.realm.client = asynctest.MagicMock(spec_set=KeycloakClient) - self.realm.client.get = asynctest.CoroutineMock() - self.realm.realm_name = 'realm-name' - self.client_id = 'client-id' - self.authz = await KeycloakAuthz(realm=self.realm, - client_id=self.client_id) - - async def tearDown(self): - await self.realm.close() - - def test_well_known_loaded(self): - assert self.realm.client.get_full_url.call_count == 1 - assert self.realm.client.get.await_count == 1 - - async def test_entitlement(self): - result = await self.authz.entitlement(token='some-token') - - self.realm.client.get_full_url.assert_any_call( - 'auth/realms/realm-name/authz/entitlement/client-id' - ) - self.realm.client.get.assert_any_await( - self.realm.client.get_full_url.return_value, - headers={ - 'Authorization': 'Bearer some-token' - } - ) - self.assertEqual(result, self.realm.client.get.return_value) diff --git a/tests/keycloak/aio/test_client.py b/tests/keycloak/aio/test_client.py deleted file mode 100644 index 8ce855e..0000000 --- a/tests/keycloak/aio/test_client.py +++ /dev/null @@ -1,209 +0,0 @@ -import asynctest - -try: - import aiohttp -except ImportError: - aiohttp = None -else: - from keycloak.aio.client import KeycloakClient - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakClientTestCase(asynctest.TestCase): - async def setUp(self): - self.Session_mock_handle = asynctest.patch( - 'keycloak.aio.client.aiohttp.client.ClientSession', - autospec=True - ) - - self.Session_mock = self.Session_mock_handle.start() - self.headers = {'initial': 'header'} - self.server_url = 'https://example.com' - self.client = await KeycloakClient( - server_url=self.server_url, - headers=self.headers, - session_factory=self.Session_mock, - loop=self.loop, - ) - - self.addCleanup(self.Session_mock_handle.stop) - - async def tearDown(self): - await self.client.close() - - def test_default_client_logger_name(self): - """ - Case: Session get requested - Expected: Session object get returned and the same one if called for - the second time - """ - - self.assertEqual(self.client.logger.name, 'KeycloakClient') - - async def test_uninitialized_client(self): - """ - Case: Session get requested - Expected: Session object get returned and the same one if called for - the second time - """ - await self.client.close() - - with self.assertRaises(RuntimeError): - _ = self.client.session # noqa: F841 - - def test_session(self): - """ - Case: Session get requested - Expected: Session object get returned and the same one if called for - the second time - """ - session = self.client.session - self.assertIsInstance(session, aiohttp.ClientSession) - - self.assertEqual(session, self.client.session) - - async def test_close_session(self): - """ - Case: Session get requested - Expected: Session object get returned and the same one if called for - the second time - """ - await self.client.close() - with self.assertRaises(RuntimeError): - _ = self.client.session # noqa: F841 - - self.assertIsNone(self.client._session) - - def test_get_full_url(self): - """ - Case: retrieve a valid url - Expected: The path get added to the base url or to the given url - """ - self.assertEqual( - self.client.get_full_url('/some/path'), - 'https://example.com/some/path' - ) - - self.assertEqual( - self.client.get_full_url('/some/path', 'https://another_url.com'), - 'https://another_url.com/some/path' - ) - - async def test_post(self): - """ - Case: A POST request get executed - Expected: The correct parameters get given to the request library - """ - self.Session_mock.return_value.headers = asynctest.MagicMock() - - self.client._handle_response = asynctest.CoroutineMock() - response = await self.client.post( - url='https://example.com/test', - data={'some': 'data'}, - headers={'some': 'header'}, - extra='param' - ) - - self.Session_mock.return_value.post.assert_called_once_with( - 'https://example.com/test', - data={'some': 'data'}, - headers={'some': 'header'}, - params={'extra': 'param'} - ) - - self.client._handle_response.assert_awaited_once_with( - self.Session_mock.return_value.post.return_value - ) - self.assertEqual(response, self.client._handle_response.return_value) - - async def test_get(self): - """ - Case: A GET request get executed - Expected: The correct parameters get given to the request library - """ - self.Session_mock.return_value.headers = asynctest.CoroutineMock() - - self.client._handle_response = asynctest.CoroutineMock() - response = await self.client.get(url='https://example.com/test', - headers={'some': 'header'}, - extra='param') - - self.Session_mock.return_value.get.assert_called_once_with( - 'https://example.com/test', - headers={'some': 'header'}, - params={'extra': 'param'} - ) - - self.client._handle_response.assert_awaited_once_with( - self.Session_mock.return_value.get.return_value - ) - self.assertEqual(response, self.client._handle_response.return_value) - - async def test_put(self): - """ - Case: A PUT request get executed - Expected: The correct parameters get given to the request library - """ - self.Session_mock.return_value.headers = asynctest.MagicMock() - - self.client._handle_response = asynctest.CoroutineMock() - response = await self.client.put(url='https://example.com/test', - data={'some': 'data'}, - headers={'some': 'header'}, - extra='param') - - self.Session_mock.return_value.put.assert_called_once_with( - 'https://example.com/test', - data={'some': 'data'}, - headers={'some': 'header'}, - params={'extra': 'param'} - ) - - self.client._handle_response.assert_awaited_once_with( - self.Session_mock.return_value.put.return_value - ) - self.assertEqual(response, self.client._handle_response.return_value) - - async def test_delete(self): - """ - Case: A DELETE request get executed - Expected: The correct parameters get given to the request library - """ - self.Session_mock.return_value.headers = asynctest.MagicMock() - self.Session_mock.return_value.delete = asynctest.CoroutineMock() - - response = await self.client.delete(url='https://example.com/test', - headers={'some': 'header'}, - extra='param') - - self.Session_mock.return_value.delete.assert_called_once_with( - 'https://example.com/test', - headers={'some': 'header'}, - extra='param' - ) - self.assertEqual( - response, - self.Session_mock.return_value.delete.return_value - ) - - async def test_handle_response(self): - """ - Case: Response get processed - Expected: Decoded json get returned else raw_response - """ - req_ctx = asynctest.MagicMock() - response = req_ctx.__aenter__.return_value - response.json = asynctest.CoroutineMock() - response.read = asynctest.CoroutineMock() - - processed_response = await self.client._handle_response(req_ctx) - - response.raise_for_status.assert_called_once_with() - response.json.assert_awaited_once_with(content_type=None) - - self.assertEqual(processed_response, await response.json()) - - response.json.side_effect = ValueError - processed_response = await self.client._handle_response(req_ctx) - - self.assertEqual(processed_response, await response.read()) diff --git a/tests/keycloak/aio/test_openid_connect.py b/tests/keycloak/aio/test_openid_connect.py deleted file mode 100644 index 558c571..0000000 --- a/tests/keycloak/aio/test_openid_connect.py +++ /dev/null @@ -1,164 +0,0 @@ -import asynctest - -try: - import aiohttp # noqa: F401 -except ImportError: - aiohttp = None -else: - from keycloak.aio.client import KeycloakClient - from keycloak.aio.openid_connect import KeycloakOpenidConnect - from keycloak.aio.realm import KeycloakRealm - from keycloak.aio.well_known import KeycloakWellKnown - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakOpenidConnectTestCase(asynctest.TestCase): - async def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.realm.client = asynctest.MagicMock(spec_set=KeycloakClient) - self.realm.client.get = asynctest.CoroutineMock() - self.realm.client.post = asynctest.CoroutineMock() - self.realm.client.put = asynctest.CoroutineMock() - self.realm.client.delete = asynctest.CoroutineMock() - - self.client_id = 'client-id' - self.client_secret = 'client-secret' - - self.openid_client = await KeycloakOpenidConnect( - realm=self.realm, - client_id=self.client_id, - client_secret=self.client_secret - ) - self.openid_client.well_known.contents = { - 'end_session_endpoint': 'https://logout', - 'jwks_uri': 'https://certs', - 'userinfo_endpoint': 'https://userinfo', - 'authorization_endpoint': 'https://authorization', - 'token_endpoint': 'https://token' - } - - async def tearDown(self): - await self.realm.close() - - def test_well_known_loaded(self): - assert self.realm.client.get_full_url.call_count == 1 - assert self.realm.client.get.await_count == 1 - - def test_well_known(self): - """ - Case: .well-known is requested - Expected: it's returned and the second time the same is returned - """ - well_known = self.openid_client.well_known - - self.assertIsInstance(well_known, KeycloakWellKnown) - self.assertEqual(well_known, self.openid_client.well_known) - - async def test_logout(self): - result = await self.openid_client.logout(refresh_token='refresh-token') - self.realm.client.post.assert_awaited_once_with( - 'https://logout', - data={ - 'refresh_token': 'refresh-token', - 'client_id': self.client_id, - 'client_secret': self.client_secret - } - ) - self.assertEqual(result, self.realm.client.post.return_value) - - async def test_certs(self): - result = await self.openid_client.certs() - self.realm.client.get('https://certs') - - self.assertEqual(result, self.realm.client.get.return_value) - - async def test_userinfo(self): - result = await self.openid_client.userinfo(token='token') - self.realm.client.get.assert_any_await( - 'https://userinfo', - headers={ - 'Authorization': 'Bearer token' - } - ) - self.assertEqual(result, self.realm.client.get.return_value) - - def test_authorization_url(self): - result = self.openid_client.authorization_url( - redirect_uri='https://redirect-url', - scope='scope other-scope', - state='some-state' - ) - self.assertEqual( - result, - 'https://authorization?client_id=client-id&' - 'redirect_uri=https%3A%2F%2Fredirect-url&' - 'response_type=code&scope=scope+other-scope&' - 'state=some-state' - ) - - async def test_authorization_code(self): - response = await self.openid_client.authorization_code( - code='some-code', - redirect_uri='https://redirect-uri' - ) - self.realm.client.post.assert_awaited_once_with( - 'https://token', - data={ - 'grant_type': 'authorization_code', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'code': 'some-code', - 'redirect_uri': 'https://redirect-uri' - } - ) - self.assertEqual(response, self.realm.client.post.return_value) - - async def test_client_credentials(self): - response = await self.openid_client.client_credentials( - scope='scope another-scope' - ) - self.realm.client.post.assert_awaited_once_with( - 'https://token', - data={ - 'grant_type': 'client_credentials', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'scope': 'scope another-scope' - } - ) - self.assertEqual(response, self.realm.client.post.return_value) - - async def test_refresh_token(self): - response = await self.openid_client.refresh_token( - refresh_token='refresh-token', - scope='scope another-scope', - ) - self.realm.client.post.assert_awaited_once_with( - 'https://token', - data={ - 'grant_type': 'refresh_token', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'scope': 'scope another-scope', - 'refresh_token': 'refresh-token' - } - ) - self.assertEqual(response, self.realm.client.post.return_value) - - async def test_token_exchange(self): - response = await self.openid_client.token_exchange( - subject_token='some-token', - audience='some-audience' - ) - self.realm.client.post.assert_awaited_once_with( - 'https://token', - data={ - 'grant_type': 'urn:ietf:params:oauth:grant-type:token-' - 'exchange', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'subject_token': 'some-token', - 'audience': 'some-audience' - } - ) - self.assertEqual(response, self.realm.client.post.return_value) diff --git a/tests/keycloak/aio/test_realm.py b/tests/keycloak/aio/test_realm.py deleted file mode 100644 index 73e06c4..0000000 --- a/tests/keycloak/aio/test_realm.py +++ /dev/null @@ -1,135 +0,0 @@ -import asynctest - -try: - import aiohttp # noqa: F401 -except ImportError: - aiohttp = None -else: - from keycloak.admin import KeycloakAdmin - from keycloak.aio.authz import KeycloakAuthz - from keycloak.aio.client import KeycloakClient - from keycloak.aio.openid_connect import KeycloakOpenidConnect - from keycloak.aio.uma import KeycloakUMA - from keycloak.aio.realm import KeycloakRealm - - -async def wraps_awaitable(return_value): - return return_value - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakRealmTestCase(asynctest.TestCase): - - async def setUp(self): - self.mocked_client_patcher = asynctest.patch( - 'keycloak.aio.realm.KeycloakClient', - autospec=True, - ) - - self.mocked_client = self.mocked_client_patcher.start() - - self.mocked_client.return_value = asynctest.CoroutineMock( - return_value=wraps_awaitable( - return_value=self.mocked_client.return_value - ) - )() - - self.realm = await KeycloakRealm( - 'https://example.com', - 'some-realm', - headers={'some': 'header'}, - loop=self.loop, - ) - self.addCleanup(self.mocked_client_patcher.stop) - - def test_instance(self): - """ - Case: Realm is instantiated - Expected: Name and server_url are exposed - """ - self.assertEqual(self.realm.realm_name, 'some-realm') - self.assertEqual(self.realm.server_url, 'https://example.com') - - async def test_client(self): - """ - Case: Client get requested - Expected: Client get returned and the second time the same get returned - """ - - async with self.realm: - client = self.realm.client - - self.assertIsInstance(client, KeycloakClient) - - self.assertEqual(client, self.realm.client) - - self.mocked_client.assert_called_once_with( - server_url='https://example.com', - headers={'some': 'header'}, - loop=self.loop - ) - - async def test_openid_connect(self): - """ - Case: OpenID client get requested - Expected: OpenID client get returned - """ - with asynctest.patch(target='keycloak.aio.realm.KeycloakOpenidConnect', - autospec=True) as mocked_openid_client: - async with self.realm: - openid_client = self.realm.open_id_connect( - client_id='client-id', - client_secret='client-secret' - ) - - self.assertIsInstance(openid_client, KeycloakOpenidConnect) - self.assertEqual( - openid_client, - mocked_openid_client.return_value - ) - mocked_openid_client.assert_called_once_with( - realm=self.realm, - client_id='client-id', - client_secret='client-secret' - ) - - async def test_admin(self): - """ - Case: Admin client get requested - Expected: Admin client get returned - """ - with asynctest.patch('keycloak.realm.KeycloakAdmin', - autospec=True) as mocked_admin_client: - async with self.realm: - admin_client = self.realm.admin - self.assertIsInstance(admin_client, KeycloakAdmin) - mocked_admin_client.assert_called_once_with(realm=self.realm) - - async def test_authz(self): - """ - Case: Authz client get requested - Expected: Authz client get returned - """ - with asynctest.patch('keycloak.aio.realm.KeycloakAuthz', - autospec=True) as mocked_authz_client: - async with self.realm: - authz_client = self.realm.authz(client_id='client-id') - - self.assertIsInstance(authz_client, KeycloakAuthz) - mocked_authz_client.assert_called_once_with( - realm=self.realm, - client_id='client-id' - ) - - async def test_uma(self): - """ - Case: UMA client get requested - Expected: UMA client get returned - """ - with asynctest.patch('keycloak.aio.realm.KeycloakUMA', - autospec=True) as mocked_uma_client: - async with self.realm: - uma_client = self.realm.uma() - - self.assertIsInstance(uma_client, KeycloakUMA) - mocked_uma_client.assert_called_once_with(realm=self.realm) diff --git a/tests/keycloak/aio/test_uma.py b/tests/keycloak/aio/test_uma.py deleted file mode 100644 index 3d063dc..0000000 --- a/tests/keycloak/aio/test_uma.py +++ /dev/null @@ -1,195 +0,0 @@ -import asynctest -try: - import aiohttp # noqa: F401 -except ImportError: - aiohttp = None -else: - from keycloak.aio.uma import KeycloakUMA - from keycloak.aio.realm import KeycloakRealm - from keycloak.aio.well_known import KeycloakWellKnown - - -@asynctest.skipIf(aiohttp is None, 'aiohttp is not installed') -class KeycloakOpenidConnectTestCase(asynctest.TestCase): - async def setUp(self): - self.realm = asynctest.MagicMock(spec_set=KeycloakRealm) - self.realm.client.get = asynctest.CoroutineMock() - self.realm.client.post = asynctest.CoroutineMock() - self.realm.client.put = asynctest.CoroutineMock() - self.realm.client.delete = asynctest.CoroutineMock() - - self.uma_client = await KeycloakUMA(realm=self.realm) - self.uma_client.well_known.contents = { - 'resource_registration_endpoint': 'https://resource_registration', - 'permission_endpoint': 'https://permission', - 'policy_endpoint': 'https://policy', - } - - async def tearDown(self): - await self.uma_client.close() - - def test_well_known(self): - """ - Case: .well-known is requested - Expected: it's returned and the second time the same is returned - """ - well_known = self.uma_client.well_known - - self.assertIsInstance(well_known, KeycloakWellKnown) - self.assertEqual(well_known, self.uma_client.well_known) - - async def test_resource_set_create(self): - result = await self.uma_client.resource_set_create( - token='test-token', - name='test-name', - optional_param='test-optional-param' - ) - - self.realm.client.post.assert_awaited_once_with( - 'https://resource_registration', - data=self.uma_client._get_data( - name='test-name', - optional_param='test-optional-param' - ), - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.post.return_value) - - async def test_resource_set_update(self): - result = await self.uma_client.resource_set_update( - token='test-token', - id='test-id', - name='test-name', - optional_param='test-optional-param' - ) - - self.realm.client.put.assert_awaited_once_with( - 'https://resource_registration/test-id', - data=self.uma_client._get_data( - name='test-name', - optional_param='test-optional-param' - ), - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.put.return_value) - - async def test_resource_set_read(self): - result = await self.uma_client.resource_set_read( - token='test-token', - id='test-id', - ) - - self.realm.client.get.assert_called_with( - 'https://resource_registration/test-id', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.get.return_value) - - async def test_resource_set_delete(self): - result = await self.uma_client.resource_set_delete( - token='test-token', - id='test-id', - ) - - self.realm.client.delete.assert_awaited_once_with( - 'https://resource_registration/test-id', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.delete.return_value) - - async def test_resource_set_list(self): - result = await self.uma_client.resource_set_list( - token='test-token', - name='test-name', - owner='test-owner' - ) - - self.realm.client.get.assert_called_with( - 'https://resource_registration', - name='test-name', - owner='test-owner', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.get.return_value) - - async def test_resource_create_ticket(self): - result = await self.uma_client.resource_create_ticket( - token='test-token', - id='test-id', - scopes=['test-scope'], - optional_param='test-optional-param' - ) - - self.realm.client.post.assert_awaited_once_with( - 'https://permission', - data=self.uma_client._dumps([ - dict( - resource_id='test-id', - resource_scopes=['test-scope'], - optional_param='test-optional-param' - ) - ]), - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.post.return_value) - - async def test_resource_associate_permission(self): - result = await self.uma_client.resource_associate_permission( - token='test-token', - id='test-id', - name='test-name', - scopes=['test-scope'], - optional_param='test-optional-param' - ) - - self.realm.client.post.assert_awaited_once_with( - 'https://policy/test-id', - data=self.uma_client._get_data( - name='test-name', - scopes=['test-scope'], - optional_param='test-optional-param' - ), - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.post.return_value) - - async def test_permission_update(self): - result = await self.uma_client.permission_update( - token='test-token', - id='test-id', - optional_param='test-optional-param' - ) - - self.realm.client.put.assert_awaited_once_with( - 'https://policy/test-id', - data='{"optional_param": "test-optional-param"}', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.put.return_value) - - async def test_permission_delete(self): - result = await self.uma_client.permission_delete( - token='test-token', - id='test-id', - ) - - self.realm.client.delete.assert_awaited_once_with( - 'https://policy/test-id', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.delete.return_value) - - async def test_permission_list(self): - result = await self.uma_client.permission_list( - token='test-token', - name='test-name', - resource='test-resource' - ) - - self.realm.client.get.assert_called_with( - 'https://policy', - name='test-name', - resource='test-resource', - headers=self.uma_client.get_headers('test-token') - ) - self.assertEqual(result, self.realm.client.get.return_value) From 8f1553ff6fea11056c5c0acde4c287e6cb647346 Mon Sep 17 00:00:00 2001 From: pehala Date: Wed, 18 Mar 2020 14:56:23 +0100 Subject: [PATCH 2/5] Add ability to specify custom session to KeyCloakClient - The client is now not responsible for proper closing of the session and does not manage its lifecycle - Fix tests --- src/keycloak/client.py | 21 +++----------- src/keycloak/realm.py | 9 +++--- tests/keycloak/test_client.py | 52 ++++++++++++----------------------- tests/keycloak/test_realm.py | 8 ++++-- 4 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/keycloak/client.py b/src/keycloak/client.py index e46f6f1..5893f2b 100644 --- a/src/keycloak/client.py +++ b/src/keycloak/client.py @@ -17,12 +17,11 @@ class KeycloakClient(object): _session = None _headers = None - def __init__(self, server_url, headers=None, logger=None): + def __init__(self, server_url, session=None, logger=None): """ - :param str server_url: The base URL where the Keycloak server can be + :param str server_url: The base URL where the Keycloak server can be found - :param dict headers: Optional extra headers to send with requests to - the server + :param requests.Session session: Custom requests session to use :param logging.Logger logger: Optional logger for client """ if logger is None: @@ -35,7 +34,7 @@ def __init__(self, server_url, headers=None, logger=None): self.logger = logger self._server_url = server_url - self._headers = headers or {} + self._session = session @property def server_url(self): @@ -52,7 +51,6 @@ def session(self): """ if self._session is None: self._session = requests.Session() - self._session.headers.update(self._headers) return self._session def get_full_url(self, path, server_url=None): @@ -92,14 +90,3 @@ def _handle_response(self, response): return response.json() except ValueError: return response.content - - def close(self): - if self._session is not None: - self._session.close() - self._session = None - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() diff --git a/src/keycloak/realm.py b/src/keycloak/realm.py index 91f5480..e2ede17 100644 --- a/src/keycloak/realm.py +++ b/src/keycloak/realm.py @@ -14,17 +14,16 @@ class KeycloakRealm(object): _headers = None _client = None - def __init__(self, server_url, realm_name, headers=None): + def __init__(self, server_url, realm_name, session=None): """ :param str server_url: The base URL where the Keycloak server can be found :param str realm_name: REALM name - :param dict headers: Optional extra headers to send with requests to - the server + :param requests.Session session: Optional requests session to use """ self._server_url = server_url self._realm_name = realm_name - self._headers = headers + self._session = session @property def client(self): @@ -33,7 +32,7 @@ def client(self): """ if self._client is None: self._client = KeycloakClient(server_url=self._server_url, - headers=self._headers) + session=self._session) return self._client @property diff --git a/tests/keycloak/test_client.py b/tests/keycloak/test_client.py index 8d0a33d..9b5e9fd 100644 --- a/tests/keycloak/test_client.py +++ b/tests/keycloak/test_client.py @@ -1,6 +1,7 @@ from unittest import TestCase import mock +import requests from requests import Session from keycloak.client import KeycloakClient @@ -10,10 +11,12 @@ class KeycloakClientTestCase(TestCase): def setUp(self): self.headers = {'initial': 'header'} + self.session = mock.MagicMock() + self.session.headers.update(self.headers) self.server_url = 'https://example.com' self.client = KeycloakClient(server_url=self.server_url, - headers=self.headers) + session=self.session) def test_default_client_logger_name(self): """ @@ -32,19 +35,8 @@ def test_session(self): """ session = self.client.session - self.assertIsInstance(session, Session) - self.assertEqual(session, self.client.session) - def test_close_session(self): - """ - Case: Session get requested - Expected: Session object get returned and the same one if called for - the second time - """ - self.client.close() - self.assertIsNone(self.client._session) - def test_get_full_url(self): """ Case: retrieve a valid url @@ -58,62 +50,55 @@ def test_get_full_url(self): 'https://another_url.com'), 'https://another_url.com/some/path') - @mock.patch('keycloak.client.requests', autospec=True) - def test_post(self, request_mock): + def test_post(self): """ Case: A POST request get executed Expected: The correct parameters get given to the request library """ - request_mock.Session.return_value.headers = mock.MagicMock() - self.client._handle_response = mock.MagicMock() response = self.client.post(url='https://example.com/test', data={'some': 'data'}, headers={'some': 'header'}, extra='param') - request_mock.Session.return_value.post.assert_called_once_with( + self.session.post.assert_called_once_with( 'https://example.com/test', data={'some': 'data'}, headers={'some': 'header'}, params={'extra': 'param'} ) self.client._handle_response.assert_called_once_with( - request_mock.Session.return_value.post.return_value + self.session.post.return_value ) self.assertEqual(response, self.client._handle_response.return_value) - @mock.patch('keycloak.client.requests', autospec=True) - def test_get(self, request_mock): + def test_get(self): """ Case: A GET request get executed Expected: The correct parameters get given to the request library """ - request_mock.Session.return_value.headers = mock.MagicMock() - self.client._handle_response = mock.MagicMock() response = self.client.get(url='https://example.com/test', headers={'some': 'header'}, extra='param') - request_mock.Session.return_value.get.assert_called_once_with( + self.session.get.assert_called_once_with( 'https://example.com/test', headers={'some': 'header'}, params={'extra': 'param'} ) self.client._handle_response.assert_called_once_with( - request_mock.Session.return_value.get.return_value + self.session.get.return_value ) self.assertEqual(response, self.client._handle_response.return_value) - @mock.patch('keycloak.client.requests', autospec=True) - def test_put(self, request_mock): + def test_put(self): """ Case: A PUT request get executed Expected: The correct parameters get given to the request library """ - request_mock.Session.return_value.headers = mock.MagicMock() + self.session.return_value.headers = mock.MagicMock() self.client._handle_response = mock.MagicMock() response = self.client.put(url='https://example.com/test', @@ -121,7 +106,7 @@ def test_put(self, request_mock): headers={'some': 'header'}, extra='param') - request_mock.Session.return_value.put.assert_called_once_with( + self.session.put.assert_called_once_with( 'https://example.com/test', data={'some': 'data'}, headers={'some': 'header'}, @@ -129,31 +114,28 @@ def test_put(self, request_mock): ) self.client._handle_response.assert_called_once_with( - request_mock.Session.return_value.put.return_value + self.session.put.return_value ) self.assertEqual(response, self.client._handle_response.return_value) - @mock.patch('keycloak.client.requests', autospec=True) - def test_delete(self, request_mock): + def test_delete(self): """ Case: A DELETE request get executed Expected: The correct parameters get given to the request library """ - request_mock.Session.return_value.headers = mock.MagicMock() - self.client._handle_response = mock.MagicMock() response = self.client.delete(url='https://example.com/test', headers={'some': 'header'}, extra='param') - request_mock.Session.return_value.delete.assert_called_once_with( + self.session.delete.assert_called_once_with( 'https://example.com/test', headers={'some': 'header'}, extra='param' ) self.assertEqual(response, - request_mock.Session.return_value.delete.return_value) + self.session.delete.return_value) def test_handle_response(self): """ diff --git a/tests/keycloak/test_realm.py b/tests/keycloak/test_realm.py index 8111c8a..4edf6db 100644 --- a/tests/keycloak/test_realm.py +++ b/tests/keycloak/test_realm.py @@ -1,6 +1,7 @@ from unittest import TestCase import mock +import requests from keycloak.admin import KeycloakAdmin from keycloak.authz import KeycloakAuthz @@ -13,8 +14,11 @@ class KeycloakRealmTestCase(TestCase): def setUp(self): + self.headers = {'initial': 'header'} + self.session = requests.Session() + self.session.headers.update(self.headers) self.realm = KeycloakRealm('https://example.com', 'some-realm', - headers={'some': 'header'}) + session=self.session) def test_instance(self): """ @@ -37,7 +41,7 @@ def test_client(self, mocked_client): self.assertEqual(client, self.realm.client) mocked_client.assert_called_once_with(server_url='https://example.com', - headers={'some': 'header'}) + session=self.session) @mock.patch('keycloak.realm.KeycloakOpenidConnect', autospec=True) def test_openid_connect(self, mocked_openid_client): From f6c868e6a8b250aab78883eb2bbf2932839b35eb Mon Sep 17 00:00:00 2001 From: pehala Date: Wed, 18 Mar 2020 15:08:15 +0100 Subject: [PATCH 3/5] Add GitHub CI - Remove version 2.7 from supported - Fix travis configuration --- .github/workflows/keycloak-tests.yml | 32 ++++++++++++++++++++++++++++ .travis.yml | 18 ++-------------- Pipfile | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/keycloak-tests.yml diff --git a/.github/workflows/keycloak-tests.yml b/.github/workflows/keycloak-tests.yml new file mode 100644 index 0000000..2d16c63 --- /dev/null +++ b/.github/workflows/keycloak-tests.yml @@ -0,0 +1,32 @@ +name: Python KeyCloakClient Tests + +on: + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.5', '3.6', '3.7' ] + name: Python ${{ matrix.python-version }} tests + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -U pip + pip install -U wheel setuptools + pip install flake8 codecov + - name: Flake8 + run: | + flake8 src/keycloak tests/keycloak + - name: Test with pytest + run: | + python setup.py test --addopts "--cov=keycloak" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 117e5ca..246edb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,9 @@ matrix: fast_finish: true python: - - "2.7" - - "3.4" - "3.5" - "3.6" + - "3.7" - "nightly" before_install: @@ -23,7 +22,7 @@ before_install: install: - pip install -e . -env: TEST_OPTS="--cov=keycloak --ignore=tests/keycloak/aio" FLAKE_OPTS="--exclude=src/keycloak/aio,tests/keycloak/aio" +env: TEST_OPTS="--cov=keycloak" script: - flake8 src/keycloak tests/keycloak $FLAKE_OPTS @@ -32,16 +31,3 @@ script: after_success: - codecov -.mixins: - - &aio-mixin - env: TEST_OPTS="--cov=keycloak" FLAKE_OPTS="" - install: pip install -e .[aio] - -jobs: - include: - - python: "3.5" - <<: *aio-mixin - - python: "3.6" - <<: *aio-mixin - - python: "nightly" - <<: *aio-mixin diff --git a/Pipfile b/Pipfile index 2d520c7..551b46d 100644 --- a/Pipfile +++ b/Pipfile @@ -13,4 +13,4 @@ mock = "*" [requires] -python_version = "3.7" +python_version = "3" From 51c3a7d28198ae241ee24a229229c9f328a8b73c Mon Sep 17 00:00:00 2001 From: pehala Date: Wed, 18 Mar 2020 15:35:05 +0100 Subject: [PATCH 4/5] Move src to root dir - update setup.py and Pipfile - fix flake8 warning --- Pipfile | 7 +++++-- {src => keycloak}/__init__.py | 0 {src/keycloak => keycloak}/admin/__init__.py | 0 {src/keycloak => keycloak}/admin/clientroles.py | 5 ++++- {src/keycloak => keycloak}/admin/clients.py | 0 {src/keycloak => keycloak}/admin/groups.py | 0 {src/keycloak => keycloak}/admin/realm.py | 1 - {src/keycloak => keycloak/admin/user}/__init__.py | 0 {src/keycloak => keycloak}/admin/user/usergroup.py | 0 {src/keycloak => keycloak}/admin/user/userroles.py | 0 {src/keycloak => keycloak}/admin/users.py | 0 {src/keycloak => keycloak}/authz.py | 0 {src/keycloak => keycloak}/client.py | 5 +---- {src/keycloak => keycloak}/exceptions.py | 0 {src/keycloak => keycloak}/mixins.py | 0 {src/keycloak => keycloak}/openid_connect.py | 9 ++++----- {src/keycloak => keycloak}/realm.py | 2 +- {src/keycloak => keycloak}/uma.py | 0 {src/keycloak => keycloak}/uma1.py | 0 {src/keycloak => keycloak}/well_known.py | 0 setup.cfg | 2 ++ setup.py | 8 +++----- src/keycloak/admin/user/__init__.py | 0 tests/keycloak/test_client.py | 2 -- 24 files changed, 20 insertions(+), 21 deletions(-) rename {src => keycloak}/__init__.py (100%) rename {src/keycloak => keycloak}/admin/__init__.py (100%) rename {src/keycloak => keycloak}/admin/clientroles.py (92%) rename {src/keycloak => keycloak}/admin/clients.py (100%) rename {src/keycloak => keycloak}/admin/groups.py (100%) rename {src/keycloak => keycloak}/admin/realm.py (99%) rename {src/keycloak => keycloak/admin/user}/__init__.py (100%) rename {src/keycloak => keycloak}/admin/user/usergroup.py (100%) rename {src/keycloak => keycloak}/admin/user/userroles.py (100%) rename {src/keycloak => keycloak}/admin/users.py (100%) rename {src/keycloak => keycloak}/authz.py (100%) rename {src/keycloak => keycloak}/client.py (95%) rename {src/keycloak => keycloak}/exceptions.py (100%) rename {src/keycloak => keycloak}/mixins.py (100%) rename {src/keycloak => keycloak}/openid_connect.py (98%) rename {src/keycloak => keycloak}/realm.py (97%) rename {src/keycloak => keycloak}/uma.py (100%) rename {src/keycloak => keycloak}/uma1.py (100%) rename {src/keycloak => keycloak}/well_known.py (100%) delete mode 100644 src/keycloak/admin/user/__init__.py diff --git a/Pipfile b/Pipfile index 551b46d..38c9bec 100644 --- a/Pipfile +++ b/Pipfile @@ -4,13 +4,16 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +flake8 = "*" pylint = "*" +mock = "*" +pytest = "*" +Sphinx = "*" +sphinx-autobuild = "*" [packages] requests = "*" python-jose = "*" -mock = "*" - [requires] python_version = "3" diff --git a/src/__init__.py b/keycloak/__init__.py similarity index 100% rename from src/__init__.py rename to keycloak/__init__.py diff --git a/src/keycloak/admin/__init__.py b/keycloak/admin/__init__.py similarity index 100% rename from src/keycloak/admin/__init__.py rename to keycloak/admin/__init__.py diff --git a/src/keycloak/admin/clientroles.py b/keycloak/admin/clientroles.py similarity index 92% rename from src/keycloak/admin/clientroles.py rename to keycloak/admin/clientroles.py index 4c23c93..e514dac 100644 --- a/src/keycloak/admin/clientroles.py +++ b/keycloak/admin/clientroles.py @@ -89,5 +89,8 @@ def __init__(self, realm_name, client_id, role_name, client): self._realm_name = realm_name self._role_name = role_name - super(ClientRole, self).__init__(url=self.get_path("single", realm=realm_name, id=client_id, role_name=role_name), + super(ClientRole, self).__init__(url=self.get_path("single", + realm=realm_name, + id=client_id, + role_name=role_name), client=client) diff --git a/src/keycloak/admin/clients.py b/keycloak/admin/clients.py similarity index 100% rename from src/keycloak/admin/clients.py rename to keycloak/admin/clients.py diff --git a/src/keycloak/admin/groups.py b/keycloak/admin/groups.py similarity index 100% rename from src/keycloak/admin/groups.py rename to keycloak/admin/groups.py diff --git a/src/keycloak/admin/realm.py b/keycloak/admin/realm.py similarity index 99% rename from src/keycloak/admin/realm.py rename to keycloak/admin/realm.py index 100a26f..2eab1ee 100644 --- a/src/keycloak/admin/realm.py +++ b/keycloak/admin/realm.py @@ -62,4 +62,3 @@ def users(self): def groups(self): from keycloak.admin.groups import Groups return Groups(realm_name=self._name, client=self._client) - diff --git a/src/keycloak/__init__.py b/keycloak/admin/user/__init__.py similarity index 100% rename from src/keycloak/__init__.py rename to keycloak/admin/user/__init__.py diff --git a/src/keycloak/admin/user/usergroup.py b/keycloak/admin/user/usergroup.py similarity index 100% rename from src/keycloak/admin/user/usergroup.py rename to keycloak/admin/user/usergroup.py diff --git a/src/keycloak/admin/user/userroles.py b/keycloak/admin/user/userroles.py similarity index 100% rename from src/keycloak/admin/user/userroles.py rename to keycloak/admin/user/userroles.py diff --git a/src/keycloak/admin/users.py b/keycloak/admin/users.py similarity index 100% rename from src/keycloak/admin/users.py rename to keycloak/admin/users.py diff --git a/src/keycloak/authz.py b/keycloak/authz.py similarity index 100% rename from src/keycloak/authz.py rename to keycloak/authz.py diff --git a/src/keycloak/client.py b/keycloak/client.py similarity index 95% rename from src/keycloak/client.py rename to keycloak/client.py index 5893f2b..1bdafbe 100644 --- a/src/keycloak/client.py +++ b/keycloak/client.py @@ -4,10 +4,7 @@ from keycloak.exceptions import KeycloakClientError -try: - from urllib.parse import urljoin # noqa: F401 -except ImportError: - from urlparse import urljoin # noqa: F401 +from urllib.parse import urljoin import requests diff --git a/src/keycloak/exceptions.py b/keycloak/exceptions.py similarity index 100% rename from src/keycloak/exceptions.py rename to keycloak/exceptions.py diff --git a/src/keycloak/mixins.py b/keycloak/mixins.py similarity index 100% rename from src/keycloak/mixins.py rename to keycloak/mixins.py diff --git a/src/keycloak/openid_connect.py b/keycloak/openid_connect.py similarity index 98% rename from src/keycloak/openid_connect.py rename to keycloak/openid_connect.py index a6c96bf..8ce382a 100644 --- a/src/keycloak/openid_connect.py +++ b/keycloak/openid_connect.py @@ -181,7 +181,7 @@ def authorization_code(self, code, redirect_uri): :return: Access token response """ token = self._token_request(grant_type='authorization_code', code=code, - redirect_uri=redirect_uri) + redirect_uri=redirect_uri) return Token(token, self) def password_credentials(self, username, password, **kwargs): @@ -189,15 +189,15 @@ def password_credentials(self, username, password, **kwargs): Retrieve access token by 'password credentials' grant. https://tools.ietf.org/html/rfc6749#section-4.3 - +continuation :param str username: The user name to obtain an access token for :param str password: The user's password :rtype: dict :return: Access token response """ token = self._token_request(grant_type='password', - username=username, password=password, - **kwargs) + username=username, password=password, + **kwargs) return Token(token, self) def client_credentials(self, **kwargs): @@ -316,4 +316,3 @@ def is_expired(self): return False except ExpiredSignatureError: return True - diff --git a/src/keycloak/realm.py b/keycloak/realm.py similarity index 97% rename from src/keycloak/realm.py rename to keycloak/realm.py index e2ede17..d47b808 100644 --- a/src/keycloak/realm.py +++ b/keycloak/realm.py @@ -19,7 +19,7 @@ def __init__(self, server_url, realm_name, session=None): :param str server_url: The base URL where the Keycloak server can be found :param str realm_name: REALM name - :param requests.Session session: Optional requests session to use + :param requests.Sessionpipe session: Optional requests session to use """ self._server_url = server_url self._realm_name = realm_name diff --git a/src/keycloak/uma.py b/keycloak/uma.py similarity index 100% rename from src/keycloak/uma.py rename to keycloak/uma.py diff --git a/src/keycloak/uma1.py b/keycloak/uma1.py similarity index 100% rename from src/keycloak/uma1.py rename to keycloak/uma1.py diff --git a/src/keycloak/well_known.py b/keycloak/well_known.py similarity index 100% rename from src/keycloak/well_known.py rename to keycloak/well_known.py diff --git a/setup.cfg b/setup.cfg index dd1590a..bf2eecc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,3 +25,5 @@ values = [aliases] test = pytest +[flake8] +max-line-length = 119 diff --git a/setup.py b/setup.py index ef09e6a..7b956fd 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup -VERSION = '0.2.3-dev' +VERSION = '0.2.4-dev' with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: README = readme.read() @@ -10,7 +10,6 @@ # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) -EXCLUDED_PACKAGES = [] TESTS_REQUIRE = [ 'pytest', 'pytest-cov', @@ -21,8 +20,7 @@ name='python-keycloak-client', version=VERSION, long_description=README, - package_dir={'': 'src'}, - packages=find_packages('src', EXCLUDED_PACKAGES), + packages=find_packages(exclude=("tests", "docs")), extras_require={ 'dev': [ 'bumpversion==0.5.3', @@ -34,7 +32,7 @@ ], }, setup_requires=[ - 'pytest-runner>=4.0,<5' + 'pytest-runner>=4.0' ], install_requires=[ 'requests', diff --git a/src/keycloak/admin/user/__init__.py b/src/keycloak/admin/user/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/keycloak/test_client.py b/tests/keycloak/test_client.py index 9b5e9fd..139c036 100644 --- a/tests/keycloak/test_client.py +++ b/tests/keycloak/test_client.py @@ -1,8 +1,6 @@ from unittest import TestCase import mock -import requests -from requests import Session from keycloak.client import KeycloakClient From 6c9e0ca983d2ea7913c562ce3cc42f49f377705a Mon Sep 17 00:00:00 2001 From: pehala Date: Wed, 18 Mar 2020 15:35:59 +0100 Subject: [PATCH 5/5] Migrate CI to pipenv --- .github/workflows/keycloak-tests.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/keycloak-tests.yml b/.github/workflows/keycloak-tests.yml index 2d16c63..36b0066 100644 --- a/.github/workflows/keycloak-tests.yml +++ b/.github/workflows/keycloak-tests.yml @@ -21,12 +21,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -U pip - pip install -U wheel setuptools - pip install flake8 codecov + pip install -U pipenv + pipenv install --dev - name: Flake8 run: | - flake8 src/keycloak tests/keycloak + pipenv run flake8 src/keycloak tests/keycloak - name: Test with pytest run: | - python setup.py test --addopts "--cov=keycloak" \ No newline at end of file + pipenv run pytest \ No newline at end of file