diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b757b..4916c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,3 +46,4 @@ All notable changes to this project will be documented in this file. ## [master] * Renamed `KeycloakOpenID.well_know` to `KeycloakOpenID.well_known` + * Add `KeycloakOpenID.token_exchange` to support Token Exchange diff --git a/README.md b/README.md index 85e3d34..d3572f5 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,9 @@ config_well_known = keycloak_openid.well_known() token = keycloak_openid.token("user", "password") token = keycloak_openid.token("user", "password", totp="012345") +# Get token using Token Exchange +token = keycloak_openid.exchange_token(token['access_token'], "my_client", "other_client", "some_user") + # Get Userinfo userinfo = keycloak_openid.userinfo(token['access_token']) diff --git a/docs/source/conf.py b/docs/source/conf.py index 2b67d12..b60a1c9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -77,7 +77,7 @@ release = "0.0.0" # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/src/keycloak/keycloak_openid.py b/src/keycloak/keycloak_openid.py index e73e963..a1f1f0a 100644 --- a/src/keycloak/keycloak_openid.py +++ b/src/keycloak/keycloak_openid.py @@ -254,6 +254,30 @@ class KeycloakOpenID: data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) return raise_error_from_response(data_raw, KeycloakGetError) + def exchange_token(self, token: str, client_id: str, audience: str, subject: str) -> dict: + """ + Use a token to obtain an entirely different token. See + https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange + + :param token: + :param client_id: + :param audience: + :param subject: + :return: + """ + params_path = {"realm-name": self.realm_name} + payload = { + "grant_type": ["urn:ietf:params:oauth:grant-type:token-exchange"], + "client_id": client_id, + "subject_token": token, + "requested_token_type": "urn:ietf:params:oauth:token-type:refresh_token", + "audience": audience, + "requested_subject": subject, + } + payload = self._add_secret_key(payload) + data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) + return raise_error_from_response(data_raw, KeycloakGetError) + def userinfo(self, token): """ The userinfo endpoint returns standard claims about the authenticated user,