def test_callback(mock_session_interface, mock_uuid4, mock_post, kc_config): mock_session_interface.return_value = MagicMock() mock_session_interface.return_value.open_session.return_value = { "state": "state123" } mock_uuid4.return_value = MagicMock() mock_uuid4.return_value.hex.return_value = "0123456789" mock_post.return_value = MagicMock() mock_post.return_value.json.return_value = {"access_token": "token123"} token_endpoint_payload = { "code": "code123", "grant_type": GrantTypes.authorization_code, "redirect_uri": "http://localhost/kc/callback", "client_id": kc_config.client.client_id, "client_secret": kc_config.client.client_secret, } userinfo_endpoint_header = auth_header("token123") expected_calls = [ call(kc_config.openid.token_endpoint, data=token_endpoint_payload), call().raise_for_status(), call().json(), call(kc_config.openid.userinfo_endpoint, headers=userinfo_endpoint_header), call().raise_for_status(), call().json(), ] app = get_app() client = app.test_client() response = client.get("http://testserver/kc/callback?state=state123&code=code123") assert response.status_code == 302 mock_post.assert_has_calls(expected_calls)
def ticket(self, resources: List = [], access_token: str = None) -> Dict: """ retrieve permission ticket from keycloak server see `docs <https://www.keycloak.org/docs/latest/authorization_services/#_overview_terminology_permission_ticket>`__ for more details >>> >>> from keycloak import Client >>> kc = Client() >>> kc.ticket(kc.resources) {'ticket': 'eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5MmE5NTI2NC0wNTAyLTQzN2ItYWE3ZS01ZGIwNjFlYzMwOWYifQ.eyJwZXJtaXNzaW9ucyI6W3sicnNpZCI6IjQ4MTUyMTI2LWFhOTEtNGE0Zi04ZWU4LTczOTI4ZjViNmMwMCJ9XSwianRpIjoiNDhlZmVmZDQtMDg5NC00NjA2LTk0YjUtMDhlNjZiNTE4YWM2LTE1ODQxODUyOTUyMzkiLCJleHAiOjE1ODQxODUzNTUsIm5iZiI6MCwiaWF0IjoxNTg0MTg1Mjk1LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwic3ViIjoiNGM5YzI0MzAtYjJlNy00ZjBiLTkzMjUtYWE4MWRmZmUwNDYzIiwiYXpwIjoia2V5Y2xvYWstY2xpZW50In0.Or_6wzK9wlQMBPpi8bWIioWCeO6QuolKjr4mKC4YWpA'} >>> :param resources: list of resources :param access_token: access token to be used :returns: dictionary """ access_token = access_token or self.access_token # type: ignore resources = resources or self.resources # type: ignore headers = auth_header(access_token, TokenType.bearer) payload = [{ "resource_id": x["_id"], "resource_scopes": x["resource_scopes"] } for x in resources] log.debug("Retrieving permission ticket from keycloak") response = httpx.post(config.uma2.permission_endpoint, json=payload, headers=headers) response.raise_for_status() log.debug("Permission ticket retrieved successfully") return response.json()
def test_kc_callback(mock_request, mock_post, kc_config): mock_request.return_value = MagicMock() mock_request.return_value.method = "GET" mock_request.return_value.session = {"state": "state123"} mock_request.return_value.query_params = { "state": "state123", "code": "code123" } mock_request.return_value.url = MagicMock() mock_request.return_value.url.path = "/kc/callback" mock_post.return_value = MagicMock() mock_post.return_value.json.return_value = {"access_token": "token12345"} payload = { "code": "code123", "grant_type": GrantTypes.authorization_code, "client_id": kc_config.client.client_id, "client_secret": kc_config.client.client_secret, "redirect_uri": "http://localhost/kc/callback", } headers = auth_header("token12345") expected_calls = [ call(kc_config.openid.token_endpoint, data=payload), call().raise_for_status(), call().json(), call(kc_config.openid.userinfo_endpoint, headers=headers), call().raise_for_status(), call().json(), ] with TestClient(app) as client: response = client.get("/kc/callback?state=state123&code=code123", allow_redirects=False) assert response.status_code == 307 assert response.headers["Location"] == "/" mock_post.assert_has_calls(expected_calls)
def rpt(self, access_token: str) -> Dict: """ retrieve request party token (RPT) see `docs <https://www.keycloak.org/docs/latest/authorization_services/#_service_rpt_overview>`__ for more details >>> >>> form keycloak import Client >>> kc = Client(username='******', password='******') >>> kc.rpt(kc.access_token) 2020-08-03 11:47:54,568 [DEBUG] Retrieving RPT from keycloak 2020-08-03 11:47:54,581 [DEBUG] RPT retrieved successfully 2020-08-03 11:47:54,581 [DEBUG] Retrieving JWKs from keycloak server 2020-08-03 11:47:54,587 [DEBUG] JWKs retrieved successfully {'exp': 1596435534, 'iat': 1596435474, 'jti': '822c7f6f-cd0a-4b9d-b55c-72803755ca7c', 'iss': 'http://localhost:8080/auth/realms/master', 'aud': ['kc', 'account'], 'sub': 'fce7f440-2e40-4161-90b4-61a1913c4b28', 'typ': 'Bearer', 'azp': 'kc', 'session_state': 'b3498962-3973-4f2d-9150-b1338e974d08', 'acr': '1', 'realm_access': {'roles': ['offline_access', 'uma_authorization']}, 'resource_access': {'account': {'roles': ['manage-account', 'manage-account-links', 'view-profile']}}, 'authorization': {'permissions': [{'rsid': '992dfa45-6098-45ac-b62e-da4d2787377f', 'rsname': 'Default Resource'}]}, 'scope': 'profile email', 'email_verified': True, 'preferred_username': '******'} >>> :param access_token: access token to be used :returns: dictionary """ payload = { "grant_type": GrantTypes.uma_ticket, "audience": config.client.client_id, } headers = auth_header(access_token, TokenType.bearer) log.debug("Retrieving RPT from keycloak") response = httpx.post(config.uma2.token_endpoint, data=payload, headers=headers) response.raise_for_status() log.debug("RPT retrieved successfully") return response.json()
def test_kc_userinfo(mock_post, kc_client, kc_config): mock_post.return_value.json = MagicMock() token = "token123456789" headers = auth_header(token) kc_client.userinfo(token) mock_post.assert_called_once_with(kc_config.openid.userinfo_endpoint, headers=headers) mock_post.return_value.json.assert_called_once()
def test_kc_userinfo_failure(mock_post, kc_client, kc_config): mock_post.return_value = MagicMock() mock_post.return_value.content = "server error" mock_post.return_value.raise_for_status = MagicMock(side_effect=HTTPError) token = "token123456789" headers = auth_header(token) with pytest.raises(HTTPError) as ex: kc_client.fetch_userinfo(token) assert ex.type == HTTPError mock_post.assert_called_once_with(kc_config.openid.userinfo_endpoint, headers=headers)
def test_kc_logout(mock_post, kc_client, kc_config): kc_client.logout("access-token", "refresh-token") payload = { "client_id": kc_config.client.client_id, "client_secret": kc_config.client.client_secret, "refresh_token": "refresh-token", } headers = auth_header("access-token") mock_post.assert_called_once_with(kc_config.openid.end_session_endpoint, data=payload, headers=headers)
async def logout(self, access_token: str = None, refresh_token: str = None) -> None: access_token = access_token or await self.access_token # type: ignore refresh_token = refresh_token or await self.refresh_token # type: ignore payload = { "client_id": config.client.client_id, "client_secret": config.client.client_secret, "refresh_token": refresh_token, } headers = auth_header(access_token) log.debug("Logging out user from server") async with httpx.AsyncClient() as client: await client.post( config.openid.end_session_endpoint, data=payload, headers=headers ) log.debug("User logged out successfully")
def logout(self, access_token: str = None, refresh_token: str = None) -> None: access_token = access_token or self.access_token # type: ignore refresh_token = refresh_token or self.refresh_token # type: ignore payload = { "client_id": config.client.client_id, "client_secret": config.client.client_secret, "refresh_token": refresh_token, } headers = auth_header(access_token) log.debug("Logging out user from server") response = httpx.post(config.openid.end_session_endpoint, data=payload, headers=headers) response.raise_for_status() log.debug("User logged out successfully")
async def find_resources(self, access_token: str = None) -> List: """ fetch resources from keycloak server >>> import asyncio >>> from keycloak import AsyncClient >>> kc= AsyncClient() >>> asyncio.run(await kc.find_resources()) [{'name': 'default Resource', 'type': 'urn:python-client:resources:default', 'owner': {'id': 'd74cc555-d46c-4ef8-8a30-ceb2b91d8823'}, 'ownerManagedAccess': False, 'attributes': {}, '_id': 'bb6a777f-a17b-4555-b035-a6ce12a1fd21', 'uris': ['/*'], 'resource_scopes': []}] >>> :param access_token: access token to be used :returns: list """ access_token = access_token or await self.access_token # type: ignore headers = auth_header(access_token) log.debug("Retrieving resources from keycloak") async with httpx.AsyncClient() as client: response = await client.get(config.uma2.resource_endpoint, headers=headers) log.debug("Resources retrieved successfully") return [await self.find_resource(x, access_token) for x in response.json()] # type: ignore
def fetch_userinfo(self, access_token: str = None) -> Dict: """ method to retrieve userinfo from the keycloak server >>> >>> from keycloak import Client >>> kc = Client() >>> kc.fetch_userinfo() {'sub': '4c9c2430-b2e7-4f0b-9325-aa81dffe0463', 'email_verified': False, 'preferred_username': '******'} >>> :param access_token: access token of the client or user :returns: dicttionary """ access_token = access_token or self.access_token # type: ignore headers = auth_header(access_token) log.debug("Retrieving user info from server") response = httpx.post(config.openid.userinfo_endpoint, headers=headers) response.raise_for_status() log.debug("User info retrieved successfully") return response.json()
async def find_resource(self, resource_id: str, access_token: str = None) -> Dict: """ Method to fetch the details of a resource >>> import asyncio >>> from keycloak import AsyncClient >>> kc= AsyncClient() >>> asyncio.run(await kc.find_resource('bb6a777f-a17b-4555-b035-a6ce12a1fd21')) {'name': 'default Resource', 'type': 'urn:python-client:resources:default', 'owner': {'id': 'd74cc555-d46c-4ef8-8a30-ceb2b91d8823'}, 'ownerManagedAccess': False, 'attributes': {}, '_id': 'bb6a777f-a17b-4555-b035-a6ce12a1fd21', 'uris': ['/*'], 'resource_scopes': []} >>> :param resource_id: id of the resource :param access_token: access token to be used :returns: list """ access_token = access_token or await self.access_token # type: ignore headers = auth_header(access_token) endpoint = f"{config.uma2.resource_endpoint}/{resource_id}" log.debug("Retrieving resource from keycloak") async with httpx.AsyncClient() as client: response = await client.get(endpoint, headers=headers) log.debug("Resource retrieved successfully") return response.json()