diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java
index 7a8004a093..5894e2ae12 100644
--- a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java
+++ b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java
@@ -237,4 +237,25 @@ public interface Session extends WikiEventListener {
*/
Subject getSubject();
+ /**
+ * Wrapper for {@link Subject#doAsPrivileged(Subject, PrivilegedAction, java.security.AccessControlContext)}
+ * that executes an action with the privileges possessed by a Session's Subject. The action executes with a null
+ * AccessControlContext, which has the effect of running it "cleanly" without the AccessControlContexts of the caller.
+ *
+ * @param session the wiki session
+ * @param action the privileged action
+ * @return the result of the privileged action; may be null
+ * @throws java.security.AccessControlException if the action is not permitted by the security policy
+ */
+ static Object doPrivileged( final Session session, final PrivilegedAction> action ) throws AccessControlException {
+ return Subject.doAsPrivileged( session.getSubject(), action, null );
+ }
+
+ /**
+ * returns the remote address of the user login session, usually an ip address
+ * @since 3.0.0
+ * @return string
+ */
+ String getRemoteAddress();
+
}
diff --git a/jspwiki-event/src/main/java/org/apache/wiki/event/WikiSecurityEvent.java b/jspwiki-event/src/main/java/org/apache/wiki/event/WikiSecurityEvent.java
index 3f3dfa590d..735c70446f 100644
--- a/jspwiki-event/src/main/java/org/apache/wiki/event/WikiSecurityEvent.java
+++ b/jspwiki-event/src/main/java/org/apache/wiki/event/WikiSecurityEvent.java
@@ -115,6 +115,8 @@ public final class WikiSecurityEvent extends WikiEvent {
/** When a low disk space is encountered . */
public static final int LOW_STORAGE = 55;
+ /** Login audit alert, multiple concurrent logins from the different ip addresses . */
+ public static final int LOGIN_ALERT = 56;
/** The security logging service. */
private static final Logger LOG = LogManager.getLogger( "SecurityLog" );
@@ -123,7 +125,7 @@ public final class WikiSecurityEvent extends WikiEvent {
private final Object m_target;
- private static final int[] ERROR_EVENTS = { LOGIN_FAILED };
+ private static final int[] ERROR_EVENTS = { LOGIN_FAILED, LOGIN_ALERT };
private static final int[] WARN_EVENTS = { LOGIN_ACCOUNT_EXPIRED, LOGIN_CREDENTIAL_EXPIRED };
@@ -249,6 +251,7 @@ public String getTypeDescription() {
case LOGIN_ACCOUNT_EXPIRED: return "login failed: expired account";
case LOGIN_CREDENTIAL_EXPIRED: return "login failed: credential expired";
case LOGIN_FAILED: return "login failed";
+ case LOGIN_ALERT: return "login alert";
case LOGOUT: return "user logged out";
case PRINCIPAL_ADD: return "new principal added";
case SESSION_EXPIRED: return "session expired";
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
index d14fe3de5b..23f5d88bde 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
@@ -72,7 +72,7 @@ public class WikiSession implements Session {
/** The Engine that created this session. */
private Engine m_engine;
-
+ private String remoteAddress;
private String antiCsrfToken;
private String m_status = ANONYMOUS;
@@ -490,7 +490,7 @@ public static Session getWikiSession( final Engine engine, final HttpServletRequ
final HttpSession session = request.getSession();
final SessionMonitor monitor = SessionMonitor.getInstance( engine );
final WikiSession wikiSession = ( WikiSession )monitor.find( session );
-
+ wikiSession.remoteAddress = request.getRemoteAddr();
// Attach reference to wiki engine
wikiSession.m_engine = engine;
wikiSession.m_cachedLocale = request.getLocale();
@@ -571,4 +571,9 @@ public static Principal[] userPrincipals( final Engine engine ) {
return monitor.userPrincipals();
}
+ @Override
+ public String getRemoteAddress() {
+ return remoteAddress;
+ }
+
}
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..d5b83dbd28 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
@@ -50,6 +50,7 @@ Licensed to the Apache Software Foundation (ASF) under one
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -193,6 +194,23 @@ public boolean login( final HttpServletRequest request ) throws WikiSecurityExce
fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, getLoginPrincipal( principals ), session, request);
}
}
+
+ if (!session.isAnonymous()) {
+ final SessionMonitor monitor = SessionMonitor.getInstance(m_engine);
+ List sessions = monitor.findOtherSessionsByUsername(session.getLoginPrincipal().getName());
+ StringBuilder sb = new StringBuilder();
+ for (Session s : sessions) {
+ if (s.getRemoteAddress() != null && !s.getRemoteAddress().equals(request.getRemoteAddr())) {
+ sb.append(request.getRemoteAddr()).append(",");
+ }
+ }
+ if (sb.length() > 0) {
+ sb.append(request.getRemoteAddr());
+ LOG.warn("AUDIT - New login for login '" + session.getLoginPrincipal().getName() + "' from " + request.getRemoteAddr()
+ + " however there are already concurrent logins from the following addresses " + sb.toString());
+ fireEvent(WikiSecurityEvent.LOGIN_ALERT, session.getLoginPrincipal(), session, request);
+ }
+ }
// If user still anonymous, use the remote address
if( session.isAnonymous() ) {
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
index 8fe7555897..76c1ab6f0d 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
@@ -33,8 +33,10 @@ Licensed to the Apache Software Foundation (ASF) under one
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import java.security.Principal;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
@@ -111,12 +113,14 @@ private Session findSession( final HttpSession session ) {
private Session findSession( final String sessionId ) {
Session wikiSession = null;
final String sid = ( sessionId == null ) ? "(null)" : sessionId;
- final Session storedSession = m_sessions.get( sid );
+ synchronized( m_sessions ){
+ final Session storedSession = m_sessions.get( sid );
- // If the weak reference returns a wiki session, return it
- if( storedSession != null ) {
- LOG.debug( "Looking up WikiSession for session ID={}... found it", sid );
- wikiSession = storedSession;
+ // If the weak reference returns a wiki session, return it
+ if( storedSession != null ) {
+ LOG.debug( "Looking up WikiSession for session ID={}... found it", sid );
+ wikiSession = storedSession;
+ }
}
return wikiSession;
@@ -294,4 +298,24 @@ public void sessionDestroyed( final HttpSessionEvent se ) {
}
}
+ /**
+ * gets a list of other sessions for the same login id for auditing purposes.
+ *
+ * @since 3.0.0
+ * @param name
+ * @return list
+ */
+ public List findOtherSessionsByUsername(String name) {
+ List otherSessions = new ArrayList<>();
+ synchronized (m_sessions) {
+
+ for (Session m : m_sessions.values()) {
+ if (m.getLoginPrincipal().getName().equals(name)) {
+ otherSessions.add(m);
+ }
+ }
+ }
+ return otherSessions;
+ }
+
}