From d89290d6ca11598443d9cf09484328b3625bb00c Mon Sep 17 00:00:00 2001 From: Jovanka Gulicoska Date: Wed, 22 Mar 2023 12:29:41 +0100 Subject: [PATCH 1/3] Fix KeycloackOpenID connection parametars. Fix logging into CKAN --- ckanext/keycloak/keycloak.py | 16 ++++---- ckanext/keycloak/views.py | 73 ++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/ckanext/keycloak/keycloak.py b/ckanext/keycloak/keycloak.py index 84a3e24..75986db 100644 --- a/ckanext/keycloak/keycloak.py +++ b/ckanext/keycloak/keycloak.py @@ -1,28 +1,28 @@ import logging from keycloak import KeycloakOpenID, KeycloakAdmin - log = logging.getLogger(__name__) class KeycloakClient: - def __init__(self, server_url, client_id, realm_name, client_secret): + def __init__(self, server_url, client_id, realm_name, client_secret_key): self.server_url = server_url self.client_id = client_id self.realm_name = realm_name - self.client_secret = client_secret - + self.client_secret_key = client_secret_key + def get_keycloak_client(self): return KeycloakOpenID( - server_url=self.server_url, client_id=self.client_id, realm_name=self.realm_name + server_url=self.server_url, client_id=self.client_id, realm_name=self.realm_name, client_secret_key=self.client_secret_key ) def get_auth_url(self, redirect_uri): - return self.get_keycloak_client().auth_url(redirect_uri=redirect_uri) + return self.get_keycloak_client().auth_url(redirect_uri=redirect_uri, scope="openid profile email") def get_token(self, code, redirect_uri): return self.get_keycloak_client().token(grant_type="authorization_code", code=code, redirect_uri=redirect_uri) def get_user_info(self, token): - return self.get_keycloak_client().userinfo(token) + print (token.get('access_token')) + return self.get_keycloak_client().userinfo(token.get('access_token')) def get_user_groups(self, token): return self.get_keycloak_client().userinfo(token).get('groups', []) @@ -30,4 +30,4 @@ def get_user_groups(self, token): def get_keycloak_admin(self): return KeycloakAdmin( username="admin", - ) + ) \ No newline at end of file diff --git a/ckanext/keycloak/views.py b/ckanext/keycloak/views.py index 509fe9c..d91c095 100644 --- a/ckanext/keycloak/views.py +++ b/ckanext/keycloak/views.py @@ -1,40 +1,57 @@ -import logging +# +import logging from flask import Blueprint, session - from ckan.plugins import toolkit as tk import ckan.lib.helpers as h import ckan.model as model from ckan.common import g from ckan.views.user import set_repoze_user, RequestResetView - from ckanext.keycloak.keycloak import KeycloakClient import ckanext.keycloak.helpers as helpers +from urllib.parse import urlencode +import ckan.lib.dictization as dictization log = logging.getLogger(__name__) - keycloak = Blueprint('keycloak', __name__, url_prefix='/user') +server_url = "https://auth.sproutopencontent.com/" +client_id = 'sprout-client' +realm_name = 'sprout' +redirect_uri = 'http://localhost:5000/user/sso_login' +client_secret_key = 'Y3Sn2ilmQWP9zaS8SwNqbKbVE6Q9yUIu' - -server_url = tk.config.get('sso.keycloak_url', 'https://auth.sproutopencontent.com/') -client_id = tk.config.get('sso.keycloak_client_id', 'sprout-client') -realm_name = tk.config.get('sso.keycloak_realm', 'sprout') -redirect_uri = tk.config.get('sso.redirect_uri', None) -client_secret = tk.config.get('sso.keycloak_client_secret', None) -client = KeycloakClient(server_url, client_id, realm_name, client_secret) - +client = KeycloakClient(server_url, client_id, realm_name, client_secret_key) def _create_user(user_dict): context = { u'ignore_auth': True, } - try: return tk.get_action(u'user_create')(context, user_dict) except tk.ValidationError as e: error_message = (e.error_summary or e.message or e.error_dict) tk.abort(400, error_message) +def _log_user_into_ckan(resp): + """ Log the user into different CKAN versions. + CKAN 2.10 introduces flask-login and login_user method. + CKAN 2.9.6 added a security change and identifies the user + with the internal id plus a serial autoincrement (currently static). + CKAN <= 2.9.5 identifies the user only using the internal id. + """ + if tk.check_ckan_version(min_version="2.10"): + from ckan.common import login_user + login_user(g.userobj) + return + + if tk.check_ckan_version(min_version="2.9.6"): + user_id = "{},1".format(g.userobj.id) + else: + user_id = g.userobj['name'] + set_repoze_user(user_id, resp) + + log.info(u'User {0}<{1}> logged in successfully'.format(g.userobj['name'], g.userobj['email'])) + def sso(): log.info("SSO Login") auth_url = None @@ -43,15 +60,12 @@ def sso(): except Exception as e: log.error("Error getting auth url: {}".format(e)) return tk.abort(500, "Error getting auth url: {}".format(e)) - return tk.redirect_to(auth_url) - def sso_login(): data = tk.request.args token = client.get_token(data['code'], redirect_uri) - userinfo = client.get_user_info(token.get('access_token')) - + userinfo = client.get_user_info(token) log.info("SSO Login: {}".format(userinfo)) if userinfo: user_dict = { @@ -63,27 +77,29 @@ def sso_login(): 'idp': 'google' } } + context = {"model": model, "session": model.Session} user = helpers.process_user(user_dict) - g.userobj = model.User.get(user['name']) g.user = user - user_id = "{},1".format(user.get('id')) - response = tk.redirect_to(tk.url_for('dashboard.index')) - set_repoze_user(user_id, response) - - log.info(u'User {0}<{1}> logged in successfully'.format(g.userobj.name, g.userobj.email)) + g.userobj = user + context['user'] = user + context['auth_user_obj'] = g.userobj + user_id = "{}".format(user.get('name')) + + response = tk.redirect_to(tk.url_for('user.me',context)) + + _log_user_into_ckan(response) + log.info("Logged in success") return response - return tk.redirect_to(tk.url_for('user.login')) + else: + return tk.redirect_to(tk.url_for('user.login')) def reset_password(): email = tk.request.form.get('user', None) - if '@' not in email: log.info(f'User requested reset link for invalid email: {email}') h.flash_error('Invalid email address') return tk.redirect_to(tk.url_for('user.request_reset')) - - user = model.User.by_email(email) - + user = model.User.by_email(email) if not user: log.info(u'User requested reset link for unknown user: {}'.format(email)) return tk.redirect_to(tk.url_for('user.login')) @@ -94,7 +110,6 @@ def reset_password(): return tk.redirect_to(tk.url_for('user.login')) return RequestResetView().post() - keycloak.add_url_rule('/sso', view_func=sso) keycloak.add_url_rule('/sso_login', view_func=sso_login) keycloak.add_url_rule('/reset_password', view_func=reset_password, methods=['POST']) From e5e0d12803cc6952df24a78b870639e07d490326 Mon Sep 17 00:00:00 2001 From: Petar Popovski Date: Thu, 23 Mar 2023 11:22:06 +0100 Subject: [PATCH 2/3] userobj get id fix --- ckanext/keycloak/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckanext/keycloak/views.py b/ckanext/keycloak/views.py index d91c095..5dd7aef 100644 --- a/ckanext/keycloak/views.py +++ b/ckanext/keycloak/views.py @@ -45,7 +45,7 @@ def _log_user_into_ckan(resp): return if tk.check_ckan_version(min_version="2.9.6"): - user_id = "{},1".format(g.userobj.id) + user_id = "{},1".format(g.userobj.get('id')) else: user_id = g.userobj['name'] set_repoze_user(user_id, resp) From cec5aae86f5a4e5cc22a19b91f4d402b8e6c4471 Mon Sep 17 00:00:00 2001 From: Jovanka Gulicoska Date: Thu, 23 Mar 2023 11:39:34 +0100 Subject: [PATCH 3/3] Fix extension variables --- README.md | 9 +++++---- ckanext/keycloak/views.py | 16 +++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 677eab9..32c8643 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,11 @@ To install ckanext-keycloak: Configuration settings to run the extension ``` - ckan.sso.keycloak_url = link_to_keycloack_authentication_url - ckan.sso.keycloak_realm = realm_name - ckan.sso.keycloak_client_id = client_id - ckan.sso.redirect_uri = redirect_url + ckanext.keycloak_url = link_to_keycloack_authentication_url + ckanext.keycloak.client_id = realm_name + ckanext.keycloak.realm_name = client_id + ckanext.keycloak.redirect_uri = redirect_url + ckanext.keycloak.client_secret_key = client_secret_key ``` ## Developer installation diff --git a/ckanext/keycloak/views.py b/ckanext/keycloak/views.py index 5dd7aef..076961a 100644 --- a/ckanext/keycloak/views.py +++ b/ckanext/keycloak/views.py @@ -1,5 +1,3 @@ -# - import logging from flask import Blueprint, session from ckan.plugins import toolkit as tk @@ -11,14 +9,18 @@ import ckanext.keycloak.helpers as helpers from urllib.parse import urlencode import ckan.lib.dictization as dictization +from os import environ log = logging.getLogger(__name__) + keycloak = Blueprint('keycloak', __name__, url_prefix='/user') -server_url = "https://auth.sproutopencontent.com/" -client_id = 'sprout-client' -realm_name = 'sprout' -redirect_uri = 'http://localhost:5000/user/sso_login' -client_secret_key = 'Y3Sn2ilmQWP9zaS8SwNqbKbVE6Q9yUIu' + + +server_url = tk.config.get('ckanext.keycloak.server_url', environ.get('CKANEXT__KEYCLOAK__SERVER_URL')) +client_id = tk.config.get('ckanext.keycloak.client_id', environ.get('CKANEXT__KEYCLOAK__CLIENT_ID')) +realm_name = tk.config.get('ckanext.keycloak.realm_name', environ.get('CKANEXT__KEYCLOAK__REALM_NAME')) +redirect_uri = tk.config.get('ckanext.keycloak.redirect_uri', environ.get('CKANEXT__KEYCLOAK__REDIRECT_URI')) +client_secret_key = tk.config.get('ckanext.keycloak.client_secret_key', environ.get('CKANEXT__KEYCLOAK__CLIENT_SECRET_KEY')) client = KeycloakClient(server_url, client_id, realm_name, client_secret_key)