Browse Source

Merge master

pull/265/head
Marcos Pereira 4 years ago
parent
commit
b1c98ed883
  1. 1
      MANIFEST.in
  2. 18
      README.md
  3. 4
      docs/source/conf.py
  4. 26
      docs/source/index.rst
  5. 5
      keycloak/connection.py
  6. 12
      keycloak/exceptions.py
  7. 762
      keycloak/keycloak_admin.py
  8. 19
      keycloak/keycloak_openid.py
  9. 26
      keycloak/urls_patterns.py
  10. 2
      setup.py

1
MANIFEST.in

@ -0,0 +1 @@
include LICENSE

18
README.md

@ -24,7 +24,7 @@ For review- see https://github.com/marcospereirampj/python-keycloak
python-keycloak depends on: python-keycloak depends on:
* Python 3 * Python 3
* [requests](http://docs.python-requests.org/en/master/)
* [requests](https://requests.readthedocs.io)
* [python-jose](http://python-jose.readthedocs.io/en/latest/) * [python-jose](http://python-jose.readthedocs.io/en/latest/)
### Tests Dependencies ### Tests Dependencies
@ -94,11 +94,11 @@ token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['ac
token_type_hint="requesting_party_token")) token_type_hint="requesting_party_token"))
# Introspect Token # Introspect Token
token_info = keycloak_openid.introspect(token['access_token']))
token_info = keycloak_openid.introspect(token['access_token'])
# Decode Token # Decode Token
KEYCLOAK_PUBLIC_KEY = "secret"
options = {"verify_signature": True, "verify_aud": True, "exp": True}
KEYCLOAK_PUBLIC_KEY = keycloak_openid.public_key()
options = {"verify_signature": True, "verify_aud": True, "verify_exp": True}
token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options) token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options)
# Get permissions by token # Get permissions by token
@ -114,7 +114,9 @@ from keycloak import KeycloakAdmin
keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/", keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/",
username='example-admin', username='example-admin',
password='secret', password='secret',
realm_name="example_realm",
realm_name="master",
user_realm_name="only_if_other_realm_than_master",
client_secret_key="client-secret",
verify=True) verify=True)
# Add user # Add user
@ -139,7 +141,7 @@ count_users = keycloak_admin.users_count()
users = keycloak_admin.get_users({}) users = keycloak_admin.get_users({})
# Get user ID from name # Get user ID from name
user-id-keycloak = keycloak_admin.get_user_id("example@example.com")
user_id_keycloak = keycloak_admin.get_user_id("example@example.com")
# Get User # Get User
user = keycloak_admin.get_user("user-id-keycloak") user = keycloak_admin.get_user("user-id-keycloak")
@ -149,7 +151,7 @@ response = keycloak_admin.update_user(user_id="user-id-keycloak",
payload={'firstName': 'Example Update'}) payload={'firstName': 'Example Update'})
# Update User Password # Update User Password
response = set_user_password(user_id="user-id-keycloak", password="secret", temporary=True)
response = keycloak_admin.set_user_password(user_id="user-id-keycloak", password="secret", temporary=True)
# Delete User # Delete User
response = keycloak_admin.delete_user(user_id="user-id-keycloak") response = keycloak_admin.delete_user(user_id="user-id-keycloak")
@ -174,7 +176,7 @@ server_info = keycloak_admin.get_server_info()
clients = keycloak_admin.get_clients() clients = keycloak_admin.get_clients()
# Get client - id (not client-id) from client by name # Get client - id (not client-id) from client by name
client_id=keycloak_admin.get_client_id("my-client")
client_id = keycloak_admin.get_client_id("my-client")
# Get representation of the client - id of client (not client-id) # Get representation of the client - id of client (not client-id)
client = keycloak_admin.get_client(client_id="client_id") client = keycloak_admin.get_client(client_id="client_id")

4
docs/source/conf.py

@ -60,9 +60,9 @@ author = 'Marcos Pereira'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.17.6'
version = '0.23.0'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.17.6'
release = '0.23.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

26
docs/source/index.rst

