mirror of https://github.com/apache/druid.git
Improve exception handling in extension druid-pac4j (#16979)
Changes: - Simplify exception handling in `CryptoService` by just catching a `Exception` - Throw a `DruidException` as the exception is user facing - Log the exception for easier debugging - Add a test to verify thrown exception
This commit is contained in:
parent
358d06abc1
commit
d5b64ba2e3
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.druid.security.pac4j;
|
package org.apache.druid.security.pac4j;
|
||||||
|
|
||||||
|
import org.apache.druid.error.DruidException;
|
||||||
import org.easymock.Capture;
|
import org.easymock.Capture;
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -41,7 +42,7 @@ public class Pac4jSessionStoreTest
|
||||||
@Test
|
@Test
|
||||||
public void testSetAndGet()
|
public void testSetAndGet()
|
||||||
{
|
{
|
||||||
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE);
|
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore<>(COOKIE_PASSPHRASE);
|
||||||
|
|
||||||
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
||||||
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
||||||
|
@ -68,7 +69,7 @@ public class Pac4jSessionStoreTest
|
||||||
@Test
|
@Test
|
||||||
public void testSetAndGetClearUserProfile()
|
public void testSetAndGetClearUserProfile()
|
||||||
{
|
{
|
||||||
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE);
|
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore<>(COOKIE_PASSPHRASE);
|
||||||
|
|
||||||
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
||||||
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
||||||
|
@ -78,6 +79,7 @@ public class Pac4jSessionStoreTest
|
||||||
EasyMock.replay(webContext1);
|
EasyMock.replay(webContext1);
|
||||||
|
|
||||||
CommonProfile profile = new CommonProfile();
|
CommonProfile profile = new CommonProfile();
|
||||||
|
profile.setId("profile1");
|
||||||
profile.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name");
|
profile.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name");
|
||||||
sessionStore.set(webContext1, "pac4jUserProfiles", profile);
|
sessionStore.set(webContext1, "pac4jUserProfiles", profile);
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ public class Pac4jSessionStoreTest
|
||||||
@Test
|
@Test
|
||||||
public void testSetAndGetClearUserMultipleProfile()
|
public void testSetAndGetClearUserMultipleProfile()
|
||||||
{
|
{
|
||||||
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE);
|
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore<>(COOKIE_PASSPHRASE);
|
||||||
|
|
||||||
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
||||||
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
||||||
|
@ -109,8 +111,10 @@ public class Pac4jSessionStoreTest
|
||||||
EasyMock.replay(webContext1);
|
EasyMock.replay(webContext1);
|
||||||
|
|
||||||
CommonProfile profile1 = new CommonProfile();
|
CommonProfile profile1 = new CommonProfile();
|
||||||
|
profile1.setId("profile1");
|
||||||
profile1.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name1");
|
profile1.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name1");
|
||||||
CommonProfile profile2 = new CommonProfile();
|
CommonProfile profile2 = new CommonProfile();
|
||||||
|
profile2.setId("profile2");
|
||||||
profile2.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name2");
|
profile2.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name2");
|
||||||
Map<String, CommonProfile> profiles = new HashMap<>();
|
Map<String, CommonProfile> profiles = new HashMap<>();
|
||||||
profiles.put("profile1", profile1);
|
profiles.put("profile1", profile1);
|
||||||
|
@ -131,4 +135,36 @@ public class Pac4jSessionStoreTest
|
||||||
Assert.assertTrue(Objects.requireNonNull(value).isPresent());
|
Assert.assertTrue(Objects.requireNonNull(value).isPresent());
|
||||||
Assert.assertEquals(2, ((Map<String, CommonProfile>) value.get()).size());
|
Assert.assertEquals(2, ((Map<String, CommonProfile>) value.get()).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetWithWrongPassphraseThrowsDruidException()
|
||||||
|
{
|
||||||
|
final WebContext webContext = EasyMock.mock(WebContext.class);
|
||||||
|
EasyMock.expect(webContext.getScheme()).andReturn("https");
|
||||||
|
|
||||||
|
final Capture<Cookie> cookieCapture = EasyMock.newCapture();
|
||||||
|
EasyMock.expect(webContext.getRequestCookies())
|
||||||
|
.andAnswer(() -> Collections.singleton(cookieCapture.getValue()));
|
||||||
|
|
||||||
|
webContext.addResponseCookie(EasyMock.capture(cookieCapture));
|
||||||
|
EasyMock.expectLastCall().once();
|
||||||
|
EasyMock.replay(webContext);
|
||||||
|
|
||||||
|
// Create a cookie with an invalid passphrase
|
||||||
|
new Pac4jSessionStore<>("invalid-passphrase").set(webContext, "key", "value");
|
||||||
|
|
||||||
|
// Verify that trying to decrypt the invalid cookie throws an exception
|
||||||
|
final Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore<>(COOKIE_PASSPHRASE);
|
||||||
|
DruidException exception = Assert.assertThrows(
|
||||||
|
DruidException.class,
|
||||||
|
() -> sessionStore.get(webContext, "key")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Decryption failed. Check service logs.",
|
||||||
|
exception.getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertNull(exception.getCause());
|
||||||
|
|
||||||
|
EasyMock.verify(webContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,25 +20,21 @@
|
||||||
package org.apache.druid.crypto;
|
package org.apache.druid.crypto;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.apache.druid.error.InternalServerError;
|
||||||
import org.apache.druid.java.util.common.StringUtils;
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.InvalidParameterSpecException;
|
|
||||||
import java.security.spec.KeySpec;
|
import java.security.spec.KeySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +46,8 @@ import java.security.spec.KeySpec;
|
||||||
*/
|
*/
|
||||||
public class CryptoService
|
public class CryptoService
|
||||||
{
|
{
|
||||||
|
private static final Logger log = new Logger(CryptoService.class);
|
||||||
|
|
||||||
// Based on Javadocs on SecureRandom, It is threadsafe as well.
|
// Based on Javadocs on SecureRandom, It is threadsafe as well.
|
||||||
private static final SecureRandom SECURE_RANDOM_INSTANCE = new SecureRandom();
|
private static final SecureRandom SECURE_RANDOM_INSTANCE = new SecureRandom();
|
||||||
|
|
||||||
|
@ -125,8 +123,9 @@ public class CryptoService
|
||||||
ecipher.doFinal(plain)
|
ecipher.doFinal(plain)
|
||||||
).toByteAray();
|
).toByteAray();
|
||||||
}
|
}
|
||||||
catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException ex) {
|
catch (Exception ex) {
|
||||||
throw new RuntimeException(ex);
|
log.noStackTrace().warn(ex, "Encryption failed");
|
||||||
|
throw InternalServerError.exception("Encryption failed. Check service logs.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +144,9 @@ public class CryptoService
|
||||||
dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(encryptedData.getIv()));
|
dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(encryptedData.getIv()));
|
||||||
return dcipher.doFinal(encryptedData.getCipher());
|
return dcipher.doFinal(encryptedData.getCipher());
|
||||||
}
|
}
|
||||||
catch (InvalidKeySpecException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) {
|
catch (Exception ex) {
|
||||||
throw new RuntimeException(ex);
|
log.noStackTrace().warn(ex, "Decryption failed");
|
||||||
|
throw InternalServerError.exception("Decryption failed. Check service logs.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue