package org.craftercms.security.social.impl; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.craftercms.commons.crypto.CryptoException; import org.craftercms.commons.crypto.TextEncryptor; import org.craftercms.profile.api.Profile; import org.craftercms.profile.api.exceptions.ProfileException; import org.craftercms.profile.api.services.ProfileService; import org.craftercms.security.authentication.Authentication; import org.craftercms.security.authentication.AuthenticationManager; import org.craftercms.security.exception.AuthenticationException; import org.craftercms.security.exception.OAuth2Exception; import org.craftercms.security.social.ProviderLoginSupport; import org.craftercms.security.utils.SecurityUtils; import org.craftercms.security.utils.social.ConnectionUtils; import org.springframework.beans.factory.annotation.Required; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionFactory; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.support.OAuth1ConnectionFactory; import org.springframework.social.connect.support.OAuth2ConnectionFactory; import org.springframework.social.connect.web.ConnectSupport; import org.springframework.util.MultiValueMap; import org.springframework.web.context.request.ServletWebRequest; /** * Default implementation of {@link ProviderLoginSupport}. On {@link #complete(String, String, HttpServletRequest)}, if the * user data of the provider connection corresponds to an existing Crafter Profile user, the profile connection data * will be updated. If a profile doesn't exist, a new one with the connection data will be created. In both cases, the * user is automatically authenticated with Crafter Profile. * * @author avasquez */ public class ProviderLoginSupportImpl implements ProviderLoginSupport { public static final String PARAM_OAUTH_TOKEN = "oauth_token"; public static final String PARAM_CODE = "code"; public static final String PARAM_ERROR = "error"; public static final String PARAM_ERROR_DESCRIPTION = "error_description"; public static final String PARAM_ERROR_URI = "error_uri"; protected ConnectSupport connectSupport; protected ConnectionFactoryLocator connectionFactoryLocator; protected ProfileService profileService; protected AuthenticationManager authenticationManager; protected TextEncryptor textEncryptor; public ProviderLoginSupportImpl() { connectSupport = new ConnectSupport(); } public void setConnectSupport(ConnectSupport connectSupport) { this.connectSupport = connectSupport; } @Required public void setConnectionFactoryLocator(ConnectionFactoryLocator connectionFactoryLocator) { this.connectionFactoryLocator = connectionFactoryLocator; } @Required public void setProfileService(ProfileService profileService) { this.profileService = profileService; } @Required public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Required public void setTextEncryptor(TextEncryptor textEncryptor) { this.textEncryptor = textEncryptor; } @Override public String start(String tenant, String providerId, HttpServletRequest request) throws AuthenticationException { return start(tenant, providerId, request, null, null); } @Override public String start(String tenant, String providerId, HttpServletRequest request, MultiValueMap<String, String> additionalUrlParams) throws AuthenticationException { return start(tenant, providerId, request, additionalUrlParams, null); } @Override public String start(String tenant, String providerId, HttpServletRequest request, MultiValueMap<String, String> additionalUrlParams, ConnectSupport connectSupport) throws AuthenticationException { if (connectSupport == null) { connectSupport = this.connectSupport; } ConnectionFactory<?> connectionFactory = getConnectionFactory(providerId); ServletWebRequest webRequest = new ServletWebRequest(request); return connectSupport.buildOAuthUrl(connectionFactory, webRequest, additionalUrlParams); } @Override public Authentication complete(String tenant, String providerId, HttpServletRequest request) throws AuthenticationException { return complete(tenant, providerId, request, null, null, null); } @Override public Authentication complete(String tenant, String providerId, HttpServletRequest request, Set<String> newUserRoles, Map<String, Object> newUserAttributes) throws AuthenticationException { return complete(tenant, providerId, request, newUserRoles, newUserAttributes, null); } @Override public Authentication complete(String tenant, String providerId, HttpServletRequest request, Set<String> newUserRoles, Map<String, Object> newUserAttributes, ConnectSupport connectSupport) throws AuthenticationException { if (connectSupport == null) { connectSupport = this.connectSupport; } Connection<?> connection = completeConnection(connectSupport, providerId, request); if (connection != null) { Profile userData = ConnectionUtils.createProfile(connection); Profile profile = getProfile(tenant, userData); if (profile == null) { if (CollectionUtils.isNotEmpty(newUserRoles)) { userData.getRoles().addAll(newUserRoles); } if (MapUtils.isNotEmpty(newUserAttributes)) { userData.getAttributes().putAll(newUserAttributes); } profile = createProfile(tenant, connection, userData); } else { profile = updateProfileConnectionData(tenant, connection, profile); } Authentication auth = authenticationManager.authenticateUser(profile); SecurityUtils.setAuthentication(request, auth); return auth; } else { return null; } } protected Connection<?> completeConnection(ConnectSupport connectSupport, String providerId, HttpServletRequest request) throws OAuth2Exception { if (StringUtils.isNotEmpty(request.getParameter(PARAM_OAUTH_TOKEN))) { OAuth1ConnectionFactory<?> connectionFactory = (OAuth1ConnectionFactory<?>)getConnectionFactory(providerId); ServletWebRequest webRequest = new ServletWebRequest(request); return connectSupport.completeConnection(connectionFactory, webRequest); } else if (StringUtils.isNotEmpty(request.getParameter(PARAM_CODE))) { OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>)getConnectionFactory(providerId); ServletWebRequest webRequest = new ServletWebRequest(request); return connectSupport.completeConnection(connectionFactory, webRequest); } else if (StringUtils.isNotEmpty(request.getParameter(PARAM_ERROR))) { String error = request.getParameter(PARAM_ERROR); String errorDescription = request.getParameter(PARAM_ERROR_DESCRIPTION); String errorUri = request.getParameter(PARAM_ERROR_URI); throw new OAuth2Exception(error, errorDescription, errorUri); } else { return null; } } protected ConnectionFactory<?> getConnectionFactory(String providerId) { return connectionFactoryLocator.getConnectionFactory(providerId); } protected Profile getProfile(String tenant, Profile userData) { try { return profileService.getProfileByUsername(tenant, userData.getUsername()); } catch (ProfileException e) { throw new AuthenticationException("Unable to retrieve current profile for user '" + userData.getUsername() + "' of tenant '" + tenant + "'", e); } } protected Profile createProfile(String tenant, Connection<?> connection, Profile userData) { try { ConnectionUtils.addConnectionData(userData, connection.createData(), textEncryptor); return profileService.createProfile(tenant, userData.getUsername(), null, userData.getEmail(), true, userData.getRoles(), userData.getAttributes(), null); } catch (CryptoException | ProfileException e) { throw new AuthenticationException("Unable to create profile of user '" + userData.getUsername() + "' in tenant '" + tenant + "'", e); } } protected Profile updateProfileConnectionData(String tenant, Connection<?> connection, Profile profile) { try { ConnectionUtils.addConnectionData(profile, connection.createData(), textEncryptor); return profileService.updateAttributes(profile.getId().toString(), profile.getAttributes()); } catch (CryptoException | ProfileException e) { throw new AuthenticationException("Unable to update connection data of user '" + profile.getUsername() + "' of tenant '" + tenant + "'", e); } } }