@ -132,7 +132,7 @@ Main methods::
# Decode Token # Decode Token
KEYCLOAK_PUBLIC_KEY = "secret" KEYCLOAK_PUBLIC_KEY = "secret"
options = {"verify_signature": True, "verify_aud": True, "exp": True}
options = {"verify_signature": True, "verify_aud": True, "verify_exp": True}
token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options) token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options)
# Get permissions by token # Get permissions by token
@ -158,6 +158,14 @@ Main methods::
# realm_name="example_realm", # realm_name="example_realm",
# verify=True, # verify=True,
# custom_headers={'CustomHeader': 'value'}) # custom_headers={'CustomHeader': 'value'})
#
# You can also authenticate with client_id and client_secret
#keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/",
# client_id="example_client",
# client_secret_key="secret",
# realm_name="example_realm",
# verify=True,
# custom_headers={'CustomHeader': 'value'})
# Add user # Add user
new_user = keycloak_admin.create_user({"email": "example@example.com", new_user = keycloak_admin.create_user({"email": "example@example.com",
@ -268,3 +276,19 @@ Main methods::
# Function to trigger user sync from provider # Function to trigger user sync from provider
sync_users(storage_id="storage_di", action="action") sync_users(storage_id="storage_di", action="action")
# List public RSA keys
components = keycloak_admin.keys
# List all keys
components = keycloak_admin.get_components(query={"parent":"example_realm", "type":"org.keycloak.keys.KeyProvider"})
# Create a new RSA key
component = keycloak_admin.create_component({"name":"rsa-generated","providerId":"rsa-generated","providerType":"org.keycloak.keys.KeyProvider","parentId":"example_realm","config":{"priority":["100"],"enabled":["true"],"active":["true"],"algorithm":["RS256"],"keySize":["2048"]}})
# Update the key
component_details['config']['active'] = ["false"]
keycloak_admin.update_component(component['id'])
# Delete the key
keycloak_admin.delete_component(component['id'])

5
keycloak/connection.py

@ -47,6 +47,7 @@ class ConnectionManager(object):
self._timeout = timeout self._timeout = timeout
self._verify = verify self._verify = verify
self._s = requests.Session() self._s = requests.Session()
self._s.auth = lambda x: x # don't let requests add auth headers
# retry once to reset connection with Keycloak after tomcat's ConnectionTimeout # retry once to reset connection with Keycloak after tomcat's ConnectionTimeout
# see https://github.com/marcospereirampj/python-keycloak/issues/36 # see https://github.com/marcospereirampj/python-keycloak/issues/36
@ -198,11 +199,12 @@ class ConnectionManager(object):
raise KeycloakConnectionError( raise KeycloakConnectionError(
"Can't connect to server (%s)" % e) "Can't connect to server (%s)" % e)
def raw_delete(self, path, **kwargs):
def raw_delete(self, path, data={}, **kwargs):
""" Submit delete request to the path. """ Submit delete request to the path.
:arg :arg
path (str): Path for request. path (str): Path for request.
data (dict): Payload for request.
:return :return
Response the request. Response the request.
:exception :exception
@ -211,6 +213,7 @@ class ConnectionManager(object):
try: try:
return self._s.delete(urljoin(self.base_url, path), return self._s.delete(urljoin(self.base_url, path),
params=kwargs, params=kwargs,
data=data,
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify) verify=self.verify)

12
keycloak/exceptions.py

@ -53,6 +53,9 @@ class KeycloakOperationError(KeycloakError):
pass pass
class KeycloakDeprecationError(KeycloakError):
pass
class KeycloakGetError(KeycloakOperationError): class KeycloakGetError(KeycloakOperationError):
pass pass
@ -73,9 +76,12 @@ class KeycloakInvalidTokenError(KeycloakOperationError):
pass pass
def raise_error_from_response(response, error, expected_code=200, skip_exists=False):
if expected_code == response.status_code:
if expected_code == requests.codes.no_content:
def raise_error_from_response(response, error, expected_codes=None, skip_exists=False):
if expected_codes is None:
expected_codes = [200, 201, 204]
if response.status_code in expected_codes:
if response.status_code == requests.codes.no_content:
return {} return {}
try: try:

762
keycloak/keycloak_admin.py
File diff suppressed because it is too large
View File

19
keycloak/keycloak_openid.py

@ -28,8 +28,9 @@ from jose import jwt
from .authorization import Authorization from .authorization import Authorization
from .connection import ConnectionManager from .connection import ConnectionManager
from .exceptions import raise_error_from_response, KeycloakGetError, \ from .exceptions import raise_error_from_response, KeycloakGetError, \
KeycloakRPTNotFound, KeycloakAuthorizationConfigError, KeycloakInvalidTokenError
KeycloakRPTNotFound, KeycloakAuthorizationConfigError, KeycloakInvalidTokenError, KeycloakDeprecationError
from .urls_patterns import ( from .urls_patterns import (
URL_REALM,
URL_AUTH, URL_AUTH,
URL_TOKEN, URL_TOKEN,
URL_USERINFO, URL_USERINFO,
@ -250,7 +251,7 @@ class KeycloakOpenID:
data_raw = self.connection.raw_post(URL_LOGOUT.format(**params_path), data_raw = self.connection.raw_post(URL_LOGOUT.format(**params_path),
data=payload) data=payload)
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
def certs(self): def certs(self):
""" """
@ -266,6 +267,17 @@ class KeycloakOpenID:
data_raw = self.connection.raw_get(URL_CERTS.format(**params_path)) data_raw = self.connection.raw_get(URL_CERTS.format(**params_path))
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def public_key(self):
"""
The public key is exposed by the realm page directly.
:return:
"""
params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_REALM.format(**params_path))
return raise_error_from_response(data_raw, KeycloakGetError)['public_key']
def entitlement(self, token, resource_server_id): def entitlement(self, token, resource_server_id):
""" """
Client applications can use a specific endpoint to obtain a special security token Client applications can use a specific endpoint to obtain a special security token
@ -280,6 +292,9 @@ class KeycloakOpenID:
params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id} params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id}
data_raw = self.connection.raw_get(URL_ENTITLEMENT.format(**params_path)) data_raw = self.connection.raw_get(URL_ENTITLEMENT.format(**params_path))
if data_raw.status_code == 404:
return raise_error_from_response(data_raw, KeycloakDeprecationError)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def introspect(self, token, rpt=None, token_type_hint=None): def introspect(self, token, rpt=None, token_type_hint=None):

26
keycloak/urls_patterns.py

@ -22,6 +22,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# OPENID URLS # OPENID URLS
URL_REALM = "realms/{realm-name}"
URL_WELL_KNOWN = "realms/{realm-name}/.well-known/openid-configuration" URL_WELL_KNOWN = "realms/{realm-name}/.well-known/openid-configuration"
URL_TOKEN = "realms/{realm-name}/protocol/openid-connect/token" URL_TOKEN = "realms/{realm-name}/protocol/openid-connect/token"
URL_USERINFO = "realms/{realm-name}/protocol/openid-connect/userinfo" URL_USERINFO = "realms/{realm-name}/protocol/openid-connect/userinfo"
@ -42,6 +43,9 @@ URL_ADMIN_RESET_PASSWORD = "admin/realms/{realm-name}/users/{id}/reset-password"
URL_ADMIN_GET_SESSIONS = "admin/realms/{realm-name}/users/{id}/sessions" URL_ADMIN_GET_SESSIONS = "admin/realms/{realm-name}/users/{id}/sessions"
URL_ADMIN_USER_CLIENT_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}" URL_ADMIN_USER_CLIENT_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}"
URL_ADMIN_USER_REALM_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/realm" URL_ADMIN_USER_REALM_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/realm"
URL_ADMIN_GROUPS_REALM_ROLES = "admin/realms/{realm-name}/groups/{id}/role-mappings/realm"
URL_ADMIN_GET_GROUPS_REALM_ROLES = "admin/realms/{realm-name}/groups/{id}/role-mappings"
URL_ADMIN_GROUPS_CLIENT_ROLES = "admin/realms/{realm-name}/groups/{id}/role-mappings/clients/{client-id}"
URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/available" URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/available"
URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/composite" URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/composite"
URL_ADMIN_USER_GROUP = "admin/realms/{realm-name}/users/{id}/groups/{group-id}" URL_ADMIN_USER_GROUP = "admin/realms/{realm-name}/users/{id}/groups/{group-id}"
@ -62,17 +66,39 @@ URL_ADMIN_CLIENT = URL_ADMIN_CLIENTS + "/{id}"
URL_ADMIN_CLIENT_SECRETS= URL_ADMIN_CLIENT + "/client-secret" URL_ADMIN_CLIENT_SECRETS= URL_ADMIN_CLIENT + "/client-secret"
URL_ADMIN_CLIENT_ROLES = URL_ADMIN_CLIENT + "/roles" URL_ADMIN_CLIENT_ROLES = URL_ADMIN_CLIENT + "/roles"
URL_ADMIN_CLIENT_ROLE = URL_ADMIN_CLIENT + "/roles/{role-name}" URL_ADMIN_CLIENT_ROLE = URL_ADMIN_CLIENT + "/roles/{role-name}"
URL_ADMIN_CLIENT_ROLE_MEMBERS = URL_ADMIN_CLIENT + "/roles/{role-name}/users"
URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT + "/authz/resource-server/settings" URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT + "/authz/resource-server/settings"
URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT + "/authz/resource-server/resource" URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT + "/authz/resource-server/resource"
URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER = URL_ADMIN_CLIENT + "/service-account-user"
URL_ADMIN_CLIENT_CERTS = URL_ADMIN_CLIENT + "/certificates/{attr}" URL_ADMIN_CLIENT_CERTS = URL_ADMIN_CLIENT + "/certificates/{attr}"
URL_ADMIN_CLIENT_INSTALLATION_PROVIDER = URL_ADMIN_CLIENT + "/installation/providers/{provider-id}"
URL_ADMIN_CLIENT_PROTOCOL_MAPPER = URL_ADMIN_CLIENT + "/protocol-mappers/models"
URL_ADMIN_CLIENT_SCOPES = "admin/realms/{realm-name}/client-scopes" URL_ADMIN_CLIENT_SCOPES = "admin/realms/{realm-name}/client-scopes"
URL_ADMIN_CLIENT_SCOPE = URL_ADMIN_CLIENT_SCOPES + "/{scope-id}" URL_ADMIN_CLIENT_SCOPE = URL_ADMIN_CLIENT_SCOPES + "/{scope-id}"
URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER = URL_ADMIN_CLIENT_SCOPE + "/protocol-mappers/models" URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER = URL_ADMIN_CLIENT_SCOPE + "/protocol-mappers/models"
URL_ADMIN_CLIENT_SCOPES_MAPPERS = URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER + "/{protocol-mapper-id}"
URL_ADMIN_REALM_ROLES = "admin/realms/{realm-name}/roles" URL_ADMIN_REALM_ROLES = "admin/realms/{realm-name}/roles"
URL_ADMIN_REALM_ROLES_MEMBERS = URL_ADMIN_REALM_ROLES + "/{role-name}/users"
URL_ADMIN_REALMS = "admin/realms" URL_ADMIN_REALMS = "admin/realms"
URL_ADMIN_REALM = "admin/realms/{realm-name}"
URL_ADMIN_IDPS = "admin/realms/{realm-name}/identity-provider/instances" URL_ADMIN_IDPS = "admin/realms/{realm-name}/identity-provider/instances"
URL_ADMIN_IDP_MAPPERS = "admin/realms/{realm-name}/identity-provider/instances/{idp-alias}/mappers"
URL_ADMIN_IDP = "admin/realms//{realm-name}/identity-provider/instances/{alias}"
URL_ADMIN_REALM_ROLES_ROLE_BY_NAME = "admin/realms/{realm-name}/roles/{role-name}"
URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE = "admin/realms/{realm-name}/roles/{role-name}/composites"
URL_ADMIN_FLOWS = "admin/realms/{realm-name}/authentication/flows" URL_ADMIN_FLOWS = "admin/realms/{realm-name}/authentication/flows"
URL_ADMIN_FLOWS_ALIAS = "admin/realms/{realm-name}/authentication/flows/{flow-id}"
URL_ADMIN_FLOWS_COPY = "admin/realms/{realm-name}/authentication/flows/{flow-alias}/copy"
URL_ADMIN_FLOWS_EXECUTIONS = "admin/realms/{realm-name}/authentication/flows/{flow-alias}/executions" URL_ADMIN_FLOWS_EXECUTIONS = "admin/realms/{realm-name}/authentication/flows/{flow-alias}/executions"
URL_ADMIN_FLOWS_EXECUTIONS_EXEUCUTION = "admin/realms/{realm-name}/authentication/flows/{flow-alias}/executions/execution"
URL_ADMIN_FLOWS_EXECUTIONS_FLOW = "admin/realms/{realm-name}/authentication/flows/{flow-alias}/executions/flow"
URL_ADMIN_COMPONENTS = "admin/realms/{realm-name}/components"
URL_ADMIN_COMPONENT = "admin/realms/{realm-name}/components/{component-id}"
URL_ADMIN_KEYS = "admin/realms/{realm-name}/keys"
URL_ADMIN_USER_FEDERATED_IDENTITIES = "admin/realms/{realm-name}/users/{id}/federated-identity"
URL_ADMIN_USER_FEDERATED_IDENTITY = "admin/realms/{realm-name}/users/{id}/federated-identity/{provider}"

2
setup.py

@ -7,7 +7,7 @@ with open("README.md", "r") as fh:
setup( setup(
name='python-keycloak', name='python-keycloak',
version='0.17.6',
version='0.23.0',
url='https://github.com/marcospereirampj/python-keycloak', url='https://github.com/marcospereirampj/python-keycloak',
license='The MIT License', license='The MIT License',
author='Marcos Pereira', author='Marcos Pereira',

Loading…
Cancel
Save