diff --git a/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java b/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java index 6d0fa97d36c..65add8ab568 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java @@ -19,6 +19,7 @@ import javax.enterprise.context.RequestScoped; import javax.faces.context.FacesContext; import javax.inject.Named; +import javax.servlet.http.HttpSession; import org.kitodo.data.database.beans.Client; import org.kitodo.data.database.beans.Project; @@ -183,4 +184,29 @@ public List getAvailableClientsOfCurrentUserSortedByName() { return getAvailableClientsOfCurrentUser().stream().sorted(Comparator.comparing(Client::getName)) .collect(Collectors.toList()); } + + /** + * Get amount of time that warning message is displayed to inform user that he will be logged + * out of the system automatically due to inactivity. Value returned in seconds. + * If the session HTTP session timeout configured in the 'web.xml' file is 60 seconds or less, + * the message will be shown 30 seconds before logout. If the timeout is between 1 and 5 minutes, + * the message will appear 60 seconds before logout. For any session timeout larger than 5 Minutes, + * it will be shown 300 seconds in advance. + * @return number of seconds the warning message is displayed to the user before automatic logout + */ + public int getAutomaticLogoutWarningSeconds() { + FacesContext facesContext = FacesContext.getCurrentInstance(); + if (Objects.nonNull(facesContext)) { + HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false); + int maxInactiveInterval = session.getMaxInactiveInterval(); + if (maxInactiveInterval <= 60) { + return 30; + } else if (maxInactiveInterval < 300) { + return 60; + } else { + return 300; + } + } + return 60; + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/ActivityMonitor.java b/Kitodo/src/main/java/org/kitodo/production/helper/ActivityMonitor.java new file mode 100644 index 00000000000..f9e2feb844e --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/helper/ActivityMonitor.java @@ -0,0 +1,49 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.production.helper; + +import java.util.Iterator; + +import javax.enterprise.context.RequestScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Named; + +import org.primefaces.PrimeFaces; + +@Named +@RequestScoped +public class ActivityMonitor { + + /** + * Event handler for 'idle' event. Triggered when user becomes idle and is about to be logged out automatically. + * Displays a warning message to inform the user he is about to get logged out soon. + */ + public void onIdle() { + String warningTitle = Helper.getTranslation("automaticLogoutWarningTitle"); + String warningDescription = Helper.getTranslation("automaticLogoutWarningDescription"); + PrimeFaces.current().executeScript("PF('sticky-notifications').renderMessage(" + + "{'summary':'" + warningTitle + "','detail':'" + warningDescription + "','severity':'error'});"); + } + + /** + * Event handler for 'active' event. Triggered when user becomes active again after being idle. + * Removes the warning message about pending automatic logout. + */ + public void onActive() { + Iterator messageIterator = FacesContext.getCurrentInstance().getMessages(); + while (messageIterator.hasNext()) { + messageIterator.next(); + messageIterator.remove(); + } + } +} diff --git a/Kitodo/src/main/java/org/kitodo/production/security/CustomLogoutSuccessHandler.java b/Kitodo/src/main/java/org/kitodo/production/security/CustomLogoutSuccessHandler.java index 3b6bb164144..4e5eda7fa65 100644 --- a/Kitodo/src/main/java/org/kitodo/production/security/CustomLogoutSuccessHandler.java +++ b/Kitodo/src/main/java/org/kitodo/production/security/CustomLogoutSuccessHandler.java @@ -12,7 +12,6 @@ package org.kitodo.production.security; import java.io.IOException; -import java.text.MessageFormat; import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -53,8 +52,8 @@ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse resp UserDetails user = (UserDetails) principal; ServiceManager.getSessionService().expireSessionsOfUser(user); } else { - logger.warn(MessageFormat.format("Cannot expire session: {0} is not an instance of UserDetails", - Helper.getObjectDescription(principal))); + logger.warn("Cannot expire session: {} is not an instance of UserDetails", + Helper.getObjectDescription(principal)); } } else { logger.warn("Cannot expire session: authentication.getDetails() is null"); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/security/SessionService.java b/Kitodo/src/main/java/org/kitodo/production/services/security/SessionService.java index 34c3e0f44b9..4541629a378 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/security/SessionService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/security/SessionService.java @@ -11,30 +11,21 @@ package org.kitodo.production.services.security; -import java.text.MessageFormat; import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import javax.servlet.http.HttpSessionEvent; -import javax.servlet.http.HttpSessionListener; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.kitodo.production.helper.Helper; import org.kitodo.production.metadata.MetadataLock; import org.kitodo.production.security.SecurityConfig; import org.kitodo.production.security.SecuritySession; import org.kitodo.production.security.SecurityUserDetails; -import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.userdetails.UserDetails; -public class SessionService implements HttpSessionListener { +public class SessionService { - private static final Logger logger = LogManager.getLogger(SessionService.class); private static volatile SessionService instance = null; private final SessionRegistry sessionRegistry; @@ -46,27 +37,6 @@ private SessionService() { this.sessionRegistry = securityConfig.getSessionRegistry(); } - /* - * This function is called when the session from the servlet container expires. - */ - @Override - public void sessionDestroyed(HttpSessionEvent se) { - Object securityContextObject = se.getSession().getAttribute("SPRING_SECURITY_CONTEXT"); - if (securityContextObject instanceof SecurityContextImpl) { - SecurityContextImpl securityContext = (SecurityContextImpl) securityContextObject; - Object principal = securityContext.getAuthentication().getPrincipal(); - if (principal instanceof SecurityUserDetails) { - expireSessionsOfUser((SecurityUserDetails) principal); - } else { - logger.warn(MessageFormat.format("Cannot expire session: {0} is not an instance of SecurityUserDetails", - Helper.getObjectDescription(principal))); - } - } else { - logger.warn(MessageFormat.format("Cannot expire session: {0} is not an instance of SecurityContextImpl", - Helper.getObjectDescription(securityContextObject))); - } - } - /** * Expires all active sessions of a spring security UserDetails object. * diff --git a/Kitodo/src/main/java/org/kitodo/production/session/CustomHttpSessionListener.java b/Kitodo/src/main/java/org/kitodo/production/session/CustomHttpSessionListener.java new file mode 100644 index 00000000000..cb59b9ace88 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/session/CustomHttpSessionListener.java @@ -0,0 +1,67 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + + +package org.kitodo.production.session; + +import java.util.Objects; + +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.production.helper.Helper; +import org.kitodo.production.security.SecurityUserDetails; +import org.kitodo.production.services.ServiceManager; +import org.springframework.security.core.context.SecurityContextImpl; + + +@WebListener +public class CustomHttpSessionListener implements HttpSessionListener { + + private static final Logger logger = LogManager.getLogger(CustomHttpSessionListener.class); + + /** + * Event handler that is triggere when an HTTP session is created. + * + * @param sessionEvent the notification event + */ + @Override + public void sessionCreated(HttpSessionEvent sessionEvent) { + logger.debug("Session created: {}", sessionEvent.getSession().getId()); + } + + /** + * Event handler that is triggered when an HTTP session expires. + * + * @param sessionEvent the notification event + */ + @Override + public void sessionDestroyed(HttpSessionEvent sessionEvent) { + Object securityContextObject = sessionEvent.getSession().getAttribute("SPRING_SECURITY_CONTEXT"); + if (Objects.nonNull(securityContextObject) && securityContextObject instanceof SecurityContextImpl) { + SecurityContextImpl securityContext = (SecurityContextImpl) securityContextObject; + Object principal = securityContext.getAuthentication().getPrincipal(); + if (principal instanceof SecurityUserDetails) { + logger.debug("Session expired: {}", sessionEvent.getSession().getId()); + ServiceManager.getSessionService().expireSessionsOfUser((SecurityUserDetails) principal); + } else { + logger.debug("Cannot expire session: {} is not an instance of SecurityUserDetails", + Helper.getObjectDescription(principal)); + } + } else { + logger.debug("Cannot expire session: {} is not an instance of SecurityContextImpl", + Helper.getObjectDescription(securityContextObject)); + } + } +} diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index c4113beb4ac..47c17ade5a3 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -62,6 +62,8 @@ actions=Aktionen audio=Audio automatic=automatisch automaticDmsImport=Automatischer DMS-Export +automaticLogoutWarningDescription=Aufgrund von Inaktivit\u00E4t werden Sie in K\u00FCrze automatisch ausgeloggt... +automaticLogoutWarningTitle=Keine Aktivit\u00E4t festgestellt automaticTask=Automatische Aufgabe automaticTasks=Automatische Aufgaben author=Autor diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index c74442431db..92674fedbdb 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -62,6 +62,8 @@ actions=Actions audio=Audio automatic=automatic automaticDmsImport=Automatic DMS export +automaticLogoutWarningDescription=Pending logout due to inactivity... +automaticLogoutWarningTitle=No activity registered automaticTask=Automatic task automaticTasks=Automatic tasks author=Author diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/defaultScript.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/defaultScript.js index 42a5680572f..deb540679ab 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/defaultScript.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/defaultScript.js @@ -12,3 +12,21 @@ $(document).ready(function() { $('#loadingScreen').hide(); }); + +window.updateLogoutCountdown = function(t) { + let growlMessage = $('#sticky-notifications_container div.ui-growl-message p'); + let currentTime; + let minutes = Math.floor(t.current / 60); + let seconds = t.current % 60; + if (seconds < 10) { + currentTime = minutes + ":0" + seconds; + } else { + currentTime = minutes + ":" + seconds; + } + let currentMessage = growlMessage.text(); + if (currentMessage.match(/\d+:\d+/g)) { + growlMessage.text(currentMessage.replace(/\d+:\d+/g, currentTime)); + } else { + growlMessage.text(currentMessage + " " + currentTime); + } +}; diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/base.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/base.xhtml index d7fc149ddb7..aceb7fb1fab 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/base.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/base.xhtml @@ -17,6 +17,7 @@ xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui" + xmlns:pe="http://primefaces.org/ui/extensions" xmlns:o="http://omnifaces.org/ui"> @@ -80,6 +81,31 @@ target="body" /> + + + + + + + + + diff --git a/Kitodo/src/main/webapp/pages/metadataEditor.xhtml b/Kitodo/src/main/webapp/pages/metadataEditor.xhtml index 6e0ee6463d4..5e7919026c2 100644 --- a/Kitodo/src/main/webapp/pages/metadataEditor.xhtml +++ b/Kitodo/src/main/webapp/pages/metadataEditor.xhtml @@ -286,6 +286,7 @@ +