From 7406f805877ca04b583c13bff5b6e4b67da9dfa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20D=C3=A9nari=C3=A9?= Date: Tue, 20 Feb 2024 09:09:16 +0100 Subject: [PATCH 1/2] feat: [SAML] Allow to identify user by email - meeds-io/meeds#1615 - EXO-69432 Before this fix, SAML authentication require username as subject in the SAML assertion response. As for the login (in which we are able to log with username or email), we would propose to have the email as subject of the assertion. --- .../valve/AbstractSPFormAuthenticator.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/saml/gatein-saml-plugin/src/main/java/org/gatein/sso/saml/plugin/valve/AbstractSPFormAuthenticator.java b/saml/gatein-saml-plugin/src/main/java/org/gatein/sso/saml/plugin/valve/AbstractSPFormAuthenticator.java index 37e42368a..3af999fa9 100644 --- a/saml/gatein-saml-plugin/src/main/java/org/gatein/sso/saml/plugin/valve/AbstractSPFormAuthenticator.java +++ b/saml/gatein-saml-plugin/src/main/java/org/gatein/sso/saml/plugin/valve/AbstractSPFormAuthenticator.java @@ -27,6 +27,10 @@ import org.apache.catalina.connector.Response; import org.apache.catalina.realm.GenericPrincipal; import org.apache.tomcat.util.descriptor.web.LoginConfig; +import org.exoplatform.commons.utils.ListAccess; +import org.exoplatform.services.organization.Query; +import org.exoplatform.services.organization.User; +import org.exoplatform.services.organization.UserHandler; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.container.PortalContainer; @@ -528,6 +532,8 @@ private boolean handleSAML2Response(Request request, Response response, LoginCon String username = principal.getName(); String password = ServiceProviderSAMLContext.EMPTY_PASSWORD; + + username = checkForEmail(username); roles.addAll(extractGateinRoles(username)); if (logger.isTraceEnabled()) { @@ -784,4 +790,26 @@ private boolean isAjaxRequest(Request request) { String requestedWithHeader = request.getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH); return requestedWithHeader != null && "XMLHttpRequest".equalsIgnoreCase(requestedWithHeader); } + + private String checkForEmail(String username) { + //allow to use email as identifier in SAML assertion + //if username is an email, we read the related user, and return his username, if there is only one result with this email + try { + if (username.contains("@")) { + OrganizationService organizationService = PortalContainer.getInstance().getComponentInstanceOfType(OrganizationService.class); + UserHandler userHandler = organizationService.getUserHandler(); + Query emailQuery = new Query(); + emailQuery.setEmail(username); + ListAccess users; + users = userHandler.findUsersByQuery(emailQuery); + if (users != null && users.getSize() == 1) { + return users.load(0, 1)[0].getUserName(); + } + } + } catch (Exception e) { + logger.samlSPHandleRequestError(e); + return null; + } + return username; + } } From c403a354ca62ed684ecfa80de78c64525ff588c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20D=C3=A9nari=C3=A9?= Date: Tue, 20 Feb 2024 09:11:45 +0100 Subject: [PATCH 2/2] feat: Remove unused class - EXO-69702 - Meeds-io/meeds#1647 The class SAML2IntegrationLoginModule is never used during SAML workflow This commit remove the class --- .../login/SAML2IntegrationLoginModule.java | 177 ------------------ 1 file changed, 177 deletions(-) delete mode 100644 agent/src/main/java/org/gatein/sso/agent/login/SAML2IntegrationLoginModule.java diff --git a/agent/src/main/java/org/gatein/sso/agent/login/SAML2IntegrationLoginModule.java b/agent/src/main/java/org/gatein/sso/agent/login/SAML2IntegrationLoginModule.java deleted file mode 100644 index 7d1c2cfb7..000000000 --- a/agent/src/main/java/org/gatein/sso/agent/login/SAML2IntegrationLoginModule.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * JBoss, a division of Red Hat - * Copyright 2012, Red Hat Middleware, LLC, and individual - * contributors as indicated by the @authors tag. See the - * copyright.txt in the distribution for a full listing of - * individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ - -package org.gatein.sso.agent.login; - -import org.exoplatform.container.ExoContainer; -import org.exoplatform.container.ExoContainerContext; -import org.exoplatform.container.PortalContainer; -import org.exoplatform.container.RootContainer; -import org.exoplatform.services.security.Authenticator; -import org.exoplatform.services.security.Identity; -import org.exoplatform.services.security.UsernameCredential; -import org.picketlink.identity.federation.bindings.jboss.auth.SAML2LoginModule; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginException; -import java.security.Principal; -import java.util.Map; - -/** - * Login module for integration with GateIn. It's running on GateIn (SAML SP) side. - * - * @author Marek Posolda - */ -public class SAML2IntegrationLoginModule extends SAML2LoginModule -{ - // Name of security-domain (actually not used by this impl) - private static final String OPTION_REALM_NAME = "realmName"; - - // Name of portalContainer - private static final String OPTION_PORTAL_CONTAINER_NAME = "portalContainerName"; - - // If this boolean property is true, then final principal will use roles from SAML. - // If false, then we don't use roles from SAML, but we will delegate filling of "Roles" principal to next login module in stack - // (actually it is JbossLoginModule, which uses JAAS roles from GateIn database) - // Default value is false, so we are preferring delegation to JbossLoginModule and using roles from portal DB. - private static final String OPTION_USE_SAML_ROLES = "useSAMLRoles"; - - private static final String[] ALL_VALID_OPTIONS = - { - OPTION_PORTAL_CONTAINER_NAME, - OPTION_REALM_NAME, - OPTION_USE_SAML_ROLES - }; - - private String portalContainerName; - private boolean useSAMLRoles; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, - Map options) - { - try - { - addValidOptions(ALL_VALID_OPTIONS); - } - catch (NoSuchMethodError ignore) - { - // Method addValidOptions is new in picketbox. Not supported on older AS 7.1.1 - } - - super.initialize(subject, callbackHandler, sharedState, options); - - // GateIn integration - this.portalContainerName = getPortalContainerName(options); - - String useSAMLRoles = (String)options.get(OPTION_USE_SAML_ROLES); - this.useSAMLRoles = useSAMLRoles != null && "true".equals(useSAMLRoles); - - if (log.isTraceEnabled()) - { - log.trace("Using options: " - + OPTION_PORTAL_CONTAINER_NAME + "=" + this.portalContainerName - + ", " + OPTION_USE_SAML_ROLES + "=" + this.useSAMLRoles); - } - } - - @Override - public boolean login() throws javax.security.auth.login.LoginException - { - if (super.login()) - { - // Username is already in sharedState thanks to superclass - String username = getUsernameFromSharedState(); - if (log.isTraceEnabled()) - { - log.trace("Found user " + username + " in shared state."); - } - - try - { - //Perform authentication by setting up the proper Application State - Authenticator authenticator = (Authenticator) getContainer().getComponentInstanceOfType(Authenticator.class); - - Identity identity = authenticator.createIdentity(username); - sharedState.put("exo.security.identity", identity); - subject.getPublicCredentials().add(new UsernameCredential(username)); - - return true; - } - catch (Exception e) - { - log.debug("Exception during login process: " + e.getMessage(), e); - throw new LoginException(e.getMessage()); - } - } - else - { - return false; - } - } - - protected String getUsernameFromSharedState() - { - Object tmp = sharedState.get("javax.security.auth.login.name"); - if (tmp == null) - { - return null; - } - else if (tmp instanceof Principal) - { - return ((Principal) tmp).getName(); - } - else - { - return (String)tmp; - } - } - - - // *********************** Helper private methods for GateIn integration ***************************** - - private String getPortalContainerName(Map options) - { - if (options != null) - { - String optionValue = (String) options.get(OPTION_PORTAL_CONTAINER_NAME); - if (optionValue != null && optionValue.length() > 0) - { - return optionValue; - } - } - return PortalContainer.DEFAULT_PORTAL_CONTAINER_NAME; - } - - private ExoContainer getContainer() throws Exception - { - ExoContainer container = ExoContainerContext.getCurrentContainer(); - if (container instanceof RootContainer) - { - container = RootContainer.getInstance().getPortalContainer(portalContainerName); - } - return container; - } - -}