/* * Copyright (C) 2015 eXo Platform SAS. * * 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.exoplatform.oauth.filter; import org.exoplatform.commons.utils.ListAccess; import org.exoplatform.oauth.OAuthConst; import org.exoplatform.services.organization.DisabledUserException; import org.exoplatform.services.organization.OrganizationService; import org.exoplatform.services.organization.Query; import org.exoplatform.services.organization.User; import org.exoplatform.services.organization.UserProfile; import org.exoplatform.services.organization.UserProfileHandler; import org.exoplatform.services.organization.UserStatus; import org.exoplatform.services.resources.ResourceBundleService; import org.exoplatform.services.security.Authenticator; import org.exoplatform.services.security.Credential; import org.exoplatform.services.security.PasswordCredential; import org.exoplatform.services.security.UsernameCredential; import org.exoplatform.web.application.AbstractApplicationMessage; import org.exoplatform.web.security.AuthenticationRegistry; import org.exoplatform.webui.exception.MessageException; import org.exoplatform.webui.form.UIForm; import org.exoplatform.webui.form.UIFormStringInput; import org.exoplatform.webui.form.validator.MandatoryValidator; import org.exoplatform.webui.form.validator.PasswordStringLengthValidator; import org.exoplatform.webui.form.validator.PersonalNameValidator; import org.exoplatform.webui.form.validator.StringLengthValidator; import org.exoplatform.webui.form.validator.UserConfigurableValidator; import org.exoplatform.webui.form.validator.Validator; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; import org.gatein.security.oauth.common.OAuthConstants; import org.gatein.security.oauth.exception.OAuthException; import org.gatein.security.oauth.exception.OAuthExceptionCode; import org.gatein.security.oauth.spi.OAuthPrincipal; import org.gatein.security.oauth.spi.OAuthProviderType; import org.gatein.security.oauth.utils.OAuthUtils; import javax.security.auth.login.LoginException; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.ResourceBundle; import java.util.Set; /** * @author <a href="mailto:tuyennt@exoplatform.com">Tuyen Nguyen The</a>. */ public class OAuthLoginServletFilter extends OAuthAbstractFilter { private static Logger log = LoggerFactory.getLogger(OAuthLoginServletFilter.class); public static final String CONTROLLER_PARAM_NAME = "login_controller"; public static final String CANCEL_OAUTH = "oauth_cancel"; public static final String CONFIRM_ACCOUNT = "confirm_account"; public static final String REGISTER_NEW_ACCOUNT = "register"; public static final String CONFIRM_REGISTER_ACCOUNT = "submit_register"; public static final String SESSION_ATTR_REGISTER_NEW_ACCOUNT = "__oauth_create_new_account"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // This is workaround to fix bug: // - error message is shown after user cancel his oauth login then do login with username and password ((HttpServletRequest)request).getSession().removeAttribute(OAuthConstants.ATTRIBUTE_EXCEPTION_OAUTH); super.doFilter(request, response, chain); } @Override protected void executeFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding("UTF-8"); AuthenticationRegistry authReg = getService(AuthenticationRegistry.class); ResourceBundleService service = getService(ResourceBundleService.class); ResourceBundle bundle = service.getResourceBundle(service.getSharedResourceBundleNames(), req.getLocale()) ; MessageResolver messageResolver = new MessageResolver(bundle); ServletContext context = getContext(); Boolean isOnFlyError = (Boolean)req.getSession().getAttribute(OAuthConst.SESSION_KEY_ON_FLY_ERROR); if (isOnFlyError != null) { req.getSession().removeAttribute(OAuthConst.SESSION_KEY_ON_FLY_ERROR); req.setAttribute("isOnFlyError", isOnFlyError); } User portalUser = (User) authReg.getAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER); User detectedUser = (User)authReg.getAttributeOfClient(req, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED); req.setAttribute("portalUser", portalUser); if (detectedUser != null) { req.setAttribute("detectedUser", detectedUser); } final String controller = req.getParameter(CONTROLLER_PARAM_NAME); if (CANCEL_OAUTH.equalsIgnoreCase(controller)) { cancelOauth(req, res, authReg); return; } else if (CONFIRM_ACCOUNT.equalsIgnoreCase(controller)) { confirmExistingAccount(req, res, messageResolver, authReg); return; } else if (CONFIRM_REGISTER_ACCOUNT.equalsIgnoreCase(controller)) { processCreateNewAccount(req, res, messageResolver, authReg, portalUser); return; } else if (REGISTER_NEW_ACCOUNT.equalsIgnoreCase(controller)) { req.getSession(true).setAttribute(SESSION_ATTR_REGISTER_NEW_ACCOUNT, new Integer(1)); } Integer createNewAccount = (Integer)req.getSession().getAttribute(SESSION_ATTR_REGISTER_NEW_ACCOUNT); if(detectedUser != null && createNewAccount == null) { RequestDispatcher invitation = context.getRequestDispatcher("/login/jsp/oauth_invitation.jsp"); if(invitation != null) { invitation.forward(req, res); return; } } else { RequestDispatcher register = context.getRequestDispatcher("/login/jsp/oauth_register.jsp"); if(register != null) { register.forward(req, res); return; } } chain.doFilter(req, res); } private void cancelOauth(HttpServletRequest req, HttpServletResponse res, AuthenticationRegistry authReg) throws IOException { req.getSession().removeAttribute(SESSION_ATTR_REGISTER_NEW_ACCOUNT); authReg.removeAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL); authReg.removeAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER); authReg.removeAttributeOfClient(req, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED); //. Redirect to last URL String initialURL = OAuthUtils.getURLToRedirectAfterLinkAccount(req, req.getSession()); if(initialURL == null) { initialURL = req.getServletPath(); } res.sendRedirect(res.encodeRedirectURL(initialURL)); } private void confirmExistingAccount(HttpServletRequest req, HttpServletResponse res, MessageResolver bundle, AuthenticationRegistry authReg) throws IOException, ServletException { String username; User detectedUser = (User)authReg.getAttributeOfClient(req, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED); if(detectedUser != null) { username = detectedUser.getUserName(); } else { req.setAttribute("invitationConfirmError", bundle.resolve("UIOAuthInvitationForm.message.not-in-oauth-login")); getContext().getRequestDispatcher("/login/jsp/oauth_invitation.jsp").forward(req, res); return; } String password = req.getParameter("password"); Credential[] credentials = new Credential[]{new UsernameCredential(username), new PasswordCredential(password)}; Authenticator authenticator = getService(Authenticator.class); try { OrganizationService orgService = getService(OrganizationService.class); String user = authenticator.validateUser(credentials); if(user != null && !user.isEmpty()) { //Update authentication OAuthPrincipal principal = (OAuthPrincipal)authReg.getAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL); OAuthProviderType providerType = principal.getOauthProviderType(); UserProfileHandler profileHandler = orgService.getUserProfileHandler(); UserProfile newUserProfile = profileHandler.findUserProfileByName(user); if (newUserProfile == null) { newUserProfile = orgService.getUserProfileHandler().createUserProfileInstance(user); } newUserProfile.setAttribute(providerType.getUserNameAttrName(), principal.getUserName()); profileHandler.saveUserProfile(newUserProfile, true); //. Redirect to last URL authReg.removeAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER); authReg.removeAttributeOfClient(req, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED); String initURL = OAuthUtils.getURLToRedirectAfterLinkAccount(req, req.getSession()); if(initURL == null) { initURL = req.getServletPath(); } res.sendRedirect(res.encodeRedirectURL(initURL)); } } catch (LoginException ex) { Exception e = authenticator.getLastExceptionOnValidateUser(); if (e != null && e instanceof DisabledUserException) { req.setAttribute("invitationConfirmError", bundle.resolve("UIOAuthInvitationForm.message.userDisabled")); } else { req.setAttribute("invitationConfirmError", bundle.resolve("UIOAuthInvitationForm.message.loginFailure")); } } catch (Exception ex) { req.setAttribute("invitationConfirmError", bundle.resolve("UIOAuthInvitationForm.message.loginUnknowException")); log.warn("Exception while checking password of user", ex); } getContext().getRequestDispatcher("/login/jsp/oauth_invitation.jsp").forward(req, res); } private void processCreateNewAccount(HttpServletRequest req, HttpServletResponse res, MessageResolver bundle, AuthenticationRegistry authReg, User portalUser) throws IOException, ServletException { String username = req.getParameter("username"); String password = req.getParameter("password"); String password2 = req.getParameter("password2"); String firstName = req.getParameter("firstName"); String lastName = req.getParameter("lastName"); String displayName = req.getParameter("displayName"); String email = req.getParameter("email"); portalUser.setUserName(username); portalUser.setPassword(password); portalUser.setFirstName(firstName); portalUser.setLastName(lastName); portalUser.setDisplayName(displayName); portalUser.setEmail(email); List<String> errors = new ArrayList<String>(); Set<String> errorFields = new HashSet<String>(); OrganizationService orgService = getService(OrganizationService.class); validateUser(portalUser, password2, orgService, bundle, errors, errorFields); if(errors.size() == 0) { try { orgService.getUserHandler().createUser(portalUser, true); UserProfileHandler profileHandler = orgService.getUserProfileHandler(); UserProfile newUserProfile = profileHandler.findUserProfileByName(portalUser.getUserName()); if (newUserProfile == null) { newUserProfile = orgService.getUserProfileHandler().createUserProfileInstance(portalUser.getUserName()); } OAuthPrincipal oauthPrincipal = (OAuthPrincipal)authReg.getAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL); newUserProfile.setAttribute(oauthPrincipal.getOauthProviderType().getUserNameAttrName(), oauthPrincipal.getUserName()); try { profileHandler.saveUserProfile(newUserProfile, true); authReg.removeAttributeOfClient(req, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER); authReg.removeAttributeOfClient(req, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED); // Successfully to register new account // Just refresh then oauth lifecycle will continue process res.sendRedirect(getContext().getContextPath()); return; } catch (OAuthException gtnOAuthException) { // Show warning message if user with this facebookUsername (or googleUsername) already exists // NOTE: It could happen only in case of parallel registration of same oauth user from more browser windows if (gtnOAuthException.getExceptionCode() == OAuthExceptionCode.DUPLICATE_OAUTH_PROVIDER_USERNAME) { // Drop new user orgService.getUserHandler().removeUser(portalUser.getUserName(), true); // Clear previous message about successful creation of user because we dropped him. Add message about duplicate oauth username errors.add(bundle.resolve("UIAccountSocial.msg.failed-registration", gtnOAuthException.getExceptionAttribute(OAuthConstants.EXCEPTION_OAUTH_PROVIDER_USERNAME), gtnOAuthException.getExceptionAttribute(OAuthConstants.EXCEPTION_OAUTH_PROVIDER_NAME))); } else { log.warn("Unknown oauth error", gtnOAuthException); errors.add(bundle.resolve("UIAccountSocial.msg.oauth-error")); } } } catch (Exception ex) { log.warn("Exception when create new account for user", ex); errors.add(bundle.resolve("UIAccountInputSet.msg.fail.create.user")); } } req.setAttribute("register_errors", errors); req.setAttribute("register_error_fields", errorFields); getContext().getRequestDispatcher("/login/jsp/oauth_register.jsp").forward(req, res); } private void validateUser(User user, String password2, OrganizationService orgService, MessageResolver bundle, List<String> errorMessages, Set<String> errorFields) { Validator validator; Validator mandatory = new MandatoryValidator(); Validator stringLength; ResourceBundle rb = bundle.getBundle(); // String username = user.getUserName(); validator = new UserConfigurableValidator(UserConfigurableValidator.USERNAME, UserConfigurableValidator.DEFAULT_LOCALIZATION_KEY); validate("username", username, new Validator[]{mandatory, validator}, rb, errorMessages, errorFields); if (!errorFields.contains("username")) { try { if (orgService.getUserHandler().findUserByName(username, UserStatus.ANY) != null) { errorFields.add("username"); errorMessages.add(bundle.resolve("UIAccountInputSet.msg.user-exist", username)); } } catch (Exception ex) { log.warn("Can not check username exist or not for: " + username); } } // String password = user.getPassword(); validator = new PasswordStringLengthValidator(6, 30); validate("password", password, new Validator[]{mandatory, validator}, rb, errorMessages, errorFields); if (!errorFields.contains("password")) { if (!password.equals(password2)) { errorMessages.add(bundle.resolve("UIAccountForm.msg.password-is-not-match")); errorFields.add("password2"); } } stringLength = new StringLengthValidator(1, 45); validator = new PersonalNameValidator(); String firstName = user.getFirstName(); String lastName = user.getLastName(); validate("firstName", firstName, new Validator[]{mandatory, stringLength, validator}, rb, errorMessages, errorFields); validate("lastName", lastName, new Validator[]{mandatory, stringLength, validator}, rb, errorMessages, errorFields); stringLength = new StringLengthValidator(0, 90); validator = new UserConfigurableValidator("displayname", UserConfigurableValidator.KEY_PREFIX + "displayname", false); String displayName = user.getDisplayName(); validate("displayName", displayName, new Validator[]{stringLength, validator}, rb, errorMessages, errorFields); // validator = new UserConfigurableValidator(UserConfigurableValidator.EMAIL); String email = user.getEmail(); validate("emailAddress", email, new Validator[]{mandatory, validator}, rb, errorMessages, errorFields); if (!errorFields.contains("emailAddress")) { try { Query query = new Query(); query.setEmail(email); ListAccess<User> users = orgService.getUserHandler().findUsersByQuery(query, UserStatus.ANY); if (users != null && users.getSize() > 0) { errorFields.add("emailAddress"); errorMessages.add(bundle.resolve("UIAccountInputSet.msg.email-exist", email)); } } catch (Exception ex) { log.warn("Can not check email exist or not for: " + email); } } } private void validate(String field, String value, Validator[] validators, final ResourceBundle bundle, List<String> errorMessages, Set<String> errorFields) { try { UIForm form = new UIForm() { @Override public String getLabel(String label) throws Exception { return this.getLabel(bundle, label); } }; form.setId("UIRegisterForm"); UIFormStringInput uiFormStringInput = new UIFormStringInput(field, field, value); form.addUIFormInput(uiFormStringInput); for(Validator validator : validators) { validator.validate(uiFormStringInput); } } catch (Exception e) { errorFields.add(field); if (e instanceof MessageException) { MessageException mex = (MessageException)e; AbstractApplicationMessage msg = mex.getDetailMessage(); msg.setResourceBundle(bundle); errorMessages.add(msg.getMessage()); } else { log.debug(e); errorMessages.add(field + " error"); } } } public static class MessageResolver { private final ResourceBundle bundle; public MessageResolver(ResourceBundle bundle) { this.bundle = bundle; } public String resolve(String key, Object... args) { try { String message = bundle.getString(key); if (message != null && args != null) { for (int i = 0; i < args.length; i++) { final Object messageArg = args[i]; if (messageArg != null) { String arg = messageArg.toString(); message = message.replace("{" + i + "}", arg); } } } return message; } catch (Exception ex) { return key; } } public ResourceBundle getBundle() { return bundle; } } }