From 5c35ffda23408671d3be033b5cb8e0d96339a4b7 Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Sun, 20 Dec 2020 11:41:51 +0100 Subject: [PATCH] Controller for EE MVC based login app --- .../libreccm/ui/login/LoginApplication.java | 44 ++++++ .../libreccm/ui/login/LoginController.java | 143 ++++++++++++++++++ .../org/libreccm/ui/login/LoginMessages.java | 137 +++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 ccm-core/src/main/java/org/libreccm/ui/login/LoginApplication.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/login/LoginMessages.java diff --git a/ccm-core/src/main/java/org/libreccm/ui/login/LoginApplication.java b/ccm-core/src/main/java/org/libreccm/ui/login/LoginApplication.java new file mode 100644 index 000000000..10d821a8f --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/login/LoginApplication.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.login; + +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * + * @author Jens Pelzetter + */ +@ApplicationPath("/@login") +public class LoginApplication extends Application { + + @Override + public Set> getClasses() { + final Set> classes = new HashSet<>(); + classes.add(LoginController.class); + + return classes; + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java b/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java new file mode 100644 index 000000000..3767f846b --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.login; + +import com.arsdigita.kernel.KernelConfig; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.security.ChallengeManager; +import org.libreccm.security.User; +import org.libreccm.security.UserRepository; +import org.libreccm.theming.mvc.ThemesMvc; + +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.mail.MessagingException; +import javax.mvc.Controller; +import javax.mvc.Models; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +/** + * + * @author Jens Pelzetter + */ +@Controller +@Path("/") +@RequestScoped +public class LoginController { + + @Inject + private ChallengeManager challengeManager; + + @Inject + private ConfigurationManager confManager; + + @Inject + private Models models; + + @Inject + private Subject subject; + + @Inject + private ThemesMvc themesMvc; + + @Inject + private UserRepository userRepository; + + @GET + @Path("/") + public String getLoginForm( + @Context final UriInfo uriInfo, + @QueryParam("return_url") final String redirectUrl + + ) { + models.put( + "emailIsPrimaryIdentifier", isEmailPrimaryIdentifier() + ); + models.put("returnUrl", redirectUrl); + return themesMvc.getMvcTemplate(uriInfo, "login-form"); + } + + @POST + @Path("/") + public String processLogin( + @Context final UriInfo uriInfo, + @FormParam("login") final String login, + @FormParam("password") final String password, + @FormParam("rememberMe") final String rememberMeValue, + @FormParam("redirectUrl") @DefaultValue("") final String redirectUrl + ) { + final UsernamePasswordToken token = new UsernamePasswordToken( + login, password + ); + token.setRememberMe("on".equals(rememberMeValue)); + try { + subject.login(token); + } catch(AuthenticationException ex) { + models.put("loginFailed", true); + return getLoginForm(uriInfo, redirectUrl); + } + + return String.format("redirect:%s", redirectUrl); + } + + @GET + @Path("/recover-password") + public String getRecoverPasswordForm(@Context final UriInfo uriInfo) { + return themesMvc.getMvcTemplate(uriInfo, "login-recover-password"); + } + + @POST + @Path("/recover-password") + public String recoverPassword( + @Context final UriInfo uriInfo, + @FormParam("email") final String email + ) { + final Optional user = userRepository.findByEmailAddress(email); + if (user.isPresent()) { + try { + challengeManager.sendPasswordRecover(user.get()); + } catch(MessagingException ex) { + models.put("failedToSendRecoverMessage", true); + return getRecoverPasswordForm(uriInfo); + } + } + + return themesMvc.getMvcTemplate(uriInfo, "login-password-recovered"); + } + + private boolean isEmailPrimaryIdentifier() { + final KernelConfig kernelConfig = confManager.findConfiguration( + KernelConfig.class + ); + return kernelConfig.emailIsPrimaryIdentifier(); + } +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/login/LoginMessages.java b/ccm-core/src/main/java/org/libreccm/ui/login/LoginMessages.java new file mode 100644 index 000000000..0c8a3e7d3 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/login/LoginMessages.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.login; + +import com.arsdigita.ui.login.LoginConstants; + +import org.libreccm.l10n.GlobalizationHelper; + +import java.text.MessageFormat; +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.inject.Named; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named("LoginMessages") +public class LoginMessages extends AbstractMap{ + + /** + * Provides access to the locale negoiated by LibreCCM. + */ + @Inject + private GlobalizationHelper globalizationHelper; + + /** + * The {@link ResourceBundle} to use. + */ + private ResourceBundle messages; + + /** + * Loads the resource bundle. + */ + @PostConstruct + private void init() { + messages = ResourceBundle.getBundle( + LoginConstants.LOGIN_BUNDLE, + globalizationHelper.getNegotiatedLocale() + ); + } + + /** + * Retrieves a message from the resource bundle. + * + * @param key The key of the message. + * @return The translated message or {@code ???message???} if the the key is + * not found in the resource bundle (message is replaced with the key). + */ + public String getMessage(final String key) { + if (messages.containsKey(key)) { + return messages.getString(key); + } else { + return String.format("???%s???", key); + } + } + + /** + * Retrieves a message with placeholders. + * + * @param key The key of the message. + * @param parameters The parameters for the placeholders. + * @return The translated message or {@code ???message???} if the the key is + * not found in the resource bundle (message is replaced with the key). + */ + public String getMessage( + final String key, final List parameters + ) { + return getMessage(key, parameters.toArray()); + } + + /** + * The translated message or {@code ???message???} if the the key is + * not found in the resource bundle (message is replaced with the key). + * + @param key The key of the message. + * @param parameters The parameters for the placeholders. + * @return The translated message or {@code ???message???} if the the key is + * not found in the resource bundle (message is replaced with the key). + */ + public String getMessage( + final String key, final Object[] parameters + ) { + if (messages.containsKey(key)) { + return MessageFormat.format(messages.getString(key), parameters); + } else { + return String.format("???%s???", key); + } + } + + @Override + public String get(final Object key) { + return get((String) key); + } + + public String get(final String key) { + return getMessage(key); + } + + @Override + public Set> entrySet() { + return messages + .keySet() + .stream() + .collect( + Collectors.toMap(key -> key, key -> messages.getString(key)) + ) + .entrySet(); + } + + +}