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) */
