This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit 169f938b745d1c07ea08bba8442a60ac687c7eb1
Author: Alex Heneveld <[email protected]>
AuthorDate: Mon Jan 20 16:23:22 2025 +0000

    Add explicit logging for user login and logout
---
 .../BrooklynSecurityProviderFilterHelper.java      | 18 ++++++-
 .../BrooklynSecurityProviderFilterJavax.java       |  5 +-
 .../BrooklynSecurityProviderFilterJersey.java      |  2 +-
 .../brooklyn/rest/resources/LogoutResource.java    |  9 +++-
 .../brooklyn/rest/security/LoginLogging.java       | 59 ++++++++++++++++++++++
 .../security/provider/LdapSecurityProvider.java    |  2 +-
 .../rest/security/provider/SecurityProvider.java   |  2 +-
 7 files changed, 89 insertions(+), 8 deletions(-)

diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
index 3ecf952b16..91fd4a3fe3 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterHelper.java
@@ -19,27 +19,34 @@
 package org.apache.brooklyn.rest.filter;
 
 import com.google.common.collect.ImmutableList;
+
+import java.security.Principal;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.function.Supplier;
 
+import javax.annotation.Nullable;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.SecurityContext;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.security.LoginLogging;
 import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
 import org.apache.brooklyn.rest.security.provider.SecurityProvider;
 import 
org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
 import org.apache.brooklyn.rest.util.MultiSessionAttributeAdapter;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.StringEscapes;
 import org.apache.brooklyn.util.text.Strings;
