|
|
@ -21,6 +21,12 @@ |
|
|
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
|
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
|
|
|
|
"""Keycloak OpenID module. |
|
|
|
|
|
|
|
The module contains mainly the implementation of KeycloakOpenID class, the main |
|
|
|
class to handle authentication and token manipulation. |
|
|
|
""" |
|
|
|
|
|
|
|
import json |
|
|
|
|
|
|
|
from jose import jwt |
|
|
@ -52,8 +58,7 @@ from .urls_patterns import ( |
|
|
|
|
|
|
|
|
|
|
|
class KeycloakOpenID: |
|
|
|
""" |
|
|
|
Keycloak OpenID client. |
|
|
|
"""Keycloak OpenID client. |
|
|
|
|
|
|
|
:param server_url: Keycloak server url |
|
|
|
:param client_id: client id |
|
|
@ -75,6 +80,7 @@ class KeycloakOpenID: |
|
|
|
proxies=None, |
|
|
|
timeout=60, |
|
|
|
): |
|
|
|
"""Init method.""" |
|
|
|
self.client_id = client_id |
|
|
|
self.client_secret_key = client_secret_key |
|
|
|
self.realm_name = realm_name |
|
|
@ -87,6 +93,7 @@ class KeycloakOpenID: |
|
|
|
|
|
|
|
@property |
|
|
|
def client_id(self): |
|
|
|
"""Get client id.""" |
|
|
|
return self._client_id |
|
|
|
|
|
|
|
@client_id.setter |
|
|
@ -95,6 +102,7 @@ class KeycloakOpenID: |
|
|
|
|
|
|
|
@property |
|
|
|
def client_secret_key(self): |
|
|
|
"""Get the client secret key.""" |
|
|
|
return self._client_secret_key |
|
|
|
|
|
|
|
@client_secret_key.setter |
|
|
@ -103,6 +111,7 @@ class KeycloakOpenID: |
|
|
|
|
|
|
|
@property |
|
|
|
def realm_name(self): |
|
|
|
"""Get the realm name.""" |
|
|
|
return self._realm_name |
|
|
|
|
|
|
|
@realm_name.setter |
|
|
@ -111,6 +120,7 @@ class KeycloakOpenID: |
|
|
|
|
|
|
|
@property |
|
|
|
def connection(self): |
|
|
|
"""Get connection.""" |
|
|
|
return self._connection |
|
|
|
|
|
|
|
@connection.setter |
|
|
@ -119,6 +129,7 @@ class KeycloakOpenID: |
|
|
|
|
|
|
|
@property |
|
|
|
def authorization(self): |
|
|
|
"""Get authorization.""" |
|
|
|
return self._authorization |
|
|
|
|
|
|
|
@authorization.setter |
|
|
@ -126,8 +137,7 @@ class KeycloakOpenID: |
|
|
|
self._authorization = value |
|
|
|
|
|
|
|
def _add_secret_key(self, payload): |
|
|
|
""" |
|
|
|
Add secret key if exist. |
|
|
|
"""Add secret key if exists. |
|
|
|
|
|
|
|
:param payload: |
|
|
|
:return: |
|
|
@ -138,7 +148,7 @@ class KeycloakOpenID: |
|
|
|
return payload |
|
|
|
|
|
|
|
def _build_name_role(self, role): |
|
|
|
""" |
|
|
|
"""Build name of a role. |
|
|
|
|
|
|
|
:param role: |
|
|
|
:return: |
|
|
@ -146,7 +156,7 @@ class KeycloakOpenID: |
|
|
|
return self.client_id + "/" + role |
|
|
|
|
|
|
|
def _token_info(self, token, method_token_info, **kwargs): |
|
|
|
""" |
|
|
|
"""Getter for the token data. |
|
|
|
|
|
|
|
:param token: |
|
|
|
:param method_token_info: |
|
|
@ -161,19 +171,20 @@ class KeycloakOpenID: |
|
|
|
return token_info |
|
|
|
|
|
|
|
def well_known(self): |
|
|
|
"""The most important endpoint to understand is the well-known configuration |
|
|
|
"""Get the well_known object. |
|
|
|
|
|
|
|
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)) |
|
|
|
return raise_error_from_response(data_raw, KeycloakGetError) |
|
|
|
|
|
|
|
def auth_url(self, redirect_uri): |
|
|
|
""" |
|
|
|
"""Get the authentication URL endpoint. |
|
|
|
|
|
|
|
http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint |
|
|
|
|
|
|
@ -196,7 +207,8 @@ class KeycloakOpenID: |
|
|
|
totp=None, |
|
|
|
**extra |
|
|
|
): |
|
|
|
""" |
|
|
|
"""Retrieve user token. |
|
|
|
|
|
|
|
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 |
|
|
@ -232,7 +244,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError) |
|
|
|
|
|
|
|
def refresh_token(self, refresh_token, grant_type=["refresh_token"]): |
|
|
|
""" |
|
|
|
"""Refresh the user token. |
|
|
|
|
|
|
|
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 |
|
|
@ -255,7 +268,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError) |
|
|
|
|
|
|
|
def exchange_token(self, token: str, client_id: str, audience: str, subject: str) -> dict: |
|
|
|
""" |
|
|
|
"""Exchange user token. |
|
|
|
|
|
|
|
Use a token to obtain an entirely different token. See |
|
|
|
https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange |
|
|
|
|
|
|
@ -279,7 +293,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError) |
|
|
|
|
|
|
|
def userinfo(self, token): |
|
|
|
""" |
|
|
|
"""Get the user info object. |
|
|
|
|
|
|
|
The userinfo endpoint returns standard claims about the authenticated user, |
|
|
|
and is protected by a bearer token. |
|
|
|
|
|
|
@ -294,8 +309,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakGetError) |
|
|
|
|
|
|
|
def logout(self, refresh_token): |
|
|
|
""" |
|
|
|
The logout endpoint logs out the authenticated user. |
|
|
|
"""Log out the authenticated user. |
|
|
|
|
|
|
|
:param refresh_token: |
|
|
|
:return: |
|
|
|
""" |
|
|
@ -306,7 +321,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) |
|
|
|
|
|
|
|
def certs(self): |
|
|
|
""" |
|
|
|
"""Get certificates. |
|
|
|
|
|
|
|
The certificate endpoint returns the public keys enabled by the realm, encoded as a |
|
|
|
JSON Web Key (JWK). Depending on the realm settings there can be one or more keys enabled |
|
|
|
for verifying tokens. |
|
|
@ -320,7 +336,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakGetError) |
|
|
|
|
|
|
|
def public_key(self): |
|
|
|
""" |
|
|
|
"""Retrieve the public key. |
|
|
|
|
|
|
|
The public key is exposed by the realm page directly. |
|
|
|
|
|
|
|
:return: |
|
|
@ -330,7 +347,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakGetError)["public_key"] |
|
|
|
|
|
|
|
def entitlement(self, token, resource_server_id): |
|
|
|
""" |
|
|
|
"""Get entitlements from the token. |
|
|
|
|
|
|
|
Client applications can use a specific endpoint to obtain a special security token |
|
|
|
called a requesting party token (RPT). This token consists of all the entitlements |
|
|
|
(or permissions) for a user as a result of the evaluation of the permissions and |
|
|
@ -349,7 +367,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakGetError) # pragma: no cover |
|
|
|
|
|
|
|
def introspect(self, token, rpt=None, token_type_hint=None): |
|
|
|
""" |
|
|
|
"""Introspect the user token. |
|
|
|
|
|
|
|
The introspection endpoint is used to retrieve the active state of a token. |
|
|
|
It is can only be invoked by confidential clients. |
|
|
|
|
|
|
@ -377,7 +396,8 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError) |
|
|
|
|
|
|
|
def decode_token(self, token, key, algorithms=["RS256"], **kwargs): |
|
|
|
""" |
|
|
|
"""Decode user token. |
|
|
|
|
|
|
|
A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data |
|
|
|
structure that represents a cryptographic key. This specification |
|
|
|
also defines a JWK Set JSON data structure that represents a set of |
|
|
@ -395,8 +415,7 @@ class KeycloakOpenID: |
|
|
|
return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs) |
|
|
|
|
|
|
|
def load_authorization_config(self, path): |
|
|
|
""" |
|
|
|
Load Keycloak settings (authorization) |
|
|
|
"""Load Keycloak settings (authorization). |
|
|
|
|
|
|
|
:param path: settings file (json) |
|
|
|
:return: |
|
|
@ -407,8 +426,7 @@ class KeycloakOpenID: |
|
|
|
self.authorization.load_config(authorization_json) |
|
|
|
|
|
|
|
def get_policies(self, token, method_token_info="introspect", **kwargs): |
|
|
|
""" |
|
|
|
Get policies by user token |
|
|
|
"""Get policies by user token. |
|
|
|
|
|
|
|
:param token: user token |
|
|
|
:return: policies list |
|
|
@ -438,8 +456,7 @@ class KeycloakOpenID: |
|
|
|
return list(set(policies)) |
|
|
|
|
|
|
|
def get_permissions(self, token, method_token_info="introspect", **kwargs): |
|
|
|
""" |
|
|
|
Get permission by user token |
|
|
|
"""Get permission by user token. |
|
|
|
|
|
|
|
:param token: user token |
|
|
|
:param method_token_info: Decode token method |
|
|
@ -471,8 +488,7 @@ class KeycloakOpenID: |
|
|
|
return list(set(permissions)) |
|
|
|
|
|
|
|
def uma_permissions(self, token, permissions=""): |
|
|
|
""" |
|
|
|
Get UMA permissions by user token with requested permissions |
|
|
|
"""Get UMA permissions by user token with requested permissions. |
|
|
|
|
|
|
|
The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be |
|
|
|
invoked by confidential clients. |
|
|
@ -499,8 +515,7 @@ class KeycloakOpenID: |
|
|
|
return raise_error_from_response(data_raw, KeycloakPostError) |
|
|
|
|
|
|
|
def has_uma_access(self, token, permissions): |
|
|
|
""" |
|
|
|
Determine whether user has uma permissions with specified user token |
|
|
|
"""Determine whether user has uma permissions with specified user token. |
|
|
|
|
|
|
|
:param token: user token |
|
|
|
:param permissions: list of uma permissions (resource:scope) |
|
|
|