From 4473ef774b8aef00bd8711b4c18212a2257cd97b Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 30 Nov 2025 16:36:15 -0500 Subject: [PATCH 1/2] JSPWIKI-1253 displays the last login timestamp and date, which are persisted in the user database manager --- .../auth/DefaultAuthenticationManager.java | 33 ++++++++++++++++++- .../resources/templates/default.properties | 1 + .../resources/templates/default_de.properties | 1 + .../resources/templates/default_es.properties | 1 + .../resources/templates/default_fi.properties | 1 + .../resources/templates/default_fr.properties | 1 + .../resources/templates/default_it.properties | 1 + .../resources/templates/default_nl.properties | 1 + .../templates/default_pt_BR.properties | 1 + .../resources/templates/default_ru.properties | 1 + .../templates/default_zh_CN.properties | 1 + .../main/webapp/templates/default/UserBox.jsp | 28 ++++++++++++++++ 12 files changed, 70 insertions(+), 1 deletion(-) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java index ef5ca423e8..94990ae716 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java @@ -48,11 +48,16 @@ Licensed to the Apache Software Foundation (ASF) under one import jakarta.servlet.http.HttpSession; import java.security.Principal; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; +import org.apache.wiki.WikiContext; +import org.apache.wiki.api.core.Context; +import org.apache.wiki.auth.user.DefaultUserProfile; +import org.apache.wiki.auth.user.UserProfile; /** @@ -201,8 +206,34 @@ public boolean login( final HttpServletRequest request ) throws WikiSecurityExce fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, getLoginPrincipal( principals ), session, request ); return true; } + } else { + //attempt to get the user profile + + try { + UserManager mgr = m_engine.getManager(UserManager.class); + UserProfile profile = mgr.getUserDatabase().findByLoginName(session.getLoginPrincipal().getName()); + if (request.getSession().getAttribute("LOGINTIMESTAMPSET") == null) { + request.getSession().setAttribute("LOGINTIMESTAMPSET", "true"); + String lastLoginAt = (String) profile.getAttributes().get("CURRENT_LOGIN_TIMESTAMP"); + String oldIp = (String) profile.getAttributes().get("CURRENT_LOGIN_IP"); + if (lastLoginAt != null) { + profile.getAttributes().put("PREVIOUS_LOGIN_TIMESTAMP", lastLoginAt); + } + if (oldIp != null) { + profile.getAttributes().put("PREVIOUS_LOGIN_IP", oldIp); + } + profile.getAttributes().put("CURRENT_LOGIN_IP", request.getRemoteAddr()); + profile.getAttributes().put("CURRENT_LOGIN_TIMESTAMP", new Date().toString()); + try { + mgr.setUserProfile(new WikiContext(m_engine, request, ""), profile); + } catch (WikiException ex) { + LOG.warn("failed to persist last login from for " + profile.getLoginName(), ex); + } + } + } catch (Exception ex) { + LOG.debug(ex.getMessage(), ex); + } } - // If by some unusual turn of events the Anonymous login module doesn't work, login failed! return false; } diff --git a/jspwiki-main/src/main/resources/templates/default.properties b/jspwiki-main/src/main/resources/templates/default.properties index e0cd5d8e98..39e3f8917e 100644 --- a/jspwiki-main/src/main/resources/templates/default.properties +++ b/jspwiki-main/src/main/resources/templates/default.properties @@ -106,6 +106,7 @@ fav.nomenu=Please make a {0} fav.greet.anonymous=G’day (anonymous guest) fav.greet.asserted=G’day, {0} (not logged in) fav.greet.authenticated=G’day, {0} (authenticated) +fav.greet.lastLogin=Last login at {0} from {1} fav.aggregatewiki.title=Aggregate the RSS feed of the entire wiki fav.productstatus.updateAvailable=A software update is available. fav.productstatus.unknown=Unable to check for product updates. diff --git a/jspwiki-main/src/main/resources/templates/default_de.properties b/jspwiki-main/src/main/resources/templates/default_de.properties index e99abc3e49..d2dcb85cdb 100644 --- a/jspwiki-main/src/main/resources/templates/default_de.properties +++ b/jspwiki-main/src/main/resources/templates/default_de.properties @@ -748,3 +748,4 @@ fav.productstatus.updateToDate=Die Software ist auf dem neuesten Stand. attach.add.maxUpload=Maximale Uploadgr\u00f6\u00dfe: notification.auditlog.subject=[0] Pr\u00fcfalarm notification.auditlog.content=Klasse: [0]\nEreignisname: [1]\nTypbeschreibung: [2]\nWann: [3]\nEreignisdetails: [4]\nHeader und Verbindungsdetails: [5] +fav.greet.lastLogin=Letzter Login um {0} von {1} diff --git a/jspwiki-main/src/main/resources/templates/default_es.properties b/jspwiki-main/src/main/resources/templates/default_es.properties index 9ecaed7f5a..47ecc102d5 100644 --- a/jspwiki-main/src/main/resources/templates/default_es.properties +++ b/jspwiki-main/src/main/resources/templates/default_es.properties @@ -707,3 +707,4 @@ attach.add.maxUpload=Tama\u00f1o m\u00e1ximo de carga: javascript.preview.zone = Zona de previsualizaci\u00f3n notification.auditlog.subject=[0] Alerta de auditor\u00eda notification.auditlog.content=Clase: [0]\nNombre del evento: [1]\nDescripci\u00f3n del tipo: [2]\n\nCu\u00e1ndo: [3]\nDetalles del evento: [4]\nEncabezados y detalles de conexi\u00f3n: [5] +fav.greet.lastLogin=\u00daltimo inicio de sesi\u00f3n a las {0} desde {1} diff --git a/jspwiki-main/src/main/resources/templates/default_fi.properties b/jspwiki-main/src/main/resources/templates/default_fi.properties index 106bdbfe60..5f2bcbe8e8 100644 --- a/jspwiki-main/src/main/resources/templates/default_fi.properties +++ b/jspwiki-main/src/main/resources/templates/default_fi.properties @@ -713,3 +713,4 @@ notification.auditlog.content=Luokka: [0]\nTapahtuman nimi: [1]\nTyypin kuvaus: fav.productstatus.updateAvailable=Ohjelmistop\u00e4ivitys on saatavilla. fav.productstatus.unknown=Tuotep\u00e4ivitysten tarkistaminen ep\u00e4onnistui. fav.productstatus.updateToDate=Ohjelmisto on ajan tasalla. +fav.greet.lastLogin=Viimeisin kirjautuminen klo {0} osoitteesta {1} diff --git a/jspwiki-main/src/main/resources/templates/default_fr.properties b/jspwiki-main/src/main/resources/templates/default_fr.properties index 27f388ee9e..43bd54079c 100644 --- a/jspwiki-main/src/main/resources/templates/default_fr.properties +++ b/jspwiki-main/src/main/resources/templates/default_fr.properties @@ -754,3 +754,4 @@ notification.auditlog.content=Classe\u00a0: [0]\nNom de l\u2019\u00e9v\u00e9neme fav.productstatus.updateAvailable=Une mise \u00e0 jour logicielle est disponible. fav.productstatus.unknown=Impossible de v\u00e9rifier les mises \u00e0 jour du produit. fav.productstatus.updateToDate=Le logiciel est \u00e0 jour. +fav.greet.lastLogin=Derni\u00e8re connexion \u00e0 {0} depuis {1} diff --git a/jspwiki-main/src/main/resources/templates/default_it.properties b/jspwiki-main/src/main/resources/templates/default_it.properties index 70ac52014a..3ae4d1a06d 100644 --- a/jspwiki-main/src/main/resources/templates/default_it.properties +++ b/jspwiki-main/src/main/resources/templates/default_it.properties @@ -742,3 +742,4 @@ notification.auditlog.content=Classe: [0]\nNome evento: [1]\nDescrizione tipo: [ fav.productstatus.updateAvailable=\u00c8 disponibile un aggiornamento software. fav.productstatus.unknown=Impossibile verificare la presenza di aggiornamenti del prodotto. fav.productstatus.updateToDate=Il software \u00e8 aggiornato. +fav.greet.lastLogin=Ultimo accesso alle {0} da {1} diff --git a/jspwiki-main/src/main/resources/templates/default_nl.properties b/jspwiki-main/src/main/resources/templates/default_nl.properties index e93419b43b..112b7ea654 100644 --- a/jspwiki-main/src/main/resources/templates/default_nl.properties +++ b/jspwiki-main/src/main/resources/templates/default_nl.properties @@ -743,3 +743,4 @@ notification.auditlog.content=Klasse: [0]\nGebeurtenisnaam: [1]\nTypebeschrijvin fav.productstatus.updateAvailable=Er is een software-update beschikbaar. fav.productstatus.unknown=Er kan niet worden gecontroleerd op productupdates. fav.productstatus.updateToDate=De software is up-to-date. +fav.greet.lastLogin=Laatste login om {0} vanaf {1} diff --git a/jspwiki-main/src/main/resources/templates/default_pt_BR.properties b/jspwiki-main/src/main/resources/templates/default_pt_BR.properties index 1824826ffa..bd85bd6bfd 100644 --- a/jspwiki-main/src/main/resources/templates/default_pt_BR.properties +++ b/jspwiki-main/src/main/resources/templates/default_pt_BR.properties @@ -688,3 +688,4 @@ notification.auditlog.content=Classe: [0]\nNome do evento: [1]\nDescri\u00e7\u00 fav.productstatus.updateAvailable=Uma atualiza\u00e7\u00e3o de software est\u00e1 dispon\u00edvel. fav.productstatus.unknown=N\u00e3o foi poss\u00edvel verificar se h\u00e1 atualiza\u00e7\u00f5es do produto. fav.productstatus.updateToDate=O software est\u00e1 atualizado. +fav.greet.lastLogin=\u00daltimo login em {0} de {1} diff --git a/jspwiki-main/src/main/resources/templates/default_ru.properties b/jspwiki-main/src/main/resources/templates/default_ru.properties index fbddd064da..032ad77252 100644 --- a/jspwiki-main/src/main/resources/templates/default_ru.properties +++ b/jspwiki-main/src/main/resources/templates/default_ru.properties @@ -728,3 +728,4 @@ notification.auditlog.content=\u041a\u043b\u0430\u0441\u0441: [0]\n\u041d\u0430\ fav.productstatus.updateAvailable=\u0414\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f. fav.productstatus.unknown=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430. fav.productstatus.updateToDate=\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e. +fav.greet.lastLogin=\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u0445\u043e\u0434 \u0432 {0} \u0438\u0437 {1} diff --git a/jspwiki-main/src/main/resources/templates/default_zh_CN.properties b/jspwiki-main/src/main/resources/templates/default_zh_CN.properties index eaa17a850c..aba3ad4cde 100644 --- a/jspwiki-main/src/main/resources/templates/default_zh_CN.properties +++ b/jspwiki-main/src/main/resources/templates/default_zh_CN.properties @@ -729,3 +729,4 @@ notification.auditlog.content=\u7c7b\u522b\uff1a[0]\n\n\u4e8b\u4ef6\u540d\u79f0\ fav.productstatus.updateAvailable=\u8f6f\u4ef6\u66f4\u65b0\u53ef\u7528\u3002 fav.productstatus.unknown=\u65e0\u6cd5\u68c0\u67e5\u4ea7\u54c1\u66f4\u65b0\u3002 fav.productstatus.updateToDate=\u8f6f\u4ef6\u5df2\u66f4\u65b0\u81f3\u6700\u65b0\u7248\u672c\u3002 +fav.greet.lastLogin=\u4e0a\u6b21\u767b\u5f55\u65f6\u95f4\u4e3a {0}\uff0c\u6765\u81ea {1} diff --git a/jspwiki-war/src/main/webapp/templates/default/UserBox.jsp b/jspwiki-war/src/main/webapp/templates/default/UserBox.jsp index 7afdf86a02..aec8a9dc3e 100644 --- a/jspwiki-war/src/main/webapp/templates/default/UserBox.jsp +++ b/jspwiki-war/src/main/webapp/templates/default/UserBox.jsp @@ -18,6 +18,8 @@ --%> <%@ taglib uri="http://jspwiki.apache.org/tags" prefix="wiki" %> +<%@page import="org.apache.wiki.auth.user.UserProfile"%> +<%@page import="org.apache.wiki.auth.UserManager"%> <%@ taglib prefix="c" uri="jakarta.tags.core" %> <%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %> <%@ page import="jakarta.servlet.jsp.jstl.fmt.*" %> @@ -27,6 +29,16 @@ <% Context c = Context.findContext(pageContext); + UserManager mgr = c.getEngine().getManager(UserManager.class); + UserProfile profile = mgr.getUserProfile(c.getWikiSession()); + String lastLoginAt=null; + String lastLoginFrom=null; + if (profile!=null) { + lastLoginAt = (String) profile.getAttributes().get("PREVIOUS_LOGIN_TIMESTAMP"); + lastLoginFrom = (String) profile.getAttributes().get("PREVIOUS_LOGIN_IP"); + } + + %> <%= c.getEngine().encodeName(c.getName()) %> @@ -60,6 +72,7 @@ +
  • <% ProductUpdateChecker checker = ProductUpdateChecker.getInstance(); @@ -151,6 +164,21 @@

    + <% + if (lastLoginAt!=null && lastLoginFrom!=null) { + %> +
    + + <%=lastLoginAt%> + <%=lastLoginFrom%> + + +
    + <% + } + %> + +
  • From 5e3e5b0ffb925ce862a4d5a6ab0d298d4689267c Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 30 Nov 2025 19:13:53 -0500 Subject: [PATCH 2/2] should fix the unit test --- .../java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java index 3ea0fdd38f..1f841b9803 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java @@ -79,7 +79,7 @@ public void testAttributes() throws Exception { UserProfile profile = m_db.findByEmail( "janne@ecyrd.com" ); Map< String, Serializable > attributes = profile.getAttributes(); - Assertions.assertEquals( 2, attributes.size() ); + Assertions.assertTrue( attributes.size() >= 2 ); Assertions.assertTrue( attributes.containsKey( "attribute1" ) ); Assertions.assertTrue( attributes.containsKey( "attribute2" ) ); Assertions.assertEquals( "some random value", attributes.get( "attribute1" ) ); @@ -93,7 +93,7 @@ public void testAttributes() throws Exception { // Retrieve the profile again and make sure our values got saved profile = m_db.findByEmail( "janne@ecyrd.com" ); attributes = profile.getAttributes(); - Assertions.assertEquals( 3, attributes.size() ); + Assertions.assertTrue( attributes.size() >= 3 ); Assertions.assertTrue( attributes.containsKey( "attribute1" ) ); Assertions.assertTrue( attributes.containsKey( "attribute2" ) ); Assertions.assertTrue( attributes.containsKey( "attribute the third" ) );