@@ -103,7 +110,7 @@ public class BrooklynSecurityProviderFilterHelper {
     
     public static final String BASIC_REALM_HEADER_VALUE = "BASIC 
realm="+StringEscapes.JavaStringEscapes.wrapJavaString(BASIC_REALM_NAME);
     
-    public void run(HttpServletRequest webRequest, ManagementContext mgmt) 
throws SecurityProviderDeniedAuthentication {
+    public void run(HttpServletRequest webRequest, ManagementContext mgmt, 
@Nullable ContainerRequestContext container) throws 
SecurityProviderDeniedAuthentication {
         SecurityProvider provider = getProvider(mgmt);
         MultiSessionAttributeAdapter preferredSessionWrapper = null;
 
@@ -177,7 +184,16 @@ public class BrooklynSecurityProviderFilterHelper {
                 
preferredSession2.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE,
 webRequest.getRemoteAddr());
                 if (user != null) {
                     
preferredSession2.setAttribute(AUTHENTICATED_USER_SESSION_ATTRIBUTE, user);
+                } else {
+                    if (container!=null) {
+                        SecurityContext securityContext = 
container.getSecurityContext();
+                        Principal userPrincipal = securityContext!=null ? 
securityContext.getUserPrincipal() : null;
+                        if (userPrincipal!=null) user = 
userPrincipal.getName();
+                    }
                 }
+                LoginLogging.logLoginIfNotLogged(preferredSession2, user,
+                        MutableMap.of("origin", webRequest.getRemoteAddr(), 
"provider", provider.getClass().getName()));
+
                 return;
             }
         } catch (WebApplicationException e) {
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
index 552fb2379b..4af227c80e 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJavax.java
@@ -36,9 +36,9 @@ import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.security.LoginLogging;
 import 
org.apache.brooklyn.rest.security.provider.SecurityProvider.SecurityProviderDeniedAuthentication;
 import org.apache.brooklyn.rest.util.ManagementContextProvider;
-import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 import org.eclipse.jetty.http.HttpHeader;
 import org.slf4j.Logger;
@@ -70,8 +70,7 @@ public class BrooklynSecurityProviderFilterJavax implements 
Filter {
             loginPage = getLoginPageFromContext(mgmt);
 
             Preconditions.checkNotNull(mgmt, "Brooklyn management context not 
available; cannot authenticate");
-            new 
BrooklynSecurityProviderFilterHelper().run((HttpServletRequest)request, mgmt);
-
+            new 
BrooklynSecurityProviderFilterHelper().run((HttpServletRequest)request, mgmt, 
null);
             chain.doFilter(request, response);
 
         } catch (SecurityProviderDeniedAuthentication e) {
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
index be2c9752b5..2407bc4638 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/BrooklynSecurityProviderFilterJersey.java
@@ -72,7 +72,7 @@ public class BrooklynSecurityProviderFilterJersey implements 
ContainerRequestFil
         log.trace("BrooklynSecurityProviderFilterJersey.filter {}", 
requestContext);
         ManagementContext mgmt = mgmtC.getContext(ManagementContext.class);
         try {
-            new BrooklynSecurityProviderFilterHelper().run(webRequest, mgmt);
+            new BrooklynSecurityProviderFilterHelper().run(webRequest, mgmt, 
requestContext);
         } catch (SecurityProviderDeniedAuthentication e) {
             Response rin = e.getResponse();
             if (rin==null) rin = Response.status(Status.UNAUTHORIZED).build();
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
index 504772cf9e..04794e50cd 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LogoutResource.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.rest.resources;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
@@ -29,10 +30,12 @@ import 
org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
 import org.apache.brooklyn.rest.api.LogoutApi;
 import org.apache.brooklyn.rest.filter.BrooklynSecurityProviderFilterHelper;
+import org.apache.brooklyn.rest.security.LoginLogging;
 import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
 import org.apache.brooklyn.rest.util.MultiSessionAttributeAdapter;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
 import org.eclipse.jetty.server.session.Session;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -124,7 +127,10 @@ public class LogoutResource extends 
AbstractBrooklynRestResource implements Logo
 //        } else {
 //            log.warn("UNABLE to swap 
request"+MultiSessionAttributeAdapter.info(jreq));
 //        }
-        
+
+        HttpSession session = multi.getPreferredSession();
+        String user = 
Strings.toString(session.getAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE));
+        String sessionId = session.getId();
         multi.configureWhetherToSetInAll(true)
             
.removeAttribute(BrooklynSecurityProviderFilterHelper.AUTHENTICATED_USER_SESSION_ATTRIBUTE);
         // security provider logout
@@ -144,5 +150,6 @@ public class LogoutResource extends 
AbstractBrooklynRestResource implements Logo
                     " is valid after invaildating 
"+MultiSessionAttributeAdapter.info(multi.getPreferredSession()));
             }
         }
+        LoginLogging.logLogout(session, user, MutableMap.of("session", 
sessionId, "origin", req.getRemoteAddr()));
     }
 }
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/LoginLogging.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/LoginLogging.java
new file mode 100644
index 0000000000..f47aedd857
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/LoginLogging.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.security;
+
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpSession;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class LoginLogging {
+
+    private static final Logger log = 
LoggerFactory.getLogger(LoginLogging.class);
+
+    static final String LOGIN_LOGGED = "brooklyn.login_logged";
+
+    public static void logLoginIfNotLogged(HttpSession session, String user, 
Map<String,String> values) {
+        if (Boolean.TRUE.equals(session.getAttribute(LOGIN_LOGGED))) {
+            return;
+        }
+        session.setAttribute(LOGIN_LOGGED, true);
+        log.debug(
+                "Login of " +
+                (user==null ? "anonymous user" : "user: "+user) +
+                        getValuesForLogging(session, values));
+    }
+
+    private static String getValuesForLogging(HttpSession session, Map<String, 
String> values) {
+        Map<String, String> v = MutableMap.copyOf(values);
+        if (!v.containsKey("session")) v.put("session", session.getId());
+        return v.entrySet().stream().filter(k -> k.getValue() != null).map(k 
-> ", " + k.getKey() + ": " + k.getValue()).collect(Collectors.joining());
+    }
+
+    public static void logLogout(HttpSession session, String user, 
Map<String,String> values) {
+        session.setAttribute(LOGIN_LOGGED, false);
+        log.debug(
+                "Logout of " +
+                        (user==null ? "anonymous user" : "user: "+user) +
+                        getValuesForLogging(session, values));
+    }
+}
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
index 7721a3c043..a5ab294015 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
@@ -124,7 +124,7 @@ public class LdapSecurityProvider extends 
AbstractSecurityProvider implements Se
         if (user == null) return false;
         String ldapRegex = getLdapRegexPattern();
         if(Strings.isNonEmpty(ldapRegex) && !user.matches(ldapRegex)){
-            LOG.debug("Rejecting authenticating attempt for user `{}` due to 
userNameRegex configuration: {}", user, ldapRegex);
+            LOG.debug("LDAP authentication not permitted for user `{}` due to 
userNameRegex configuration: {}", user, ldapRegex);
             return false;
         }
         checkCanLoad();
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
index 458df7ffd8..bbeef64b05 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
@@ -52,7 +52,7 @@ public interface SecurityProvider {
     public boolean isAuthenticated(@Nullable HttpSession session);
     
     /** whether this provider requires a user/pass; if this returns false, the 
framework can
-     * send null/null as the user/pass to {@link #authenticate(HttpSession, 
String, String)},
+     * send null/null as the user/pass to {@link 
#authenticate(HttpServletRequest, Supplier, String, String)},
      * and should do that if user/pass info is not immediately available
      * (ie for things like oauth, the framework should not require basic auth 
if this method returns false)
      */

Reply via email to