def from_kc_oidc_json(app, redirect_uri, config_path=None, logout_path=None, heartbeat_path=None, keycloak_kwargs=None, authorization_settings=None, uri_whitelist=None, login_path=None): # Read config, assumed to be in Keycloak OIDC JSON format. config_path = "keycloak.json" if config_path is None else config_path with open(config_path, 'r') as f: config_data = json.load(f) # Setup the Keycloak connection. keycloak_config = dict( server_url=config_data["auth-server-url"], realm_name=config_data["realm"], client_id=config_data["resource"], client_secret_key=config_data["credentials"]["secret"], verify=config_data["ssl-required"] != "none") if keycloak_kwargs is not None: keycloak_config = {**keycloak_config, **keycloak_kwargs} keycloak_openid = KeycloakOpenID(**keycloak_config) if authorization_settings is not None: keycloak_openid.load_authorization_config(authorization_settings) return FlaskKeycloak(app, keycloak_openid, redirect_uri, logout_path=logout_path, heartbeat_path=heartbeat_path, uri_whitelist=uri_whitelist, login_path=login_path)
class KeycloakMiddleware(MiddlewareMixin): def __init__(self, get_response): """ :param get_response: """ self.config = settings.APPLICATION_CONFIG # Read configurations try: self.server_url = get_application_config_for_key('KEYCLOAK_SERVER_URL') self.client_id = get_application_config_for_key('KEYCLOAK_CLIENT_ID') self.realm = get_application_config_for_key('KEYCLOAK_REALM') except KeyError as e: raise Exception("KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or KEYCLOAK_REALM not found.") self.client_secret_key = get_application_config_for_key('KEYCLOAK_CLIENT_SECRET_KEY') self.default_access = get_application_config_for_key('KEYCLOAK_DEFAULT_ACCESS') self.method_validate_token = get_application_config_for_key('KEYCLOAK_METHOD_VALIDATE_TOKEN') self.keycloak_authorization_config = get_application_config_for_key('KEYCLOAK_AUTHORIZATION_CONFIG') # Create Keycloak instance self.keycloak = KeycloakOpenID(server_url=self.server_url, client_id=self.client_id, realm_name=self.realm, client_secret_key=self.client_secret_key, ) # Read policies if self.keycloak_authorization_config: self.keycloak.load_authorization_config(self.keycloak_authorization_config) # Django self.get_response = get_response print("KEYCLOAK_SERVER URL", get_application_config_for_key('KEYCLOAK_SERVER_URL')) print("KEYCLOAK_CLIENT_ID URL", get_application_config_for_key('KEYCLOAK_CLIENT_ID')) print("KEYCLOAK_REALM URL", get_application_config_for_key('KEYCLOAK_REALM')) @property def keycloak(self): return self._keycloak @keycloak.setter def keycloak(self, value): self._keycloak = value @property def config(self): return self._config @config.setter def config(self, value): self._config = value @property def server_url(self): return self._server_url @server_url.setter def server_url(self, value): self._server_url = value @property def client_id(self): return self._client_id @client_id.setter def client_id(self, value): self._client_id = value @property def client_secret_key(self): return self._client_secret_key @client_secret_key.setter def client_secret_key(self, value): self._client_secret_key = value @property def client_public_key(self): return self._client_public_key @client_public_key.setter def client_public_key(self, value): self._client_public_key = value @property def realm(self): return self._realm @realm.setter def realm(self, value): self._realm = value @property def keycloak_authorization_config(self): return self._keycloak_authorization_config @keycloak_authorization_config.setter def keycloak_authorization_config(self, value): self._keycloak_authorization_config = value @property def method_validate_token(self): return self._method_validate_token @method_validate_token.setter def method_validate_token(self, value): self._method_validate_token = value def __call__(self, request): """ :param request: :return: """ return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): """ Validate only the token introspect. :param request: django request :param view_func: :param view_args: view args :param view_kwargs: view kwargs :return: """ print ('DEBUG:', request) # do not block the access to root! if request.path_info == '/': logger.debug('** exclude path found, skipping') return None # CONTINGENCY SOLUTION!!! review API to avoid this # if request.path_info[-7:] == '/series': # return None whitelist = [ '/api/v1/api', '/api/v1/alive', '/api/v1/ready', '/oformat/WORD/documentation', '/oformat/EXCEL/documentation', '/export_download' ] for iwhite in whitelist: if request.path_info.endswith(iwhite): print ('skipped:', request.path_info) return None if hasattr(settings, 'KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS'): path = request.path_info.lstrip('/') if any(re.match(m, path) for m in settings.KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS): logger.debug('** exclude path found, skipping') return None try: roles = view_func.cls.roles except AttributeError as e: return JsonResponse({"detail": NotAuthenticated.default_detail}, status=NotAuthenticated.status_code) if 'HTTP_AUTHORIZATION' not in request.META: return JsonResponse({"detail": NotAuthenticated.default_detail}, status=NotAuthenticated.status_code) auth_header = request.META.get('HTTP_AUTHORIZATION').split() token = auth_header[1] if len(auth_header) == 2 else auth_header[0] try: userinfo = self.keycloak.userinfo(token) except KeycloakError as e: return JsonResponse({"detail": AuthenticationFailed.default_detail}, status=AuthenticationFailed.status_code) has_role = True for role in roles: if role not in userinfo['groups']: has_role = False # In case we need to verify token. But since we verify it by using userinfo call, it is not necessary atm. # KEYCLOAK_PUBLIC_KEY = self.client_public_key # options = {"verify_signature": True, "verify_aud": True, "verify_exp": True} # token_info = self.keycloak.decode_token(token, key=KEYCLOAK_PUBLIC_KEY, options=options) # if token_info['realm_access'] and token_info['realm_access']['roles']: # print(token_info['realm_access']['roles']) if has_role: return None # User Permission Denied return JsonResponse({"detail": PermissionDenied.default_detail}, status=PermissionDenied.status_code)
class KeycloakMiddleware(MiddlewareMixin): def __init__(self, get_response): """ :param get_response: """ self.config = settings.KEYCLOAK_CONFIG # Read configurations try: self.server_url = self.config['KEYCLOAK_SERVER_URL'] self.client_id = self.config['KEYCLOAK_CLIENT_ID'] self.realm = self.config['KEYCLOAK_REALM'] except KeyError as e: raise Exception("KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or KEYCLOAK_REALM not found.") self.client_secret_key = self.config.get('KEYCLOAK_CLIENT_SECRET_KEY', None) self.client_public_key = self.config.get('KEYCLOAK_CLIENT_PUBLIC_KEY', None) self.default_access = self.config.get('KEYCLOAK_DEFAULT_ACCESS', "DENY") self.method_validate_token = self.config.get('KEYCLOAK_METHOD_VALIDATE_TOKEN', "INTROSPECT") self.keycloak_authorization_config = self.config.get('KEYCLOAK_AUTHORIZATION_CONFIG', None) # Create Keycloak instance self.keycloak = KeycloakOpenID(server_url=self.server_url, client_id=self.client_id, realm_name=self.realm, client_secret_key=self.client_secret_key) # Read policies if self.keycloak_authorization_config: self.keycloak.load_authorization_config(self.keycloak_authorization_config) # Django self.get_response = get_response @property def keycloak(self): return self._keycloak @keycloak.setter def keycloak(self, value): self._keycloak = value @property def config(self): return self._config @config.setter def config(self, value): self._config = value @property def server_url(self): return self._server_url @server_url.setter def server_url(self, value): self._server_url = value @property def client_id(self): return self._client_id @client_id.setter def client_id(self, value): self._client_id = value @property def client_secret_key(self): return self._client_secret_key @client_secret_key.setter def client_secret_key(self, value): self._client_secret_key = value @property def client_public_key(self): return self._client_public_key @client_public_key.setter def client_public_key(self, value): self._client_public_key = value @property def realm(self): return self._realm @realm.setter def realm(self, value): self._realm = value @property def keycloak_authorization_config(self): return self._keycloak_authorization_config @keycloak_authorization_config.setter def keycloak_authorization_config(self, value): self._keycloak_authorization_config = value @property def method_validate_token(self): return self._method_validate_token @method_validate_token.setter def method_validate_token(self, value): self._method_validate_token = value def __call__(self, request): """ :param request: :return: """ return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): """ Validate only the token introspect. :param request: django request :param view_func: :param view_args: view args :param view_kwargs: view kwargs :return: """ if hasattr(settings, 'KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS'): path = request.path_info.lstrip('/') if any(re.match(m, path) for m in settings.KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS): logger.debug('** exclude path found, skipping') return None try: view_scopes = view_func.cls.keycloak_scopes except AttributeError as e: logger.debug('Allowing free acesss, since no authorization configuration (keycloak_scopes) found for this request route :%s',request) return None if 'HTTP_AUTHORIZATION' not in request.META: return JsonResponse({"detail": NotAuthenticated.default_detail}, status=NotAuthenticated.status_code) auth_header = request.META.get('HTTP_AUTHORIZATION').split() token = auth_header[1] if len(auth_header) == 2 else auth_header[0] # Get default if method is not defined. required_scope = view_scopes.get(request.method, None) \ if view_scopes.get(request.method, None) else view_scopes.get('DEFAULT', None) # DEFAULT scope not found and DEFAULT_ACCESS is DENY if not required_scope and self.default_access == 'DENY': return JsonResponse({"detail": PermissionDenied.default_detail}, status=PermissionDenied.status_code) try: user_permissions = self.keycloak.get_permissions(token, method_token_info=self.method_validate_token.lower(), key=self.client_public_key) except KeycloakInvalidTokenError as e: return JsonResponse({"detail": AuthenticationFailed.default_detail}, status=AuthenticationFailed.status_code) for perm in user_permissions: if required_scope in perm.scopes: return None # User Permission Denied return JsonResponse({"detail": PermissionDenied.default_detail}, status=PermissionDenied.status_code)
class KeycloakMiddleware(object): header_key = "HTTP_AUTHORIZATION" set_session_state_cookie = True def __init__(self, get_response): self.config = settings.KEYCLOAK_CONFIG # Read configurations try: self.server_url = self.config['KEYCLOAK_SERVER_URL'] self.client_id = self.config['KEYCLOAK_CLIENT_ID'] self.realm = self.config['KEYCLOAK_REALM'] except KeyError: raise Exception('KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or ' 'KEYCLOAK_REALM not found.') self.client_secret_key = self.config.get('KEYCLOAK_CLIENT_SECRET_KEY', None) self.client_public_key = self.config.get('KEYCLOAK_CLIENT_PUBLIC_KEY', None) self.default_access = self.config.get('KEYCLOAK_DEFAULT_ACCESS', 'DENY') self.method_validate_token = self.config.get( 'KEYCLOAK_METHOD_VALIDATE_TOKEN', 'INTROSPECT') self.keycloak_authorization_config = self.config.get( 'KEYCLOAK_AUTHORIZATION_CONFIG', None) # Create Keycloak instance self.keycloak = KeycloakOpenID( server_url=self.server_url, client_id=self.client_id, realm_name=self.realm, client_secret_key=self.client_secret_key, ) # Read policies if self.keycloak_authorization_config: self.keycloak.load_authorization_config( self.keycloak_authorization_config) # Django self.get_response = get_response def __call__(self, request): # Code to be executed for each request before # the view (and later middleware) are called. response = self.get_response(request) # Code to be executed for each request/response after # the view is called. return response def process_view(self, request, view_func, view_args, view_kwargs): """ Validate only the token introspect. :param request: django request :param view_func: view function :param view_args: view args :param view_kwargs: view kwargs :return: JSON response, HTML response or None """ # Load permissions from Keycloak keycloak_permissions = self.get_keycloak_permissions(request) # Set permissions for anonymous user if keycloak_permissions is None: keycloak_permissions = { 'acquisition_mode:view', 'authority:view', 'acquisition_mode:view', 'collection_language:view', 'collection_location:view', 'collection_publisher:view', 'collection:view', 'collectioncollectors:view', 'coupe:view', 'dance:view', 'document_fonds:view', 'document_institution:view', 'document_mission:view', 'document_collection:view', 'document_item:view', 'domain_music:view', 'domain_song:view', 'domain_tale:view', 'domain_vocal:view', 'emit_vox:view', 'fond:view', 'hornbostelsachs:view', 'institution:view', 'instrument:view', 'item_author:view', 'item_coirault:view', 'item_collector:view', 'item_compositor:view', 'item_dance:view', 'item_domain_music:view', 'item_domain_song:view', 'item_domain_tale:view', 'item_domain_vocal:view', 'item_informer:view', 'item_language:view', 'item_musical_group:view', 'item_musical_organization:view', 'item_performance:view', 'item_thematic:view', 'item_usefulness:view', 'item:view', 'language:view', 'legal_rights:view', 'location_gis:view', 'location_gis_collection:view', 'mediatype:view', 'metadata_author:view', 'mission:view', 'musical_group:view', 'musical_organization:view', 'performance_collection_musician:view', 'performance_collection:view', 'publisher:view', 'recording_context:view', 'skos_collection:view', 'skos_concept:view', 'thematic:view', 'usefulness:view' } # Build Django permissions request.user._perm_cache = { 'keycloak.%s' % perm.replace(':', '_') for perm in keycloak_permissions } # Extract required Keycloak permissions from view view_class = getattr(view_func, 'cls', None) \ or getattr(view_func, 'view_class', None) view_scopes = getattr(view_class, 'keycloak_scopes', None) if view_scopes is None: logger.debug( 'Allowing free access, since no authorization ' 'configuration (keycloak_scopes) found for this ' 'request route: %s', request) return None # Get DEFAULT required permission if method is not defined required_permission = view_scopes.get(request.method, None) \ or view_scopes.get('DEFAULT', None) # DEFAULT scope not found and DEFAULT_ACCESS is DENY if not required_permission and self.default_access == 'DENY': return self.create_permission_denied_response(request, view_class) # Check required permission from user Keycloak permissions if required_permission not in keycloak_permissions: return self.create_permission_denied_response(request, view_class) def create_permission_denied_response(self, request, view_class): """ Create permission denied response. :param request: django request :param view_class: class of view function :return: JSON response or HTML response """ # Create HTML response (Front request) default_403_error_message = getattr(view_class, 'default_403_error_message', None) if default_403_error_message is not None: return render( request, 'error.html', { 'exception': default_403_error_message or _('Accès interdit.'), }, status=403) # Create JSON response (Back request) return JsonResponse( {'detail': PermissionDenied.default_detail}, status=PermissionDenied.status_code, ) def get_keycloak_permissions(self, request): """ Extract permissions from Keycloak for current user. :param request: django request :return: set of permissions (as string) or None for anonymous user """ # Get session token access_token = request.session.get('oidc_access_token', '') if not access_token: try: access_token = request.META['HTTP_AUTHORIZATION'].split(' ')[1] except: return None # No token is anonymous user: unknown permissions if not access_token: return None # Get permissions from Keycloak try: permissions = self.keycloak.get_permissions(access_token) except KeycloakInvalidTokenError: return None # Extract permissions from scopes return {p for perm in permissions for p in perm.scopes}
# Instropect RPT token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['access_token'], rpt=rpt['rpt'], token_type_hint="requesting_party_token")) # Introspect Token token_info = keycloak_openid.introspect(token['access_token'])) # Decode Token KEYCLOAK_PUBLIC_KEY = keycloak_openid.public_key() options = {"verify_signature": True, "verify_aud": True, "exp": True} token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options) # Get permissions by token token = keycloak_openid.token("user", "password") keycloak_openid.load_authorization_config("example-authz-config.json") policies = keycloak_openid.get_policies(token['access_token'], method_token_info='decode', key=KEYCLOAK_PUBLIC_KEY) permissions = keycloak_openid.get_permissions(token['access_token'], method_token_info='introspect') # KEYCLOAK ADMIN from keycloak import KeycloakAdmin keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/", username='******', password='******', realm_name="example_realm", client_secret_key="client-secret", verify=True) # Add user