Skip to content

Commit

Permalink
ARTEMIS-4267 original exception lost for NoCacheLoginException
Browse files Browse the repository at this point in the history
When skipping the authentication cache details for the original
exception are not logged.

This commit ensures these details are logged and adopts the
ExceptionUtils class from Apache Commons Lang in lieu of the previous
custom implementation.
  • Loading branch information
jbertram authored and brusdev committed May 8, 2023
1 parent 5e32a1a commit c2bada6
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 127 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.ExceptionUtil;
import org.apache.activemq.artemis.utils.collections.TypedProperties;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -479,7 +479,7 @@ public void initChannel(Channel channel) throws Exception {
pipeline.addLast("ssl", getSslHandler(channel.alloc(), peerInfo.getA(), peerInfo.getB()));
pipeline.addLast("sslHandshakeExceptionHandler", new SslHandshakeExceptionHandler());
} catch (Exception e) {
Throwable rootCause = ExceptionUtil.getRootCause(e);
Throwable rootCause = ExceptionUtils.getRootCause(e);
ActiveMQServerLogger.LOGGER.gettingSslHandlerFailed(channel.remoteAddress().toString(), rootCause.getClass().getName() + ": " + rootCause.getMessage());

logger.debug("Getting SSL handler failed", e);
Expand Down Expand Up @@ -1036,7 +1036,7 @@ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause.getMessage() != null && cause.getMessage().startsWith(SSLHandshakeException.class.getName())) {
Throwable rootCause = ExceptionUtil.getRootCause(cause);
Throwable rootCause = ExceptionUtils.getRootCause(cause);
String errorMessage = rootCause.getClass().getName() + ": " + rootCause.getMessage();

ActiveMQServerLogger.LOGGER.sslHandshakeFailed(ctx.channel().remoteAddress().toString(), errorMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public String authenticate(final String user,
authenticationCache.put(createAuthenticationCacheKey(user, password, connection), new Pair<>(subject != null, subject));
validatedUser = getUserFromSubject(subject);
} catch (NoCacheLoginException e) {
logger.debug("Skipping authentication cache due to exception", e);
handleNoCacheLoginException(e);
}
} else if (securityManager instanceof ActiveMQSecurityManager4) {
validatedUser = ((ActiveMQSecurityManager4) securityManager).validateUser(user, password, connection, securityDomain);
Expand Down Expand Up @@ -413,12 +413,17 @@ private Subject getSubjectForAuthorization(SecurityAuth auth, ActiveMQSecurityMa
authenticationCache.put(createAuthenticationCacheKey(auth.getUsername(), auth.getPassword(), auth.getRemotingConnection()), new Pair<>(subject != null, subject));
return subject;
} catch (NoCacheLoginException e) {
handleNoCacheLoginException(e);
return null;
}
}
return cached.getB();
}

private void handleNoCacheLoginException(NoCacheLoginException e) {
logger.debug("Skipping authentication cache due to exception: {}", e.getMessage());
}

// public for testing purposes
public void invalidateAuthorizationCache() {
authorizationCache.invalidateAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.lang.invoke.MethodHandles;
import java.util.Set;

import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration;
Expand All @@ -28,11 +29,9 @@
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.NoCacheLoginException;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
import org.apache.activemq.artemis.utils.ExceptionUtil;
import org.apache.activemq.artemis.utils.SecurityManagerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;

import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCertsFromConnection;

Expand Down Expand Up @@ -90,11 +89,14 @@ public boolean validateUser(String user, String password) {
}

@Override
public Subject authenticate(final String user, final String password, RemotingConnection remotingConnection, final String securityDomain) {
public Subject authenticate(final String user, final String password, RemotingConnection remotingConnection, final String securityDomain) throws NoCacheLoginException {
try {
return getAuthenticatedSubject(user, password, remotingConnection, securityDomain);
} catch (LoginException e) {
logger.debug("Couldn't validate user", e);
if (e instanceof NoCacheLoginException) {
throw (NoCacheLoginException) e;
}
return null;
}
}
Expand Down Expand Up @@ -138,16 +140,7 @@ private Subject getAuthenticatedSubject(final String user,
} else {
lc = new LoginContext(configurationName, null, new JaasCallbackHandler(user, password, remotingConnection), configuration);
}
try {
lc.login();
} catch (LoginException e) {
Throwable rootCause = ExceptionUtil.getRootCause(e);
if (rootCause instanceof NoCacheLoginException) {
throw (NoCacheLoginException) rootCause;
} else {
throw e;
}
}
lc.login();
return lc.getSubject();
} finally {
if (thisLoader != currentLoader) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.NoCacheLoginException;

/**
* Used to validate whether a user is authorized to connect to the
Expand All @@ -44,7 +45,7 @@ public interface ActiveMQSecurityManager5 extends ActiveMQSecurityManager {
* @param securityDomain the name of the JAAS security domain to use (can be null)
* @return the Subject of the authenticated user, else null
*/
Subject authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain);
Subject authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain) throws NoCacheLoginException;

