diff --git a/README.md b/README.md index f12f14c..e37a47a 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,118 @@ # Invenio-CERN-sync -Integrates CERN databases and login with Invenio. +Integrates CERN databases and SSO login with Invenio. -## Users sync +## SSO login -This module connects to LDAP to fetch users, updates already existing users -and inserts missing ones. +This module provides configurable integration with the CERN SSO login. +To integrate the CERN SSO, add this to your application configuration: +```python +from invenio_cern_sync.sso import cern_remote_app_name, cern_keycloak +OAUTHCLIENT_REMOTE_APPS = { + cern_remote_app_name: cern_keycloak.remote_app, +} +CERN_APP_CREDENTIALS = { + "consumer_key": "CHANGE ME", + "consumer_secret": "CHANGE ME", +} -To get the extra user fields stored in the user profile, set the following: +from invenio_cern_sync.sso.api import confirm_registration_form +OAUTHCLIENT_SIGNUP_FORM = confirm_registration_form -from invenio_cern_sync.users.profile import CERNUserProfileSchema -ACCOUNTS_USER_PROFILE_SCHEMA = CERNUserProfileSchema() +OAUTHCLIENT_CERN_REALM_URL = cern_keycloak.realm_url +OAUTHCLIENT_CERN_USER_INFO_URL = cern_keycloak.user_info_url +OAUTHCLIENT_CERN_VERIFY_EXP = True +OAUTHCLIENT_CERN_VERIFY_AUD = False +OAUTHCLIENT_CERN_USER_INFO_FROM_ENDPOINT = True +``` -You can also provide your own schema. +Define, use the env var to inject the right configuration +for your env (local, prod, etc.): +- INVENIO_CERN_SYNC_KEYCLOAK_BASE_URL +- INVENIO_SITE_UI_URL -Define -- ACCOUNTS_DEFAULT_USER_VISIBILITY -- ACCOUNTS_DEFAULT_EMAIL_VISIBILITY + +## Sync users and groups + +You can sync users and groups from the CERN AuthZ service or LDAP +with the local Invenio db. + +First, decide what fields you would like to get from the CERN database. +By default, only the field in `invenio_cern_sync.users.profile.CERNUserProfileSchema` +are kept when syncing. + +If you need to customize that, you will need to: + +1. Provide your own schema class, and assign it the config var `ACCOUNTS_USER_PROFILE_SCHEMA` +2. Change the mappers, to serialize the fetched users from the CERN format to your + local format. If you are using AuthZ, assign your custom serializer func + to `CERN_SYNC_AUTHZ_USERPROFILE_MAPPER`. + If you are using LDAP, assign it to `CERN_SYNC_LDAP_USERPROFILE_MAPPER`. +3. You can also customize what extra data can be stored in the RemoteAccount.extra_data fields + via the config `CERN_SYNC_AUTHZ_USER_EXTRADATA_MAPPER` or `CERN_SYNC_LDAP_USER_EXTRADATA_MAPPER`. + +If are only using the CERN SSO as unique login method, you will probably also configure: + +```python +ACCOUNTS_DEFAULT_USER_VISIBILITY = True +ACCOUNTS_DEFAULT_EMAIL_VISIBILITY = True +``` + +### AuthZ + +In your app, define the following configuration: + +```python +CERN_SYNC_KEYCLOAK_BASE_URL = "" +CERN_SYNC_AUTHZ_BASE_URL = "" +``` + +The above `CERN_APP_CREDENTIALS` configuration must be already configured. +You will also need to make sure that those credentials are allowed to fetch +the entire CERN database of user and groups. + +Then, create a new celery task and sync users: + +```python +from invenio_cern_sync.users.sync import sync + +def sync_users_task(): + user_ids = sync(method="AuthZ") + # you can optionally pass extra kwargs for the AuthZ client APIs. + + # make sure that you re-index users if needed. For example, in InvenioRDM: + # from invenio_users_resources.services.users.tasks import reindex_users + # reindex_users.delay(user_ids) +``` + +To fetch groups: + +```python +from invenio_cern_sync.groups.sync import sync + +def sync_groups_task(): + roles_ids = sync() +``` + +### LDAP + +You can use LDAP instead. Define the LDAP url: + +```python +CERN_SYNC_LDAP_URL = +``` + +Then, create a new celery task and sync users: + +```python +from invenio_cern_sync.users.sync import sync + +def sync_users_task(): + user_ids = sync(method="LDAP") + # you can optionally pass extra kwargs for the LDAP client APIs. +``` diff --git a/invenio_cern_sync/authz/client.py b/invenio_cern_sync/authz/client.py index 3dd5ec8..01e3dfa 100644 --- a/invenio_cern_sync/authz/client.py +++ b/invenio_cern_sync/authz/client.py @@ -45,9 +45,12 @@ class KeycloakService: def __init__(self, base_url=None, client_id=None, client_secret=None): """Constructor.""" self.base_url = base_url or current_app.config["CERN_SYNC_KEYCLOAK_BASE_URL"] - self.client_id = client_id or current_app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] + self.client_id = ( + client_id or current_app.config["CERN_APP_CREDENTIALS"]["consumer_key"] + ) self.client_secret = ( - client_secret or current_app.config["CERN_SYNC_KEYCLOAK_CLIENT_SECRET"] + client_secret + or current_app.config["CERN_APP_CREDENTIALS"]["consumer_secret"] ) def get_authz_token(self): @@ -75,7 +78,6 @@ def get_authz_token(self): "cernGroup", # "CA" "cernSection", # "IR" "instituteName", # "CERN" - "instituteAbbreviation", # "CERN" "preferredCernLanguage", # "EN" "orcid", "primaryAccountEmail", diff --git a/invenio_cern_sync/authz/mapper.py b/invenio_cern_sync/authz/mapper.py index 7cd2dec..fcbc8d0 100644 --- a/invenio_cern_sync/authz/mapper.py +++ b/invenio_cern_sync/authz/mapper.py @@ -11,21 +11,18 @@ def userprofile_mapper(cern_identity): """Map the CERN Identity fields to the Invenio user profile schema. - :param cern_identity: the identity dict - :param profile_schema: the Invenio user profile schema to map to - :return: a serialized dict, containing all the keys that will appear in the - User.profile JSON column. Any unwanted key should be removed. - """ + The returned dict structure must match the user profile schema defined via + the config ACCOUNTS_USER_PROFILE_SCHEMA.""" return dict( + affiliations=cern_identity["instituteName"], cern_department=cern_identity["cernDepartment"], cern_group=cern_identity["cernGroup"], cern_section=cern_identity["cernSection"], family_name=cern_identity["lastName"], full_name=cern_identity["displayName"], given_name=cern_identity["firstName"], - institute_abbreviation=cern_identity["instituteAbbreviation"], - institute=cern_identity["instituteName"], mailbox=cern_identity.get("postOfficeBox", ""), + orcid=cern_identity.get("orcid", ""), person_id=cern_identity["personId"], ) diff --git a/invenio_cern_sync/config.py b/invenio_cern_sync/config.py index 8530080..c158ae6 100644 --- a/invenio_cern_sync/config.py +++ b/invenio_cern_sync/config.py @@ -13,31 +13,14 @@ from .ldap.mapper import userprofile_mapper as ldap_userprofile_mapper ################################################################################### -# Required config - -CERN_SYNC_KEYCLOAK_CLIENT_ID = "" -"""Set the unique id/name of the CERN SSO app, also called `consumer_key`. - -This corresponds to the RemoteAccount `client_id` column. -""" - -CERN_SYNC_REMOTE_APP_NAME = None -"""Set the configured remote (oauth) app name for the CERN login. - -This corresponds to the UserIdentity `method` column. -""" - -################################################################################### +# CERN AuthZ # Required config when using the AuthZ method to sync users, or when syncing groups -CERN_SYNC_KEYCLOAK_BASE_URL = "" -""".""" - -CERN_SYNC_KEYCLOAK_CLIENT_SECRET = "" -""".""" +CERN_SYNC_KEYCLOAK_BASE_URL = "https://keycloak-qa.cern.ch/" +"""Base URL of the CERN SSO Keycloak endpoint.""" -CERN_SYNC_AUTHZ_BASE_URL = "" -""".""" +CERN_SYNC_AUTHZ_BASE_URL = "https://authorization-service-api-qa.web.cern.ch/" +"""Base URL of the Authorization Service API endpoint.""" CERN_SYNC_AUTHZ_USERPROFILE_MAPPER = authz_userprofile_mapper """Map the AuthZ response to Invenio user profile schema. @@ -50,6 +33,7 @@ ################################################################################### +# CERN LDAP # Required config when using the LDAP method to sync users CERN_SYNC_LDAP_URL = None diff --git a/invenio_cern_sync/ldap/client.py b/invenio_cern_sync/ldap/client.py index 74de302..8e1af61 100644 --- a/invenio_cern_sync/ldap/client.py +++ b/invenio_cern_sync/ldap/client.py @@ -18,7 +18,6 @@ "cernAccountType", "cernActiveStatus", "cernGroup", - "cernInstituteAbbreviation", "cernInstituteName", "cernSection", "cn", # username diff --git a/invenio_cern_sync/ldap/mapper.py b/invenio_cern_sync/ldap/mapper.py index 9ae7b56..a9a16b4 100644 --- a/invenio_cern_sync/ldap/mapper.py +++ b/invenio_cern_sync/ldap/mapper.py @@ -13,20 +13,16 @@ def userprofile_mapper(ldap_user): """Map the LDAP fields to the Invenio user profile schema. - :param ldap_user: the ldap dict - :param profile_schema: the Invenio user profile schema to map to - :return: a serialized dict, containing all the keys that will appear in the - User.profile JSON column. Any unwanted key should be removed. - """ + The returned dict structure must match the user profile schema defined via + the config ACCOUNTS_USER_PROFILE_SCHEMA.""" return dict( + affiliations=first_or_default(ldap_user, "cernInstituteName"), cern_department=first_or_default(ldap_user, "division"), cern_group=first_or_default(ldap_user, "cernGroup"), cern_section=first_or_default(ldap_user, "cernSection"), family_name=first_or_default(ldap_user, "sn"), full_name=first_or_default(ldap_user, "displayName"), given_name=first_or_default(ldap_user, "givenName"), - institute_abbreviation=first_or_default(ldap_user, "cernInstituteAbbreviation"), - institute=first_or_default(ldap_user, "cernInstituteName"), mailbox=first_or_default(ldap_user, "postOfficeBox"), person_id=first_or_default(ldap_user, "employeeID"), ) diff --git a/invenio_cern_sync/sso/__init__.py b/invenio_cern_sync/sso/__init__.py new file mode 100644 index 0000000..5b92221 --- /dev/null +++ b/invenio_cern_sync/sso/__init__.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2024 CERN. +# +# Invenio-CERN-sync is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Invenio-CERN-sync SSO module.""" + +################################################################################### +# CERN SSO +# Pre-configured settings for CERN SSO + +import os +from urllib.parse import quote + +from invenio_oauthclient.contrib.keycloak import KeycloakSettingsHelper + +from .api import ( + cern_groups_handler, + cern_groups_serializer, + cern_info_handler, + cern_info_serializer, + cern_setup_handler, +) + +_base_url = os.environ.get( + "INVENIO_CERN_SYNC_KEYCLOAK_BASE_URL", "https://keycloak-qa.cern.ch/" +) +_site_ui_url = os.environ.get("INVENIO_SITE_UI_URL", "https://127.0.0.1") + +cern_remote_app_name = "cern" # corresponds to the UserIdentity `method` column + +cern_keycloak = KeycloakSettingsHelper( + title="CERN", + description="CERN SSO authentication", + base_url=_base_url, + realm="cern", + app_key="CERN_APP_CREDENTIALS", # config key for the app credentials + logout_url="{}auth/realms/cern/protocol/openid-connect/logout?redirect_uri={}".format( + _base_url, quote(_site_ui_url) + ), +) + +handlers = cern_keycloak.get_handlers() +handlers["signup_handler"] = { + **handlers["signup_handler"], + "info": cern_info_handler, + "info_serializer": cern_info_serializer, + "groups_serializer": cern_groups_serializer, + "groups": cern_groups_handler, + "setup": cern_setup_handler, +} +rest_handlers = cern_keycloak.get_rest_handlers() +rest_handlers["signup_handler"] = { + **rest_handlers["signup_handler"], + "info": cern_info_handler, + "info_serializer": cern_info_serializer, + "groups_serializer": cern_groups_serializer, + "groups": cern_groups_handler, + "setup": cern_setup_handler, +} diff --git a/invenio_cern_sync/sso/api.py b/invenio_cern_sync/sso/api.py new file mode 100644 index 0000000..7d1d75c --- /dev/null +++ b/invenio_cern_sync/sso/api.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2024 CERN. +# +# Invenio-CERN-sync is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Invenio-CERN-sync SSO api.""" + +from flask import current_app, g +from invenio_db import db +from invenio_oauthclient import current_oauthclient, oauth_link_external_id +from invenio_oauthclient.contrib.keycloak.handlers import get_user_info +from invenio_userprofiles.forms import confirm_register_form_preferences_factory +from werkzeug.local import LocalProxy + +###################################################################################### +# User profile custom form + +_security = LocalProxy(lambda: current_app.extensions["security"]) + + +def confirm_registration_form(*args, **kwargs): + """Custom confirm form.""" + Form = confirm_register_form_preferences_factory(_security.confirm_register_form) + + class _Form(Form): + password = None + recaptcha = None + submit = None # defined in the template + + return _Form(*args, **kwargs) + + +###################################################################################### +# User handler + + +def cern_setup_handler(remote, token, resp): + """Perform additional setup after the user has been logged in.""" + token_user_info, _ = get_user_info(remote, resp) + + with db.session.begin_nested(): + username = token_user_info["sub"] + person_id = token_user_info["cern_person_id"] + extra_data = { + "keycloak_id": username, + "person_id": person_id, + } + token.remote_account.extra_data = extra_data + + user = token.remote_account.user + user_identity = {"id": person_id, "method": remote.name} + + # link User with UserIdentity + oauth_link_external_id(user, user_identity) + + +def cern_info_handler(remote, resp): + """Info handler.""" + token_user_info, user_info = get_user_info(remote, resp) + + # Add the user_info to the request, so it can be used in the groups handler + # to avoid yet another request to the user info endpoint + g._cern_groups = user_info.get("groups", []) + + handlers = current_oauthclient.signup_handlers[remote.name] + return handlers["info_serializer"](resp, token_user_info, user_info) + + +def cern_info_serializer(remote, resp, token_user_info, user_info): + """Info serializer.""" + user_info = user_info or {} + + email = token_user_info["email"] + external_id = token_user_info["cern_person_id"] + preferred_language = user_info.get("cern_preferred_language", "en").lower() + return { + "user": { + "active": True, + "email": email, + "profile": { + "affiliations": user_info.get("home_institute", ""), + "full_name": user_info["name"], + "username": token_user_info["sub"], + }, + "prefs": { + "visibility": "public", + "email_visibility": "public", + "locale": preferred_language, + }, + }, + "external_id": external_id, + "external_method": remote.name, + } + + +###################################################################################### +# Groups handler + + +def cern_groups_handler(remote, resp): + """Retrieves groups from remote account. + + Groups are in the user info response. + """ + groups = g.pop("_cern_groups", []) + handlers = current_oauthclient.signup_handlers[remote.name] + # `remote` param automatically injected via `make_handler` helper + return handlers["groups_serializer"](groups) + + +def cern_groups_serializer(remote, groups, **kwargs): + """Serialize the groups response object.""" + serialized_groups = [] + # E-groups do have unique names and this name cannot be updated, + # therefore the name can act as an ID for Invenio + for group_name in groups: + serialized_groups.append({"id": group_name, "name": group_name}) + + return serialized_groups diff --git a/invenio_cern_sync/users/api.py b/invenio_cern_sync/users/api.py index e6281e7..4398bc0 100644 --- a/invenio_cern_sync/users/api.py +++ b/invenio_cern_sync/users/api.py @@ -13,6 +13,7 @@ from invenio_db import db from invenio_oauthclient.models import RemoteAccount, UserIdentity +from invenio_cern_sync.sso import cern_remote_app_name from invenio_cern_sync.utils import _is_different @@ -33,18 +34,17 @@ def _create_user(cern_user): def _create_user_identity(user, cern_user): """Create new user identity.""" - remote_app_name = current_app.config["CERN_SYNC_REMOTE_APP_NAME"] - assert remote_app_name + assert cern_remote_app_name return UserIdentity.create( user, - remote_app_name, + cern_remote_app_name, cern_user["user_identity_id"], ) def _create_remote_account(user, cern_user): """Return new user entry.""" - client_id = current_app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] + client_id = current_app.config["CERN_APP_CREDENTIALS"]["consumer_key"] assert client_id return RemoteAccount.create( client_id=client_id, @@ -134,7 +134,7 @@ def _update_useridentity(user_id, user_identity, cern_user): def _update_remote_account(user, cern_user): """Update RemoteAccount table.""" extra_data = cern_user["remote_account_extra_data"] - client_id = current_app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] + client_id = current_app.config["CERN_APP_CREDENTIALS"]["consumer_key"] assert client_id remote_account = RemoteAccount.get(user.id, client_id) diff --git a/invenio_cern_sync/users/profile.py b/invenio_cern_sync/users/profile.py index dc09f9f..1d84ccc 100644 --- a/invenio_cern_sync/users/profile.py +++ b/invenio_cern_sync/users/profile.py @@ -19,8 +19,6 @@ class CERNUserProfileSchema(Schema): full_name = fields.String() given_name = fields.String() group = fields.String() - institute_abbreviation = fields.String() - institute = fields.String() mailbox = fields.String() orcid = fields.String() person_id = fields.String() diff --git a/setup.cfg b/setup.cfg index 382fb9e..e78f2df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ zip_safe = False install_requires = invenio-accounts>=5.0.0,<6.0.0 invenio-oauthclient>=4.0.0,<5.0.0 + invenio_userprofiles>=3.0.0,<4.0.0 python-ldap>=3.4.0 [options.extras_require] diff --git a/tests/conftest.py b/tests/conftest.py index fcfbaf8..3a2c0eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,14 +15,13 @@ class CustomProfile(Schema): """A custom user profile schema that matches the default mapper.""" + affiliations = fields.String() cern_department = fields.String() cern_group = fields.String() cern_section = fields.String() family_name = fields.String() full_name = fields.String() given_name = fields.String() - institute_abbreviation = fields.String() - institute = fields.String() mailbox = fields.String() person_id = fields.String() @@ -34,16 +33,12 @@ def client_id(app_config): @pytest.fixture(scope="module") -def remote_app_name(app_config): - """Return a test remote app name.""" - return "cern" - - -@pytest.fixture(scope="module") -def app_config(app_config, client_id, remote_app_name): +def app_config( + app_config, + client_id, +): """Application config override.""" - app_config["CERN_SYNC_REMOTE_APP_NAME"] = remote_app_name - app_config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] = client_id + app_config["CERN_APP_CREDENTIALS"] = {"consumer_key": client_id} app_config["ACCOUNTS_USER_PROFILE_SCHEMA"] = CustomProfile() return app_config @@ -72,7 +67,6 @@ def cern_identities(): "cernGroup": "CA", "cernSection": "IR", "instituteName": "CERN", - "instituteAbbreviation": "CERN", "preferredCernLanguage": "EN", "orcid": f"0000-0002-2227-122{i}", "primaryAccountEmail": f"john.doe{i}@cern.ch", @@ -91,7 +85,6 @@ def ldap_users(): "cernAccountType": [b"Primary"], "cernActiveStatus": [b"Active"], "cernGroup": [b"CA"], - "cernInstituteAbbreviation": [b"CERN"], "cernInstituteName": [b"CERN"], "cernSection": [b"IR"], "cn": [bytes("jdoe" + str(i), encoding="utf-8")], diff --git a/tests/test_authz_client.py b/tests/test_authz_client.py index f5e5a4e..1e7a42c 100644 --- a/tests/test_authz_client.py +++ b/tests/test_authz_client.py @@ -20,8 +20,10 @@ def app_with_extra_config(app): app.config.update( { "CERN_SYNC_KEYCLOAK_BASE_URL": "https://keycloak.test", - "CERN_SYNC_KEYCLOAK_CLIENT_ID": "test-client-id", - "CERN_SYNC_KEYCLOAK_CLIENT_SECRET": "test-client-secret", + "CERN_APP_CREDENTIALS": { + "consumer_key": "test-client-id", + "consumer_secret": "test-client-secret", + }, "CERN_SYNC_AUTHZ_BASE_URL": "https://authz.test", } ) diff --git a/tests/test_authz_serializer.py b/tests/test_authz_serializer.py index 3ab16f4..66b76e9 100644 --- a/tests/test_authz_serializer.py +++ b/tests/test_authz_serializer.py @@ -29,14 +29,13 @@ def test_serialize(app, cern_identities): assert serialized_identities[i]["email"] == f"john.doe{i}@cern.ch" assert serialized_identities[i]["username"] == f"jdoe{i}" assert serialized_identities[i]["user_profile"] == { + "affiliations": "CERN", "cern_department": "IT", "cern_group": "CA", "cern_section": "IR", "family_name": f"Doe {i}", "full_name": f"John Doe {i}", "given_name": "John", - "institute_abbreviation": "CERN", - "institute": "CERN", "mailbox": "", "person_id": f"1234{i}", } diff --git a/tests/test_ldap_serializer.py b/tests/test_ldap_serializer.py index 388bf5a..3659574 100644 --- a/tests/test_ldap_serializer.py +++ b/tests/test_ldap_serializer.py @@ -26,16 +26,15 @@ def test_serialize_ldap_users(app, ldap_users): assert first_user["preferences"]["locale"] == "en" profile = first_user["user_profile"] + assert profile["affiliations"] == "CERN" assert profile["cern_department"] == "IT" + assert profile["cern_group"] == "CA" + assert profile["cern_section"] == "IR" assert profile["family_name"] == "Doe 0" assert profile["full_name"] == "John Doe 0" assert profile["given_name"] == "John" - assert profile["cern_group"] == "CA" - assert profile["institute_abbreviation"] == "CERN" - assert profile["institute"] == "CERN" assert profile["mailbox"] == "M123ABC0" assert profile["person_id"] == "12340" - assert profile["cern_section"] == "IR" extra_data = first_user["remote_account_extra_data"] assert extra_data["person_id"] == "12340" @@ -121,13 +120,12 @@ def test_serialize_ldap_users_missing_optional_fields(app): assert first_user["preferences"]["locale"] == "en" profile = first_user["user_profile"] + assert profile["affiliations"] == "" assert profile["cern_department"] == "" assert profile["family_name"] == "" assert profile["full_name"] == "" assert profile["given_name"] == "" assert profile["cern_group"] == "" - assert profile["institute_abbreviation"] == "" - assert profile["institute"] == "" assert profile["mailbox"] == "" assert profile["person_id"] == "12340" assert profile["cern_section"] == "" diff --git a/tests/test_sync_users.py b/tests/test_sync_users.py index 0f7125f..3f17830 100644 --- a/tests/test_sync_users.py +++ b/tests/test_sync_users.py @@ -13,6 +13,7 @@ from invenio_accounts.models import User from invenio_oauthclient.models import RemoteAccount, UserIdentity +from invenio_cern_sync.sso import cern_remote_app_name from invenio_cern_sync.users.sync import sync from invenio_cern_sync.utils import first_or_default, first_or_raise @@ -47,7 +48,7 @@ def _assert_log_called(mock_log_info): ) -def _assert_cern_identity(expected_identity, client_id, remote_app_name): +def _assert_cern_identity(expected_identity, client_id): """Assert CERN identity.""" user = User.query.filter_by(email=expected_identity["primaryAccountEmail"]).one() user_identity = UserIdentity.query.filter_by(id=expected_identity["personId"]).one() @@ -56,23 +57,20 @@ def _assert_cern_identity(expected_identity, client_id, remote_app_name): assert user.username == expected_identity["upn"] assert user.email == expected_identity["primaryAccountEmail"] profile = user.user_profile + assert profile["affiliations"] == expected_identity["affiliations"] assert profile["cern_department"] == expected_identity["cernDepartment"] assert profile["cern_group"] == expected_identity["cernGroup"] assert profile["cern_section"] == expected_identity["cernSection"] assert profile["family_name"] == expected_identity["lastName"] assert profile["full_name"] == expected_identity["displayName"] assert profile["given_name"] == expected_identity["firstName"] - assert ( - profile["institute_abbreviation"] == expected_identity["instituteAbbreviation"] - ) - assert profile["institute"] == expected_identity["instituteName"] assert profile["mailbox"] == expected_identity.get("postOfficeBox", "") assert profile["person_id"] == expected_identity["personId"] preferences = user.preferences assert preferences["locale"] == expected_identity["preferredCernLanguage"].lower() # assert user identity data assert user_identity.id_user == user.id - assert user_identity.method == remote_app_name + assert user_identity.method == cern_remote_app_name # assert remote account data assert remote_account.extra_data["person_id"] == expected_identity["personId"] assert remote_account.extra_data["uidNumber"] == expected_identity["uid"] @@ -91,13 +89,12 @@ def test_sync_authz( ): """Test sync with AuthZ.""" MockAuthZService.return_value.get_identities.return_value = cern_identities - client_id = app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] - remote_app_name = app.config["CERN_SYNC_REMOTE_APP_NAME"] + client_id = app.config["CERN_APP_CREDENTIALS"]["consumer_key"] results = sync(method="AuthZ") for expected_identity in list(cern_identities): - _assert_cern_identity(expected_identity, client_id, remote_app_name) + _assert_cern_identity(expected_identity, client_id) assert len(results) == len(cern_identities) _assert_log_called(mock_log_info) @@ -108,8 +105,7 @@ def test_sync_authz( def test_sync_ldap(mock_log_info, MockLdapClient, app, ldap_users): """Test sync with LDAP.""" MockLdapClient.return_value.get_primary_accounts.return_value = ldap_users - client_id = app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] - remote_app_name = app.config["CERN_SYNC_REMOTE_APP_NAME"] + client_id = app.config["CERN_APP_CREDENTIALS"]["consumer_key"] results = sync(method="LDAP") @@ -123,16 +119,15 @@ def test_sync_ldap(mock_log_info, MockLdapClient, app, ldap_users): assert user.username == first_or_raise(ldap_user, "cn").lower() assert user.email == email.lower() profile = user.user_profile + assert profile["affiliations"] == [ + first_or_default(ldap_user, "cernInstituteName") + ] assert profile["cern_department"] == first_or_default(ldap_user, "division") assert profile["cern_group"] == first_or_default(ldap_user, "cernGroup") assert profile["cern_section"] == first_or_default(ldap_user, "cernSection") assert profile["family_name"] == first_or_default(ldap_user, "sn") assert profile["full_name"] == first_or_default(ldap_user, "displayName") assert profile["given_name"] == first_or_default(ldap_user, "givenName") - assert profile["institute_abbreviation"] == first_or_default( - ldap_user, "cernInstituteAbbreviation" - ) - assert profile["institute"] == first_or_default(ldap_user, "cernInstituteName") assert profile["mailbox"] == first_or_default(ldap_user, "postOfficeBox") assert profile["person_id"] == person_id preferences = user.preferences @@ -142,7 +137,7 @@ def test_sync_ldap(mock_log_info, MockLdapClient, app, ldap_users): ) # assert user identity data assert user_identity.id_user == user.id - assert user_identity.method == remote_app_name + assert user_identity.method == cern_remote_app_name # assert remote account data assert remote_account.extra_data["person_id"] == first_or_raise( ldap_user, "employeeID" @@ -169,8 +164,7 @@ def test_sync_update_insert( ): """Test sync personId change.""" # prepare the db with the initial data - client_id = app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] - remote_app_name = app.config["CERN_SYNC_REMOTE_APP_NAME"] + client_id = app.config["CERN_APP_CREDENTIALS"]["consumer_key"] MockAuthZService.return_value.get_identities.return_value = cern_identities sync(method="AuthZ") @@ -208,7 +202,7 @@ def test_sync_update_insert( sync(method="AuthZ") for expected_identity in [first, new_user]: - _assert_cern_identity(expected_identity, client_id, remote_app_name) + _assert_cern_identity(expected_identity, client_id) @patch("invenio_cern_sync.users.sync.KeycloakService") @@ -223,8 +217,7 @@ def test_sync_person_id_change( ): """Test sync personId change.""" # prepare the db with the initial data - client_id = app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] - remote_app_name = app.config["CERN_SYNC_REMOTE_APP_NAME"] + client_id = app.config["CERN_APP_CREDENTIALS"]["consumer_key"] MockAuthZService.return_value.get_identities.return_value = cern_identities sync(method="AuthZ") @@ -268,8 +261,7 @@ def test_sync_username_email_change( ): """Test sync username/email change.""" # prepare the db with the initial data - client_id = app.config["CERN_SYNC_KEYCLOAK_CLIENT_ID"] - remote_app_name = app.config["CERN_SYNC_REMOTE_APP_NAME"] + client_id = app.config["CERN_APP_CREDENTIALS"]["consumer_key"] MockAuthZService.return_value.get_identities.return_value = cern_identities sync(method="AuthZ")