From 65af2d1a369defb69412f23d1a2b6c36f4d7bd34 Mon Sep 17 00:00:00 2001 From: Xander Smeets Date: Thu, 4 Apr 2024 23:02:38 +0200 Subject: [PATCH] Import changes from https://github.com/jacekkow/keycloak-protocol-cas/pull/49 --- .../protocol/cas/CASLoginProtocol.java | 6 ++ .../cas/endpoints/SamlValidateEndpoint.java | 3 +- .../endpoints/ServiceValidateEndpoint.java | 3 +- .../cas/mappers/CASUsernameMapper.java | 11 +++ .../UserAttributeCasUsernameMapper.java | 77 +++++++++++++++++++ .../cas/utils/UsernameMapperHelper.java | 32 ++++++++ .../org.keycloak.protocol.ProtocolMapper | 1 + 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/keycloak/protocol/cas/mappers/CASUsernameMapper.java create mode 100644 src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeCasUsernameMapper.java create mode 100644 src/main/java/org/keycloak/protocol/cas/utils/UsernameMapperHelper.java diff --git a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java index dfe8321..7b58d3c 100644 --- a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java +++ b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java @@ -14,6 +14,7 @@ import org.keycloak.models.*; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.cas.utils.LogoutHelper; +import org.keycloak.protocol.cas.utils.UsernameMapperHelper; import org.keycloak.protocol.oidc.utils.OAuth2Code; import org.keycloak.protocol.oidc.utils.OAuth2CodeParser; import org.keycloak.services.ErrorPage; @@ -95,6 +96,11 @@ public CASLoginProtocol setEventBuilder(EventBuilder event) { public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession(); + // Verify that if a username mapper is set - there is a username being returned + if(UsernameMapperHelper.getMappedUsername(session, clientSession) == null) { + return ErrorPage.error(session, authSession, Response.Status.INTERNAL_SERVER_ERROR, "Unable to map username for CAS client"); + } + String service = authSession.getRedirectUri(); //TODO validate service diff --git a/src/main/java/org/keycloak/protocol/cas/endpoints/SamlValidateEndpoint.java b/src/main/java/org/keycloak/protocol/cas/endpoints/SamlValidateEndpoint.java index 5442d70..00c9ac0 100644 --- a/src/main/java/org/keycloak/protocol/cas/endpoints/SamlValidateEndpoint.java +++ b/src/main/java/org/keycloak/protocol/cas/endpoints/SamlValidateEndpoint.java @@ -15,6 +15,7 @@ import org.keycloak.protocol.cas.representations.CASErrorCode; import org.keycloak.protocol.cas.representations.SamlResponseHelper; import org.keycloak.protocol.cas.utils.CASValidationException; +import org.keycloak.protocol.cas.utils.UsernameMapperHelper; import org.keycloak.services.Urls; import org.xml.sax.InputSource; @@ -61,7 +62,7 @@ public Response validate(String input) { Map attributes = getUserAttributes(); - SAML11ResponseType response = SamlResponseHelper.successResponse(issuer, user.getUsername(), attributes); + SAML11ResponseType response = SamlResponseHelper.successResponse(issuer, UsernameMapperHelper.getMappedUsername(session,clientSession), attributes); return Response.ok(SamlResponseHelper.soap(response)).build(); diff --git a/src/main/java/org/keycloak/protocol/cas/endpoints/ServiceValidateEndpoint.java b/src/main/java/org/keycloak/protocol/cas/endpoints/ServiceValidateEndpoint.java index c0c1e2b..aaa80c1 100644 --- a/src/main/java/org/keycloak/protocol/cas/endpoints/ServiceValidateEndpoint.java +++ b/src/main/java/org/keycloak/protocol/cas/endpoints/ServiceValidateEndpoint.java @@ -10,6 +10,7 @@ import org.keycloak.protocol.cas.utils.CASValidationException; import org.keycloak.protocol.cas.utils.ContentTypeHelper; import org.keycloak.protocol.cas.utils.ServiceResponseHelper; +import org.keycloak.protocol.cas.utils.UsernameMapperHelper; import java.util.Map; @@ -22,7 +23,7 @@ public ServiceValidateEndpoint(KeycloakSession session, RealmModel realm, EventB protected Response successResponse() { UserSessionModel userSession = clientSession.getUserSession(); Map attributes = getUserAttributes(); - CASServiceResponse serviceResponse = ServiceResponseHelper.createSuccess(userSession.getUser().getUsername(), attributes); + CASServiceResponse serviceResponse = ServiceResponseHelper.createSuccess(UsernameMapperHelper.getMappedUsername(session,clientSession), attributes); return prepare(Response.Status.OK, serviceResponse); } diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/CASUsernameMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/CASUsernameMapper.java new file mode 100644 index 0000000..0f6e310 --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/CASUsernameMapper.java @@ -0,0 +1,11 @@ +package org.keycloak.protocol.cas.mappers; + +import org.keycloak.models.*; +import org.keycloak.protocol.ProtocolMapper; + +public interface CASUsernameMapper extends ProtocolMapper { + + String getMappedUsername(ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, AuthenticatedClientSessionModel clientSession); + +} \ No newline at end of file diff --git a/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeCasUsernameMapper.java b/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeCasUsernameMapper.java new file mode 100644 index 0000000..caa998e --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/mappers/UserAttributeCasUsernameMapper.java @@ -0,0 +1,77 @@ +package org.keycloak.protocol.cas.mappers; + +import org.keycloak.Config; +import org.keycloak.models.*; +import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.cas.CASLoginProtocol; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.ArrayList; +import java.util.List; + +public class UserAttributeCasUsernameMapper extends AbstractCASProtocolMapper implements CASUsernameMapper { + public static final String PROVIDER_ID = "cas-usermodel-username-mapper"; + public static final String USERNAME_MAPPER_CATEGORY = "CAS Username Mapper"; + private static final String CONF_FALLBACK_TO_USERNAME_IF_NULL = "username_fallback"; + + private static final List configProperties = new ArrayList(); + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); + property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); + configProperties.add(property); + + property = new ProviderConfigProperty(); + property.setName(CONF_FALLBACK_TO_USERNAME_IF_NULL); + property.setLabel("Use username if attribute is missing"); + property.setHelpText("Should the User's username be used if the specified attribute is blank?"); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue(false); + configProperties.add(property); + + + } + + @Override + public final String getDisplayCategory() { + return USERNAME_MAPPER_CATEGORY; + } + + @Override + public final String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Attribute Mapper For CAS Username"; + } + + @Override + public String getHelpText() { + return "Maps a user attribute to CAS Username value."; + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getMappedUsername(ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) { + + boolean defaultIfNull = Boolean.parseBoolean(mappingModel.getConfig().get(CONF_FALLBACK_TO_USERNAME_IF_NULL)); + UserModel user = userSession.getUser(); + String mappedUsername = user.getFirstAttribute(mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE)); + + if(mappedUsername == null && defaultIfNull) { + mappedUsername = user.getUsername(); + } + return mappedUsername; + } +} \ No newline at end of file diff --git a/src/main/java/org/keycloak/protocol/cas/utils/UsernameMapperHelper.java b/src/main/java/org/keycloak/protocol/cas/utils/UsernameMapperHelper.java new file mode 100644 index 0000000..907cdfa --- /dev/null +++ b/src/main/java/org/keycloak/protocol/cas/utils/UsernameMapperHelper.java @@ -0,0 +1,32 @@ +package org.keycloak.protocol.cas.utils; + +import org.keycloak.models.*; +import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.cas.mappers.CASUsernameMapper; +import org.keycloak.services.util.DefaultClientSessionContext; + +import java.util.Map; + +public class UsernameMapperHelper { + public static String getMappedUsername(KeycloakSession session, AuthenticatedClientSessionModel clientSession) { + // CAS protocol does not support scopes, so pass null scopeParam + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, null, session); + UserSessionModel userSession = clientSession.getUserSession(); + + + Map.Entry mapperPair = ProtocolMapperUtils.getSortedProtocolMappers(session,clientSessionCtx) + .filter(e -> e.getValue() instanceof CASUsernameMapper) + .findFirst() + .orElse(null); + + String mappedUsername = userSession.getUser().getUsername(); + + if(mapperPair != null) { + ProtocolMapperModel mapping = mapperPair.getKey(); + CASUsernameMapper casUsernameMapper = (CASUsernameMapper) mapperPair.getValue(); + mappedUsername = casUsernameMapper.getMappedUsername(mapping, session, userSession, clientSession); + } + return mappedUsername; + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 353f1c8..17f03ec 100644 --- a/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -23,3 +23,4 @@ org.keycloak.protocol.cas.mappers.UserClientRoleMappingMapper org.keycloak.protocol.cas.mappers.UserPropertyMapper org.keycloak.protocol.cas.mappers.UserRealmRoleMappingMapper org.keycloak.protocol.cas.mappers.UserSessionNoteMapper +org.keycloak.protocol.cas.mappers.UserAttributeCasUsernameMapper