From 33ca73dafe3de2b5f595e6758e9f3ea659ff87c8 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 14 Aug 2017 16:20:18 -0300 Subject: [PATCH] Added token, userinfo and logout. --- keycloak/__init__.py | 83 ++++++++++++++++++++++++++++++++++++--- keycloak/connection.py | 2 +- keycloak/exceptions.py | 4 ++ keycloak/urls_patterns.py | 5 ++- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/keycloak/__init__.py b/keycloak/__init__.py index 0aeb7e6..8e0f14d 100644 --- a/keycloak/__init__.py +++ b/keycloak/__init__.py @@ -5,8 +5,8 @@ import json from keycloak.exceptions import raise_error_from_response, KeycloakGetError +from .urls_patterns import URL_AUTH, URL_TOKEN, URL_USERINFO, URL_WELL_KNOWN, URL_LOGOUT from .connection import ConnectionManager -from .urls_patterns import URL_WELL_KNOWN class Keycloak: @@ -20,16 +20,89 @@ class Keycloak: headers={}, timeout=60) - def get_well_know(self): - params = {"realm-name": self.__realm_name} - data_raw = self.__connection.raw_get(URL_WELL_KNOWN.format(**params)) + def well_know(self): + """ The most important endpoint to understand is the well-known configuration + endpoint. It lists endpoints and other configuration options relevant to + the OpenID Connect implementation in Keycloak. + + :return It lists endpoints and other configuration options relevant. + """ + + params_path = {"realm-name": self.__realm_name} + data_raw = self.__connection.raw_get(URL_WELL_KNOWN.format(**params_path)) raise_error_from_response(data_raw, KeycloakGetError) + return json.loads(data_raw.text) - def auth(self): + def auth_url(self, redirect_uri): """ http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint :return: """ + return NotImplemented + + def token(self, username, password, grant_type=["password",]): + """ + The token endpoint is used to obtain tokens. Tokens can either be obtained by + exchanging an authorization code or by supplying credentials directly depending on + what flow is used. The token endpoint is also used to obtain new access tokens + when they expire. + + http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + + :param username: + :param password: + :param grant_type: + :return: + """ + params_path = {"realm-name": self.__realm_name} + payload = {"username": username, "password": password, + "client_id": self.__client_id, "grant_type": grant_type} + + if self.__client_secret_key: + payload.update({"client_secret": self.__client_secret_key}) + + data_raw = self.__connection.raw_post(URL_TOKEN.format(**params_path), + data=payload) + raise_error_from_response(data_raw, KeycloakGetError) + + return json.loads(data_raw.text) + + def userinfo(self, token): + """ + The userinfo endpoint returns standard claims about the authenticated user, + and is protected by a bearer token. + + http://openid.net/specs/openid-connect-core-1_0.html#UserInfo + + :param token: + :return: + """ + + self.__connection.add_param_headers("Authorization", "Bearer " + token) + params_path = {"realm-name": self.__realm_name} + + data_raw = self.__connection.raw_get(URL_USERINFO.format(**params_path)) + raise_error_from_response(data_raw, KeycloakGetError) + + return json.loads(data_raw.text) + + def logout(self, refresh_token): + """ + The logout endpoint logs out the authenticated user. + :param refresh_token: + :return: + """ + params_path = {"realm-name": self.__realm_name} + payload = {"client_id": self.__client_id, "refresh_token": refresh_token} + + if self.__client_secret_key: + payload.update({"client_secret": self.__client_secret_key}) + + data_raw = self.__connection.raw_post(URL_LOGOUT.format(**params_path), + data=payload) + raise_error_from_response(data_raw, KeycloakGetError, expected_code=204) + + return None diff --git a/keycloak/connection.py b/keycloak/connection.py index e49b9d3..06aa6ef 100644 --- a/keycloak/connection.py +++ b/keycloak/connection.py @@ -3,7 +3,7 @@ """ import requests -from urllib.parse import urljoin +from urllib.parse import urljoin, urlencode from .exceptions import * diff --git a/keycloak/exceptions.py b/keycloak/exceptions.py index 4ea69f6..9d645b1 100644 --- a/keycloak/exceptions.py +++ b/keycloak/exceptions.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +import requests class KeycloakError(Exception): def __init__(self, error_message="", response_code=None, @@ -86,7 +87,10 @@ class KeycloakBlockError(KeycloakOperationError): def raise_error_from_response(response, error, expected_code=200): + if expected_code == response.status_code: + if expected_code == requests.codes.no_content: + return {} return response.json() try: diff --git a/keycloak/urls_patterns.py b/keycloak/urls_patterns.py index 0dabecb..8ffa3e1 100644 --- a/keycloak/urls_patterns.py +++ b/keycloak/urls_patterns.py @@ -1,3 +1,6 @@ URL_WELL_KNOWN = "realms/{realm-name}/.well-known/openid-configuration" -URL_WELL_KNOWN = "realms/{realm-name}/protocol/openid-connect/auth" +URL_AUTH = "realms/{realm-name}/protocol/openid-connect/auth" +URL_TOKEN = "realms/{realm-name}/protocol/openid-connect/token" +URL_USERINFO = "realms/{realm-name}/protocol/openid-connect/userinfo" +URL_LOGOUT = "realms/{realm-name}/protocol/openid-connect/logout"