/**
* Determine whether the given user has the correct role for the given check type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
Expand All @@ -59,11 +60,10 @@
import java.util.Set;

import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.utils.ExceptionUtil;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;

public class LDAPLoginModule implements AuditLoginModule {

Expand Down Expand Up @@ -262,9 +262,9 @@ public boolean commit() throws LoginException {
}

private LoginException handleException(LoginException e) {
Throwable t = ExceptionUtil.getRootCause(e);
if (noCacheExceptions.contains(t.getClass().getName())) {
t.initCause(new NoCacheLoginException());
Throwable rootCause = ExceptionUtils.getRootCause(e);
if (noCacheExceptions.contains(rootCause.getClass().getName())) {
return (NoCacheLoginException) new NoCacheLoginException(rootCause.getClass().getName() + (rootCause.getMessage() == null ? "" : ": " + rootCause.getMessage())).initCause(e);
}
return e;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@

package org.apache.activemq.artemis.spi.core.security.jaas;

public class NoCacheLoginException extends RuntimeException {
import javax.security.auth.login.LoginException;

public class NoCacheLoginException extends LoginException {
public NoCacheLoginException() {
super();
}
public NoCacheLoginException(Exception e) {
super(e);
public NoCacheLoginException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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.activemq.artemis.core.security.jaas;

import javax.security.auth.Subject;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

@RunWith(Parameterized.class)
public class JAASSecurityManagerClassLoadingTest {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@Parameterized.Parameters(name = "newLoader=({0})")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {{true}, {false}});
}

static {
String path = System.getProperty("java.security.auth.login.config");
if (path == null) {
URL resource = PropertiesLoginModuleTest.class.getClassLoader().getResource("login.config");
if (resource != null) {
try {
path = URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8.name());
System.setProperty("java.security.auth.login.config", path);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
}

@Parameterized.Parameter
public boolean usingNewLoader;

@Rule
public TemporaryFolder tmpDir = new TemporaryFolder();

@Test
public void testLoginClassloading() throws Exception {
ClassLoader existingLoader = Thread.currentThread().getContextClassLoader();
logger.debug("loader: {}", existingLoader);
try {
if (usingNewLoader) {
URLClassLoader simulatedLoader = new URLClassLoader(new URL[]{tmpDir.getRoot().toURI().toURL()}, null);
Thread.currentThread().setContextClassLoader(simulatedLoader);
}
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("PropertiesLogin");

Subject result = securityManager.authenticate("first", "secret", null, null);

assertNotNull(result);
assertEquals("first", SecurityStoreImpl.getUserFromSubject(result));

Role role = new Role("programmers", true, true, true, true, true, true, true, true, true, true);
Set<Role> roles = new HashSet<>();
roles.add(role);
boolean authorizationResult = securityManager.authorize(result, roles, CheckType.SEND, "someaddress");

assertTrue(authorizationResult);

} finally {
Thread.currentThread().setContextClassLoader(existingLoader);
}
}
}

0 comments on commit c2bada6

Please sign in to comment.